├── client ├── Makefile ├── .env.development ├── .env.production ├── .prettierrc ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── fonts │ │ └── tabler-webfont │ │ │ ├── tabler-webfont.eot │ │ │ ├── tabler-webfont.ttf │ │ │ ├── tabler-webfont.woff │ │ │ └── tabler-webfont.woff2 │ ├── manifest.json │ ├── css │ │ ├── tabler-charts.min.css │ │ ├── tabler-charts.min.min.css │ │ ├── tabler-charts.css │ │ ├── tabler-charts.min.css.map │ │ └── tabler-charts.min.min.css.map │ ├── index.html │ └── js │ │ └── tabler-charts.min.js ├── src │ ├── modules │ │ ├── compagny │ │ │ ├── models │ │ │ │ └── Compagny.js │ │ │ ├── constants │ │ │ │ ├── add.js │ │ │ │ ├── join.js │ │ │ │ ├── list.js │ │ │ │ └── leave.js │ │ │ ├── components │ │ │ │ └── form │ │ │ │ │ ├── validators │ │ │ │ │ ├── compagny.js │ │ │ │ │ └── join.js │ │ │ │ │ ├── JoinForm.js │ │ │ │ │ ├── CompagnyForm.js │ │ │ │ │ └── CompagnyRow.js │ │ │ ├── reducers │ │ │ │ ├── index.js │ │ │ │ ├── add.js │ │ │ │ ├── join.js │ │ │ │ ├── leave.js │ │ │ │ └── list.js │ │ │ ├── middlewares │ │ │ │ ├── list.js │ │ │ │ ├── leave.js │ │ │ │ ├── add.js │ │ │ │ └── join.js │ │ │ └── actions │ │ │ │ ├── add.js │ │ │ │ ├── join.js │ │ │ │ ├── list.js │ │ │ │ └── leave.js │ │ ├── user │ │ │ ├── constants │ │ │ │ ├── add.js │ │ │ │ ├── edit.js │ │ │ │ ├── list.js │ │ │ │ ├── delete.js │ │ │ │ ├── password.js │ │ │ │ └── currentCompagny.js │ │ │ ├── models │ │ │ │ └── LoggedUser.js │ │ │ ├── middlewares │ │ │ │ ├── password.js │ │ │ │ ├── delete.js │ │ │ │ ├── add.js │ │ │ │ ├── edit.js │ │ │ │ ├── currentCompagny.js │ │ │ │ └── list.js │ │ │ ├── actions │ │ │ │ ├── edit.js │ │ │ │ ├── password.js │ │ │ │ ├── add.js │ │ │ │ ├── list.js │ │ │ │ ├── currentCompagny.js │ │ │ │ └── delete.js │ │ │ ├── reducers │ │ │ │ ├── index.js │ │ │ │ ├── add.js │ │ │ │ ├── edit.js │ │ │ │ ├── delete.js │ │ │ │ ├── password.js │ │ │ │ ├── currentCompagny.js │ │ │ │ └── list.js │ │ │ └── components │ │ │ │ ├── form │ │ │ │ ├── validators │ │ │ │ │ ├── profile.js │ │ │ │ │ ├── password.js │ │ │ │ │ └── user.js │ │ │ │ ├── PasswordForm.js │ │ │ │ ├── ProfileForm.js │ │ │ │ └── UserForm.js │ │ │ │ └── Search.js │ │ ├── auth │ │ │ ├── constants │ │ │ │ ├── registration.js │ │ │ │ └── authentication.js │ │ │ ├── reducers │ │ │ │ ├── index.js │ │ │ │ └── registration.js │ │ │ ├── actions │ │ │ │ ├── registration.js │ │ │ │ └── authentication.js │ │ │ ├── components │ │ │ │ └── form │ │ │ │ │ ├── validators │ │ │ │ │ ├── authentication.js │ │ │ │ │ └── registration.js │ │ │ │ │ ├── AuthenticationForm.js │ │ │ │ │ └── RegistrationForm.js │ │ │ └── middlewares │ │ │ │ ├── registration.js │ │ │ │ └── authentication.js │ │ ├── input │ │ │ ├── constants │ │ │ │ ├── add.js │ │ │ │ └── list.js │ │ │ ├── reducers │ │ │ │ ├── index.js │ │ │ │ ├── list.js │ │ │ │ └── add.js │ │ │ ├── middlewares │ │ │ │ ├── add.js │ │ │ │ └── list.js │ │ │ ├── actions │ │ │ │ ├── add.js │ │ │ │ └── list.js │ │ │ └── components │ │ │ │ └── InputRow.js │ │ ├── quote │ │ │ ├── constants │ │ │ │ ├── add.js │ │ │ │ ├── list.js │ │ │ │ ├── show.js │ │ │ │ └── delete.js │ │ │ ├── components │ │ │ │ ├── form │ │ │ │ │ ├── validators │ │ │ │ │ │ └── quote.js │ │ │ │ │ └── QuoteForm.js │ │ │ │ └── QuoteRow.js │ │ │ ├── reducers │ │ │ │ ├── index.js │ │ │ │ ├── add.js │ │ │ │ ├── show.js │ │ │ │ ├── delete.js │ │ │ │ └── list.js │ │ │ ├── middlewares │ │ │ │ ├── delete.js │ │ │ │ ├── show.js │ │ │ │ ├── add.js │ │ │ │ └── list.js │ │ │ └── actions │ │ │ │ ├── add.js │ │ │ │ ├── list.js │ │ │ │ ├── show.js │ │ │ │ └── delete.js │ │ └── common │ │ │ ├── components │ │ │ ├── LoadingComponent.js │ │ │ ├── SuccessMessage.js │ │ │ ├── Layout │ │ │ │ ├── Layout.js │ │ │ │ └── Footer.js │ │ │ ├── ServerErrors.js │ │ │ ├── SecuredRoute.js │ │ │ ├── Pagination.js │ │ │ └── Form │ │ │ │ ├── RadioInput.js │ │ │ │ └── TextInput.js │ │ │ └── views │ │ │ └── HomeView.js │ ├── i18n │ │ └── index.js │ ├── utils │ │ ├── tokenStorage.js │ │ ├── errorFormater.js │ │ └── axios.js │ ├── store │ │ ├── reducers.js │ │ └── index.js │ └── index.js ├── .gitignore └── package.json ├── api ├── src │ ├── Application │ │ ├── IQuery.ts │ │ ├── ICommand.ts │ │ ├── Adapter │ │ │ ├── ICodeGenerator.ts │ │ │ ├── IQueryBusAdapter.ts │ │ │ ├── ICommandBusAdapter.ts │ │ │ └── IEncryptionAdapter.ts │ │ ├── User │ │ │ ├── View │ │ │ │ ├── UsernameView.ts │ │ │ │ ├── UpdatedMeView.ts │ │ │ │ └── UserView.ts │ │ │ ├── Query │ │ │ │ ├── GetUserByApiTokenQuery.ts │ │ │ │ ├── GetUserByIdQuery.ts │ │ │ │ ├── GetUsersByCompagnyQuery.ts │ │ │ │ ├── GetUserByIdQueryHandler.ts │ │ │ │ ├── GetUserByApiTokenQueryHandler.ts │ │ │ │ └── GetUsersByCompagnyQueryHandler.ts │ │ │ └── Command │ │ │ │ ├── DeleteUserCompagnyCommand.ts │ │ │ │ ├── ChangeCurrentCompagnyCommand.ts │ │ │ │ ├── UpdatePasswordCommand.ts │ │ │ │ ├── CreateUserCompagnyCommand.ts │ │ │ │ ├── UpdateMeCommand.ts │ │ │ │ ├── CreateUserCommand.ts │ │ │ │ ├── CreateUserCompagnyCommandHandler.ts │ │ │ │ ├── UpdatePasswordCommandHandler.ts │ │ │ │ ├── UpdateMeCommandHandler.ts │ │ │ │ ├── ChangeCurrentCompagnyCommandHandler.ts │ │ │ │ └── DeleteUserCompagnyCommandHandler.ts │ │ ├── Compagny │ │ │ ├── View │ │ │ │ └── CompagnyView.ts │ │ │ ├── Query │ │ │ │ ├── GetCompaniesByUserQuery.ts │ │ │ │ ├── GetCompagnyByIdQuery.ts │ │ │ │ ├── GetCompagnyByIdQueryHandler.ts │ │ │ │ └── GetCompaniesByUserQueryHandler.ts │ │ │ └── Command │ │ │ │ ├── LeaveCompagnyCommand.ts │ │ │ │ ├── JoinCompagnyCommand.ts │ │ │ │ ├── CreateCompagnyCommand.ts │ │ │ │ ├── LeaveCompagnyCommandHandler.ts │ │ │ │ └── JoinCompagnyCommandHandler.ts │ │ ├── Auth │ │ │ ├── View │ │ │ │ └── AuthenticatedView.ts │ │ │ └── Command │ │ │ │ ├── LoginCommand.ts │ │ │ │ ├── RegisterCommand.ts │ │ │ │ ├── RegisterCommandHandler.ts │ │ │ │ └── LoginCommandHandler.ts │ │ ├── Input │ │ │ ├── View │ │ │ │ ├── InputListView.ts │ │ │ │ ├── InputView.ts │ │ │ │ └── QuoteView.ts │ │ │ ├── Command │ │ │ │ ├── DeleteQuoteCommand.ts │ │ │ │ ├── CreateQuoteCommand.ts │ │ │ │ ├── CreateInputCommand.ts │ │ │ │ ├── DeleteQuoteCommandHandler.ts │ │ │ │ └── CreateQuoteCommandHandler.ts │ │ │ └── Query │ │ │ │ ├── GetQuotesByIdQuery.ts │ │ │ │ ├── GetInputsByCompagnyQuery.ts │ │ │ │ ├── GetQuotesByCompagnyQuery.ts │ │ │ │ ├── GetInputsByCompagnyQueryHandler.ts │ │ │ │ ├── GetQuotesByCompagnyQueryHandler.ts │ │ │ │ └── GetQuotesByIdQueryHandler.ts │ │ └── Common │ │ │ └── Pagination.ts │ ├── Infrastructure │ │ ├── Input │ │ │ └── Controller │ │ │ │ ├── Dto │ │ │ │ ├── QuoteFiltersDto.ts │ │ │ │ └── InputFiltersDto.ts │ │ │ │ ├── GetQuoteController.ts │ │ │ │ ├── CreateInputController.ts │ │ │ │ ├── CreateQuoteController.ts │ │ │ │ ├── DeleteQuoteController.ts │ │ │ │ ├── GetInputsByCompagnyController.ts │ │ │ │ └── GetQuotesByCompagnyController.ts │ │ ├── User │ │ │ ├── Controller │ │ │ │ ├── Dto │ │ │ │ │ ├── CurrentCompagnyDto.ts │ │ │ │ │ └── UserFiltersDto.ts │ │ │ │ ├── UpdatePasswordController.ts │ │ │ │ ├── UpdateMeController.ts │ │ │ │ ├── GetUsersByCompagnyController.ts │ │ │ │ ├── CreateUserController.ts │ │ │ │ ├── DeleteUserCompagnyController.ts │ │ │ │ └── ChangeCurrentCompagnyController.ts │ │ │ ├── Decorator │ │ │ │ └── LoggedUser.ts │ │ │ └── Repository │ │ │ │ └── UserRepository.ts │ │ ├── Common │ │ │ ├── Dto │ │ │ │ └── PaginationDto.ts │ │ │ └── BusModule.ts │ │ ├── Adapter │ │ │ ├── EncryptionAdapter.ts │ │ │ ├── CodeGeneratorAdapter.ts │ │ │ ├── QueryBusAdapter.ts │ │ │ └── CommandBusAdapter.ts │ │ ├── bus.module.ts │ │ ├── Auth │ │ │ ├── Controller │ │ │ │ ├── LoginController.ts │ │ │ │ └── RegisterController.ts │ │ │ ├── Strategy │ │ │ │ └── TokenStrategy.ts │ │ │ └── AuthModule.ts │ │ └── Compagny │ │ │ ├── Controller │ │ │ ├── JoinCompagnyController.ts │ │ │ ├── CreateCompagnyController.ts │ │ │ ├── GetCompaniesByUserController.ts │ │ │ └── LeaveCompagnyController.ts │ │ │ └── Repository │ │ │ └── CompagnyRepository.ts │ ├── Domain │ │ ├── User │ │ │ ├── Repository │ │ │ │ ├── IUserRepository.ts │ │ │ │ └── IUserCompagnyRepository.ts │ │ │ ├── CanUserRegister.ts │ │ │ ├── IsMemberOfCompagny.ts │ │ │ ├── IsAdminOfCompagny.ts │ │ │ ├── UserCompagny.entity.ts │ │ │ └── User.entity.ts │ │ ├── Compagny │ │ │ ├── Repository │ │ │ │ └── ICompagnyRepository.ts │ │ │ └── Compagny.entity.ts │ │ └── Input │ │ │ ├── Repository │ │ │ ├── IInputRepository.ts │ │ │ └── IQuoteRepository.ts │ │ │ ├── Quote.entity.ts │ │ │ └── Input.entity.ts │ ├── app.module.ts │ └── main.ts ├── nest-cli.json ├── .prettierrc ├── tsconfig.build.json ├── server.config.js ├── Makefile ├── ormconfig.json.dist ├── tsconfig.json ├── migrations │ ├── 1570607325020-CompagnyVoucher.ts │ └── 1570008008858-AddCompagnyToInputs.ts ├── .gitignore ├── tslint.json ├── docker-compose.yml └── .eslintrc.js └── README.md /client/Makefile: -------------------------------------------------------------------------------- 1 | start: 2 | PORT=3001 yarn start 3 | -------------------------------------------------------------------------------- /api/src/Application/IQuery.ts: -------------------------------------------------------------------------------- 1 | export interface IQuery {} 2 | -------------------------------------------------------------------------------- /api/src/Application/ICommand.ts: -------------------------------------------------------------------------------- 1 | export interface ICommand {} 2 | -------------------------------------------------------------------------------- /client/.env.development: -------------------------------------------------------------------------------- 1 | REACT_APP_API_URL=http://localhost:3000 2 | -------------------------------------------------------------------------------- /client/.env.production: -------------------------------------------------------------------------------- 1 | REACT_APP_API_URL=https://api.topflop.app 2 | -------------------------------------------------------------------------------- /client/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } 5 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /api/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmarchois/topflop/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /api/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "bracketSpacing": false 5 | } 6 | -------------------------------------------------------------------------------- /api/src/Application/Adapter/ICodeGenerator.ts: -------------------------------------------------------------------------------- 1 | export interface ICodeGeneratorAdapter { 2 | generate(): string; 3 | } 4 | -------------------------------------------------------------------------------- /api/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /api/src/Application/User/View/UsernameView.ts: -------------------------------------------------------------------------------- 1 | export class UsernameView { 2 | constructor(public firstName: string, public lastName: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /client/public/fonts/tabler-webfont/tabler-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmarchois/topflop/HEAD/client/public/fonts/tabler-webfont/tabler-webfont.eot -------------------------------------------------------------------------------- /client/public/fonts/tabler-webfont/tabler-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmarchois/topflop/HEAD/client/public/fonts/tabler-webfont/tabler-webfont.ttf -------------------------------------------------------------------------------- /client/public/fonts/tabler-webfont/tabler-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmarchois/topflop/HEAD/client/public/fonts/tabler-webfont/tabler-webfont.woff -------------------------------------------------------------------------------- /client/public/fonts/tabler-webfont/tabler-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmarchois/topflop/HEAD/client/public/fonts/tabler-webfont/tabler-webfont.woff2 -------------------------------------------------------------------------------- /api/src/Application/Adapter/IQueryBusAdapter.ts: -------------------------------------------------------------------------------- 1 | import {IQuery} from '../IQuery'; 2 | 3 | export interface IQueryBusAdapter { 4 | execute(query: IQuery): any; 5 | } 6 | -------------------------------------------------------------------------------- /api/src/Application/Adapter/ICommandBusAdapter.ts: -------------------------------------------------------------------------------- 1 | import {ICommand} from '../ICommand'; 2 | 3 | export interface ICommandBusAdapter { 4 | execute(command: ICommand): any; 5 | } 6 | -------------------------------------------------------------------------------- /client/src/modules/compagny/models/Compagny.js: -------------------------------------------------------------------------------- 1 | export default class Compagny { 2 | constructor(compagny) { 3 | this.id = compagny.id; 4 | this.name = compagny.name; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /api/src/Application/Adapter/IEncryptionAdapter.ts: -------------------------------------------------------------------------------- 1 | export interface IEncryptionAdapter { 2 | hash(payload: string): Promise; 3 | compare(payload: string, hash: string): Promise; 4 | } 5 | -------------------------------------------------------------------------------- /api/src/Infrastructure/Input/Controller/Dto/QuoteFiltersDto.ts: -------------------------------------------------------------------------------- 1 | import { PaginationDto } from 'src/Infrastructure/Common/Dto/PaginationDto'; 2 | 3 | export class QuoteFiltersDto extends PaginationDto {} 4 | -------------------------------------------------------------------------------- /api/src/Application/User/Query/GetUserByApiTokenQuery.ts: -------------------------------------------------------------------------------- 1 | import {IQuery} from 'src/Application/IQuery'; 2 | 3 | export class GetUserByApiToken implements IQuery { 4 | constructor(public readonly apiToken: string) {} 5 | } 6 | -------------------------------------------------------------------------------- /api/src/Application/User/View/UpdatedMeView.ts: -------------------------------------------------------------------------------- 1 | export class UpdatedMeView { 2 | constructor( 3 | public readonly firstName: string, 4 | public readonly lastName: string, 5 | public readonly email: string 6 | ) {} 7 | } 8 | -------------------------------------------------------------------------------- /client/src/modules/user/constants/add.js: -------------------------------------------------------------------------------- 1 | export const USER_ADD_LOADING = 'USER_ADD_LOADING'; 2 | export const USER_ADD_ERROR = 'USER_ADD_ERROR'; 3 | export const USER_ADD_RESET = 'USER_ADD_RESET'; 4 | export const USER_ADD_SUCCESS = 'USER_ADD_SUCCESS'; 5 | -------------------------------------------------------------------------------- /client/src/modules/auth/constants/registration.js: -------------------------------------------------------------------------------- 1 | export const AUTH_REGISTRATION_LOADING = 'AUTH_REGISTRATION_LOADING'; 2 | export const AUTH_REGISTRATION_ERROR = 'AUTH_REGISTRATION_ERROR'; 3 | export const AUTH_REGISTRATION_RESET = 'AUTH_REGISTRATION_RESET'; 4 | -------------------------------------------------------------------------------- /client/src/modules/input/constants/add.js: -------------------------------------------------------------------------------- 1 | export const INPUT_ADD_LOADING = 'INPUT_ADD_LOADING'; 2 | export const INPUT_ADD_ERROR = 'INPUT_ADD_ERROR'; 3 | export const INPUT_ADD_RESET = 'INPUT_ADD_RESET'; 4 | export const INPUT_ADD_SUCCESS = 'INPUT_ADD_SUCCESS'; 5 | -------------------------------------------------------------------------------- /client/src/modules/user/constants/edit.js: -------------------------------------------------------------------------------- 1 | export const USER_EDIT_LOADING = 'USER_EDIT_LOADING'; 2 | export const USER_EDIT_ERROR = 'USER_EDIT_ERROR'; 3 | export const USER_EDIT_RESET = 'USER_EDIT_RESET'; 4 | export const USER_EDIT_SUCCESS = 'USER_EDIT_SUCCESS'; 5 | -------------------------------------------------------------------------------- /client/src/modules/user/constants/list.js: -------------------------------------------------------------------------------- 1 | export const USER_LIST_LOADING = 'USER_LIST_LOADING'; 2 | export const USER_LIST_ERROR = 'USER_LIST_ERROR'; 3 | export const USER_LIST_RESET = 'USER_LIST_RESET'; 4 | export const USER_LIST_SUCCESS = 'USER_LIST_SUCCESS'; 5 | -------------------------------------------------------------------------------- /api/src/Application/Compagny/View/CompagnyView.ts: -------------------------------------------------------------------------------- 1 | export class CompagnyView { 2 | constructor( 3 | public readonly id: string, 4 | public readonly name: string, 5 | public readonly voucher: string, 6 | public readonly role?: string 7 | ) {} 8 | } 9 | -------------------------------------------------------------------------------- /client/src/modules/input/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { listReducers } from './list'; 3 | import { addReducers } from './add'; 4 | 5 | export default combineReducers({ 6 | list: listReducers, 7 | add: addReducers, 8 | }); 9 | -------------------------------------------------------------------------------- /client/src/modules/quote/constants/add.js: -------------------------------------------------------------------------------- 1 | export const QUOTES_ADD_LOADING = 'QUOTES_ADD_LOADING'; 2 | export const QUOTES_ADD_ERROR = 'QUOTES_ADD_ERROR'; 3 | export const QUOTES_ADD_RESET = 'QUOTES_ADD_RESET'; 4 | export const QUOTES_ADD_SUCCESS = 'QUOTES_ADD_SUCCESS'; 5 | -------------------------------------------------------------------------------- /api/src/Application/Auth/View/AuthenticatedView.ts: -------------------------------------------------------------------------------- 1 | import {UserView} from 'src/Application/User/View/UserView'; 2 | 3 | export class AuthenticatedView { 4 | constructor( 5 | public readonly user: UserView, 6 | public readonly apiToken: string 7 | ) {} 8 | } 9 | -------------------------------------------------------------------------------- /client/src/modules/input/constants/list.js: -------------------------------------------------------------------------------- 1 | export const INPUT_LIST_LOADING = 'INPUT_LIST_LOADING'; 2 | export const INPUT_LIST_SUCCESS = 'INPUT_LIST_SUCCESS'; 3 | export const INPUT_LIST_ERROR = 'INPUT_LIST_ERROR'; 4 | export const INPUT_LIST_RESET = 'INPUT_LIST_RESET'; 5 | -------------------------------------------------------------------------------- /api/src/Application/Input/View/InputListView.ts: -------------------------------------------------------------------------------- 1 | import {UsernameView} from 'src/Application/User/View/UsernameView'; 2 | 3 | export class InputListView { 4 | constructor( 5 | public readonly counter: number, 6 | public readonly author: UsernameView 7 | ) {} 8 | } 9 | -------------------------------------------------------------------------------- /client/src/modules/quote/constants/list.js: -------------------------------------------------------------------------------- 1 | export const QUOTES_LIST_LOADING = 'QUOTES_LIST_LOADING'; 2 | export const QUOTES_LIST_SUCCESS = 'QUOTES_LIST_SUCCESS'; 3 | export const QUOTES_LIST_ERROR = 'QUOTES_LIST_ERROR'; 4 | export const QUOTES_LIST_RESET = 'QUOTES_LIST_RESET'; 5 | -------------------------------------------------------------------------------- /client/src/modules/quote/constants/show.js: -------------------------------------------------------------------------------- 1 | export const QUOTES_SHOW_LOADING = 'QUOTES_SHOW_LOADING'; 2 | export const QUOTES_SHOW_ERROR = 'QUOTES_SHOW_ERROR'; 3 | export const QUOTES_SHOW_RESET = 'QUOTES_SHOW_RESET'; 4 | export const QUOTES_SHOW_SUCCESS = 'QUOTES_SHOW_SUCCESS'; 5 | -------------------------------------------------------------------------------- /client/src/modules/user/constants/delete.js: -------------------------------------------------------------------------------- 1 | export const USER_DELETE_LOADING = 'USER_DELETE_LOADING'; 2 | export const USER_DELETE_ERROR = 'USER_DELETE_ERROR'; 3 | export const USER_DELETE_RESET = 'USER_DELETE_RESET'; 4 | export const USER_DELETE_SUCCESS = 'USER_DELETE_SUCCESS'; 5 | -------------------------------------------------------------------------------- /api/src/Application/Compagny/Query/GetCompaniesByUserQuery.ts: -------------------------------------------------------------------------------- 1 | import {IQuery} from 'src/Application/IQuery'; 2 | import {User} from 'src/Domain/User/User.entity'; 3 | 4 | export class GetCompaniesByUserQuery implements IQuery { 5 | constructor(public readonly user: User) {} 6 | } 7 | -------------------------------------------------------------------------------- /client/src/i18n/index.js: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { initReactI18next } from 'react-i18next'; 3 | import fr from './fr.json'; 4 | 5 | i18n.use(initReactI18next).init({ 6 | resources: { 7 | fr, 8 | }, 9 | lng: 'fr', 10 | }); 11 | 12 | export default i18n; 13 | -------------------------------------------------------------------------------- /client/src/modules/compagny/constants/add.js: -------------------------------------------------------------------------------- 1 | export const COMPAGNY_ADD_LOADING = 'COMPAGNY_ADD_LOADING'; 2 | export const COMPAGNY_ADD_ERROR = 'COMPAGNY_ADD_ERROR'; 3 | export const COMPAGNY_ADD_RESET = 'COMPAGNY_ADD_RESET'; 4 | export const COMPAGNY_ADD_SUCCESS = 'COMPAGNY_ADD_SUCCESS'; 5 | -------------------------------------------------------------------------------- /client/src/modules/compagny/constants/join.js: -------------------------------------------------------------------------------- 1 | export const COMPAGNY_JOIN_LOADING = 'COMPAGNY_JOIN_LOADING'; 2 | export const COMPAGNY_JOIN_ERROR = 'COMPAGNY_JOIN_ERROR'; 3 | export const COMPAGNY_JOIN_RESET = 'COMPAGNY_JOIN_RESET'; 4 | export const COMPAGNY_JOIN_SUCCESS = 'COMPAGNY_JOIN_SUCCESS'; 5 | -------------------------------------------------------------------------------- /client/src/modules/compagny/constants/list.js: -------------------------------------------------------------------------------- 1 | export const COMPAGNY_LIST_LOADING = 'COMPAGNY_LIST_LOADING'; 2 | export const COMPAGNY_LIST_SUCCESS = 'COMPAGNY_LIST_SUCCESS'; 3 | export const COMPAGNY_LIST_ERROR = 'COMPAGNY_LIST_ERROR'; 4 | export const COMPAGNY_LIST_RESET = 'COMPAGNY_LIST_RESET'; 5 | -------------------------------------------------------------------------------- /client/src/modules/quote/constants/delete.js: -------------------------------------------------------------------------------- 1 | export const QUOTES_DELETE_LOADING = 'QUOTES_DELETE_LOADING'; 2 | export const QUOTES_DELETE_ERROR = 'QUOTES_DELETE_ERROR'; 3 | export const QUOTES_DELETE_RESET = 'QUOTES_DELETE_RESET'; 4 | export const QUOTES_DELETE_SUCCESS = 'QUOTES_DELETE_SUCCESS'; 5 | -------------------------------------------------------------------------------- /client/src/modules/user/constants/password.js: -------------------------------------------------------------------------------- 1 | export const USER_PASSWORD_LOADING = 'USER_PASSWORD_LOADING'; 2 | export const USER_PASSWORD_ERROR = 'USER_PASSWORD_ERROR'; 3 | export const USER_PASSWORD_RESET = 'USER_PASSWORD_RESET'; 4 | export const USER_PASSWORD_SUCCESS = 'USER_PASSWORD_SUCCESS'; 5 | -------------------------------------------------------------------------------- /api/src/Application/Input/Command/DeleteQuoteCommand.ts: -------------------------------------------------------------------------------- 1 | import {ICommand} from 'src/Application/ICommand'; 2 | import {GetQuotesByIdQuery} from '../Query/GetQuotesByIdQuery'; 3 | 4 | export class DeleteQuoteCommand implements ICommand { 5 | constructor(public readonly query: GetQuotesByIdQuery) {} 6 | } 7 | -------------------------------------------------------------------------------- /client/src/modules/compagny/constants/leave.js: -------------------------------------------------------------------------------- 1 | export const COMPAGNY_LEAVE_LOADING = 'COMPAGNY_LEAVE_LOADING'; 2 | export const COMPAGNY_LEAVE_ERROR = 'COMPAGNY_LEAVE_ERROR'; 3 | export const COMPAGNY_LEAVE_RESET = 'COMPAGNY_LEAVE_RESET'; 4 | export const COMPAGNY_LEAVE_SUCCESS = 'COMPAGNY_LEAVE_SUCCESS'; 5 | -------------------------------------------------------------------------------- /api/src/Infrastructure/User/Controller/Dto/CurrentCompagnyDto.ts: -------------------------------------------------------------------------------- 1 | import {ApiProperty} from '@nestjs/swagger'; 2 | import {IsNotEmpty, IsUUID} from 'class-validator'; 3 | 4 | export class CurrentCompagnyDto { 5 | @ApiProperty() 6 | @IsNotEmpty() 7 | @IsUUID() 8 | public compagny: string; 9 | } 10 | -------------------------------------------------------------------------------- /api/src/Application/User/Command/DeleteUserCompagnyCommand.ts: -------------------------------------------------------------------------------- 1 | import {ICommand} from 'src/Application/ICommand'; 2 | import {User} from 'src/Domain/User/User.entity'; 3 | 4 | export class DeleteUserCompagnyCommand implements ICommand { 5 | constructor(public readonly loggedUser: User, public readonly user: User) {} 6 | } 7 | -------------------------------------------------------------------------------- /api/src/Infrastructure/Common/Dto/PaginationDto.ts: -------------------------------------------------------------------------------- 1 | import {ApiProperty} from '@nestjs/swagger'; 2 | import {IsNotEmpty, IsNumberString} from 'class-validator'; 3 | 4 | export abstract class PaginationDto { 5 | @ApiProperty() 6 | @IsNotEmpty() 7 | @IsNumberString() 8 | public readonly page: number = 1; 9 | } 10 | -------------------------------------------------------------------------------- /api/server.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps: [ 3 | { 4 | name: 'TopFlop API', 5 | script: './dist/src/main.js', 6 | instances: 2, 7 | exec_mode: 'cluster', 8 | watch: true, 9 | env: { 10 | NODE_ENV: 'production', 11 | PORT: '3000' 12 | } 13 | } 14 | ] 15 | }; 16 | -------------------------------------------------------------------------------- /client/src/modules/auth/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { registrationReducers } from './registration'; 3 | import { authenticationReducers } from './authentication'; 4 | 5 | export default combineReducers({ 6 | registration: registrationReducers, 7 | authentication: authenticationReducers, 8 | }); 9 | -------------------------------------------------------------------------------- /api/src/Application/Input/View/InputView.ts: -------------------------------------------------------------------------------- 1 | import {UsernameView} from 'src/Application/User/View/UsernameView'; 2 | 3 | export class InputView { 4 | constructor( 5 | public readonly id: string, 6 | public readonly type: string, 7 | public readonly createdAt: Date, 8 | public readonly author: UsernameView 9 | ) {} 10 | } 11 | -------------------------------------------------------------------------------- /api/src/Domain/User/Repository/IUserRepository.ts: -------------------------------------------------------------------------------- 1 | import {User} from '../User.entity'; 2 | 3 | export interface IUserRepository { 4 | findOneByEmail(email: string): Promise; 5 | findOneByApiToken(apiToken: string): Promise; 6 | findOneById(id: string): Promise; 7 | save(user: User): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /client/src/modules/compagny/components/form/validators/compagny.js: -------------------------------------------------------------------------------- 1 | import i18n from '../../../../../i18n'; 2 | 3 | const validate = payload => { 4 | const errors = {}; 5 | 6 | if (!payload.name) { 7 | errors.name = i18n.t('form.errors.requiredField'); 8 | } 9 | 10 | return errors; 11 | }; 12 | 13 | export default validate; 14 | -------------------------------------------------------------------------------- /client/src/modules/compagny/components/form/validators/join.js: -------------------------------------------------------------------------------- 1 | import i18n from '../../../../../i18n'; 2 | 3 | const validate = payload => { 4 | const errors = {}; 5 | 6 | if (!payload.voucher) { 7 | errors.voucher = i18n.t('form.errors.requiredField'); 8 | } 9 | 10 | return errors; 11 | }; 12 | 13 | export default validate; 14 | -------------------------------------------------------------------------------- /client/src/modules/quote/components/form/validators/quote.js: -------------------------------------------------------------------------------- 1 | import i18n from '../../../../../i18n'; 2 | 3 | const validate = payload => { 4 | const errors = {}; 5 | 6 | if (!payload.sentence) { 7 | errors.sentence = i18n.t('form.errors.requiredField'); 8 | } 9 | 10 | return errors; 11 | }; 12 | 13 | export default validate; 14 | -------------------------------------------------------------------------------- /api/src/Application/Input/View/QuoteView.ts: -------------------------------------------------------------------------------- 1 | import {UsernameView} from 'src/Application/User/View/UsernameView'; 2 | 3 | export class QuoteView { 4 | constructor( 5 | public readonly id: string, 6 | public readonly sentence: string, 7 | public readonly createdAt: Date, 8 | public readonly author: UsernameView 9 | ) {} 10 | } 11 | -------------------------------------------------------------------------------- /api/src/Application/User/Query/GetUserByIdQuery.ts: -------------------------------------------------------------------------------- 1 | import {IQuery} from 'src/Application/IQuery'; 2 | import {ApiProperty} from '@nestjs/swagger'; 3 | import {IsNotEmpty, IsUUID} from 'class-validator'; 4 | 5 | export class GetUserByIdQuery implements IQuery { 6 | @ApiProperty() 7 | @IsNotEmpty() 8 | @IsUUID() 9 | public id: string; 10 | } 11 | -------------------------------------------------------------------------------- /client/src/modules/user/constants/currentCompagny.js: -------------------------------------------------------------------------------- 1 | export const USER_CURRENT_COMPAGNY_LOADING = 'USER_CURRENT_COMPAGNY_LOADING'; 2 | export const USER_CURRENT_COMPAGNY_ERROR = 'USER_CURRENT_COMPAGNY_ERROR'; 3 | export const USER_CURRENT_COMPAGNY_RESET = 'USER_CURRENT_COMPAGNY_RESET'; 4 | export const USER_CURRENT_COMPAGNY_SUCCESS = 'USER_CURRENT_COMPAGNY_SUCCESS'; 5 | -------------------------------------------------------------------------------- /api/src/Application/Compagny/Query/GetCompagnyByIdQuery.ts: -------------------------------------------------------------------------------- 1 | import {IQuery} from 'src/Application/IQuery'; 2 | import {ApiProperty} from '@nestjs/swagger'; 3 | import {IsUUID, IsNotEmpty} from 'class-validator'; 4 | 5 | export class GetCompagnyByIdQuery implements IQuery { 6 | @ApiProperty() 7 | @IsNotEmpty() 8 | @IsUUID() 9 | public id: string; 10 | } 11 | -------------------------------------------------------------------------------- /api/src/Application/Compagny/Command/LeaveCompagnyCommand.ts: -------------------------------------------------------------------------------- 1 | import {ICommand} from 'src/Application/ICommand'; 2 | import {Compagny} from 'src/Domain/Compagny/Compagny.entity'; 3 | import {User} from 'src/Domain/User/User.entity'; 4 | 5 | export class LeaveCompagnyCommand implements ICommand { 6 | constructor(public readonly compagny: Compagny, public readonly user: User) {} 7 | } 8 | -------------------------------------------------------------------------------- /api/Makefile: -------------------------------------------------------------------------------- 1 | start: 2 | docker-compose -p topflop up -d 3 | stop: 4 | docker-compose -p topflop stop 5 | build: 6 | docker-compose -p topflop build 7 | down: 8 | docker-compose -p topflop down 9 | ps: 10 | docker-compose -p topflop ps 11 | logs: 12 | docker-compose -p topflop logs -f api 13 | init-database: 14 | docker-compose -p topflop run api npm run migration:migrate 15 | -------------------------------------------------------------------------------- /api/src/Domain/Compagny/Repository/ICompagnyRepository.ts: -------------------------------------------------------------------------------- 1 | import {Compagny} from '../Compagny.entity'; 2 | 3 | export interface ICompagnyRepository { 4 | save(compagny: Compagny): Promise; 5 | findOneById(id: string): Promise; 6 | findOneByName(name: string): Promise; 7 | findOneByVoucher(voucher: string): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /api/src/Application/User/Command/ChangeCurrentCompagnyCommand.ts: -------------------------------------------------------------------------------- 1 | import {ICommand} from 'src/Application/ICommand'; 2 | import {User} from 'src/Domain/User/User.entity'; 3 | import {Compagny} from 'src/Domain/Compagny/Compagny.entity'; 4 | 5 | export class ChangeCurrentCompagnyCommand implements ICommand { 6 | constructor(public readonly user: User, public readonly compagny: Compagny) {} 7 | } 8 | -------------------------------------------------------------------------------- /api/src/Infrastructure/User/Controller/Dto/UserFiltersDto.ts: -------------------------------------------------------------------------------- 1 | import {PaginationDto} from 'src/Infrastructure/Common/Dto/PaginationDto'; 2 | import {ApiPropertyOptional} from '@nestjs/swagger'; 3 | import {IsOptional} from 'class-validator'; 4 | 5 | export class UserFiltersDto extends PaginationDto { 6 | @ApiPropertyOptional() 7 | @IsOptional() 8 | public search: string; 9 | } 10 | -------------------------------------------------------------------------------- /api/src/Application/Common/Pagination.ts: -------------------------------------------------------------------------------- 1 | export const MAX_ITEMS_PER_PAGE = 20; 2 | 3 | export class Pagination { 4 | public readonly pageCount: number; 5 | 6 | constructor( 7 | public readonly items: PaginationObject[], 8 | public readonly totalItems: number 9 | ) { 10 | this.pageCount = Math.ceil(this.totalItems / MAX_ITEMS_PER_PAGE); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /api/src/Infrastructure/User/Decorator/LoggedUser.ts: -------------------------------------------------------------------------------- 1 | import {createParamDecorator, ExecutionContext} from '@nestjs/common'; 2 | import {User} from 'src/Domain/User/User.entity'; 3 | 4 | export const LoggedUser = createParamDecorator( 5 | (data: unknown, ctx: ExecutionContext): User => { 6 | const request = ctx.switchToHttp().getRequest(); 7 | 8 | return request.user; 9 | } 10 | ); 11 | -------------------------------------------------------------------------------- /client/src/modules/common/components/LoadingComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const LoadingComponent = ({ isLoading, error }) => { 4 | if (isLoading) { 5 | return
Loading...
; 6 | } else if (error) { 7 | return
Sorry, there was a problem loading the page.
; 8 | } else { 9 | return null; 10 | } 11 | }; 12 | 13 | export default LoadingComponent; 14 | -------------------------------------------------------------------------------- /client/src/modules/user/models/LoggedUser.js: -------------------------------------------------------------------------------- 1 | import Compagny from '../../compagny/models/Compagny'; 2 | 3 | export default class LoggedUser { 4 | constructor(user) { 5 | this.firstName = user.firstName; 6 | this.lastName = user.lastName; 7 | this.email = user.email; 8 | this.role = user.role; 9 | this.compagny = user.compagny ? new Compagny(user.compagny) : null; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /api/ormconfig.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "type": "postgres", 3 | "host": "database", 4 | "port": 5432, 5 | "username": "docker", 6 | "password": "docker", 7 | "database": "topflop", 8 | "synchronize": false, 9 | "logging": ["error"], 10 | "entities": ["dist/src/Domain/**/*.entity{.ts,.js}"], 11 | "migrations": ["dist/migrations/**/*{.ts,.js}"], 12 | "migrationsTableName": "migrations" 13 | } 14 | -------------------------------------------------------------------------------- /api/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 | -------------------------------------------------------------------------------- /client/src/modules/compagny/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { addReducers } from './add'; 3 | import { listReducers } from './list'; 4 | import { joinReducers } from './join'; 5 | import { leaveReducers } from './leave'; 6 | 7 | export default combineReducers({ 8 | add: addReducers, 9 | list: listReducers, 10 | join: joinReducers, 11 | leave: leaveReducers, 12 | }); 13 | -------------------------------------------------------------------------------- /client/src/modules/quote/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { listReducers } from './list'; 3 | import { addReducers } from './add'; 4 | import { showReducers } from './show'; 5 | import { deleteReducers } from './delete'; 6 | 7 | export default combineReducers({ 8 | list: listReducers, 9 | add: addReducers, 10 | show: showReducers, 11 | delete: deleteReducers, 12 | }); 13 | -------------------------------------------------------------------------------- /api/src/Application/Compagny/Command/JoinCompagnyCommand.ts: -------------------------------------------------------------------------------- 1 | import {ApiProperty} from '@nestjs/swagger'; 2 | import {IsNotEmpty} from 'class-validator'; 3 | import {ICommand} from 'src/Application/ICommand'; 4 | import {User} from 'src/Domain/User/User.entity'; 5 | 6 | export class JoinCompagnyCommand implements ICommand { 7 | @ApiProperty() 8 | @IsNotEmpty() 9 | public voucher: string; 10 | public user: User; 11 | } 12 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /client/src/utils/tokenStorage.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie'; 2 | 3 | export const TOKEN_KEY = 'topflop_token'; 4 | 5 | export class TokenStorage { 6 | static save = token => { 7 | Cookies.set(TOKEN_KEY, token, { 8 | secure: process.env.NODE_ENV === 'production', 9 | }); 10 | }; 11 | 12 | static get = () => Cookies.get(TOKEN_KEY); 13 | 14 | static remove = () => Cookies.remove(TOKEN_KEY); 15 | } 16 | -------------------------------------------------------------------------------- /api/src/Application/Compagny/Command/CreateCompagnyCommand.ts: -------------------------------------------------------------------------------- 1 | import {ApiProperty} from '@nestjs/swagger'; 2 | import {IsNotEmpty} from 'class-validator'; 3 | import {ICommand} from 'src/Application/ICommand'; 4 | import {User} from 'src/Domain/User/User.entity'; 5 | 6 | export class CreateCompagnyCommand implements ICommand { 7 | @ApiProperty() 8 | @IsNotEmpty() 9 | public name: string; 10 | public user: User; 11 | } 12 | -------------------------------------------------------------------------------- /client/src/modules/common/components/SuccessMessage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const SuccessMessage = ({ message }) => { 5 | return ( 6 |
7 | {message} 8 |
9 | ); 10 | }; 11 | 12 | SuccessMessage.propTypes = { 13 | message: PropTypes.string.isRequired, 14 | }; 15 | 16 | export default SuccessMessage; 17 | -------------------------------------------------------------------------------- /api/src/Application/Auth/Command/LoginCommand.ts: -------------------------------------------------------------------------------- 1 | import {ICommand} from 'src/Application/ICommand'; 2 | import {IsNotEmpty, IsEmail} from 'class-validator'; 3 | import {ApiProperty} from '@nestjs/swagger'; 4 | 5 | export class LoginCommand implements ICommand { 6 | @IsNotEmpty() 7 | @IsEmail() 8 | @ApiProperty() 9 | public email: string; 10 | 11 | @IsNotEmpty() 12 | @ApiProperty() 13 | public password: string; 14 | } 15 | -------------------------------------------------------------------------------- /api/src/Application/User/Command/UpdatePasswordCommand.ts: -------------------------------------------------------------------------------- 1 | import {ICommand} from 'src/Application/ICommand'; 2 | import {ApiProperty} from '@nestjs/swagger'; 3 | import {IsNotEmpty} from 'class-validator'; 4 | import {User} from 'src/Domain/User/User.entity'; 5 | 6 | export class UpdatePasswordCommand implements ICommand { 7 | @ApiProperty() 8 | @IsNotEmpty() 9 | public password: string; 10 | 11 | public user: User; 12 | } 13 | -------------------------------------------------------------------------------- /api/src/Application/User/Query/GetUsersByCompagnyQuery.ts: -------------------------------------------------------------------------------- 1 | import {User} from 'src/Domain/User/User.entity'; 2 | import {IQuery} from 'src/Application/IQuery'; 3 | import {UserFiltersDto} from 'src/Infrastructure/User/Controller/Dto/UserFiltersDto'; 4 | 5 | export class GetUsersByCompagnyQuery implements IQuery { 6 | constructor( 7 | public readonly user: User, 8 | public readonly filters: UserFiltersDto 9 | ) {} 10 | } 11 | -------------------------------------------------------------------------------- /api/src/Application/Input/Query/GetQuotesByIdQuery.ts: -------------------------------------------------------------------------------- 1 | import {IQuery} from 'src/Application/IQuery'; 2 | import {ApiProperty} from '@nestjs/swagger'; 3 | import {IsNotEmpty, IsUUID} from 'class-validator'; 4 | import {User} from 'src/Domain/User/User.entity'; 5 | 6 | export class GetQuotesByIdQuery implements IQuery { 7 | @ApiProperty() 8 | @IsNotEmpty() 9 | @IsUUID() 10 | public id: string; 11 | 12 | public user: User; 13 | } 14 | -------------------------------------------------------------------------------- /api/src/Application/Input/Query/GetInputsByCompagnyQuery.ts: -------------------------------------------------------------------------------- 1 | import {IQuery} from 'src/Application/IQuery'; 2 | import {Compagny} from 'src/Domain/Compagny/Compagny.entity'; 3 | import {InputFiltersDto} from 'src/Infrastructure/Input/Controller/Dto/InputFiltersDto'; 4 | 5 | export class GetInputsByCompagnyQuery implements IQuery { 6 | constructor( 7 | public readonly compagny: Compagny, 8 | public readonly filters: InputFiltersDto 9 | ) {} 10 | } 11 | -------------------------------------------------------------------------------- /api/src/Application/Input/Query/GetQuotesByCompagnyQuery.ts: -------------------------------------------------------------------------------- 1 | import {IQuery} from 'src/Application/IQuery'; 2 | import {Compagny} from 'src/Domain/Compagny/Compagny.entity'; 3 | import {QuoteFiltersDto} from 'src/Infrastructure/Input/Controller/Dto/QuoteFiltersDto'; 4 | 5 | export class GetQuotesByCompagnyQuery implements IQuery { 6 | constructor( 7 | public readonly compagny: Compagny, 8 | public readonly filters: QuoteFiltersDto 9 | ) {} 10 | } 11 | -------------------------------------------------------------------------------- /client/src/utils/errorFormater.js: -------------------------------------------------------------------------------- 1 | const errorFormater = exception => { 2 | const message = exception.message; 3 | if (Array.isArray(message)) { 4 | const errors = []; 5 | 6 | for (const msg of message) { 7 | for (const constraint of Object.values(msg.constraints)) { 8 | errors.push(constraint); 9 | } 10 | } 11 | 12 | return errors; 13 | } 14 | 15 | return [message]; 16 | }; 17 | 18 | export default errorFormater; 19 | -------------------------------------------------------------------------------- /api/src/Infrastructure/Adapter/EncryptionAdapter.ts: -------------------------------------------------------------------------------- 1 | import * as argon2 from 'argon2'; 2 | import {IEncryptionAdapter} from 'src/Application/Adapter/IEncryptionAdapter'; 3 | 4 | export class EncryptionAdapter implements IEncryptionAdapter { 5 | public hash(payload: string): Promise { 6 | return argon2.hash(payload); 7 | } 8 | 9 | public compare(payload: string, hash: string): Promise { 10 | return argon2.verify(hash, payload); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /client/src/modules/auth/constants/authentication.js: -------------------------------------------------------------------------------- 1 | export const AUTH_AUTHENTICATION_LOADING = 'AUTH_AUTHENTICATION_LOADING'; 2 | export const AUTH_AUTHENTICATION_ERROR = 'AUTH_AUTHENTICATION_ERROR'; 3 | export const AUTH_AUTHENTICATION_RESET = 'AUTH_AUTHENTICATION_RESET'; 4 | export const AUTH_AUTHENTICATION_LOGOUT = 'AUTH_AUTHENTICATION_LOGOUT'; 5 | export const AUTH_AUTHENTICATION_USER = 'AUTH_AUTHENTICATION_USER'; 6 | export const AUTH_AUTHENTICATION_AUTHENTICATED = 7 | 'AUTH_AUTHENTICATION_AUTHENTICATED'; 8 | -------------------------------------------------------------------------------- /client/src/modules/quote/middlewares/delete.js: -------------------------------------------------------------------------------- 1 | import { loading, success, errors } from '../actions/delete'; 2 | import errorFormater from '../../../utils/errorFormater'; 3 | 4 | export const deleteQuote = id => async (dispatch, getState, axios) => { 5 | dispatch(loading(true)); 6 | 7 | try { 8 | await axios.delete(`quotes/${id}`); 9 | 10 | dispatch(success(id)); 11 | } catch (e) { 12 | dispatch(errors(errorFormater(e))); 13 | } finally { 14 | dispatch(loading(false)); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /api/src/Infrastructure/Adapter/CodeGeneratorAdapter.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@nestjs/common'; 2 | import * as shortid from 'shortid'; 3 | import {ICodeGeneratorAdapter} from 'src/Application/Adapter/ICodeGenerator'; 4 | 5 | @Injectable() 6 | export class CodeGeneratorAdapter implements ICodeGeneratorAdapter { 7 | public generate = () => { 8 | shortid.characters( 9 | '0123456789abcdefghijklmnopqrstuvwxyz$@ABCDEFGHIJKLMNOPQRSTUVWXYZ' 10 | ); 11 | 12 | return shortid.generate(); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /api/src/Infrastructure/Adapter/QueryBusAdapter.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@nestjs/common'; 2 | import {QueryBus} from '@nestjs/cqrs'; 3 | import {IQuery} from 'src/Application/IQuery'; 4 | import {IQueryBusAdapter} from 'src/Application/Adapter/IQueryBusAdapter'; 5 | 6 | @Injectable() 7 | export class QueryBusAdapter implements IQueryBusAdapter { 8 | constructor(private readonly queryBus: QueryBus) {} 9 | 10 | public execute = (query: IQuery): any => { 11 | return this.queryBus.execute(query); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /client/src/store/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { reducer as formReducer } from 'redux-form'; 3 | import auth from '../modules/auth/reducers'; 4 | import compagny from '../modules/compagny/reducers'; 5 | import quote from '../modules/quote/reducers'; 6 | import user from '../modules/user/reducers'; 7 | import input from '../modules/input/reducers'; 8 | 9 | export default combineReducers({ 10 | auth, 11 | compagny, 12 | quote, 13 | user, 14 | input, 15 | form: formReducer, 16 | }); 17 | -------------------------------------------------------------------------------- /api/src/Application/Input/Command/CreateQuoteCommand.ts: -------------------------------------------------------------------------------- 1 | import {ApiProperty} from '@nestjs/swagger'; 2 | import {IsNotEmpty, IsUUID} from 'class-validator'; 3 | import {ICommand} from 'src/Application/ICommand'; 4 | import {User} from 'src/Domain/User/User.entity'; 5 | 6 | export class CreateQuoteCommand implements ICommand { 7 | @ApiProperty() 8 | @IsNotEmpty() 9 | public sentence: string; 10 | 11 | @ApiProperty() 12 | @IsNotEmpty() 13 | @IsUUID() 14 | public authorId: string; 15 | 16 | public user: User; 17 | } 18 | -------------------------------------------------------------------------------- /client/src/modules/user/middlewares/password.js: -------------------------------------------------------------------------------- 1 | import { loading, errors, success } from '../actions/password'; 2 | import errorFormater from '../../../utils/errorFormater'; 3 | 4 | export const editPassword = payload => async (dispatch, getState, axios) => { 5 | dispatch(loading(true)); 6 | 7 | try { 8 | await axios.put('users/me/password', payload); 9 | 10 | dispatch(success(true)); 11 | } catch (e) { 12 | dispatch(errors(errorFormater(e))); 13 | } finally { 14 | dispatch(loading(false)); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /api/src/Application/User/Command/CreateUserCompagnyCommand.ts: -------------------------------------------------------------------------------- 1 | import {ICommand} from 'src/Application/ICommand'; 2 | import {User} from 'src/Domain/User/User.entity'; 3 | import {Compagny} from 'src/Domain/Compagny/Compagny.entity'; 4 | import {UserRole} from 'src/Domain/User/UserCompagny.entity'; 5 | 6 | export class CreateUserCompagnyCommand implements ICommand { 7 | constructor( 8 | public readonly user: User, 9 | public readonly compagny: Compagny, 10 | public readonly role: string = UserRole.ADMIN 11 | ) {} 12 | } 13 | -------------------------------------------------------------------------------- /client/src/modules/compagny/middlewares/list.js: -------------------------------------------------------------------------------- 1 | import { loading, success, errors } from '../actions/list'; 2 | import errorFormater from '../../../utils/errorFormater'; 3 | 4 | export const listCompanies = () => async (dispatch, getState, axios) => { 5 | dispatch(loading(true)); 6 | 7 | try { 8 | const response = await axios.get('users/me/companies'); 9 | dispatch(success(response.data)); 10 | } catch (e) { 11 | dispatch(errors(errorFormater(e))); 12 | } finally { 13 | dispatch(loading(false)); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /client/src/modules/quote/middlewares/show.js: -------------------------------------------------------------------------------- 1 | import { loading, success, errors } from '../actions/show'; 2 | import errorFormater from '../../../utils/errorFormater'; 3 | 4 | export const getQuote = id => async (dispatch, getState, axios) => { 5 | dispatch(loading(true)); 6 | 7 | try { 8 | const response = await axios.get(`quotes/${id}/detail`); 9 | 10 | dispatch(success(response.data)); 11 | } catch (e) { 12 | dispatch(errors(errorFormater(e))); 13 | } finally { 14 | dispatch(loading(false)); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /api/migrations/1570607325020-CompagnyVoucher.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | 3 | export class CompagnyVoucher1570607325020 implements MigrationInterface { 4 | 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.query(`ALTER TABLE "compagny" ADD "voucher" character varying`); 7 | } 8 | 9 | public async down(queryRunner: QueryRunner): Promise { 10 | await queryRunner.query(`ALTER TABLE "compagny" DROP COLUMN "voucher"`); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /client/src/modules/user/middlewares/delete.js: -------------------------------------------------------------------------------- 1 | import { loading, success, errors } from '../actions/delete'; 2 | import errorFormater from '../../../utils/errorFormater'; 3 | 4 | export const deleteUser = userId => async (dispatch, getState, axios) => { 5 | dispatch(loading(true)); 6 | 7 | try { 8 | await axios.delete(`users/me/current-compagny/users/${userId}`); 9 | dispatch(success(userId)); 10 | } catch (e) { 11 | dispatch(errors(errorFormater(e))); 12 | } finally { 13 | dispatch(loading(false)); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /api/src/Infrastructure/Adapter/CommandBusAdapter.ts: -------------------------------------------------------------------------------- 1 | import {CommandBus} from '@nestjs/cqrs'; 2 | import {ICommandBusAdapter} from 'src/Application/Adapter/ICommandBusAdapter'; 3 | import {ICommand} from 'src/Application/ICommand'; 4 | import {Injectable} from '@nestjs/common'; 5 | 6 | @Injectable() 7 | export class CommandBusAdapter implements ICommandBusAdapter { 8 | constructor(private readonly commandBus: CommandBus) {} 9 | 10 | public execute = (command: ICommand): any => { 11 | return this.commandBus.execute(command); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /api/src/Infrastructure/bus.module.ts: -------------------------------------------------------------------------------- 1 | import {Module} from '@nestjs/common'; 2 | import {CqrsModule} from '@nestjs/cqrs'; 3 | import {CommandBusAdapter} from './Adapter/CommandBusAdapter'; 4 | import {QueryBusAdapter} from './Adapter/QueryBusAdapter'; 5 | 6 | const providers = [ 7 | {provide: 'ICommandBus', useClass: CommandBusAdapter}, 8 | {provide: 'IQueryBus', useClass: QueryBusAdapter} 9 | ]; 10 | 11 | @Module({ 12 | imports: [CqrsModule], 13 | providers: [...providers], 14 | exports: [...providers] 15 | }) 16 | export class BusModule {} 17 | -------------------------------------------------------------------------------- /api/src/Domain/User/CanUserRegister.ts: -------------------------------------------------------------------------------- 1 | import {IUserRepository} from './Repository/IUserRepository'; 2 | import {Injectable, Inject} from '@nestjs/common'; 3 | import {User} from './User.entity'; 4 | 5 | @Injectable() 6 | export class CanUserRegister { 7 | constructor( 8 | @Inject('IUserRepository') 9 | private readonly userRepository: IUserRepository 10 | ) {} 11 | 12 | public isSatisfiedBy = async (email: string): Promise => { 13 | return !((await this.userRepository.findOneByEmail(email)) instanceof User); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /client/src/modules/user/actions/edit.js: -------------------------------------------------------------------------------- 1 | import { 2 | USER_EDIT_LOADING, 3 | USER_EDIT_SUCCESS, 4 | USER_EDIT_ERROR, 5 | USER_EDIT_RESET, 6 | } from '../constants/edit'; 7 | 8 | export const loading = loading => ({ 9 | type: USER_EDIT_LOADING, 10 | loading, 11 | }); 12 | 13 | export const success = payload => ({ 14 | type: USER_EDIT_SUCCESS, 15 | payload, 16 | }); 17 | 18 | export const errors = errors => ({ 19 | type: USER_EDIT_ERROR, 20 | errors, 21 | }); 22 | 23 | export const reset = () => ({ 24 | type: USER_EDIT_RESET, 25 | }); 26 | -------------------------------------------------------------------------------- /client/src/modules/common/components/Layout/Layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Header from './Header'; 3 | import Footer from './Footer'; 4 | import Nav from './Nav'; 5 | 6 | const Layout = ({ children }) => { 7 | return ( 8 | <> 9 |
10 |
11 |
16 |