├── .github
└── workflows
│ ├── auth-service.yml
│ ├── dockerimage.yml
│ ├── npmpackages.yml
│ └── text-service.yml
├── .gitignore
├── LICENSE
├── README.md
├── auth-service
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── .snyk
├── Dockerfile
├── README.md
├── nest-cli.json
├── package.json
├── src
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.interface.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ ├── auth
│ │ ├── auth.controller.spec.ts
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ ├── auth.service.ts
│ │ ├── constants.ts
│ │ ├── jwt-auth.guard.ts
│ │ ├── jwt.strategy.ts
│ │ ├── local-auth.guard.ts
│ │ ├── local.strategy.ts
│ │ ├── twitter.strategy.ts
│ │ └── types.ts
│ ├── config
│ │ └── configuration.ts
│ ├── consts
│ │ └── index.ts
│ ├── main.ts
│ ├── users
│ │ ├── user.entity.ts
│ │ ├── users.controller.spec.ts
│ │ ├── users.controller.ts
│ │ ├── users.module.ts
│ │ └── users.service.ts
│ ├── utils.ts
│ └── validators
│ │ └── index.ts
├── test
│ ├── app.e2e-spec.ts
│ ├── jest-e2e.json
│ └── users.e2e-sepc.ts
├── tsconfig.build.json
├── tsconfig.json
└── yarn.lock
├── chrome-extension
├── background.js
├── images
│ ├── icon128.png
│ ├── icon16.png
│ ├── icon256.png
│ ├── icon32.png
│ ├── icon48.png
│ └── icon_big.png
├── manifest.json
├── options.html
├── options.js
├── popup.html
└── popup.js
├── contribiuting.md
├── docker-compose.yml
├── elastic
├── README.md
└── docker-compose.yml
├── logo.png
├── mysql
├── README.md
└── docker-compose.yml
├── package.json
├── run_auth_server.sh
├── text-ml-service
└── Dockerfile
├── text-service
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── Dockerfile
├── nest-cli.json
├── package.json
├── src
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ ├── config
│ │ └── configuration.ts
│ ├── elastic
│ │ └── index.ts
│ ├── main.ts
│ └── search
│ │ ├── search.controller.spec.ts
│ │ ├── search.controller.ts
│ │ ├── search.module.ts
│ │ └── search.service.ts
├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── tsconfig.build.json
├── tsconfig.json
└── yarn.lock
├── tsconfig.json
├── type-test
├── index.js
├── index.ts
├── package-lock.json
└── package.json
├── types
├── bb-tests.ts
├── index.d.ts
├── index.js
├── index.ts
├── package-lock.json
├── package.json
├── package.json.old
├── tsconfig.json
└── tslint.json
├── web-ui
├── .gitignore
├── .prettierrc
├── .storybook
│ ├── tsconfig.json
│ └── webpack.config.js
├── Dockerfile
├── Dockerfile-dev
├── README.md
├── package-lock.json
├── package.json
├── semantic.json
├── src
│ ├── app
│ │ ├── components
│ │ │ ├── CustomInput.tsx
│ │ │ ├── Footer
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.css
│ │ │ ├── Header
│ │ │ │ └── index.tsx
│ │ │ ├── Navigation.tsx
│ │ │ ├── Password.tsx
│ │ │ ├── PrivateRoute.tsx
│ │ │ ├── TodoItem
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.css
│ │ │ ├── TodoList
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.css
│ │ │ ├── TodoTextInput
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.css
│ │ │ └── index.ts
│ │ ├── constants
│ │ │ ├── index.ts
│ │ │ ├── stores.ts
│ │ │ └── todos.ts
│ │ ├── containers
│ │ │ ├── Root
│ │ │ │ └── index.tsx
│ │ │ └── TodoApp
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.css
│ │ ├── history.ts
│ │ ├── index.tsx
│ │ ├── models
│ │ │ ├── TodoModel.ts
│ │ │ └── index.ts
│ │ ├── routes
│ │ │ ├── authentication
│ │ │ │ ├── components
│ │ │ │ │ ├── FacebookButton.tsx
│ │ │ │ │ ├── GithubButton.tsx
│ │ │ │ │ ├── Login.tsx
│ │ │ │ │ ├── Register.tsx
│ │ │ │ │ ├── TabsWrapper.tsx
│ │ │ │ │ └── TwitterButton.tsx
│ │ │ │ ├── constants
│ │ │ │ │ └── index.tsx
│ │ │ │ └── index.tsx
│ │ │ └── main
│ │ │ │ └── index.ts
│ │ ├── stores
│ │ │ ├── RouterStore.ts
│ │ │ ├── TodoStore.ts
│ │ │ ├── createStore.ts
│ │ │ └── index.ts
│ │ ├── styledComponents
│ │ │ └── Sidebar
│ │ │ │ ├── Sidebar.js
│ │ │ │ ├── index.tsx
│ │ │ │ └── sidebar.story.js
│ │ └── theme
│ │ │ ├── dark.ts
│ │ │ └── light.ts
│ ├── assets
│ │ ├── favicon.ico
│ │ └── index.html
│ └── main.tsx
├── tsconfig.json
├── types
│ └── global.d.ts
├── webpack.config.js
└── yarn.lock
└── yarn.lock
/.github/workflows/auth-service.yml:
--------------------------------------------------------------------------------
1 | name: Auth-service
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 | env:
10 | working-directory: ./auth-service
11 | steps:
12 | - uses: actions/checkout@v2
13 | - name: Build, lint and tests
14 | run: |
15 | yarn
16 | yarn run lint
17 | # yarn run test
18 | # yarn run test:cov
19 | working-directory: ${{ env.working-directory }}
20 | - name: Build the Auth service Docker image and publish to GitHub Packages
21 | working-directory: ${{ env.working-directory }}
22 | run: |
23 | docker build . --tag docker.pkg.github.com/brainbackup/bb8/$SERVICE_NAME:$VERSION
24 | docker login docker.pkg.github.com --username ynahmany --password ${{ secrets.GITHUB_TOKEN }}
25 | docker push docker.pkg.github.com/brainbackup/bb8/$SERVICE_NAME:$VERSION
26 | env:
27 | VERSION: latest
28 | SERVICE_NAME: auth-service
29 |
--------------------------------------------------------------------------------
/.github/workflows/dockerimage.yml:
--------------------------------------------------------------------------------
1 | name: Docker Image CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'releases/**'
7 |
8 | jobs:
9 |
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v1
15 | - name: Build the $SERVICE_NAME Docker image and publish to GitHub Packages
16 | run: |
17 | docker build ./$SERVICE_NAME --file ./$SERVICE_NAME/Dockerfile --tag docker.pkg.github.com/brainbackup/code-snippet/$SERVICE_NAME:$VERSION
18 | docker login docker.pkg.github.com --username ynahmany --password ${{ secrets.GITHUB_TOKEN }}
19 | docker push docker.pkg.github.com/brainbackup/code-snippet/$SERVICE_NAME:$VERSION
20 | env:
21 | VERSION: latest
22 | SERVICE_NAME: web-ui
23 | - name: Build the $SERVICE_NAME Docker image and publish to GitHub Packages
24 | run: |
25 | docker build ./$SERVICE_NAME --file ./$SERVICE_NAME/Dockerfile --tag docker.pkg.github.com/brainbackup/code-snippet/$SERVICE_NAME:$VERSION
26 | docker login docker.pkg.github.com --username ynahmany --password ${{ secrets.GITHUB_TOKEN }}
27 | docker push docker.pkg.github.com/brainbackup/code-snippet/$SERVICE_NAME:$VERSION
28 | env:
29 | VERSION: latest
30 | SERVICE_NAME: search-service
--------------------------------------------------------------------------------
/.github/workflows/npmpackages.yml:
--------------------------------------------------------------------------------
1 | name: NPM packages
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'releases/**'
7 |
8 | jobs:
9 |
10 | publish-npm-packges:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - uses: actions/setup-node@v1
15 | with:
16 | node-version: 12
17 | registry-url: https://registry.npmjs.org/
18 | - run: |
19 | cd ./types
20 | npm version patch
21 | npm publish --access public
22 | env:
23 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
24 |
--------------------------------------------------------------------------------
/.github/workflows/text-service.yml:
--------------------------------------------------------------------------------
1 | name: Text-service
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 | env:
10 | working-directory: ./text-service
11 | steps:
12 | - uses: actions/checkout@v2
13 | - name: Build, lint and tests
14 | run: |
15 | yarn
16 | yarn run lint
17 | yarn run test
18 | yarn run test:cov
19 | working-directory: ${{ env.working-directory }}
20 | - name: Build the Text service Docker image and publish to GitHub Packages
21 | working-directory: ${{ env.working-directory }}
22 | run: |
23 | docker build . --tag docker.pkg.github.com/brainbackup/bb8/$SERVICE_NAME:$VERSION
24 | docker login docker.pkg.github.com --username ynahmany --password ${{ secrets.GITHUB_TOKEN }}
25 | docker push docker.pkg.github.com/brainbackup/bb8/$SERVICE_NAME:$VERSION
26 | env:
27 | VERSION: latest
28 | SERVICE_NAME: text-service
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | node_modules
3 | .env
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Yohay Nahmany
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BB8: modular, powerful and extendable project for memory improvment
2 |
3 |
4 |
5 |
6 | * * *
7 | [](https://opensource.org/licenses/MIT)
8 | 
9 | 
10 | 
11 | 
12 | 
13 |
14 | ## Overview
15 |
16 | * Provide users an ability to save code snippets, highlighted data from anywhere on the web.
17 | * Code snippets will be provided once the user starts typing in an IDE, searching, and analytics using a web application.
18 | * The users will be able to add data using chrome-extension, keyboard shortcut, right-click and simple user interface.
19 |
20 | ## Simple development process
21 |
22 | ### local development using docker
23 |
24 | `docker-compose build`
25 |
26 | `docker-compose up`
27 |
28 | * process.env.NODE_ENV - development, test and production - should be passed as env variable
29 |
30 | ### Authentication service
31 |
32 | * Require mysql: `cd mysql && docker-compose up`
33 | * Running locally: `cd auth-service && yarn start:dev`
34 | * Swagger: go to `http://localhost:3010/api/`
35 | * Health check: `http://localhost:3010/health`
36 |
37 | ### Text service
38 |
39 | * Require elastic: `cd elastic && docker-compose up`
40 | * Running locally: `cd text-service && yarn start:dev`
41 | * Swagger: go to `http://localhost:3009/api/`
42 | * Health check: `http://localhost:3009/health`
43 |
44 | ### Chrome extension
45 |
46 | * Side loading the chrome extension: go to chrome://extensions, click on Load unpacked, select the chrome-extension directoy
47 | * Open issue: the header should include `chrome-extension::` and follow up the subject of the issue.
48 | * TODO: Automatic way to update the app.
49 |
50 | ### Test NLP service
51 |
52 | * MLQA reserch from Facebook® should be a good start.
53 |
54 |
--------------------------------------------------------------------------------
/auth-service/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | project: 'tsconfig.json',
5 | sourceType: 'module',
6 | },
7 | plugins: ['@typescript-eslint/eslint-plugin'],
8 | extends: [
9 | 'plugin:@typescript-eslint/eslint-recommended',
10 | 'plugin:@typescript-eslint/recommended',
11 | 'prettier',
12 | 'prettier/@typescript-eslint',
13 | ],
14 | root: true,
15 | env: {
16 | node: true,
17 | jest: true,
18 | },
19 | rules: {
20 | '@typescript-eslint/interface-name-prefix': 'off',
21 | '@typescript-eslint/explicit-function-return-type': 'off',
22 | '@typescript-eslint/no-explicit-any': 'off',
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/auth-service/.gitignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist
3 | /node_modules
4 |
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 | lerna-debug.log*
12 |
13 | # OS
14 | .DS_Store
15 |
16 | # Tests
17 | /coverage
18 | /.nyc_output
19 |
20 | # IDEs and editors
21 | /.idea
22 | .project
23 | .classpath
24 | .c9/
25 | *.launch
26 | .settings/
27 | *.sublime-workspace
28 |
29 | # IDE - VSCode
30 | .vscode/*
31 | !.vscode/settings.json
32 | !.vscode/tasks.json
33 | !.vscode/launch.json
34 | !.vscode/extensions.json
--------------------------------------------------------------------------------
/auth-service/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all"
4 | }
--------------------------------------------------------------------------------
/auth-service/.snyk:
--------------------------------------------------------------------------------
1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
2 | version: v1.14.1
3 | ignore: {}
4 | # patches apply the minimum changes required to fix a vulnerability
5 | patch:
6 | SNYK-JS-LODASH-567746:
7 | - '@nestjs/swagger > lodash':
8 | patched: '2020-04-30T23:42:18.842Z'
9 |
--------------------------------------------------------------------------------
/auth-service/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12.16
2 | WORKDIR /usr/app
3 | COPY . .
4 | RUN yarn
5 | RUN yarn build
6 | CMD yarn start:prod
--------------------------------------------------------------------------------
/auth-service/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master
6 | [travis-url]: https://travis-ci.org/nestjs/nest
7 | [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux
8 | [linux-url]: https://travis-ci.org/nestjs/nest
9 |
10 | A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
26 |
27 | ## Description
28 |
29 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
30 |
31 | ## Installation
32 |
33 | ```bash
34 | $ yarn
35 | ```
36 |
37 | ## Running the app
38 |
39 | ```bash
40 | # development
41 | $ yarn run start
42 |
43 | # watch mode
44 | $ yarn run start:dev
45 |
46 | # production mode
47 | $ yarn run start:prod
48 | ```
49 |
50 | ## Test
51 |
52 | ```bash
53 | # unit tests
54 | $ yarn run test
55 |
56 | # e2e tests
57 | $ yarn run test:e2e
58 |
59 | # test coverage
60 | $ yarn run test:cov
61 | ```
62 | # Swagger
63 |
64 | $ http://localhost:3000/api/
--------------------------------------------------------------------------------
/auth-service/nest-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "collection": "@nestjs/schematics",
3 | "sourceRoot": "src"
4 | }
5 |
--------------------------------------------------------------------------------
/auth-service/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "auth-service",
3 | "version": "0.0.1",
4 | "description": "Authentication service",
5 | "author": "Yohay Nahmany",
6 | "private": true,
7 | "license": "MIT",
8 | "scripts": {
9 | "prebuild": "rimraf dist",
10 | "build": "nest build",
11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
12 | "start": "nest start",
13 | "start:dev": "nest start --watch",
14 | "start:debug": "nest start --debug --watch",
15 | "start:prod": "node dist/main",
16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
17 | "test": "jest",
18 | "test:watch": "jest --watch",
19 | "test:cov": "jest --coverage",
20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
21 | "test:e2e": "jest --config ./test/jest-e2e.json",
22 | "snyk-protect": "snyk protect",
23 | "prepare": "yarn run snyk-protect"
24 | },
25 | "dependencies": {
26 | "@hapi/joi": "^17.1.1",
27 | "@nestjs/common": "^8.0.7",
28 | "@nestjs/config": "^0.6.2",
29 | "@nestjs/core": "^7.6.17",
30 | "@nestjs/jwt": "^10.0.0",
31 | "@nestjs/passport": "^7.1.5",
32 | "@nestjs/platform-express": "^7.6.17",
33 | "@nestjs/swagger": "^4.8.1",
34 | "@nestjs/typeorm": "^7.1.5",
35 | "@types/passport-local": "^1.0.33",
36 | "mysql": "^2.18.1",
37 | "n": "^6.7.1",
38 | "passport": "^0.4.1",
39 | "passport-jwt": "^4.0.1",
40 | "passport-local": "^1.0.0",
41 | "passport-twitter": "^1.0.4",
42 | "reflect-metadata": "^0.1.13",
43 | "rimraf": "^3.0.2",
44 | "rxjs": "^6.6.7",
45 | "snyk": "^1.611.0",
46 | "swagger-ui-express": "^4.1.6",
47 | "typeorm": "^0.2.33"
48 | },
49 | "devDependencies": {
50 | "@nestjs/cli": "^7.0.0",
51 | "@nestjs/schematics": "^7.0.0",
52 | "@nestjs/testing": "^7.0.0",
53 | "@types/express": "^4.17.3",
54 | "@types/hapi__joi": "^16.0.12",
55 | "@types/jest": "25.1.4",
56 | "@types/node": "^13.9.1",
57 | "@types/passport-jwt": "^3.0.3",
58 | "@types/supertest": "^2.0.8",
59 | "@typescript-eslint/eslint-plugin": "^2.23.0",
60 | "@typescript-eslint/parser": "^2.23.0",
61 | "eslint": "^6.8.0",
62 | "eslint-config-prettier": "^6.10.0",
63 | "eslint-plugin-import": "^2.20.1",
64 | "jest": "^25.1.0",
65 | "prettier": "^1.19.1",
66 | "supertest": "^4.0.2",
67 | "ts-jest": "25.2.1",
68 | "ts-loader": "^6.2.1",
69 | "ts-node": "^8.6.2",
70 | "tsconfig-paths": "^3.9.0",
71 | "typescript": "^3.7.4"
72 | },
73 | "jest": {
74 | "moduleFileExtensions": [
75 | "js",
76 | "json",
77 | "ts"
78 | ],
79 | "rootDir": "src",
80 | "testRegex": ".spec.ts$",
81 | "transform": {
82 | "^.+\\.(t|j)s$": "ts-jest"
83 | },
84 | "coverageDirectory": "../coverage",
85 | "testEnvironment": "node"
86 | },
87 | "snyk": true
88 | }
89 |
--------------------------------------------------------------------------------
/auth-service/src/app.controller.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { AppController } from './app.controller';
3 | import { AppService } from './app.service';
4 |
5 | describe('AppController', () => {
6 | let appController: AppController;
7 |
8 | beforeEach(async () => {
9 | const app: TestingModule = await Test.createTestingModule({
10 | controllers: [AppController],
11 | providers: [AppService],
12 | }).compile();
13 |
14 | appController = app.get(AppController);
15 | });
16 |
17 | describe('root', () => {
18 | it('Health check function', () => {
19 | expect(appController.checkHealth()).toStrictEqual({"services": [{"name": "mysql", "status": "UP", "version": "10.1.2"}], "status": "UP"});
20 | });
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/auth-service/src/app.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Request, Get, Post, UseGuards } from '@nestjs/common';
2 | import { AppService } from './app.service';
3 | import { LocalAuthGuard } from './auth/local-auth.guard';
4 | import { JwtAuthGuard } from './auth/jwt-auth.guard';
5 | import { HealthCheck } from './app.interface';
6 | import { AuthService } from './auth/auth.service';
7 |
8 | @Controller()
9 | export class AppController {
10 | constructor(
11 | private readonly appService: AppService,
12 | private readonly authService: AuthService
13 | ) {}
14 |
15 | @Get('/health')
16 | checkHealth(): HealthCheck {
17 | return this.appService.checkHealth();
18 | }
19 | @UseGuards(LocalAuthGuard)
20 | @Post('auth/login')
21 | async login(@Request() req) {
22 | return this.authService.login(req.user);
23 | }
24 |
25 | @UseGuards(JwtAuthGuard)
26 | @Get('profile')
27 | getProfile(@Request() req) {
28 | return req.user;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/auth-service/src/app.interface.ts:
--------------------------------------------------------------------------------
1 | export enum Status {
2 | UP = `UP`,
3 | DOWN = `DOWN`
4 | }
5 | export interface Service {
6 | name: string;
7 | version: string;
8 | status: Status;
9 | }
10 | export interface HealthCheck {
11 | status: Status;
12 | services: Service[]
13 | }
--------------------------------------------------------------------------------
/auth-service/src/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Module, HttpModule } from '@nestjs/common';
2 | import { AppController } from './app.controller';
3 | import { AppService } from './app.service';
4 | import { AuthController } from './auth/auth.controller';
5 | import { UsersModule } from './users/users.module';
6 | import { AuthModule } from './auth/auth.module';
7 | import { TypeOrmModule } from '@nestjs/typeorm';
8 | import { ConfigModule } from '@nestjs/config';
9 | import configuration from './config/configuration';
10 | const Configuration = configuration();
11 |
12 | @Module({
13 | imports: [
14 | HttpModule,
15 | TypeOrmModule.forRoot({
16 | "type": "mysql",
17 | "host": Configuration.database.host,
18 | "port": Configuration.database.port,
19 | "username": Configuration.database.username,
20 | "password": Configuration.database.password,
21 | "database": Configuration.database.name,
22 | "entities": ["dist/**/*.entity.js"],
23 | "synchronize": true
24 | }),
25 | UsersModule,
26 | AuthModule,
27 | ConfigModule.forRoot({
28 | load: [configuration]
29 | })
30 | ],
31 | controllers: [AppController, AuthController],
32 | providers: [AppService],
33 | })
34 | export class AppModule{}
35 |
--------------------------------------------------------------------------------
/auth-service/src/app.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { HealthCheck, Status } from './app.interface';
3 | @Injectable()
4 | export class AppService {
5 | checkHealth(): HealthCheck {
6 | // the status of the connections to the infrastructure services used by the service instance
7 | // the status of the host, e.g. disk space
8 | // application specific logic
9 | const healthCheck: HealthCheck = {
10 | status: Status.UP,
11 | services: [{ name: 'mysql', version: '10.1.2', status: Status.UP }]
12 | }
13 | return healthCheck;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/auth-service/src/auth/auth.controller.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { AuthController } from './auth.controller';
3 | import { AuthService } from './auth.service';
4 | import { JwtService } from '@nestjs/jwt';
5 | import { UsersModule } from '../users/users.module';
6 | import { AuthModule } from './auth.module';
7 |
8 | describe('AuthController', () => {
9 | let authController: AuthController;
10 |
11 | beforeEach(async () => {
12 | const app: TestingModule = await Test.createTestingModule({
13 | imports: [AuthModule],
14 | controllers: [AuthController],
15 | providers: [AuthService, JwtService],
16 | }).compile();
17 |
18 | authController = app.get(AuthController);
19 | });
20 |
21 | describe('root', () => {
22 | it('Login', () => {
23 | expect(authController).toBeDefined();
24 | });
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/auth-service/src/auth/auth.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Post, Body, UsePipes } from '@nestjs/common';
2 | import * as Joi from '@hapi/joi';
3 | import { AuthService } from './auth.service';
4 | import { ILogin } from './types';
5 | import { JoiValidationPipe } from '../validators';
6 |
7 | const createLoginSchema = () =>
8 | Joi.object({
9 | mailAddress: Joi.string().required(),
10 | password: Joi.string().required()
11 | });
12 | @Controller('/auth')
13 | export class AuthController {
14 | constructor(private readonly authService: AuthService) {}
15 |
16 | @Post('login')
17 | @UsePipes(new JoiValidationPipe(createLoginSchema()))
18 | async login(@Body() user: ILogin) {
19 | return await this.authService.login(user);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/auth-service/src/auth/auth.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { AuthService } from './auth.service';
3 | import { UsersModule } from '../users/users.module';
4 | import { JwtModule } from '@nestjs/jwt';
5 | import { PassportModule } from '@nestjs/passport';
6 | import { LocalStrategy } from './local.strategy';
7 | import { jwtConstants } from './constants';
8 | import { JwtStrategy } from './jwt.strategy';
9 | import { TwitterStrategy } from './twitter.strategy';
10 | import { ConfigModule } from '@nestjs/config';
11 |
12 |
13 | @Module({
14 | imports: [
15 | PassportModule.register({ defaultStrategy: 'jwt' }),
16 | JwtModule.register({
17 | secret: jwtConstants.secret,
18 | signOptions: { expiresIn: '60s' },
19 | }),
20 | UsersModule,
21 | ConfigModule
22 | ],
23 | providers: [AuthService, LocalStrategy, JwtStrategy, TwitterStrategy],
24 | exports: [AuthService],
25 | })
26 | export class AuthModule {}
27 |
--------------------------------------------------------------------------------
/auth-service/src/auth/auth.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, UnauthorizedException } from '@nestjs/common';
2 | import { UsersService } from '../users/users.service';
3 | import { JwtService } from '@nestjs/jwt';
4 | import { ILogin } from './types';
5 | import { hashPassword } from '../utils';
6 |
7 | @Injectable()
8 | export class AuthService {
9 | constructor(
10 | private jwtService: JwtService,
11 | private usersService: UsersService,
12 | ) {}
13 |
14 | async validateUser(mailAddress: string, password: string): Promise {
15 | const user = await this.usersService.getUserByMailAddress(mailAddress);
16 | if (user && user.isActive && user.password === hashPassword(password, user.salt)) {
17 | return {
18 | id: user.id,
19 | fullName: user.fullName,
20 | isAdmin: user.isAdmin,
21 | email: user.email,
22 | username: user.username,
23 | createdAt: user.createdAt
24 | }
25 | }
26 | throw new UnauthorizedException();
27 | }
28 |
29 | async login(user: ILogin) {
30 | const res = await this.validateUser(user.mailAddress, user.password);
31 | return this.jwtService.sign(res);
32 | }
33 | }
--------------------------------------------------------------------------------
/auth-service/src/auth/constants.ts:
--------------------------------------------------------------------------------
1 | export const jwtConstants = {
2 | secret: 'secretKey',
3 | };
--------------------------------------------------------------------------------
/auth-service/src/auth/jwt-auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { AuthGuard } from '@nestjs/passport';
3 |
4 | @Injectable()
5 | export class JwtAuthGuard extends AuthGuard('jwt') {}
6 |
--------------------------------------------------------------------------------
/auth-service/src/auth/jwt.strategy.ts:
--------------------------------------------------------------------------------
1 | import { ExtractJwt, Strategy } from 'passport-jwt';
2 | import { PassportStrategy } from '@nestjs/passport';
3 | import { Injectable } from '@nestjs/common';
4 | import { jwtConstants } from './constants';
5 |
6 | @Injectable()
7 | export class JwtStrategy extends PassportStrategy(Strategy) {
8 | constructor() {
9 | super({
10 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
11 | ignoreExpiration: false,
12 | secretOrKey: jwtConstants.secret,
13 | });
14 | }
15 |
16 | async validate(payload: any) {
17 | return { userId: payload.sub, username: payload.username };
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/auth-service/src/auth/local-auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { AuthGuard } from '@nestjs/passport';
3 |
4 | @Injectable()
5 | export class LocalAuthGuard extends AuthGuard('local') {}
--------------------------------------------------------------------------------
/auth-service/src/auth/local.strategy.ts:
--------------------------------------------------------------------------------
1 | import { Strategy } from 'passport-local';
2 | import { PassportStrategy } from '@nestjs/passport';
3 | import { Injectable, UnauthorizedException } from '@nestjs/common';
4 | import { AuthService } from './auth.service';
5 |
6 | @Injectable()
7 | export class LocalStrategy extends PassportStrategy(Strategy) {
8 | constructor(private authService: AuthService) {
9 | super();
10 | }
11 |
12 | async validate(username: string, password: string): Promise {
13 | const user = await this.authService.validateUser(username, password);
14 | if (!user) {
15 | throw new UnauthorizedException();
16 | }
17 | return user;
18 | }
19 | }
--------------------------------------------------------------------------------
/auth-service/src/auth/twitter.strategy.ts:
--------------------------------------------------------------------------------
1 | import { Strategy } from 'passport-twitter';
2 | import { PassportStrategy } from '@nestjs/passport';
3 | import { Injectable } from '@nestjs/common';
4 | import { ConfigService } from '@nestjs/config';
5 |
6 | @Injectable()
7 | export class TwitterStrategy extends PassportStrategy(Strategy) {
8 | constructor(private configService: ConfigService) {
9 | super({
10 | consumerKey: configService.get('twitter.key'),
11 | consumerSecret: configService.get('twitter.secret'),
12 | callbackURL: configService.get('twitter.callback')
13 | });
14 | }
15 |
16 | async validate(payload: any) {
17 | return { userId: payload.sub, username: payload.username };
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/auth-service/src/auth/types.ts:
--------------------------------------------------------------------------------
1 | export interface ILogin {
2 | mailAddress: string;
3 | password: string;
4 | }
5 |
--------------------------------------------------------------------------------
/auth-service/src/config/configuration.ts:
--------------------------------------------------------------------------------
1 | interface IDatabase {
2 | host: string;
3 | port: number;
4 | username: string;
5 | password: string;
6 | name: string;
7 | }
8 | interface Auth {
9 | key: string;
10 | secret: string;
11 | callback: string;
12 | }
13 | interface Configuration {
14 | port: number,
15 | database: IDatabase,
16 | twitter?: Auth
17 | }
18 | interface ConfigurationResult {
19 | development: Configuration;
20 | testing: Configuration;
21 | production?: Configuration;
22 | }
23 | const configuration: ConfigurationResult = {
24 | development: {
25 | port: parseInt(process.env.PORT, 10) || 3010,
26 | database: {
27 | host: 'localhost',
28 | port: 3306,
29 | username: 'root',
30 | password: 'w3lc0me!',
31 | name: 'auth_service',
32 | },
33 | twitter: {
34 | key: '5GvNgRMSQFaab70EBfV5psXW8',
35 | secret: 'D6qXsHHpJGk2427lJ9AveC3XBjcFfyBp3ZaaEB95y65RGm7Zb8',
36 | callback: 'http://localhost:3010'
37 | }
38 | },
39 | testing: {
40 | port: parseInt(process.env.PORT, 10) || 3010,
41 | database: {
42 | host: 'localhost',
43 | port: 3306,
44 | username: 'root',
45 | password: 'w3lc0me!',
46 | name: 'auth_service',
47 | }
48 | }
49 | };
50 | export default (): Configuration => configuration[process.env.NODE_ENV] || configuration['development'];
51 |
--------------------------------------------------------------------------------
/auth-service/src/consts/index.ts:
--------------------------------------------------------------------------------
1 | export const DATABASE_CONNECTION = `DATABASE_CONNECTION`;
--------------------------------------------------------------------------------
/auth-service/src/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from '@nestjs/core';
2 | import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
3 | import { AppModule } from './app.module';
4 | import configuration from './config/configuration';
5 |
6 | async function bootstrap() {
7 | const app = await NestFactory.create(AppModule);
8 | const Configuration = configuration();
9 | const options = new DocumentBuilder()
10 | .setTitle('Auth Service')
11 | .setDescription('Auth service should provide all apis for authentication and authorization for the bb projects')
12 | .setVersion('1.0')
13 | .addTag('auth')
14 | .build();
15 | const document = SwaggerModule.createDocument(app, options);
16 | SwaggerModule.setup('api', app, document);
17 | app.enableCors();
18 | await app.listen(Configuration.port);
19 | }
20 | bootstrap();
21 |
--------------------------------------------------------------------------------
/auth-service/src/users/user.entity.ts:
--------------------------------------------------------------------------------
1 | import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
2 | import { ApiProperty } from '@nestjs/swagger';
3 |
4 |
5 | @Entity()
6 | export class Users {
7 |
8 | @PrimaryGeneratedColumn('increment') id: number;
9 | @ApiProperty() @Column({ length: 25 }) fullName : string;
10 | @ApiProperty() @Column() email: string;
11 | @ApiProperty() @Column() username: string;
12 | @ApiProperty() @Column() salt: string;
13 | @ApiProperty() @Column() password: string;
14 | @ApiProperty() @Column() createdAt : Date;
15 | @ApiProperty() @Column() confirmationToken : string;
16 | @ApiProperty() @Column() isVerified : boolean;
17 | @ApiProperty() @Column() isAdmin : boolean;
18 | @ApiProperty() @Column() isActive : boolean;
19 | }
20 |
--------------------------------------------------------------------------------
/auth-service/src/users/users.controller.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { UsersController } from './users.controller';
3 | import { UsersService } from './users.service';
4 |
5 | describe('UsersController', () => {
6 | let usersController: UsersController;
7 |
8 | beforeEach(async () => {
9 | const app: TestingModule = await Test.createTestingModule({
10 | controllers: [UsersController],
11 | providers: [UsersService],
12 | }).compile();
13 |
14 | usersController = app.get(UsersController);
15 | });
16 |
17 | describe('Get', () => {
18 | it('', () => {
19 | expect(usersController.get(0)).toBeFalsy();
20 | })
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/auth-service/src/users/users.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Post, Body, Get, Put, Delete, Param, UsePipes} from '@nestjs/common';
2 | import { UsersService } from './users.service';
3 | import { Users } from './user.entity';
4 | import { ApiQuery, ApiParam } from '@nestjs/swagger';
5 | import { v4 as uuid } from 'uuid';
6 | import { JoiValidationPipe } from '../validators';
7 | import { createSalt, hashPassword } from '../utils';
8 | import * as Joi from '@hapi/joi';
9 |
10 | interface WebRegister {
11 | firstName: string;
12 | lastName: string;
13 | mailAddress: string;
14 | password: string;
15 | }
16 | const createWebRegisterSchema = () =>
17 | Joi.object({
18 | firstName: Joi.string().required(),
19 | lastName: Joi.string().required(),
20 | mailAddress: Joi.string().email().required(),
21 | password: Joi.string().required()
22 | });
23 | @Controller('users')
24 | export class UsersController {
25 |
26 | constructor(private service: UsersService) { }
27 |
28 | @Get('/all')
29 | getAll() {
30 | return this.service.getUsers();
31 | }
32 | @Get(':id')
33 | @ApiParam({ name: 'id', type: Number })
34 | get(@Param('id') id) {
35 | return this.service.getUser(id);
36 | }
37 |
38 | @Post()
39 | @UsePipes(new JoiValidationPipe(createWebRegisterSchema()))
40 | create(@Body() user: WebRegister) {
41 | // TODO: validate it has firstname, lastname, mail and password.
42 | const salt = createSalt();
43 | const _user: Users = {
44 | id: 0,
45 | fullName: `${user.firstName} ${user.lastName}`,
46 | email: user.mailAddress,
47 | username: uuid(),
48 | salt,
49 | password: hashPassword(user.password, salt),
50 | isVerified: false,
51 | createdAt: new Date,
52 | isAdmin: false,
53 | isActive: true,
54 | confirmationToken: createSalt()
55 | }
56 | return this.service.createUser(_user);
57 | }
58 |
59 | @Put()
60 | @ApiQuery({ name: 'id'})
61 | update(@Param() param: Partial, @Body() user: Users) {
62 | return this.service.updateUser(param.id, user);
63 | }
64 |
65 | @Delete(':id')
66 | @ApiParam({ name: 'id', type: Number })
67 | deleteUser(@Param() params) {
68 | return this.service.deleteUser(params.id);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/auth-service/src/users/users.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { TypeOrmModule } from '@nestjs/typeorm';
3 | import { UsersService } from './users.service';
4 | import { UsersController } from './users.controller';
5 | import { Users } from './user.entity';
6 |
7 | @Module({
8 | imports: [TypeOrmModule.forFeature([Users])],
9 | providers: [UsersService],
10 | controllers: [UsersController],
11 | exports: [UsersService]
12 | })
13 |
14 | export class UsersModule { }
--------------------------------------------------------------------------------
/auth-service/src/users/users.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { InjectRepository } from '@nestjs/typeorm';
3 | import { Repository } from 'typeorm';
4 | import { Users } from './user.entity';
5 |
6 | @Injectable()
7 | export class UsersService {
8 |
9 | constructor(@InjectRepository(Users) private usersRepository: Repository) { }
10 |
11 | async getUsers(): Promise {
12 | return await this.usersRepository.find({
13 | order: {
14 | salt: "ASC"
15 | }
16 | });
17 | }
18 |
19 | async getUser(_id: number): Promise {
20 | return await this.usersRepository.find({
21 | select: ["fullName", "isActive", "email"],
22 | where: [{ "id": _id }]
23 | });
24 | }
25 | async getUserByUsername(username: string): Promise {
26 | return await this.usersRepository.findOne({
27 | where: [{ "username": username }]
28 | });
29 | }
30 | async getUserByMailAddress(mailAddress: string): Promise {
31 | return await this.usersRepository.findOne({
32 | where: [{ email: mailAddress }]
33 | })
34 | }
35 | async createUser(user: Users) {
36 | this.usersRepository.save(user);
37 | }
38 | async updateUser(_id: number, user: Users) {
39 | const oldUser: Users = await this.usersRepository.findOne(_id);
40 | this.usersRepository.save({
41 | ...oldUser,
42 | ...user
43 | });
44 | }
45 |
46 | async deleteUser(user: Users) {
47 | this.usersRepository.delete(user);
48 | }
49 | }
--------------------------------------------------------------------------------
/auth-service/src/utils.ts:
--------------------------------------------------------------------------------
1 | import * as crypto from 'crypto';
2 |
3 | export const createSalt = () =>
4 | crypto.randomBytes(16)
5 | .toString('hex')
6 | .slice(0, 32);
7 | export const hashPassword = (password: string, salt: string) => {
8 | const hash = crypto.createHmac('sha512', salt); /** Hashing algorithm sha512 */
9 | hash.update(password);
10 | return hash.digest('hex');
11 | }
--------------------------------------------------------------------------------
/auth-service/src/validators/index.ts:
--------------------------------------------------------------------------------
1 | import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
2 | import { ObjectSchema } from '@hapi/joi';
3 |
4 | @Injectable()
5 | export class JoiValidationPipe implements PipeTransform {
6 | constructor(private schema: ObjectSchema) {}
7 |
8 | transform(value: any) {
9 | const { error } = this.schema.validate(value);
10 | if (error) {
11 | throw new BadRequestException('Validation failed');
12 | }
13 | return value;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/auth-service/test/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { INestApplication } from '@nestjs/common';
3 | import * as request from 'supertest';
4 | import { AppModule } from './../src/app.module';
5 |
6 | describe('AppController (e2e)', () => {
7 | let app: INestApplication;
8 |
9 | beforeEach(async () => {
10 | const moduleFixture: TestingModule = await Test.createTestingModule({
11 | imports: [AppModule],
12 | }).compile();
13 |
14 | app = moduleFixture.createNestApplication();
15 | await app.init();
16 | });
17 |
18 | it('/health (GET)', () => {
19 | return request(app.getHttpServer())
20 | .get('/health')
21 | .expect(200);
22 | });
23 | it('/auth (GET)', () => {
24 | return request(app.getHttpServer())
25 | .get('/auth')
26 | .expect(200);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/auth-service/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 |
--------------------------------------------------------------------------------
/auth-service/test/users.e2e-sepc.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { INestApplication } from '@nestjs/common';
3 | import * as request from 'supertest';
4 | import { AppModule } from '../src/app.module';
5 | import { UsersModule } from '../src/users/users.module';
6 | import { TypeOrmModule } from '@nestjs/typeorm';
7 |
8 |
9 | describe('AppController (e2e)', () => {
10 | let app: INestApplication;
11 |
12 | beforeEach(async () => {
13 | const moduleFixture: TestingModule = await Test.createTestingModule({
14 | imports: [
15 | AppModule,
16 | TypeOrmModule.forRoot(),
17 | UsersModule
18 | ],
19 | }).compile();
20 |
21 | app = moduleFixture.createNestApplication();
22 | await app.init();
23 | });
24 |
25 | it('/users/:id (GET)', () => {
26 | return request(app.getHttpServer())
27 | .get('/users/15')
28 | .expect(200);
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/auth-service/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/auth-service/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "declaration": true,
5 | "removeComments": true,
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "target": "es2017",
9 | "sourceMap": true,
10 | "outDir": "./dist",
11 | "baseUrl": "./",
12 | "incremental": true
13 | },
14 | "exclude": ["node_modules", "dist"]
15 | }
16 |
--------------------------------------------------------------------------------
/chrome-extension/background.js:
--------------------------------------------------------------------------------
1 | chrome.runtime.onInstalled.addListener(function() {
2 | chrome.storage.sync.set({color: '#3aa757'}, function() {
3 | console.log('The color is green.');
4 | });
5 | chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
6 | chrome.declarativeContent.onPageChanged.addRules([{
7 | conditions: [new chrome.declarativeContent.PageStateMatcher({
8 | pageUrl: {hostEquals: 'developer.chrome.com'},
9 | })
10 | ],
11 | actions: [new chrome.declarativeContent.ShowPageAction()]
12 | }]);
13 | });
14 |
15 | chrome.contextMenus.create({
16 | id: 'saveCodeSnippet',
17 | title: 'Save code snippet',
18 | contexts: ['all']
19 | });
20 | chrome.contextMenus.create({
21 | id: 'highlight',
22 | title: 'Highlight',
23 | contexts: ['all']
24 | });
25 | chrome.contextMenus.onClicked.addListener(function(data) {
26 | const url = 'http://localhost:3009/api/v1/snippets';
27 | fetch(url, {
28 | method: 'POST', // *GET, POST, PUT, DELETE, etc.
29 | mode: 'cors', // no-cors, *cors, same-origin
30 | cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
31 | credentials: 'same-origin', // include, *same-origin, omit
32 | headers: {
33 | 'Content-Type': 'application/json'
34 | },
35 | redirect: 'follow', // manual, *follow, error
36 | referrerPolicy: 'no-referrer', // no-referrer, *client
37 | body: JSON.stringify({
38 | menuItemId: data.menuItemId,
39 | saveCodeSnippet: data.selectionText,
40 | pageUrl: data.pageUrl
41 | }) // body data type must match "Content-Type" header
42 | }).then(d => console.log('response from server',d))
43 | .catch(err => console.error(err));
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/chrome-extension/images/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrainBackup/bb8/1562da57815536d5da7a48df37e5e018e69b0413/chrome-extension/images/icon128.png
--------------------------------------------------------------------------------
/chrome-extension/images/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrainBackup/bb8/1562da57815536d5da7a48df37e5e018e69b0413/chrome-extension/images/icon16.png
--------------------------------------------------------------------------------
/chrome-extension/images/icon256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrainBackup/bb8/1562da57815536d5da7a48df37e5e018e69b0413/chrome-extension/images/icon256.png
--------------------------------------------------------------------------------
/chrome-extension/images/icon32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrainBackup/bb8/1562da57815536d5da7a48df37e5e018e69b0413/chrome-extension/images/icon32.png
--------------------------------------------------------------------------------
/chrome-extension/images/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrainBackup/bb8/1562da57815536d5da7a48df37e5e018e69b0413/chrome-extension/images/icon48.png
--------------------------------------------------------------------------------
/chrome-extension/images/icon_big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrainBackup/bb8/1562da57815536d5da7a48df37e5e018e69b0413/chrome-extension/images/icon_big.png
--------------------------------------------------------------------------------
/chrome-extension/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Coding snippet",
3 | "version": "1.0",
4 | "description": "Make coding great again!",
5 | "permissions": ["activeTab", "declarativeContent", "storage", "contextMenus"],
6 | "background": {
7 | "scripts": ["background.js"],
8 | "persistent": false
9 | },
10 | "browser_action": {
11 | "default_popup": "popup.html"
12 | },
13 | "icons": {
14 | "16": "images/icon16.png",
15 | "32": "images/icon32.png",
16 | "48": "images/icon48.png",
17 | "128": "images/icon128.png",
18 | "256": "images/icon256.png"
19 | },
20 | "options_page": "options.html",
21 | "manifest_version": 2
22 | }
--------------------------------------------------------------------------------
/chrome-extension/options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
14 |
15 |
16 |
17 |
Choose a different background color!
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/chrome-extension/options.js:
--------------------------------------------------------------------------------
1 | let page = document.getElementById('buttonDiv');
2 | const kButtonColors = ['#3aa757', '#e8453c', '#f9bb2d', '#4688f1'];
3 | function constructOptions(kButtonColors) {
4 | for (let item of kButtonColors) {
5 | let button = document.createElement('button');
6 | button.style.backgroundColor = item;
7 | button.addEventListener('click', function() {
8 | chrome.storage.sync.set({color: item}, function() {
9 | console.log('color is ' + item);
10 | })
11 | });
12 | page.appendChild(button);
13 | }
14 | }
15 | constructOptions(kButtonColors);
--------------------------------------------------------------------------------
/chrome-extension/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 | TEst header
14 |
15 |
16 |
--------------------------------------------------------------------------------
/chrome-extension/popup.js:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Chromium Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | 'use strict';
6 |
7 | let changeColor = document.getElementById('changeColor');
8 |
9 | chrome.storage.sync.get('color', function(data) {
10 | changeColor.style.backgroundColor = data.color;
11 | changeColor.setAttribute('value', data.color);
12 | });
13 | changeColor.onclick = function(element) {
14 | let color = element.target.value;
15 | chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
16 | chrome.tabs.executeScript(
17 | tabs[0].id,
18 | {code: 'document.body.style.backgroundColor = "' + color + '";'});
19 | });
20 | };
--------------------------------------------------------------------------------
/contribiuting.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 | s
3 | When contributing to this repository, please first discuss the change you wish to make via issue,
4 | email, or any other method with the owners of this repository before making a change.
5 |
6 | Please note we have a code of conduct, please follow it in all your interactions with the project.
7 |
8 | ## Pull Request Process
9 |
10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a
11 | build.
12 | 2. Update the README.md with details of changes to the interface, this includes new environment
13 | variables, exposed ports, useful file locations and container parameters.
14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this
15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
17 | do not have permission to do that, you may request the second reviewer to merge it for you.
18 |
19 | ## Code of Conduct
20 |
21 | ### Our Pledge
22 |
23 | In the interest of fostering an open and welcoming environment, we as
24 | contributors and maintainers pledge to making participation in our project and
25 | our community a harassment-free experience for everyone, regardless of age, body
26 | size, disability, ethnicity, gender identity and expression, level of experience,
27 | nationality, personal appearance, race, religion, or sexual identity and
28 | orientation.
29 |
30 | ### Our Standards
31 |
32 | Examples of behavior that contributes to creating a positive environment
33 | include:
34 |
35 | * Using welcoming and inclusive language
36 | * Being respectful of differing viewpoints and experiences
37 | * Gracefully accepting constructive criticism
38 | * Focusing on what is best for the community
39 | * Showing empathy towards other community members
40 |
41 | Examples of unacceptable behavior by participants include:
42 |
43 | * The use of sexualized language or imagery and unwelcome sexual attention or
44 | advances
45 | * Trolling, insulting/derogatory comments, and personal or political attacks
46 | * Public or private harassment
47 | * Publishing others' private information, such as a physical or electronic
48 | address, without explicit permission
49 | * Other conduct which could reasonably be considered inappropriate in a
50 | professional setting
51 |
52 | ### Our Responsibilities
53 |
54 | Project maintainers are responsible for clarifying the standards of acceptable
55 | behavior and are expected to take appropriate and fair corrective action in
56 | response to any instances of unacceptable behavior.
57 |
58 | Project maintainers have the right and responsibility to remove, edit, or
59 | reject comments, commits, code, wiki edits, issues, and other contributions
60 | that are not aligned to this Code of Conduct, or to ban temporarily or
61 | permanently any contributor for other behaviors that they deem inappropriate,
62 | threatening, offensive, or harmful.
63 |
64 | ### Scope
65 |
66 | This Code of Conduct applies both within project spaces and in public spaces
67 | when an individual is representing the project or its community. Examples of
68 | representing a project or community include using an official project e-mail
69 | address, posting via an official social media account, or acting as an appointed
70 | representative at an online or offline event. Representation of a project may be
71 | further defined and clarified by project maintainers.
72 |
73 | ### Enforcement
74 |
75 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
76 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
77 | complaints will be reviewed and investigated and will result in a response that
78 | is deemed necessary and appropriate to the circumstances. The project team is
79 | obligated to maintain confidentiality with regard to the reporter of an incident.
80 | Further details of specific enforcement policies may be posted separately.
81 |
82 | Project maintainers who do not follow or enforce the Code of Conduct in good
83 | faith may face temporary or permanent repercussions as determined by other
84 | members of the project's leadership.
85 |
86 | ### Attribution
87 |
88 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
89 | available at [http://contributor-covenant.org/version/1/4][version]
90 |
91 | [homepage]: http://contributor-covenant.org
92 | [version]: http://contributor-covenant.org/version/1/4/
93 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | # docker-compose.yml file
2 |
3 | version: '3.6'
4 |
5 | services:
6 | search_service:
7 | build:
8 | context: ./search-service
9 | dockerfile: Dockerfile-dev
10 | volumes:
11 | - type: bind
12 | source: ./search-service/
13 | target: /usr/app
14 | depends_on:
15 | - elasticsearch
16 | links:
17 | - elasticsearch
18 | ports:
19 | - 3009:3009
20 | command: npm run install_and_run
21 | web_ui:
22 | build:
23 | context: ./web-ui
24 | dockerfile: Dockerfile-dev
25 | volumes:
26 | - type: bind
27 | source: ./web-ui/
28 | target: /usr/app
29 | ports:
30 | - 3003:3003
31 | command: npm run start
32 | auth_service:
33 | build:
34 | context: ./auth-service
35 | dockerfile: Dockerfile-dev
36 | volumes:
37 | - type: bind
38 | source: ./auth-service/
39 | target: /usr/app
40 | depends_on:
41 | - elasticsearch
42 | links:
43 | - elasticsearch
44 | ports:
45 | - 3010:3010
46 | command: npm run install_and_run
47 | elasticsearch:
48 | image: docker.elastic.co/elasticsearch/elasticsearch:7.5.1
49 | environment:
50 | - discovery.type=single-node
51 | ports:
52 | - 9300:9300
53 | - 9200:9200
54 | kibana:
55 | image: docker.elastic.co/kibana/kibana:7.5.2
56 | ports:
57 | - 5601:5601
58 | environment:
59 | ELASTICSEARCH_HOSTS: http://elasticsearch:9200
60 | rabbitmq:
61 | image: rabbitmq
62 | ports:
63 | - "15672:15672"
64 | - "5672:5672"
65 | mysql:
66 | image: mysql
67 | command: --default-authentication-plugin=mysql_native_password
68 | restart: always
69 | environment:
70 | MYSQL_ROOT_PASSWORD: w3lc0me!
71 | adminer:
72 | image: adminer
73 | restart: always
74 | ports:
75 | - 8080:8080
76 |
77 |
--------------------------------------------------------------------------------
/elastic/README.md:
--------------------------------------------------------------------------------
1 | # Elastic
2 |
3 | ## Run locally with docker-compose
4 |
5 | `$ docker-compose up`
6 |
7 | ## Browse
8 |
9 | * go to : `http://localhost:9200/` for elasticsearch
10 | * go to : `http://localhost:5601/` for Kibana
11 |
--------------------------------------------------------------------------------
/elastic/docker-compose.yml:
--------------------------------------------------------------------------------
1 | # docker-compose.yml file
2 |
3 | version: '3.6'
4 |
5 | services:
6 | elasticsearch:
7 | image: docker.elastic.co/elasticsearch/elasticsearch:7.5.1
8 | container_name: elasticsearch
9 | environment:
10 | - discovery.type=single-node
11 | ports:
12 | - 9300:9300
13 | - 9200:9200
14 | kibana:
15 | image: docker.elastic.co/kibana/kibana:7.5.2
16 | container_name: kibana
17 | ports:
18 | - 5601:5601
19 | environment:
20 | ELASTICSEARCH_HOSTS: http://elasticsearch:9200
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrainBackup/bb8/1562da57815536d5da7a48df37e5e018e69b0413/logo.png
--------------------------------------------------------------------------------
/mysql/README.md:
--------------------------------------------------------------------------------
1 | # Mysql DB
2 |
3 | ## Run locally with docker-compose
4 |
5 | `$ docker-compose up --build`
6 |
7 | ## Connect to web ui - Adminer
8 |
9 | `$ open localhost:8080`
10 |
11 | `$ user: root; password: check docker-compose.yml file`
12 |
--------------------------------------------------------------------------------
/mysql/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.6'
2 |
3 | services:
4 | mysql:
5 | image: mysql
6 | command: --default-authentication-plugin=mysql_native_password
7 | restart: always
8 | environment:
9 | MYSQL_ROOT_PASSWORD: w3lc0me!
10 | MYSQL_DATABASE: auth_service
11 | ports:
12 | - 3306:3306
13 | adminer:
14 | image: adminer
15 | restart: always
16 | ports:
17 | - 8080:8080
18 |
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "workspaces": [
4 | "auth-service",
5 | "search-service",
6 | "text-service",
7 | "web-ui"
8 | ]
9 | }
--------------------------------------------------------------------------------
/run_auth_server.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # dir=$(pwd) -v $dir:/app
3 | cd auth-service
4 | docker build -t auth_service .
5 | docker run -p 3010:3010 -t auth_service
6 |
--------------------------------------------------------------------------------
/text-ml-service/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM pytorch/pytorch
2 | WORKDIR /usr/app
--------------------------------------------------------------------------------
/text-service/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | project: 'tsconfig.json',
5 | sourceType: 'module',
6 | },
7 | plugins: ['@typescript-eslint/eslint-plugin'],
8 | extends: [
9 | 'plugin:@typescript-eslint/eslint-recommended',
10 | 'plugin:@typescript-eslint/recommended',
11 | 'prettier',
12 | 'prettier/@typescript-eslint',
13 | ],
14 | root: true,
15 | env: {
16 | node: true,
17 | jest: true,
18 | },
19 | rules: {
20 | '@typescript-eslint/interface-name-prefix': 'off',
21 | '@typescript-eslint/explicit-function-return-type': 'off',
22 | '@typescript-eslint/no-explicit-any': 'off',
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/text-service/.gitignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist
3 | /node_modules
4 |
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 | lerna-debug.log*
12 |
13 | # OS
14 | .DS_Store
15 |
16 | # Tests
17 | /coverage
18 | /.nyc_output
19 |
20 | # IDEs and editors
21 | /.idea
22 | .project
23 | .classpath
24 | .c9/
25 | *.launch
26 | .settings/
27 | *.sublime-workspace
28 |
29 | # IDE - VSCode
30 | .vscode/*
31 | !.vscode/settings.json
32 | !.vscode/tasks.json
33 | !.vscode/launch.json
34 | !.vscode/extensions.json
--------------------------------------------------------------------------------
/text-service/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all"
4 | }
--------------------------------------------------------------------------------
/text-service/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12.16
2 | WORKDIR /usr/app
3 | COPY . .
4 | RUN yarn
5 | RUN yarn build
6 | CMD yarn start:prod
--------------------------------------------------------------------------------
/text-service/nest-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "collection": "@nestjs/schematics",
3 | "sourceRoot": "src"
4 | }
5 |
--------------------------------------------------------------------------------
/text-service/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "text-service",
3 | "version": "0.0.1",
4 | "description": "",
5 | "author": "Yohay Nahmany",
6 | "private": true,
7 | "license": "MIT",
8 | "scripts": {
9 | "prebuild": "rimraf dist",
10 | "build": "nest build",
11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
12 | "start": "nest start",
13 | "start:dev": "nest start --watch",
14 | "start:debug": "nest start --debug --watch",
15 | "start:prod": "node dist/main",
16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
17 | "test": "jest",
18 | "test:watch": "jest --watch",
19 | "test:cov": "jest --coverage",
20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
21 | "test:e2e": "jest --config ./test/jest-e2e.json"
22 | },
23 | "dependencies": {
24 | "@elastic/elasticsearch": "^7.6.1",
25 | "@nestjs/common": "^7.0.0",
26 | "@nestjs/config": "^0.4.1",
27 | "@nestjs/core": "^7.0.0",
28 | "@nestjs/elasticsearch": "^7.1.0",
29 | "@nestjs/platform-express": "^7.0.0",
30 | "@nestjs/swagger": "^4.5.5",
31 | "reflect-metadata": "^0.1.13",
32 | "rimraf": "^3.0.2",
33 | "rxjs": "^6.5.4"
34 | },
35 | "devDependencies": {
36 | "@nestjs/cli": "^7.0.0",
37 | "@nestjs/schematics": "^7.0.0",
38 | "@nestjs/testing": "^7.0.0",
39 | "@types/express": "^4.17.3",
40 | "@types/jest": "25.1.4",
41 | "@types/node": "^13.9.1",
42 | "@types/supertest": "^2.0.8",
43 | "@typescript-eslint/eslint-plugin": "^2.23.0",
44 | "@typescript-eslint/parser": "^2.23.0",
45 | "eslint": "^6.8.0",
46 | "eslint-config-prettier": "^6.10.0",
47 | "eslint-plugin-import": "^2.20.1",
48 | "jest": "^25.1.0",
49 | "prettier": "^1.19.1",
50 | "supertest": "^4.0.2",
51 | "ts-jest": "25.2.1",
52 | "ts-loader": "^6.2.1",
53 | "ts-node": "^8.6.2",
54 | "tsconfig-paths": "^3.9.0",
55 | "typescript": "^3.7.4"
56 | },
57 | "jest": {
58 | "moduleFileExtensions": [
59 | "js",
60 | "json",
61 | "ts"
62 | ],
63 | "rootDir": "src",
64 | "testRegex": ".spec.ts$",
65 | "transform": {
66 | "^.+\\.(t|j)s$": "ts-jest"
67 | },
68 | "coverageDirectory": "../coverage",
69 | "testEnvironment": "node"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/text-service/src/app.controller.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { AppController } from './app.controller';
3 | import { AppService } from './app.service';
4 |
5 | describe('AppController', () => {
6 | let appController: AppController;
7 |
8 | beforeEach(async () => {
9 | const app: TestingModule = await Test.createTestingModule({
10 | controllers: [AppController],
11 | providers: [AppService],
12 | }).compile();
13 |
14 | appController = app.get(AppController);
15 | });
16 |
17 | describe('root', () => {
18 | it('should return UP status from health"', () => {
19 | expect(appController.getHealthStatus().status).toBe('UP');
20 | });
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/text-service/src/app.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get } from '@nestjs/common';
2 | import { AppService, HealthCheck } from './app.service';
3 |
4 | @Controller()
5 | export class AppController {
6 | constructor(private readonly appService: AppService) {}
7 |
8 | @Get('/health')
9 | getHealthStatus(): HealthCheck {
10 | return this.appService.checkHealth();
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/text-service/src/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Module, HttpModule } from '@nestjs/common';
2 | import { AppController } from './app.controller';
3 | import { AppService } from './app.service';
4 | import { SearchController } from './search/search.controller';
5 | import { ConfigModule } from '@nestjs/config';
6 | import Configuration from './config/configuration';
7 | import { SearchService } from './search/search.service';
8 |
9 | @Module({
10 | imports: [
11 | HttpModule,
12 | ConfigModule.forRoot({
13 | load: [Configuration]
14 | })
15 | ],
16 | controllers: [AppController, SearchController],
17 | providers: [AppService, SearchService],
18 | })
19 | export class AppModule {}
20 |
--------------------------------------------------------------------------------
/text-service/src/app.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 |
3 | interface Service {
4 | name: string;
5 | version: string;
6 | status: string;
7 | }
8 | export interface HealthCheck {
9 | status: string;
10 | services: Service[];
11 | }
12 | @Injectable()
13 | export class AppService {
14 | checkHealth(): HealthCheck {
15 | return { status: 'UP', services: [{ name: 'elastic', version: '7.5.1', status: 'UP' }]}
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/text-service/src/config/configuration.ts:
--------------------------------------------------------------------------------
1 | interface IElastic {
2 | host: string;
3 | port: number;
4 | username?: string;
5 | password?: string;
6 | name?: string;
7 | }
8 | interface Auth {
9 | key: string;
10 | secret: string;
11 | callback: string;
12 | }
13 | interface Configuration {
14 | port: number,
15 | elastic: IElastic,
16 | twitter?: Auth
17 | }
18 | interface ConfigurationResult {
19 | development: Configuration;
20 | testing: Configuration;
21 | production?: Configuration;
22 | }
23 | const configuration: ConfigurationResult = {
24 | development: {
25 | port: parseInt(process.env.PORT, 10) || 3009,
26 | elastic: {
27 | host: 'localhost',
28 | port: 9200,
29 | username: 'root',
30 | password: 'w3lc0me!',
31 | name: 'auth_service',
32 | },
33 | },
34 | testing: {
35 | port: parseInt(process.env.PORT, 10) || 3009,
36 | elastic: {
37 | host: 'localhost',
38 | port: 9200,
39 | username: 'root',
40 | password: 'w3lc0me!',
41 | name: 'auth_service',
42 | }
43 | }
44 | };
45 | export default (): Configuration => configuration[process.env.NODE_ENV] || configuration['development'];
46 |
--------------------------------------------------------------------------------
/text-service/src/elastic/index.ts:
--------------------------------------------------------------------------------
1 | import { Client as NativeClient } from '@elastic/elasticsearch';
2 | import Configuration from '../config/configuration';
3 | const configuration = Configuration();
4 | const convertArrayToObject = (array: any, key: string) => {
5 | const initialValue = {};
6 | return array.reduce((obj: any, item: any) => {
7 | return {
8 | ...obj,
9 | [item[key]]: item,
10 | };
11 | }, initialValue);
12 | };
13 | const client = new NativeClient({
14 | node: `http://${configuration.elastic.host}:${configuration.elastic.port}`
15 | });
16 |
17 |
18 | const GetClient = () => {
19 | return client;
20 | }
21 | interface Body {
22 | properties: Record
23 | }
24 | interface Scheme {
25 | index: string,
26 | body: Body
27 | }
28 | const Snippets: Scheme = {
29 | index: 'snippets',
30 | body: {
31 | "properties": {
32 | "selectionText": {
33 | "type": "text"
34 | },
35 | "pageUrl": {
36 | "type": "text"
37 | }
38 | }
39 | }
40 | }
41 | const Test: Scheme = {
42 | index: 'test',
43 | body: Object.create({})
44 | }
45 | const Schemes: Array = [Snippets, Test];
46 | const SchemesNameToIndices = convertArrayToObject(Schemes, 'index');
47 |
48 | const init = async (): Promise => {
49 | try {
50 | const client = GetClient();
51 | const indices = Object.keys(SchemesNameToIndices);
52 |
53 | await Promise.all(indices.map(async index => {
54 | const { body: isIndexExist } = await client.indices.exists({ index });
55 | if (!isIndexExist) {
56 | await client.indices.create({ index })
57 | await client.indices.putMapping(SchemesNameToIndices[index])
58 | }
59 | }));
60 | }
61 | catch(err) {
62 | console.log(err);
63 | throw err;
64 | }
65 | }
66 |
67 | export default { GetClient, Schemes, SchemesNameToIndices, init };
68 |
--------------------------------------------------------------------------------
/text-service/src/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from '@nestjs/core';
2 | import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
3 | import { AppModule } from './app.module';
4 | import configuration from './config/configuration';
5 |
6 |
7 | async function bootstrap() {
8 | const app = await NestFactory.create(AppModule);
9 | const Configuration = configuration();
10 | const options = new DocumentBuilder()
11 | .setTitle('Text Service')
12 | .setDescription('Text service should provide all apis for storing and retrieve text data for bb project')
13 | .setVersion('1.0')
14 | .addTag('text')
15 | .build();
16 | const document = SwaggerModule.createDocument(app, options);
17 | SwaggerModule.setup('api', app, document);
18 | app.enableCors();
19 | await app.listen(Configuration.port);
20 | }
21 | bootstrap();
22 |
--------------------------------------------------------------------------------
/text-service/src/search/search.controller.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { SearchController } from './search.controller';
3 | import { SearchService } from './search.service';
4 |
5 | describe('Search Controller', () => {
6 | let controller: SearchController;
7 |
8 | beforeEach(async () => {
9 | const module: TestingModule = await Test.createTestingModule({
10 | controllers: [SearchController],
11 | providers: [SearchService]
12 | }).compile();
13 |
14 | controller = module.get(SearchController);
15 | });
16 |
17 | it('should be defined', () => {
18 | expect(controller).toBeDefined();
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/text-service/src/search/search.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get, Post, Body } from '@nestjs/common';
2 | import { SearchService } from './search.service';
3 |
4 | import Elastic from '../elastic';
5 | import { ApiResponse, RequestParams } from '@elastic/elasticsearch'
6 | const SNIPPET_INDEX = Elastic.SchemesNameToIndices['snippets'].index;
7 |
8 | interface Snippet {
9 | pageUrl: string,
10 | selectionText: string,
11 | menuItemId: string
12 | }
13 |
14 | @Controller('search')
15 | export class SearchController {
16 | constructor(private service: SearchService) { }
17 | @Get('/')
18 | async getText(): Promise {
19 | try {
20 | const params1: RequestParams.Search = {
21 | index: SNIPPET_INDEX
22 | }
23 | const client = Elastic.GetClient();
24 | const result: ApiResponse = await client.search(params1);
25 | return result.body.hits.hits;
26 | }
27 | catch (err) {
28 | return err;
29 | }
30 |
31 | }
32 | @Post('/')
33 | // @UsePipes(new JoiValidationPipe(createWebRegisterSchema()))
34 | async createText(@Body() data: Snippet): Promise {
35 | try {
36 | const client = Elastic.GetClient();
37 | const doc: RequestParams.Index = {
38 | index: SNIPPET_INDEX,
39 | refresh: "true",
40 | body: {
41 | selectionText: data.selectionText,
42 | pageUrl: data.pageUrl
43 | }
44 | }
45 | await client.index(doc);
46 | return 'Created successfully';
47 | }
48 | catch (err) {
49 | return err;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/text-service/src/search/search.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { SearchService } from './search.service';
3 | import { SearchController } from './search.controller';
4 |
5 | @Module({
6 | providers: [SearchService],
7 | controllers: [SearchController],
8 | exports: [SearchService]
9 | })
10 | export class SearchModule {}
11 |
--------------------------------------------------------------------------------
/text-service/src/search/search.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | @Injectable()
3 | export class SearchService {
4 | getTest() {
5 | return 'another text';
6 | }
7 |
8 | }
--------------------------------------------------------------------------------
/text-service/test/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { INestApplication } from '@nestjs/common';
3 | import * as request from 'supertest';
4 | import { AppModule } from './../src/app.module';
5 |
6 | describe('AppController (e2e)', () => {
7 | let app: INestApplication;
8 |
9 | beforeEach(async () => {
10 | const moduleFixture: TestingModule = 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 |
--------------------------------------------------------------------------------
/text-service/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 |
--------------------------------------------------------------------------------
/text-service/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/text-service/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "declaration": true,
5 | "removeComments": true,
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "target": "es2017",
9 | "sourceMap": true,
10 | "outDir": "./dist",
11 | "baseUrl": "./",
12 | "incremental": true
13 | },
14 | "exclude": ["node_modules", "dist"]
15 | }
16 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noErrorTruncation": true,
4 | "noImplicitAny": true,
5 | "suppressImplicitAnyIndexErrors": false,
6 | "strictNullChecks": true,
7 | "noFallthroughCasesInSwitch": true,
8 | "noImplicitReturns": true,
9 | "noUnusedLocals": true,
10 | "noUnusedParameters": false,
11 | "noImplicitThis": false,
12 | "forceConsistentCasingInFileNames": true,
13 | "skipLibCheck": true,
14 | "removeComments": false,
15 | "preserveConstEnums": true,
16 | "sourceMap": true,
17 | "experimentalDecorators": true,
18 | "emitDecoratorMetadata": true,
19 | "target": "es2015",
20 | "module": "commonjs",
21 | "jsx": "react",
22 | "lib": [
23 | "DOM",
24 | "ESnext"
25 | ],
26 | "baseUrl": ".",
27 | "outDir": "dist",
28 | "rootDir": ".",
29 | "paths": {
30 | "~/*": ["*/*"]
31 | }
32 | },
33 | "exclude": [
34 | "node_modules",
35 | "dist"
36 | ]
37 | }
--------------------------------------------------------------------------------
/type-test/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | exports.__esModule = true;
3 | var index_1 = require("@brain-backup/types/index");
4 | var q = {
5 | a: 'what is the time',
6 | q: ' dont know',
7 | date: new Date()
8 | };
9 | console.log(index_1.mlqa(q));
10 |
--------------------------------------------------------------------------------
/type-test/index.ts:
--------------------------------------------------------------------------------
1 | import { mlqa, Question } from '@brain-backup/types/index';
2 |
3 | const q: Question = {
4 | a: 'what is the time',
5 | q: ' dont know',
6 | date: new Date()
7 | }
8 | console.log(mlqa(q));
--------------------------------------------------------------------------------
/type-test/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "type-test",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@brain-backup/types": {
8 | "version": "0.0.1",
9 | "resolved": "https://registry.npmjs.org/@brain-backup/types/-/types-0.0.1.tgz",
10 | "integrity": "sha512-KBXdsxv+fQWPx6YXb8ssjTGeT0ayLEjYnP7Esjh/mWfdyfHGPYVe/vAnhbWBNIHAcnUabkANdFcq4Taf5TctVg=="
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/type-test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "type-test",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "@brain-backup/types": "0.0.1"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/types/bb-tests.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrainBackup/bb8/1562da57815536d5da7a48df37e5e018e69b0413/types/bb-tests.ts
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | export interface Question {
2 | q?: string;
3 | a?: string;
4 | date: Date;
5 | }
6 |
--------------------------------------------------------------------------------
/types/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | exports.__esModule = true;
3 | function mlqa(q) {
4 | return 9;
5 | }
6 | exports.mlqa = mlqa;
7 |
--------------------------------------------------------------------------------
/types/index.ts:
--------------------------------------------------------------------------------
1 | export interface Question {
2 | q?: string;
3 | a?: string;
4 | date: Date;
5 | }
6 |
7 | export function mlqa(q: Question) : number {
8 | return 9;
9 | }
10 |
--------------------------------------------------------------------------------
/types/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@bb/types",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1
5 | }
6 |
--------------------------------------------------------------------------------
/types/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@brain-backup/types",
3 | "version": "0.0.2",
4 | "description": "types for Brain backup(bb) organization.",
5 | "main": "index.js",
6 | "typings": "index.d.ts",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "author": "Yohay Nahmany",
11 | "license": "ISC"
12 | }
13 |
--------------------------------------------------------------------------------
/types/package.json.old:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@bb/types",
3 | "author": "Yohay Nahmany",
4 | "version": "1.0.0",
5 | "types": "./index.d.ts"
6 | }
--------------------------------------------------------------------------------
/types/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "node",
4 | "module": "commonjs",
5 | "lib": [
6 | "es6"
7 | ],
8 | "noImplicitAny": true,
9 | "noImplicitThis": true,
10 | "strictNullChecks": true,
11 | "strictFunctionTypes": true,
12 | "types": [],
13 | "noEmit": true,
14 | "forceConsistentCasingInFileNames": true
15 | },
16 | "files": [
17 | "index.d.ts"
18 | ]
19 | }
--------------------------------------------------------------------------------
/types/tslint.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrainBackup/bb8/1562da57815536d5da7a48df37e5e018e69b0413/types/tslint.json
--------------------------------------------------------------------------------
/web-ui/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | .DS_STORE
3 | node_modules
4 | .module-cache
5 | *.log*
6 | build
7 | dist
--------------------------------------------------------------------------------
/web-ui/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "always",
3 | "semi": true,
4 | "useTabs": false,
5 | "tabWidth": 2,
6 | "bracketSpacing": true,
7 | "singleQuote": true
8 | }
9 |
--------------------------------------------------------------------------------
/web-ui/.storybook/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "allowSyntheticDefaultImports": true,
5 | "module": "es2015",
6 | "target": "es5",
7 | "lib": ["es6", "dom"],
8 | "sourceMap": true,
9 | "allowJs": false,
10 | "jsx": "react",
11 | "moduleResolution": "node",
12 | "rootDir": "../",
13 | "outDir": "dist",
14 | "noImplicitReturns": true,
15 | "noImplicitThis": true,
16 | "noImplicitAny": true,
17 | "strictNullChecks": true,
18 | "declaration": true
19 | },
20 | "include": [
21 | "src/**/*"
22 | ],
23 | "exclude": [
24 | "node_modules",
25 | "build",
26 | "dist",
27 | "scripts",
28 | "acceptance-tests",
29 | "webpack",
30 | "jest",
31 | "src/setupTests.ts",
32 | "**/*/*.test.ts",
33 | "examples"
34 | ]
35 | }
--------------------------------------------------------------------------------
/web-ui/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const SRC_PATH = path.join(__dirname, '../src');
3 | // const STORIES_PATH = path.join(__dirname, '../stories');
4 | //dont need stories path if you have your stories inside your //components folder
5 | module.exports = ({config}) => {
6 | config.module.rules.push({
7 | test: /\.(ts|tsx)$/,
8 | include: [SRC_PATH],
9 | use: [
10 | {
11 | loader: require.resolve('awesome-typescript-loader'),
12 | options: {
13 | configFileName: './.storybook/tsconfig.json'
14 | }
15 | },
16 | { loader: require.resolve('react-docgen-typescript-loader') }
17 | ]
18 | });
19 | config.resolve.extensions.push('.ts', '.tsx');
20 | return config;
21 | };
--------------------------------------------------------------------------------
/web-ui/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:13.6
2 | WORKDIR /usr/app
3 | COPY . .
4 | RUN npm cache clean --force
5 | RUN rm package-lock.json
6 | RUN npm install
7 | CMD npm run start
8 |
--------------------------------------------------------------------------------
/web-ui/Dockerfile-dev:
--------------------------------------------------------------------------------
1 | FROM node:13.6
2 | WORKDIR /usr/app
3 |
--------------------------------------------------------------------------------
/web-ui/README.md:
--------------------------------------------------------------------------------
1 | # Brain backup - web ui
2 |
3 | ```
4 | $ yarn start
5 | ```
6 |
7 | ```
8 | $ yarn install
9 | ```
10 |
11 | ```
12 | $ yarn run storybook
13 | ```
14 |
--------------------------------------------------------------------------------
/web-ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web-ui",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "Web application for brain backup project",
6 | "author": "Yohay Nahmany",
7 | "main": "index.js",
8 | "scripts": {
9 | "test": "echo \"Error: no test specified\" && exit 1",
10 | "start": "webpack-dev-server --mode development --host 0.0.0.0 --hot --progress --colors --port 3003 --open",
11 | "build": "webpack -p --progress --colors",
12 | "prettier": "prettier --write \"src/**/*.{ts,tsx,css}\"",
13 | "storybook": "start-storybook -p 9001 -c .storybook"
14 | },
15 | "license": "MIT",
16 | "devDependencies": {
17 | "@babel/core": "^7.2.2",
18 | "@storybook/react": "^5.3.12",
19 | "@types/axios": "^0.14.0",
20 | "@types/classnames": "^2.2.7",
21 | "@types/node": "^10.12.18",
22 | "@types/react": "^16.7.20",
23 | "@types/react-dom": "^16.0.11",
24 | "@types/react-router": "^4.4.3",
25 | "@types/webpack": "^4.4.23",
26 | "awesome-typescript-loader": "^5.2.1",
27 | "babel-loader": "^8.0.5",
28 | "css-loader": "^2.1.0",
29 | "file-loader": "^3.0.1",
30 | "html-loader": "^1.0.0-alpha.0",
31 | "html-webpack-plugin": "^3.2.0",
32 | "mini-css-extract-plugin": "^0.5.0",
33 | "mobx-react-devtools": "^6.0.3",
34 | "postcss": "^8.2.10",
35 | "postcss-browser-reporter": "^0.5.0",
36 | "postcss-import": "^12.0.1",
37 | "postcss-loader": "^3.0.0",
38 | "postcss-preset-env": "^6.5.0",
39 | "postcss-reporter": "^6.0.1",
40 | "postcss-url": "^8.0.0",
41 | "prettier": "^1.16.0",
42 | "react-docgen-typescript-loader": "^3.6.0",
43 | "react-hot-loader": "^4.6.3",
44 | "storybook": "^5.3.12",
45 | "style-loader": "^0.23.1",
46 | "ts-loader": "^5.3.3",
47 | "typescript": "^3.2.4",
48 | "url-loader": "^1.1.2",
49 | "webpack": "^4.29.0",
50 | "webpack-cleanup-plugin": "^0.5.1",
51 | "webpack-cli": "^3.2.1",
52 | "webpack-dev-server": "^3.1.14",
53 | "webpack-hot-middleware": "^2.24.3"
54 | },
55 | "dependencies": {
56 | "@material-ui/core": "~4.11.4",
57 | "@material-ui/icons": "~4.11.2",
58 | "axios": "^1.7.8",
59 | "classnames": "~2.3.1",
60 | "mobx": "~5.15.7",
61 | "mobx-react": "~5.4.3",
62 | "mobx-react-router": "~4.0.5",
63 | "react": "~16.14.0",
64 | "react-dom": "~16.14.0",
65 | "react-icons": "^3.11.0",
66 | "react-router": "~4.3.1"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/web-ui/semantic.json:
--------------------------------------------------------------------------------
1 | {
2 | "base": "semantic/",
3 | "paths": {
4 | "source": {
5 | "config": "src/theme.config",
6 | "definitions": "src/definitions/",
7 | "site": "src/site/",
8 | "themes": "src/themes/"
9 | },
10 | "output": {
11 | "packaged": "dist/",
12 | "uncompressed": "dist/components/",
13 | "compressed": "dist/components/",
14 | "themes": "dist/themes/"
15 | },
16 | "clean": "dist/"
17 | },
18 | "permission": false,
19 | "autoInstall": false,
20 | "rtl": false,
21 | "version": "2.4.2"
22 | }
--------------------------------------------------------------------------------
/web-ui/src/app/components/CustomInput.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { FormControl, InputLabel, Input } from '@material-ui/core';
3 |
4 | interface IUsername {
5 | onChange: React.ChangeEventHandler,
6 | label: string,
7 | fullWidth?: boolean,
8 | style?: object
9 | }
10 | const CustomInput: React.FunctionComponent = ({ label, onChange, ...props }) => {
11 | return (
12 |
13 | {label}
14 |
15 |
16 | )
17 | }
18 | export default CustomInput;
19 |
--------------------------------------------------------------------------------
/web-ui/src/app/components/Footer/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as classNames from 'classnames';
3 | import * as style from './style.css';
4 | import {
5 | TodoFilter,
6 | TODO_FILTER_TITLES,
7 | TODO_FILTER_TYPES
8 | } from 'app/constants';
9 |
10 | export interface FooterProps {
11 | filter: TodoFilter;
12 | activeCount: number;
13 | completedCount: number;
14 | onChangeFilter: (filter: TodoFilter) => any;
15 | onClearCompleted: () => any;
16 | }
17 |
18 | export interface FooterState {
19 | /* empty */
20 | }
21 |
22 | export class Footer extends React.Component {
23 | renderTodoCount() {
24 | const { activeCount } = this.props;
25 | const itemWord = activeCount === 1 ? 'item' : 'items';
26 |
27 | return (
28 |
29 | {activeCount || 'No'} {itemWord} left
30 |
31 | );
32 | }
33 |
34 | renderFilterLink(filter: TodoFilter) {
35 | const title = TODO_FILTER_TITLES[filter];
36 | const { filter: selectedFilter, onChangeFilter } = this.props;
37 | const className = classNames({
38 | [style.selected]: filter === selectedFilter
39 | });
40 |
41 | return (
42 | onChangeFilter(filter)}
46 | >
47 | {title}
48 |
49 | );
50 | }
51 |
52 | renderClearButton() {
53 | const { completedCount, onClearCompleted } = this.props;
54 | if (completedCount > 0) {
55 | return (
56 |
57 | );
58 | }
59 | }
60 |
61 | render() {
62 | return (
63 |
72 | );
73 | }
74 | }
75 |
76 | export default Footer;
77 |
--------------------------------------------------------------------------------
/web-ui/src/app/components/Footer/style.css:
--------------------------------------------------------------------------------
1 | .normal {
2 | color: #777;
3 | padding: 10px 15px;
4 | height: 20px;
5 | text-align: center;
6 | border-top: 1px solid #e6e6e6;
7 | }
8 |
9 | .normal:before {
10 | content: '';
11 | position: absolute;
12 | right: 0;
13 | bottom: 0;
14 | left: 0;
15 | height: 50px;
16 | overflow: hidden;
17 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
18 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
19 | 0 17px 2px -6px rgba(0, 0, 0, 0.2);
20 | }
21 |
22 | .filters {
23 | margin: 0;
24 | padding: 0;
25 | list-style: none;
26 | position: absolute;
27 | right: 0;
28 | left: 0;
29 | }
30 |
31 | .filters li {
32 | display: inline;
33 | }
34 |
35 | .filters li a {
36 | color: inherit;
37 | margin: 3px;
38 | padding: 3px 7px;
39 | text-decoration: none;
40 | border: 1px solid transparent;
41 | border-radius: 3px;
42 | }
43 |
44 | .filters li a.selected,
45 | .filters li a:hover {
46 | border-color: rgba(175, 47, 47, 0.1);
47 | }
48 |
49 | .filters li a.selected {
50 | border-color: rgba(175, 47, 47, 0.2);
51 | }
52 |
53 | .count {
54 | float: left;
55 | text-align: left;
56 | }
57 |
58 | .count strong {
59 | font-weight: 300;
60 | }
61 |
62 | .clearCompleted,
63 | html .clearCompleted:active {
64 | float: right;
65 | position: relative;
66 | line-height: 20px;
67 | text-decoration: none;
68 | cursor: pointer;
69 | visibility: hidden;
70 | position: relative;
71 | }
72 |
73 | .clearCompleted::after {
74 | visibility: visible;
75 | content: 'Clear completed';
76 | position: absolute;
77 | right: 0;
78 | white-space: nowrap;
79 | }
80 |
81 | .clearCompleted:hover::after {
82 | text-decoration: underline;
83 | }
84 |
85 | @media (max-width: 430px) {
86 | .normal {
87 | height: 50px;
88 | }
89 | .filters {
90 | bottom: 10px;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/web-ui/src/app/components/Header/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { TodoTextInput } from 'app/components/TodoTextInput';
3 | import { TodoModel } from 'app/models/TodoModel';
4 |
5 | export interface HeaderProps {
6 | addTodo: (todo: Partial) => any;
7 | }
8 |
9 | export interface HeaderState {
10 | /* empty */
11 | }
12 |
13 | export class Header extends React.Component {
14 | private handleSave = (text: string) => {
15 | if (text.length) {
16 | this.props.addTodo({ text });
17 | }
18 | };
19 |
20 | render() {
21 | return (
22 |
30 | );
31 | }
32 | }
33 |
34 | export default Header;
35 |
--------------------------------------------------------------------------------
/web-ui/src/app/components/Navigation.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import clsx from 'clsx';
3 | import { createStyles, makeStyles, useTheme, Theme } from '@material-ui/core/styles';
4 | import Drawer from '@material-ui/core/Drawer';
5 | import AppBar from '@material-ui/core/AppBar';
6 | import Toolbar from '@material-ui/core/Toolbar';
7 | import List from '@material-ui/core/List';
8 | import CssBaseline from '@material-ui/core/CssBaseline';
9 | import Typography from '@material-ui/core/Typography';
10 | import Divider from '@material-ui/core/Divider';
11 | import IconButton from '@material-ui/core/IconButton';
12 | import MenuIcon from '@material-ui/icons/Menu';
13 | import ChevronLeftIcon from '@material-ui/icons/ChevronLeft';
14 | import ChevronRightIcon from '@material-ui/icons/ChevronRight';
15 | import ListItem from '@material-ui/core/ListItem';
16 | import ListItemIcon from '@material-ui/core/ListItemIcon';
17 | import ListItemText from '@material-ui/core/ListItemText';
18 | import SettingsIcon from '@material-ui/icons/Settings';
19 | import CodeIcon from '@material-ui/icons/Code';
20 | import DynamicFeedIcon from '@material-ui/icons/DynamicFeed';
21 | import PersonOutlineIcon from '@material-ui/icons/PersonOutline';
22 | import history from '../history';
23 |
24 | const drawerWidth = 240;
25 |
26 | const useStyles = makeStyles((theme: Theme) =>
27 | createStyles({
28 | root: {
29 | display: 'flex'
30 | },
31 | appBar: {
32 | zIndex: theme.zIndex.drawer + 1,
33 | transition: theme.transitions.create(['width', 'margin'], {
34 | easing: theme.transitions.easing.sharp,
35 | duration: theme.transitions.duration.leavingScreen,
36 | }),
37 | },
38 | appBarShift: {
39 | marginLeft: drawerWidth,
40 | width: `calc(100% - ${drawerWidth}px)`,
41 | transition: theme.transitions.create(['width', 'margin'], {
42 | easing: theme.transitions.easing.sharp,
43 | duration: theme.transitions.duration.enteringScreen,
44 | }),
45 | },
46 | menuButton: {
47 | marginRight: 36,
48 | },
49 | hide: {
50 | display: 'none',
51 | },
52 | drawer: {
53 | width: drawerWidth,
54 | flexShrink: 0,
55 | whiteSpace: 'nowrap',
56 | },
57 | drawerOpen: {
58 | // backgroundColor: '#080f21',
59 | width: drawerWidth,
60 | transition: theme.transitions.create('width', {
61 | easing: theme.transitions.easing.sharp,
62 | duration: theme.transitions.duration.enteringScreen,
63 | }),
64 | },
65 | drawerClose: {
66 | transition: theme.transitions.create('width', {
67 | easing: theme.transitions.easing.sharp,
68 | duration: theme.transitions.duration.leavingScreen,
69 | }),
70 | overflowX: 'hidden',
71 | width: theme.spacing(7) + 1,
72 | [theme.breakpoints.up('sm')]: {
73 | width: theme.spacing(9) + 1,
74 | },
75 | },
76 | toolbar: {
77 | display: 'flex',
78 | alignItems: 'center',
79 | justifyContent: 'flex-end',
80 | padding: theme.spacing(0, 1),
81 | ...theme.mixins.toolbar,
82 | },
83 | content: {
84 | flexGrow: 1,
85 | padding: theme.spacing(3),
86 | },
87 | }),
88 | );
89 | interface NavigationInterface {
90 | children: React.ReactNode | React.ReactChild | React.ReactFragment | React.ReactPortal | boolean | null | undefined;
91 | }
92 | const MiniDrawer: React.FunctionComponent = ({ children }) => {
93 | const classes = useStyles();
94 | const theme = useTheme();
95 | const [open, setOpen] = React.useState(false);
96 |
97 | const handleDrawerOpen = () => {
98 | setOpen(true);
99 | };
100 |
101 | const handleDrawerClose = () => {
102 | setOpen(false);
103 | };
104 |
105 | return (
106 |
107 |
108 |
114 |
115 |
124 |
125 |
126 |
127 | Brain Backup
128 |
129 |
130 |
131 |
144 |
145 |
146 | {theme.direction === 'rtl' ? : }
147 |
148 |
149 |
150 |
151 | history.push('/')}>
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 | history.push('/login')}>
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 | {children}
175 |
176 |
177 | );
178 | }
179 | export default MiniDrawer;
180 |
--------------------------------------------------------------------------------
/web-ui/src/app/components/Password.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { FormControl, InputLabel, Input } from '@material-ui/core';
3 |
4 | interface IPassword {
5 | onChange: React.ChangeEventHandler
6 | }
7 | const Password: React.FunctionComponent = ({ onChange, ...props }) => {
8 | return (
9 |
10 | Password
11 |
18 |
19 | )
20 | }
21 | export default Password;
22 |
--------------------------------------------------------------------------------
/web-ui/src/app/components/PrivateRoute.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Route, Redirect } from 'react-router';
3 |
4 | interface IPrivateRoute {
5 | component: any,
6 | authed: boolean,
7 | address: string,
8 | [x:string]: any;
9 | }
10 | const PrivateRoute: React.FunctionComponent = ({ component: Component, authed, address, ...props }) => {
11 | return (
12 | authed === true
15 | ?
16 | : }
17 | />
18 | );
19 | }
20 | export default PrivateRoute;
21 |
--------------------------------------------------------------------------------
/web-ui/src/app/components/TodoItem/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as classNames from 'classnames';
3 | import { TodoTextInput } from 'app/components/TodoTextInput';
4 | import { TodoModel } from 'app/models/TodoModel';
5 | import * as style from './style.css';
6 |
7 | export interface TodoActions {
8 | editTodo: (id: number, data: Partial) => any;
9 | deleteTodo: (id: number) => any;
10 | }
11 |
12 | export interface TodoProps extends TodoActions {
13 | todo: TodoModel;
14 | }
15 |
16 | export interface TodoState {
17 | editing: boolean;
18 | }
19 |
20 | export class TodoItem extends React.Component {
21 | constructor(props?: TodoProps, context?: any) {
22 | super(props, context);
23 | this.state = { editing: false };
24 | }
25 |
26 | private handleDoubleClick = (e: React.SyntheticEvent) => {
27 | this.setState({ editing: true });
28 | };
29 |
30 | private handleToggleCheckbox = (e: React.SyntheticEvent) => {
31 | const { todo } = this.props;
32 | const target = e.target as any;
33 | if (
34 | target &&
35 | target.checked !== undefined &&
36 | target.checked !== todo.completed
37 | ) {
38 | this.updateTodo({ completed: target.checked });
39 | }
40 | };
41 |
42 | private handleClickDeleteButton = (e: React.SyntheticEvent) => {
43 | const { todo, deleteTodo } = this.props;
44 | deleteTodo(todo.id);
45 | };
46 |
47 | private updateTodo = (data: Partial) => {
48 | const { todo } = this.props;
49 | if (data.text !== undefined && data.text.trim().length === 0) {
50 | this.props.deleteTodo(todo.id);
51 | } else {
52 | this.props.editTodo(todo.id, data);
53 | }
54 | this.setState({ editing: false });
55 | };
56 |
57 | render() {
58 | const { todo } = this.props;
59 |
60 | const element = this.state.editing ? (
61 | this.updateTodo({ text })}
65 | />
66 | ) : (
67 |
68 |
74 |
75 |
76 |
77 |
81 |
82 | );
83 |
84 | const classes = classNames({
85 | [style.completed]: todo.completed,
86 | [style.editing]: this.state.editing,
87 | [style.normal]: !this.state.editing
88 | });
89 |
90 | return {element};
91 | }
92 | }
93 |
94 | export default TodoItem;
95 |
--------------------------------------------------------------------------------
/web-ui/src/app/components/TodoItem/style.css:
--------------------------------------------------------------------------------
1 | .normal .toggle {
2 | text-align: center;
3 | width: 40px;
4 | /* auto, since non-WebKit browsers doesn't support input styling */
5 | height: auto;
6 | position: absolute;
7 | top: 0;
8 | bottom: 0;
9 | margin: auto 0;
10 | /* Mobile Safari */
11 | border: none;
12 | appearance: none;
13 | }
14 |
15 | .normal .toggle:after {
16 | content: url('data:image/svg+xml;utf8,');
17 | }
18 |
19 | .normal .toggle:checked:after {
20 | content: url('data:image/svg+xml;utf8,');
21 | }
22 |
23 | .normal label {
24 | white-space: pre-line;
25 | word-break: break-all;
26 | padding: 15px 60px 15px 15px;
27 | margin-left: 45px;
28 | display: block;
29 | line-height: 1.2;
30 | transition: color 0.4s;
31 | }
32 |
33 | .normal .destroy {
34 | display: none;
35 | position: absolute;
36 | top: 0;
37 | right: 10px;
38 | bottom: 0;
39 | width: 40px;
40 | height: 40px;
41 | margin: auto 0;
42 | font-size: 30px;
43 | color: #cc9a9a;
44 | margin-bottom: 11px;
45 | transition: color 0.2s ease-out;
46 | }
47 |
48 | .normal .destroy:hover {
49 | color: #af5b5e;
50 | }
51 |
52 | .normal .destroy:after {
53 | content: '×';
54 | }
55 |
56 | .normal:hover .destroy {
57 | display: block;
58 | }
59 |
60 | .normal .edit {
61 | display: none;
62 | }
63 |
64 | .editing {
65 | border-bottom: none;
66 | padding: 0;
67 | composes: normal;
68 | }
69 |
70 | .editing:last-child {
71 | margin-bottom: -1px;
72 | }
73 |
74 | .editing .edit {
75 | display: block;
76 | width: 506px;
77 | padding: 13px 17px 12px 17px;
78 | margin: 0 0 0 43px;
79 | }
80 |
81 | .editing .view {
82 | display: none;
83 | }
84 |
85 | .completed label {
86 | color: #d9d9d9;
87 | text-decoration: line-through;
88 | }
89 |
90 | /*
91 | Hack to remove background from Mobile Safari.
92 | Can't use it globally since it destroys checkboxes in Firefox
93 | */
94 |
95 | @media screen and (-webkit-min-device-pixel-ratio: 0) {
96 | .normal .toggle {
97 | background: none;
98 | }
99 | .normal .toggle {
100 | height: 40px;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/web-ui/src/app/components/TodoList/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { TodoItem, TodoActions } from 'app/components/TodoItem';
3 | import { TodoModel } from 'app/models/TodoModel';
4 | import * as style from './style.css';
5 |
6 | export interface TodoListProps extends TodoActions {
7 | todos: TodoModel[];
8 | completeAll: () => any;
9 | }
10 |
11 | export interface TodoListState {}
12 |
13 | export class TodoList extends React.Component {
14 | constructor(props?: TodoListProps, context?: any) {
15 | super(props, context);
16 | }
17 |
18 | private handleToggleAll = (e: React.SyntheticEvent) => {
19 | e.preventDefault();
20 | this.props.completeAll();
21 | };
22 |
23 | renderToggleAll() {
24 | const { todos } = this.props;
25 | const completedCount = todos.length;
26 | if (todos.length > 0) {
27 | return (
28 |
34 | );
35 | }
36 | }
37 |
38 | render() {
39 | const { todos, ...actions } = this.props;
40 | return (
41 |
42 | {this.renderToggleAll()}
43 |
44 | {todos.map((todo) => (
45 |
46 | ))}
47 |
48 |
49 | );
50 | }
51 | }
52 |
53 | export default TodoList;
54 |
--------------------------------------------------------------------------------
/web-ui/src/app/components/TodoList/style.css:
--------------------------------------------------------------------------------
1 | .main {
2 | position: relative;
3 | z-index: 2;
4 | border-top: 1px solid #e6e6e6;
5 | }
6 |
7 | .normal {
8 | margin: 0;
9 | padding: 0;
10 | list-style: none;
11 | }
12 |
13 | .normal li {
14 | position: relative;
15 | font-size: 24px;
16 | border-bottom: 1px solid #ededed;
17 | }
18 |
19 | .normal li:last-child {
20 | border-bottom: none;
21 | }
22 |
23 | .normal li.editing {
24 | border-bottom: none;
25 | padding: 0;
26 | }
27 |
28 | .normal li.editing .edit {
29 | display: block;
30 | width: 506px;
31 | padding: 13px 17px 12px 17px;
32 | margin: 0 0 0 43px;
33 | }
34 |
35 | .normal li.editing .view {
36 | display: none;
37 | }
38 |
39 | .normal li .toggle {
40 | text-align: center;
41 | width: 40px;
42 | /* auto, since non-WebKit browsers doesn't support input styling */
43 | height: auto;
44 | position: absolute;
45 | top: 0;
46 | bottom: 0;
47 | margin: auto 0;
48 | /* Mobile Safari */
49 | border: none;
50 | appearance: none;
51 | }
52 |
53 | .normal li .toggle:after {
54 | content: url('data:image/svg+xml;utf8,');
55 | }
56 |
57 | .normal li .toggle:checked:after {
58 | content: url('data:image/svg+xml;utf8,');
59 | }
60 |
61 | .normal li label {
62 | white-space: pre-line;
63 | word-break: break-all;
64 | padding: 15px 60px 15px 15px;
65 | margin-left: 45px;
66 | display: block;
67 | line-height: 1.2;
68 | transition: color 0.4s;
69 | }
70 |
71 | .normal li.completed label {
72 | color: #d9d9d9;
73 | text-decoration: line-through;
74 | }
75 |
76 | .normal li .destroy {
77 | display: none;
78 | position: absolute;
79 | top: 0;
80 | right: 10px;
81 | bottom: 0;
82 | width: 40px;
83 | height: 40px;
84 | margin: auto 0;
85 | font-size: 30px;
86 | color: #cc9a9a;
87 | margin-bottom: 11px;
88 | transition: color 0.2s ease-out;
89 | }
90 |
91 | .normal li .destroy:hover {
92 | color: #af5b5e;
93 | }
94 |
95 | .normal li .destroy:after {
96 | content: '×';
97 | }
98 |
99 | .normal li:hover .destroy {
100 | display: block;
101 | }
102 |
103 | .normal li .edit {
104 | display: none;
105 | }
106 |
107 | .normal li.editing:last-child {
108 | margin-bottom: -1px;
109 | }
110 |
111 | .toggleAll {
112 | position: absolute;
113 | top: -55px;
114 | left: -12px;
115 | width: 60px;
116 | height: 34px;
117 | text-align: center;
118 | border: none;
119 | /* Mobile Safari */
120 | }
121 |
122 | .toggleAll:before {
123 | content: '❯';
124 | font-size: 22px;
125 | color: #e6e6e6;
126 | padding: 10px 27px 10px 27px;
127 | }
128 |
129 | .toggleAll:checked:before {
130 | color: #737373;
131 | }
132 |
133 | /*
134 | Hack to remove background from Mobile Safari.
135 | Can't use it globally since it destroys checkboxes in Firefox
136 | */
137 |
138 | @media screen and (-webkit-min-device-pixel-ratio: 0) {
139 | .toggleAll,
140 | .normal li .toggle {
141 | background: none;
142 | }
143 | .normal li .toggle {
144 | height: 40px;
145 | }
146 | .toggleAll {
147 | transform: rotate(90deg);
148 | appearance: none;
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/web-ui/src/app/components/TodoTextInput/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as classNames from 'classnames';
3 | import * as style from './style.css';
4 |
5 | export interface TodoTextInputProps {
6 | text?: string;
7 | placeholder?: string;
8 | newTodo?: boolean;
9 | editing?: boolean;
10 | onSave: (text: string) => any;
11 | }
12 |
13 | export interface TodoTextInputState {
14 | text: string;
15 | }
16 |
17 | export class TodoTextInput extends React.Component<
18 | TodoTextInputProps,
19 | TodoTextInputState
20 | > {
21 | constructor(props?: TodoTextInputProps, context?: any) {
22 | super(props, context);
23 | this.state = {
24 | text: this.props.text || ''
25 | };
26 | }
27 |
28 | private handleSubmit = (e) => {
29 | const text = e.target.value.trim();
30 | if (e.which === 13) {
31 | this.props.onSave(text);
32 | if (this.props.newTodo) {
33 | this.setState({ text: '' });
34 | }
35 | }
36 | };
37 |
38 | private handleChange = (e) => {
39 | this.setState({ text: e.target.value });
40 | };
41 |
42 | private handleBlur = (e) => {
43 | const text = e.target.value.trim();
44 | if (!this.props.newTodo) {
45 | this.props.onSave(text);
46 | }
47 | };
48 |
49 | render() {
50 | const classes = classNames(
51 | {
52 | [style.edit]: this.props.editing,
53 | [style.new]: this.props.newTodo
54 | },
55 | style.normal
56 | );
57 |
58 | return (
59 |
69 | );
70 | }
71 | }
72 |
73 | export default TodoTextInput;
74 |
--------------------------------------------------------------------------------
/web-ui/src/app/components/TodoTextInput/style.css:
--------------------------------------------------------------------------------
1 | .new,
2 | .edit {
3 | position: relative;
4 | margin: 0;
5 | width: 100%;
6 | font-size: 24px;
7 | font-family: inherit;
8 | font-weight: inherit;
9 | line-height: 1.4em;
10 | border: 0;
11 | outline: none;
12 | color: inherit;
13 | padding: 6px;
14 | border: 1px solid #999;
15 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
16 | box-sizing: border-box;
17 | font-smoothing: antialiased;
18 | }
19 |
20 | .new {
21 | padding: 16px 16px 16px 60px;
22 | border: none;
23 | background: rgba(0, 0, 0, 0.003);
24 | box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
25 | }
26 |
--------------------------------------------------------------------------------
/web-ui/src/app/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Footer';
2 | export * from './Header';
3 | export * from './TodoItem';
4 | export * from './TodoList';
5 | export * from './TodoTextInput';
6 |
--------------------------------------------------------------------------------
/web-ui/src/app/constants/index.ts:
--------------------------------------------------------------------------------
1 | export * from './stores';
2 | export * from './todos';
--------------------------------------------------------------------------------
/web-ui/src/app/constants/stores.ts:
--------------------------------------------------------------------------------
1 | export const STORE_TODO = 'todo';
2 | export const STORE_ROUTER = 'router';
3 |
--------------------------------------------------------------------------------
/web-ui/src/app/constants/todos.ts:
--------------------------------------------------------------------------------
1 | export enum TodoFilter {
2 | ALL = 0,
3 | ACTIVE,
4 | COMPLETED
5 | }
6 |
7 | export const TODO_FILTER_TYPES = [
8 | TodoFilter.ALL,
9 | TodoFilter.ACTIVE,
10 | TodoFilter.COMPLETED
11 | ];
12 |
13 | export const TODO_FILTER_TITLES = {
14 | [TodoFilter.ALL]: 'All',
15 | [TodoFilter.ACTIVE]: 'Active',
16 | [TodoFilter.COMPLETED]: 'Completed'
17 | };
18 |
19 | export const TODO_FILTER_LOCATION_HASH = {
20 | [TodoFilter.ALL]: '#',
21 | [TodoFilter.ACTIVE]: '#active',
22 | [TodoFilter.COMPLETED]: '#completed'
23 | };
24 |
--------------------------------------------------------------------------------
/web-ui/src/app/containers/Root/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Navigation from 'app/components/Navigation';
3 | import theme from 'app/theme/dark';
4 | import { MuiThemeProvider } from '@material-ui/core';
5 | export class Root extends React.Component {
6 | renderDevTool() {
7 | if (process.env.NODE_ENV !== 'production') {
8 | const DevTools = require('mobx-react-devtools').default;
9 | return ;
10 | }
11 | }
12 |
13 | render() {
14 | return (
15 |
16 |
17 |
18 | {this.props.children}
19 | {this.renderDevTool()}
20 |
21 |
22 |
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/web-ui/src/app/containers/TodoApp/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as style from './style.css';
3 | import { inject, observer } from 'mobx-react';
4 | import { RouteComponentProps } from 'react-router';
5 | import { Header } from 'app/components/Header';
6 | import { TodoList } from 'app/components/TodoList';
7 | import { Footer } from 'app/components/Footer';
8 | import { TodoStore, RouterStore } from 'app/stores';
9 | import {
10 | STORE_TODO,
11 | STORE_ROUTER,
12 | TODO_FILTER_LOCATION_HASH,
13 | TodoFilter
14 | } from 'app/constants';
15 |
16 | export interface TodoAppProps extends RouteComponentProps {
17 | /** MobX Stores will be injected via @inject() **/
18 | // [STORE_ROUTER]: RouterStore;
19 | // [STOURE_TODO]: TodoStore;
20 | }
21 |
22 | export interface TodoAppState {
23 | filter: TodoFilter;
24 | }
25 |
26 | @inject(STORE_TODO, STORE_ROUTER)
27 | @observer
28 | export class TodoApp extends React.Component {
29 | constructor(props: TodoAppProps, context: any) {
30 | super(props, context);
31 | this.state = { filter: TodoFilter.ALL };
32 | }
33 |
34 | componentWillMount() {
35 | this.checkLocationChange();
36 | }
37 |
38 | componentWillReceiveProps(nextProps: TodoAppProps, nextContext: any) {
39 | this.checkLocationChange();
40 | }
41 |
42 | checkLocationChange() {
43 | const router = this.props[STORE_ROUTER] as RouterStore;
44 | const filter = Object.keys(TODO_FILTER_LOCATION_HASH)
45 | .map((key) => Number(key) as TodoFilter)
46 | .find(
47 | (filter) => TODO_FILTER_LOCATION_HASH[filter] === router.location.hash
48 | );
49 | this.setState({ filter });
50 | }
51 |
52 | private handleFilter = (filter: TodoFilter) => {
53 | const router = this.props[STORE_ROUTER] as RouterStore;
54 | const currentHash = router.location.hash;
55 | const nextHash = TODO_FILTER_LOCATION_HASH[filter];
56 | if (currentHash !== nextHash) {
57 | router.replace(nextHash);
58 | }
59 | };
60 |
61 | getFilteredTodo(filter: TodoFilter) {
62 | const todoStore = this.props[STORE_TODO] as TodoStore;
63 | switch (filter) {
64 | case TodoFilter.ACTIVE:
65 | return todoStore.activeTodos;
66 | case TodoFilter.COMPLETED:
67 | return todoStore.completedTodos;
68 | default:
69 | return todoStore.todos;
70 | }
71 | }
72 |
73 | render() {
74 | const todoStore = this.props[STORE_TODO] as TodoStore;
75 | const { children } = this.props;
76 | const { filter } = this.state;
77 | const filteredTodos = this.getFilteredTodo(filter);
78 |
79 | const footer = todoStore.todos.length && (
80 |
87 | );
88 | return (
89 |
90 |
91 |
97 | {footer}
98 | {children}
99 |
100 | );
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/web-ui/src/app/containers/TodoApp/style.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Roboto:300,400,500');
2 |
3 | html,
4 | body {
5 | margin: 0;
6 | padding: 0;
7 | }
8 |
9 | button {
10 | margin: 0;
11 | padding: 0;
12 | border: 0;
13 | background: none;
14 | font-size: 100%;
15 | vertical-align: baseline;
16 | font-family: inherit;
17 | font-weight: inherit;
18 | color: inherit;
19 | appearance: none;
20 | font-smoothing: antialiased;
21 | }
22 |
23 | body {
24 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
25 | line-height: 1.4em;
26 | background: #f5f5f5;
27 | color: #4d4d4d;
28 | min-width: 230px;
29 | max-width: 550px;
30 | margin: 0 auto;
31 | -webkit-font-smoothing: antialiased;
32 | -moz-font-smoothing: antialiased;
33 | -ms-font-smoothing: antialiased;
34 | font-smoothing: antialiased;
35 | font-weight: 300;
36 | }
37 |
38 | button,
39 | input[type='checkbox'] {
40 | outline: none;
41 | }
42 |
43 | .normal {
44 | background: #fff;
45 | margin: 200px 0 40px 0;
46 | position: relative;
47 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
48 | }
49 |
50 | .normal input::-webkit-input-placeholder {
51 | font-style: italic;
52 | font-weight: 300;
53 | color: #e6e6e6;
54 | }
55 |
56 | .normal input::-moz-placeholder {
57 | font-style: italic;
58 | font-weight: 300;
59 | color: #e6e6e6;
60 | }
61 |
62 | .normal input::input-placeholder {
63 | font-style: italic;
64 | font-weight: 300;
65 | color: #e6e6e6;
66 | }
67 |
68 | .normal h1 {
69 | position: absolute;
70 | top: -155px;
71 | width: 100%;
72 | font-size: 100px;
73 | font-weight: 100;
74 | text-align: center;
75 | color: rgba(175, 47, 47, 0.15);
76 | -webkit-text-rendering: optimizeLegibility;
77 | -moz-text-rendering: optimizeLegibility;
78 | -ms-text-rendering: optimizeLegibility;
79 | text-rendering: optimizeLegibility;
80 | }
81 |
--------------------------------------------------------------------------------
/web-ui/src/app/history.ts:
--------------------------------------------------------------------------------
1 | import { createBrowserHistory } from 'history';
2 | const history = createBrowserHistory();
3 | export default history;
4 |
--------------------------------------------------------------------------------
/web-ui/src/app/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { hot } from 'react-hot-loader/root';
3 | import { Router, Route, Switch } from 'react-router';
4 | import { Root } from 'app/containers/Root';
5 | import { TodoApp } from 'app/containers/TodoApp';
6 | import PrivateRoute from 'app/components/PrivateRoute';
7 | import Login from 'app/routes/authentication';
8 |
9 | export const App = hot(({ history }) => {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | )
20 | });
21 |
--------------------------------------------------------------------------------
/web-ui/src/app/models/TodoModel.ts:
--------------------------------------------------------------------------------
1 | import { observable } from 'mobx';
2 |
3 | export class TodoModel {
4 | readonly id: number;
5 | @observable public text: string;
6 | @observable public completed: boolean;
7 |
8 | constructor(text: string, completed: boolean = false) {
9 | this.id = TodoModel.generateId();
10 | this.text = text;
11 | this.completed = completed;
12 | }
13 |
14 | static nextId = 1;
15 | static generateId() {
16 | return this.nextId++;
17 | }
18 | }
19 |
20 | export default TodoModel;
21 |
--------------------------------------------------------------------------------
/web-ui/src/app/models/index.ts:
--------------------------------------------------------------------------------
1 | import TodoModel from './TodoModel';
2 |
3 | export { TodoModel };
4 |
--------------------------------------------------------------------------------
/web-ui/src/app/routes/authentication/components/FacebookButton.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Button } from '@material-ui/core';
3 | import { FaFacebook } from 'react-icons/fa';
4 | //TODO: export it and reuse in other buttons
5 | interface IButton {
6 | onClick: React.ChangeEventHandler
7 | }
8 | const FacebookButton: React.FunctionComponent = ({ onClick, ...props }) => {
9 | return (
10 | }
14 | fullWidth
15 | >
16 | Login With Facebook
17 |
18 | )
19 | }
20 | export default FacebookButton;
21 |
--------------------------------------------------------------------------------
/web-ui/src/app/routes/authentication/components/GithubButton.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Button } from '@material-ui/core';
3 | import { FaGithub } from 'react-icons/fa';
4 | //TODO: export it and reuse in other buttons
5 | interface IButton {
6 | onClick: React.ChangeEventHandler
7 | }
8 | const GithubButton: React.FunctionComponent = ({ onClick, ...props }) => {
9 | return (
10 | }
14 | fullWidth
15 | >
16 | Login With Github
17 |
18 | )
19 | }
20 | export default GithubButton;
21 |
--------------------------------------------------------------------------------
/web-ui/src/app/routes/authentication/components/Login.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Button } from '@material-ui/core';
3 | import CustomInput from 'app/components/CustomInput';
4 | import Password from 'app/components/Password';
5 | import axios, { AxiosResponse } from 'axios';
6 |
7 | interface ILogin {}
8 | interface IForm {
9 | mailAddress: string,
10 | password: string
11 | };
12 | const Login: React.FunctionComponent = ({ }) => {
13 | const initialState: IForm = {
14 | mailAddress: '',
15 | password: 's'
16 | };
17 | const [form, setForm] = React.useState(initialState);
18 | const onSubmit = () => {
19 | axios.post('http://localhost:3010/auth/login', form).then((data: AxiosResponse) => sessionStorage.setItem('jwt', data.data));
20 | }
21 | return (
22 |
23 |
setForm({ ...form, mailAddress: e.target.value })} />
24 | setForm({ ...form, password: e.target.value })}/>
25 |
32 |
33 | )
34 | };
35 | export default Login;
36 |
--------------------------------------------------------------------------------
/web-ui/src/app/routes/authentication/components/Register.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Button } from '@material-ui/core';
3 | import Password from 'app/components/Password';
4 | import CustomInput from 'app/components/CustomInput';
5 | import axios from 'axios';
6 |
7 | interface IRegister {
8 |
9 | }
10 | interface IForm {
11 | firstName: string,
12 | lastName: string,
13 | mailAddress: string,
14 | password: string
15 | };
16 | const IRegister: React.FunctionComponent = ({ }) => {
17 | const initialState: IForm = {
18 | firstName: '',
19 | lastName: '',
20 | mailAddress: '',
21 | password: ''
22 | };
23 | const [form, setForm] = React.useState(initialState);
24 | // const onSubmit = () => {
25 | // fetch('http://localhost:3010/users',
26 | // {
27 | // method: 'POST',
28 | // body: JSON.stringify(form),
29 | // headers: {
30 | // 'Access-Control-Allow-Origin': '*'
31 | // }
32 | // })
33 | // .then(data => console.log(data))
34 | // .catch(err => console.error(err));
35 | // }
36 |
37 | const onSubmit = () => {
38 | // , { headers: { 'Access-Control-Allow-Origin':'*',"Access-Control-Allow-Methods":"GET,PUT,POST,DELETE","Access-Control-Allow-Headers":"Content-Type","X-Frame-Options":"ALLOWALL" }}
39 | axios.post('http://localhost:3010/users', form).then(data => console.log(data));
40 | // fetch('http://localhost:3010/users/al2l').then(resp => console.log(resp));
41 | }
42 | return (
43 |
44 | {/* TODO: add header */}
45 |
46 | setForm({ ...form, firstName: e.target.value })} />
47 | setForm({ ...form, lastName: e.target.value })} />
48 |
49 |
setForm({ ...form, mailAddress: e.target.value })} />
50 | setForm({ ...form, password: e.target.value })}/>
51 |
58 |
59 | )
60 | };
61 | export default IRegister;
62 |
--------------------------------------------------------------------------------
/web-ui/src/app/routes/authentication/components/TabsWrapper.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { makeStyles } from '@material-ui/core/styles';
3 | import { Tab, Tabs } from '@material-ui/core';
4 |
5 | const useStyles = makeStyles({
6 | tabsRoot: {
7 | },
8 | tabsIndicator: {
9 | backgroundColor: '#3e90ff'
10 | },
11 | tabRoot: {
12 | 'color': '#9191a9',
13 | 'textTransform': 'initial',
14 | 'fontSize': '18px',
15 | '&:hover': {
16 | color: '#3e90ff',
17 | opacity: 1
18 | },
19 | '&tabSelected': {
20 | color: '#3e90ff'
21 | },
22 | '&:focus': {
23 | color: '#3e90ff'
24 | }
25 | }
26 | });
27 | // TODO: enable this and fix the Dispatch error
28 | // interface ITabs {
29 | // onChange: Dispatch>,
30 | // activeTab: number
31 | // }
32 | // const TabsWrapper: React.FunctionComponent = ({ }) => {
33 | // https://github.com/reduxjs/redux-thunk/issues/242
34 | const TabsWrapper = ({ onChange, activeTab }) => {
35 | const classes = useStyles();
36 | return (
37 | onChange(value)}
40 | classes={{ root: classes.tabsRoot, indicator: classes.tabsIndicator }}
41 | >
42 |
47 |
52 |
53 | )
54 |
55 | };
56 | export default TabsWrapper;
57 |
--------------------------------------------------------------------------------
/web-ui/src/app/routes/authentication/components/TwitterButton.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Button } from '@material-ui/core';
3 | import { FaTwitter } from 'react-icons/fa';
4 |
5 | interface IButton {
6 | onClick(): void
7 | }
8 | const TwitterButton: React.FunctionComponent = ({ onClick, ...props }) => {
9 | return (
10 | }
14 | onClick={onClick}
15 | fullWidth
16 | >
17 | Login With Twitter
18 |
19 | )
20 | }
21 | export default TwitterButton;
22 |
--------------------------------------------------------------------------------
/web-ui/src/app/routes/authentication/constants/index.tsx:
--------------------------------------------------------------------------------
1 | export const TABS = {
2 | LOGIN: 0,
3 | REGISTER: 1
4 | };
--------------------------------------------------------------------------------
/web-ui/src/app/routes/authentication/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { makeStyles } from '@material-ui/core/styles';
3 | import { Grid, Divider, Card, CardContent } from '@material-ui/core';
4 | import TwitterButton from './components/TwitterButton';
5 | import GithubButton from './components/GithubButton';
6 | import FacebookButton from './components/FacebookButton';
7 | import TabsWrapper from './components/TabsWrapper';
8 | import { TABS } from './constants';
9 | import Login from './components/Login';
10 | import Register from './components/Register';
11 |
12 | const useStyles = makeStyles({
13 | root: {
14 | position: 'fixed',
15 | left: '20%',
16 | top: '30%',
17 | maxWidth: 900,
18 | },
19 | bullet: {
20 | display: 'inline-block',
21 | margin: '0 2px',
22 | transform: 'scale(0.8)',
23 | },
24 | title: {
25 | fontSize: 14,
26 | },
27 | pos: {
28 | marginBottom: 12,
29 | },
30 | });
31 | interface IAuth {
32 |
33 | }
34 | const Authentication: React.FunctionComponent = ({ }) => {
35 | const [activeTab, setActiveTab] = React.useState(TABS.LOGIN);
36 | // export default function Authentication() {
37 | const classes = useStyles();
38 | const viewActiveTab = {
39 | [TABS.LOGIN]: ,
40 | [TABS.REGISTER]:
41 | };
42 | const onTwitterClick = () => {
43 | fetch('https://api.twitter.com/oauth/request_token',
44 | {
45 | method: 'POST'
46 | })
47 | .then(data => console.log(data))
48 | .catch(err => console.error(err));
49 | }
50 | return (
51 |
52 |
53 |
54 |
55 |
56 |
57 | console.log('on click')}/>
58 | console.log('on click')}/>
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | { viewActiveTab[activeTab] }
68 |
69 |
70 |
71 |
72 | );
73 | }
74 | export default Authentication;
75 |
--------------------------------------------------------------------------------
/web-ui/src/app/routes/main/index.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrainBackup/bb8/1562da57815536d5da7a48df37e5e018e69b0413/web-ui/src/app/routes/main/index.ts
--------------------------------------------------------------------------------
/web-ui/src/app/stores/RouterStore.ts:
--------------------------------------------------------------------------------
1 | import { History } from 'history';
2 | import {
3 | RouterStore as BaseRouterStore,
4 | syncHistoryWithStore
5 | } from 'mobx-react-router';
6 |
7 | export class RouterStore extends BaseRouterStore {
8 | constructor(history?: History) {
9 | super();
10 | if (history) {
11 | this.history = syncHistoryWithStore(history, this);
12 | }
13 | }
14 | }
15 |
16 | export default RouterStore;
17 |
--------------------------------------------------------------------------------
/web-ui/src/app/stores/TodoStore.ts:
--------------------------------------------------------------------------------
1 | import { observable, computed, action } from 'mobx';
2 | import { TodoModel } from 'app/models';
3 |
4 | export class TodoStore {
5 | constructor(fixtures: TodoModel[]) {
6 | this.todos = fixtures;
7 | }
8 |
9 | @observable public todos: Array;
10 |
11 | @computed
12 | get activeTodos() {
13 | return this.todos.filter((todo) => !todo.completed);
14 | }
15 |
16 | @computed
17 | get completedTodos() {
18 | return this.todos.filter((todo) => todo.completed);
19 | }
20 |
21 | @action
22 | addTodo = (item: Partial): void => {
23 | this.todos.push(new TodoModel(item.text, item.completed));
24 | };
25 |
26 | @action
27 | editTodo = (id: number, data: Partial): void => {
28 | this.todos = this.todos.map((todo) => {
29 | if (todo.id === id) {
30 | if (typeof data.completed == 'boolean') {
31 | todo.completed = data.completed;
32 | }
33 | if (typeof data.text == 'string') {
34 | todo.text = data.text;
35 | }
36 | }
37 | return todo;
38 | });
39 | };
40 |
41 | @action
42 | deleteTodo = (id: number): void => {
43 | this.todos = this.todos.filter((todo) => todo.id !== id);
44 | };
45 |
46 | @action
47 | completeAll = (): void => {
48 | this.todos = this.todos.map((todo) => ({ ...todo, completed: true }));
49 | };
50 |
51 | @action
52 | clearCompleted = (): void => {
53 | this.todos = this.todos.filter((todo) => !todo.completed);
54 | };
55 | }
56 |
57 | export default TodoStore;
58 |
--------------------------------------------------------------------------------
/web-ui/src/app/stores/createStore.ts:
--------------------------------------------------------------------------------
1 | import { History } from 'history';
2 | import { TodoModel } from 'app/models';
3 | import { TodoStore } from './TodoStore';
4 | import { RouterStore } from './RouterStore';
5 | import { STORE_TODO, STORE_ROUTER } from 'app/constants';
6 |
7 | export function createStores(history: History, defaultTodos?: TodoModel[]) {
8 | const todoStore = new TodoStore(defaultTodos);
9 | const routerStore = new RouterStore(history);
10 | return {
11 | [STORE_TODO]: todoStore,
12 | [STORE_ROUTER]: routerStore
13 | };
14 | }
15 |
--------------------------------------------------------------------------------
/web-ui/src/app/stores/index.ts:
--------------------------------------------------------------------------------
1 | export * from './TodoStore';
2 | export * from './RouterStore';
3 | export * from './createStore';
4 |
--------------------------------------------------------------------------------
/web-ui/src/app/styledComponents/Sidebar/Sidebar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function Sidebar() {
4 | return (
5 | TESTing side bar with storybook
6 | )
7 | }
8 | export default Sidebar;
9 |
--------------------------------------------------------------------------------
/web-ui/src/app/styledComponents/Sidebar/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | function Sidebar({}) {
4 | return (
5 |
6 | Test asd
7 |
8 | )
9 | }
10 | export default Sidebar;
11 |
--------------------------------------------------------------------------------
/web-ui/src/app/styledComponents/Sidebar/sidebar.story.js:
--------------------------------------------------------------------------------
1 | // // import * as React from 'react';
2 | // // import Sidebar from './index';
3 | import Sidebar from './Sidebar';
4 |
5 | // export default { title: 'Button' };
6 |
7 | // // export const withEmoji = () => Sidebar;
8 | // // export const story = () => Sidebar;
9 | // import React from 'react';
10 | import { storiesOf } from '@storybook/react';
11 | // import Toggle from './index';
12 | // import { boolean, text } from '@storybook/addon-knobs';
13 |
14 | const stories = storiesOf('Toggle', module);
15 |
16 | stories.add('On or off', () => {
17 | return (
18 |
19 | );
20 | });
--------------------------------------------------------------------------------
/web-ui/src/app/theme/dark.ts:
--------------------------------------------------------------------------------
1 | import { createMuiTheme } from "@material-ui/core";
2 |
3 | // #212121
4 | // #323232
5 | // #0d7377
6 | // #14ffec
7 | const theme = createMuiTheme({
8 | palette: {
9 | primary: {
10 | main: '#212121'
11 | },
12 | secondary: {
13 | light: '#323232',
14 | main: '#0d7377',
15 | contrastText: '#14ffec',
16 | },
17 | contrastThreshold: 3,
18 | tonalOffset: 0.2
19 | },
20 | typography: {
21 |
22 | },
23 | overrides: {
24 | // MuiListItemIcon: {
25 | // root: {
26 | // color: 'white'
27 | // }
28 | // },
29 | // MuiDivider: {
30 | // root: {
31 | // backgroundColor: 'white'
32 | // }
33 | // }
34 | }
35 | });
36 | export default theme;
37 | // 7045af
38 | // e14594
--------------------------------------------------------------------------------
/web-ui/src/app/theme/light.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrainBackup/bb8/1562da57815536d5da7a48df37e5e018e69b0413/web-ui/src/app/theme/light.ts
--------------------------------------------------------------------------------
/web-ui/src/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrainBackup/bb8/1562da57815536d5da7a48df37e5e018e69b0413/web-ui/src/assets/favicon.ico
--------------------------------------------------------------------------------
/web-ui/src/assets/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Brain Backup
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/web-ui/src/main.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import { Provider } from 'mobx-react';
4 | import { TodoModel } from 'app/models';
5 | import { createStores } from 'app/stores';
6 | import { App } from 'app';
7 | import history from './app/history';
8 |
9 | // default fixtures for TodoStore
10 | const defaultTodos = [
11 | new TodoModel('Use Mobx'),
12 | new TodoModel('Use React'),
13 | new TodoModel('Make ui works', true)
14 | ];
15 |
16 | // prepare MobX stores
17 | const rootStore = createStores(history, defaultTodos);
18 |
19 | // render react DOM
20 | ReactDOM.render(
21 |
22 |
23 | ,
24 | document.getElementById('root')
25 | );
26 |
--------------------------------------------------------------------------------
/web-ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "target": "es5",
5 | "jsx": "react",
6 | "module": "es6",
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "declaration": false,
11 | "noImplicitAny": false,
12 | "noImplicitReturns": false,
13 | "noUnusedLocals": true,
14 | "removeComments": true,
15 | "strictNullChecks": false,
16 | "outDir": "build",
17 | "lib": ["es6", "es7", "dom"],
18 | "baseUrl": "src",
19 | "paths": {
20 | "app/*": ["./app/*"]
21 | }
22 | },
23 | "exclude": ["dist", "build", "node_modules"]
24 | }
25 |
--------------------------------------------------------------------------------
/web-ui/types/global.d.ts:
--------------------------------------------------------------------------------
1 | /** Global definitions for developement **/
2 |
3 | // for style loader
4 | declare module '*.css' {
5 | const styles: any;
6 | export = styles;
7 | }
8 |
--------------------------------------------------------------------------------
/web-ui/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var path = require('path');
3 |
4 | // variables
5 | var isProduction =
6 | process.argv.indexOf('-p') >= 0 || process.env.NODE_ENV === 'production';
7 | var sourcePath = path.join(__dirname, './src');
8 | var outPath = path.join(__dirname, './build');
9 |
10 | // plugins
11 | var HtmlWebpackPlugin = require('html-webpack-plugin');
12 | var MiniCssExtractPlugin = require('mini-css-extract-plugin');
13 | var WebpackCleanupPlugin = require('webpack-cleanup-plugin');
14 |
15 |
16 | module.exports = {
17 | context: sourcePath,
18 | entry: {
19 | app: './main.tsx'
20 | },
21 | output: {
22 | path: outPath,
23 | filename: isProduction ? '[contenthash].js' : '[hash].js',
24 | chunkFilename: isProduction ? '[name].[contenthash].js' : '[name].[hash].js'
25 | },
26 | target: 'web',
27 | resolve: {
28 | extensions: ['.js', '.ts', '.tsx'],
29 | // Fix webpack's default behavior to not load packages with jsnext:main module
30 | // (jsnext:main directs not usually distributable es6 format, but es6 sources)
31 | mainFields: ['module', 'browser', 'main'],
32 | modules: [path.resolve(__dirname, '../node_modules')],
33 | alias: {
34 | app: path.resolve(__dirname, 'src/app/'),
35 | }
36 | },
37 | module: {
38 | rules: [
39 | // .ts, .tsx
40 | {
41 | test: /\.tsx?$/,
42 | use: [
43 | !isProduction && {
44 | loader: 'babel-loader',
45 | options: { plugins: ['react-hot-loader/babel'] }
46 | },
47 | 'ts-loader'
48 | ].filter(Boolean)
49 | },
50 | // css
51 | {
52 | test: /\.css$/,
53 | use: [
54 | isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
55 | {
56 | loader: 'css-loader',
57 | query: {
58 | modules: true,
59 | sourceMap: !isProduction,
60 | importLoaders: 1,
61 | localIdentName: isProduction
62 | ? '[hash:base64:5]'
63 | : '[local]__[hash:base64:5]'
64 | }
65 | },
66 | {
67 | loader: 'postcss-loader',
68 | options: {
69 | ident: 'postcss',
70 | plugins: [
71 | require('postcss-import')({ addDependencyTo: webpack }),
72 | require('postcss-url')(),
73 | require('postcss-preset-env')({
74 | /* use stage 2 features (defaults) */
75 | stage: 2
76 | }),
77 | require('postcss-reporter')(),
78 | require('postcss-browser-reporter')({
79 | disabled: isProduction
80 | })
81 | ]
82 | }
83 | }
84 | ]
85 | },
86 | // static assets
87 | { test: /\.html$/, use: 'html-loader' },
88 | { test: /\.(a?png|svg)$/, use: 'url-loader?limit=10000' },
89 | {
90 | test: /\.(jpe?g|gif|bmp|mp3|mp4|ogg|wav|eot|ttf|woff|woff2)$/,
91 | use: 'file-loader'
92 | }
93 | ]
94 | },
95 | optimization: {
96 | splitChunks: {
97 | name: true,
98 | cacheGroups: {
99 | commons: {
100 | chunks: 'initial',
101 | minChunks: 2
102 | },
103 | vendors: {
104 | test: /[\\/]node_modules[\\/]/,
105 | chunks: 'all',
106 | priority: -10,
107 | filename: isProduction ? 'vendor.[contenthash].js' : 'vendor.[hash].js'
108 | }
109 | }
110 | },
111 | runtimeChunk: true
112 | },
113 | plugins: [
114 | new webpack.EnvironmentPlugin({
115 | NODE_ENV: 'development', // use 'development' unless process.env.NODE_ENV is defined
116 | DEBUG: false
117 | }),
118 | new WebpackCleanupPlugin(),
119 | new MiniCssExtractPlugin({
120 | filename: isProduction ? '[contenthash].css' : '[hash].css',
121 | disable: !isProduction
122 | }),
123 | new HtmlWebpackPlugin({
124 | template: 'assets/index.html'
125 | })
126 | ],
127 | devServer: {
128 | contentBase: sourcePath,
129 | hot: true,
130 | inline: true,
131 | historyApiFallback: {
132 | disableDotRule: true
133 | },
134 | stats: 'minimal',
135 | clientLogLevel: 'warning'
136 | },
137 | // https://webpack.js.org/configuration/devtool/
138 | devtool: isProduction ? 'hidden-source-map' : 'cheap-module-eval-source-map',
139 | node: {
140 | // workaround for webpack-dev-server issue
141 | // https://github.com/webpack/webpack-dev-server/issues/60#issuecomment-103411179
142 | fs: 'empty',
143 | net: 'empty'
144 | }
145 | };
146 |
--------------------------------------------------------------------------------