├── .gitignore ├── client ├── .gitignore ├── next-env.d.ts ├── graphql │ └── user │ │ └── mutations │ │ └── login.ts ├── interfaces │ └── index.ts ├── components │ ├── auth │ │ └── login │ │ │ ├── loginConnector.tsx │ │ │ ├── loginController.tsx │ │ │ └── loginView.tsx │ ├── partials │ │ └── InputField.tsx │ └── Layout.tsx ├── lib │ └── apollo.js ├── pages │ ├── auth │ │ └── login.tsx │ └── checkout.tsx ├── tsconfig.json ├── package.json └── README.md └── server ├── services ├── shared │ ├── .gitignore │ ├── dist │ │ ├── dto │ │ │ ├── index.js.map │ │ │ ├── index.js │ │ │ ├── user.dto.js.map │ │ │ ├── order.dto.js │ │ │ ├── order.dto.js.map │ │ │ ├── user.dto.js │ │ │ ├── address.dto.js │ │ │ ├── address.dto.js.map │ │ │ ├── product.dto.js │ │ │ └── product.dto.js.map │ │ ├── index.js.map │ │ ├── index.js │ │ └── config │ │ │ ├── index.js.map │ │ │ └── index.js │ ├── src │ │ ├── dto │ │ │ ├── auth-token.dto.ts │ │ │ ├── address.dto.ts │ │ │ ├── index.ts │ │ │ ├── product.dto.ts │ │ │ ├── payment.dto.ts │ │ │ ├── user.dto.ts │ │ │ └── order.dto.ts │ │ ├── index.ts │ │ └── config │ │ │ └── index.ts │ ├── .env │ ├── package.json │ ├── tslint.json │ └── tsconfig.json ├── gateway │ ├── src │ │ ├── contracts │ │ │ └── nest-dataloader.ts │ │ ├── utils │ │ │ ├── redis.ts │ │ │ └── base-resolver.ts │ │ ├── shared │ │ │ └── validation │ │ │ │ └── uuid.validation.ts │ │ ├── orders │ │ │ ├── create-order.validation.ts │ │ │ ├── order.gql │ │ │ ├── orders.module.ts │ │ │ ├── order.service.ts │ │ │ └── order.resolver.ts │ │ ├── users │ │ │ ├── login-user.validation.ts │ │ │ ├── users.module.ts │ │ │ ├── register-user.validation.ts │ │ │ ├── user.gql │ │ │ ├── user.resolver.ts │ │ │ └── user.service.ts │ │ ├── filters │ │ │ └── graphql-exception.filter.ts │ │ ├── payments │ │ │ ├── create-payment-card.validation.ts │ │ │ ├── payment-card.gql │ │ │ ├── payment.module.ts │ │ │ ├── payment.service.ts │ │ │ └── payment.resolver.ts │ │ ├── middlewares │ │ │ ├── seller.guard.ts │ │ │ └── auth.guard.ts │ │ ├── products │ │ │ ├── product.gql │ │ │ ├── create-product.validation.ts │ │ │ ├── products.module.ts │ │ │ ├── product.resolver.ts │ │ │ └── product.service.ts │ │ ├── loaders │ │ │ ├── user.loader.ts │ │ │ └── order-product.loader.ts │ │ ├── interceptors │ │ │ └── logging.interceptor.ts │ │ ├── pipes │ │ │ └── validation.pipe.ts │ │ ├── app.module.ts │ │ ├── main.ts │ │ └── schemas │ │ │ └── graphql.d.ts │ ├── test │ │ ├── jest-e2e.json │ │ └── app.e2e-spec.ts │ ├── tslint.json │ ├── .gitignore │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── payments │ ├── tsconfig.build.json │ ├── src │ │ ├── utils │ │ │ └── stripe.ts │ │ ├── app.module.ts │ │ ├── payments │ │ │ ├── payments.module.ts │ │ │ ├── payment.entity.ts │ │ │ ├── payment.controller.ts │ │ │ └── payment.service.ts │ │ └── main.ts │ ├── test │ │ ├── jest-e2e.json │ │ └── app.e2e-spec.ts │ ├── tslint.json │ ├── .gitignore │ ├── ormconfig.json │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── products │ ├── tsconfig.build.json │ ├── test │ │ ├── jest-e2e.json │ │ └── app.e2e-spec.ts │ ├── src │ │ ├── app.module.ts │ │ ├── products │ │ │ ├── products.module.ts │ │ │ ├── product.entity.ts │ │ │ ├── product.controller.ts │ │ │ └── product.service.ts │ │ └── main.ts │ ├── tslint.json │ ├── .gitignore │ ├── ormconfig.json │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── users │ ├── tsconfig.build.json │ ├── test │ │ ├── jest-e2e.json │ │ └── app.e2e-spec.ts │ ├── src │ │ ├── app.module.ts │ │ ├── users │ │ │ ├── users.module.ts │ │ │ ├── address.entity.ts │ │ │ ├── user.controller.ts │ │ │ ├── user.entity.ts │ │ │ └── user.service.ts │ │ └── main.ts │ ├── tslint.json │ ├── .gitignore │ ├── tsconfig.json │ ├── ormconfig.json │ ├── package.json │ └── README.md └── orders │ ├── tsconfig.build.json │ ├── test │ ├── jest-e2e.json │ └── app.e2e-spec.ts │ ├── src │ ├── app.module.ts │ ├── orders │ │ ├── orders.module.ts │ │ ├── order.entity.ts │ │ ├── order.controller.ts │ │ └── order.service.ts │ └── main.ts │ ├── tslint.json │ ├── tsconfig.json │ ├── .gitignore │ ├── ormconfig.json │ ├── package.json │ └── README.md ├── nest-cli.json ├── .gitignore ├── package.json ├── tslint.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | server/services/shared/dist/** 2 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | -------------------------------------------------------------------------------- /server/services/shared/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /node_modules -------------------------------------------------------------------------------- /client/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /server/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "ts", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src" 5 | } 6 | -------------------------------------------------------------------------------- /server/services/gateway/src/contracts/nest-dataloader.ts: -------------------------------------------------------------------------------- 1 | export interface IDataLoader { 2 | load(id: K): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /server/services/shared/dist/dto/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/dto/index.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /server/services/payments/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /server/services/products/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /server/services/shared/dist/dto/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /server/services/shared/src/dto/auth-token.dto.ts: -------------------------------------------------------------------------------- 1 | import { ObjectID } from "typeorm"; 2 | 3 | export interface AuthToken { 4 | id: ObjectID; 5 | } 6 | -------------------------------------------------------------------------------- /server/services/users/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /server/services/orders/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /server/services/shared/dist/dto/user.dto.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"user.dto.js","sourceRoot":"","sources":["../../src/dto/user.dto.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /server/services/gateway/src/utils/redis.ts: -------------------------------------------------------------------------------- 1 | import Redis from "ioredis"; 2 | export const redisProductsKey = "index-products"; 3 | export const redis = new Redis(); 4 | -------------------------------------------------------------------------------- /server/services/shared/dist/dto/order.dto.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=order.dto.js.map -------------------------------------------------------------------------------- /server/services/shared/dist/dto/order.dto.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"order.dto.js","sourceRoot":"","sources":["../../src/dto/order.dto.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /server/services/shared/dist/dto/user.dto.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=user.dto.js.map -------------------------------------------------------------------------------- /server/services/shared/dist/dto/address.dto.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=address.dto.js.map -------------------------------------------------------------------------------- /server/services/shared/dist/dto/address.dto.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"address.dto.js","sourceRoot":"","sources":["../../src/dto/address.dto.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /server/services/shared/dist/dto/product.dto.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=product.dto.js.map -------------------------------------------------------------------------------- /server/services/shared/dist/dto/product.dto.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"product.dto.js","sourceRoot":"","sources":["../../src/dto/product.dto.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /server/services/payments/src/utils/stripe.ts: -------------------------------------------------------------------------------- 1 | import * as Stripe from "stripe"; 2 | import { config } from "@commerce/shared"; 3 | export const stripe = new Stripe(config.STRIPE_SECRET_KEY); 4 | -------------------------------------------------------------------------------- /server/services/shared/dist/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,yBAAuB;AAOvB,mCAAkC;AAAzB,0BAAA,MAAM,CAAA"} -------------------------------------------------------------------------------- /server/services/gateway/src/shared/validation/uuid.validation.ts: -------------------------------------------------------------------------------- 1 | import { IsUUID, IsNotEmpty } from "class-validator"; 2 | 3 | export class UUID { 4 | @IsUUID() 5 | @IsNotEmpty() 6 | id: string; 7 | } 8 | -------------------------------------------------------------------------------- /server/services/shared/src/dto/address.dto.ts: -------------------------------------------------------------------------------- 1 | export interface AddressDTO { 2 | address_1: string; 3 | address_2: string; 4 | city: string; 5 | state: string; 6 | country: string; 7 | zip: number; 8 | } 9 | -------------------------------------------------------------------------------- /server/services/shared/src/dto/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./address.dto"; 2 | export * from "./user.dto"; 3 | export * from "./product.dto"; 4 | export * from "./order.dto"; 5 | export * from "./auth-token.dto"; 6 | export * from "./payment.dto"; 7 | -------------------------------------------------------------------------------- /server/services/shared/.env: -------------------------------------------------------------------------------- 1 | JWT_TOKEN=onqwodnqwodnoqwnd 2 | JWT_TOKEN_EXPIRATION=7d 3 | REDIS_URL=127.0.0.1 4 | GATEWAY_PORT=8000 5 | GATEWAY_HOST=127.0.0.1 6 | APP_ENV=development 7 | REDIS_PORT=6379 8 | STRIPE_SECRET_KEY=sk_test_J29GVsb1mCVACXR6LIz1wrW7 -------------------------------------------------------------------------------- /server/services/shared/dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | require("dotenv/config"); 4 | var config_1 = require("./config"); 5 | exports.config = config_1.config; 6 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /server/services/shared/src/index.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | 3 | export * from "./dto/address.dto"; 4 | 5 | export * from "./dto/user.dto"; 6 | export * from "./dto/product.dto"; 7 | export * from "./dto/order.dto"; 8 | export { config } from "./config"; 9 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | yarn.lock 10 | # OS 11 | .DS_Store 12 | 13 | # Tests 14 | 15 | # IDEs and editors 16 | *.sublime-workspace 17 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "workspaces": [ 4 | "services/*" 5 | ], 6 | "description": "E-commerce using NestJS Microservices architecture.", 7 | "author": "Mohammed Osama", 8 | "license": "MIT" 9 | } 10 | -------------------------------------------------------------------------------- /server/services/gateway/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 | -------------------------------------------------------------------------------- /server/services/orders/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 | -------------------------------------------------------------------------------- /server/services/payments/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 | -------------------------------------------------------------------------------- /server/services/products/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 | -------------------------------------------------------------------------------- /server/services/users/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 | -------------------------------------------------------------------------------- /server/services/shared/src/dto/product.dto.ts: -------------------------------------------------------------------------------- 1 | import { UserDTO } from "./user.dto"; 2 | export interface ProductDTO { 3 | user: UserDTO; 4 | title: string; 5 | description: string; 6 | image: string; 7 | price: string; 8 | created_at: Date; 9 | } 10 | -------------------------------------------------------------------------------- /client/graphql/user/mutations/login.ts: -------------------------------------------------------------------------------- 1 | import { gql } from "apollo-boost"; 2 | 3 | export default gql` 4 | mutation login($email: String!, $password: String!) { 5 | login(data: { email: $email, password: $password }) { 6 | id 7 | token 8 | } 9 | } 10 | `; 11 | -------------------------------------------------------------------------------- /server/services/shared/src/dto/payment.dto.ts: -------------------------------------------------------------------------------- 1 | import { UserDTO } from "./user.dto"; 2 | export interface PaymentCardDTO { 3 | id: string; 4 | user: UserDTO; 5 | last_four: string; 6 | brand: string; 7 | default: boolean; 8 | provider_id: string; 9 | created_at: Date; 10 | } 11 | -------------------------------------------------------------------------------- /client/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | // You can include shared interfaces/types in a separate file 2 | // and then use them in any component by importing them. For 3 | // example, to import the interface below do: 4 | // 5 | // import User from 'path/to/interfaces'; 6 | 7 | export type User = { 8 | id: number 9 | name: string 10 | } 11 | -------------------------------------------------------------------------------- /server/services/shared/src/dto/user.dto.ts: -------------------------------------------------------------------------------- 1 | import { AddressDTO } from "./address.dto"; 2 | import { ObjectID } from "typeorm"; 3 | 4 | export interface UserDTO { 5 | id: ObjectID; 6 | name: string; 7 | readonly password: string; 8 | seller: boolean; 9 | address: AddressDTO; 10 | created_at: Date; 11 | } 12 | -------------------------------------------------------------------------------- /server/services/users/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { UsersModule } from "./users/users.module"; 3 | import { TypeOrmModule } from "@nestjs/typeorm"; 4 | const ormconfig = require("../ormconfig.json"); 5 | @Module({ 6 | imports: [TypeOrmModule.forRoot(ormconfig[0]), UsersModule] 7 | }) 8 | export class AppModule {} 9 | -------------------------------------------------------------------------------- /server/services/orders/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { OrdersModule } from "./orders/orders.module"; 3 | import { TypeOrmModule } from "@nestjs/typeorm"; 4 | const ormconfig = require("../ormconfig.json"); 5 | @Module({ 6 | imports: [TypeOrmModule.forRoot(ormconfig[0]), OrdersModule] 7 | }) 8 | export class AppModule {} 9 | -------------------------------------------------------------------------------- /client/components/auth/login/loginConnector.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { LoginController } from "./loginController"; 3 | 4 | import { LoginView } from "./loginView"; 5 | 6 | export const LoginConnector: React.FC = () => ( 7 | 8 | {({ submit }: any) => } 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /server/services/shared/src/dto/order.dto.ts: -------------------------------------------------------------------------------- 1 | import { ProductDTO } from "./product.dto"; 2 | import { UserDTO } from "./user.dto"; 3 | interface ProductOrder { 4 | product: ProductDTO; 5 | quantity: number; 6 | } 7 | export interface OrderDTO { 8 | user: UserDTO; 9 | totalPrice: number; 10 | products: ProductOrder[]; 11 | created_at: Date; 12 | } 13 | -------------------------------------------------------------------------------- /server/services/payments/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { PaymentsModule } from "./payments/payments.module"; 3 | import { TypeOrmModule } from "@nestjs/typeorm"; 4 | const ormconfig = require("../ormconfig.json"); 5 | 6 | @Module({ 7 | imports: [TypeOrmModule.forRoot(ormconfig[0]), PaymentsModule] 8 | }) 9 | export class AppModule {} 10 | -------------------------------------------------------------------------------- /server/services/products/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { ProductsModule } from "./products/products.module"; 3 | import { TypeOrmModule } from "@nestjs/typeorm"; 4 | const ormconfig = require("../ormconfig.json"); 5 | 6 | @Module({ 7 | imports: [TypeOrmModule.forRoot(ormconfig[0]), ProductsModule] 8 | }) 9 | export class AppModule {} 10 | -------------------------------------------------------------------------------- /client/lib/apollo.js: -------------------------------------------------------------------------------- 1 | import { withData } from "next-apollo"; 2 | import { HttpLink } from "apollo-boost"; 3 | 4 | const config = { 5 | link: new HttpLink({ 6 | uri: "http://localhost:8000/graphql", 7 | opts: { 8 | credentials: "same-origin" // Additional fetch() options like `credentials` or `headers` 9 | } 10 | }) 11 | }; 12 | 13 | export default withData(config); 14 | -------------------------------------------------------------------------------- /server/services/gateway/src/orders/create-order.validation.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, Min, IsInt, IsUUID, Validate } from "class-validator"; 2 | 3 | import { InputType, Field } from "type-graphql"; 4 | @InputType() 5 | export class CreateOrder { 6 | @Min(1) 7 | @IsNotEmpty() 8 | @IsInt() 9 | @Field() 10 | quantity: number; 11 | @IsUUID() 12 | @IsNotEmpty() 13 | id: string; 14 | } 15 | -------------------------------------------------------------------------------- /server/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:latest", "tslint-config-prettier"], 4 | "jsRules": {}, 5 | "rules": { 6 | "no-console": false, 7 | "member-access": false, 8 | "object-literal-sort-keys": false, 9 | "ordered-imports": false, 10 | "interface-name": false, 11 | "no-submodule-imports": false 12 | }, 13 | "rulesDirectory": [] 14 | } 15 | -------------------------------------------------------------------------------- /server/services/gateway/src/users/login-user.validation.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsEmail, MinLength, MaxLength } from "class-validator"; 2 | import { InputType, Field } from "type-graphql"; 3 | @InputType() 4 | export class LoginUser { 5 | @IsEmail() 6 | @IsNotEmpty() 7 | @Field() 8 | email: string; 9 | 10 | @IsNotEmpty() 11 | @MinLength(8) 12 | @MaxLength(32) 13 | @Field() 14 | password: string; 15 | } 16 | -------------------------------------------------------------------------------- /server/services/gateway/src/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { APP_INTERCEPTOR } from "@nestjs/core"; 2 | import { Module } from "@nestjs/common"; 3 | 4 | import { UserDataLoader } from "../loaders/user.loader"; 5 | import { UserResolver } from "./user.resolver"; 6 | import { UserService } from "./user.service"; 7 | 8 | @Module({ 9 | providers: [UserResolver, UserService], 10 | exports: [UserService] 11 | }) 12 | export class UsersModule {} 13 | -------------------------------------------------------------------------------- /server/services/users/src/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { TypeOrmModule } from "@nestjs/typeorm"; 3 | 4 | import { UserController } from "./user.controller"; 5 | import { UserEntity } from "./user.entity"; 6 | import { UserService } from "./user.service"; 7 | 8 | @Module({ 9 | imports: [TypeOrmModule.forFeature([UserEntity])], 10 | providers: [UserService], 11 | controllers: [UserController] 12 | }) 13 | export class UsersModule {} 14 | -------------------------------------------------------------------------------- /server/services/orders/src/orders/orders.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { TypeOrmModule } from "@nestjs/typeorm"; 3 | 4 | import { OrderController } from "./order.controller"; 5 | import { OrderEntity } from "./order.entity"; 6 | import { OrderService } from "./order.service"; 7 | 8 | @Module({ 9 | imports: [TypeOrmModule.forFeature([OrderEntity])], 10 | providers: [OrderService], 11 | controllers: [OrderController] 12 | }) 13 | export class OrdersModule {} 14 | -------------------------------------------------------------------------------- /server/services/payments/src/payments/payments.module.ts: -------------------------------------------------------------------------------- 1 | import { TypeOrmModule } from "@nestjs/typeorm"; 2 | import { Module } from "@nestjs/common"; 3 | import { PaymentService } from "./payment.service"; 4 | import { PaymentEntity } from "./payment.entity"; 5 | import { PaymentController } from "./payment.controller"; 6 | @Module({ 7 | imports: [TypeOrmModule.forFeature([PaymentEntity])], 8 | providers: [PaymentService], 9 | controllers: [PaymentController] 10 | }) 11 | export class PaymentsModule {} 12 | -------------------------------------------------------------------------------- /server/services/products/src/products/products.module.ts: -------------------------------------------------------------------------------- 1 | import { TypeOrmModule } from "@nestjs/typeorm"; 2 | import { Module } from "@nestjs/common"; 3 | import { ProductService } from "./product.service"; 4 | import { ProductEntity } from "./product.entity"; 5 | import { ProductController } from "./product.controller"; 6 | @Module({ 7 | imports: [TypeOrmModule.forFeature([ProductEntity])], 8 | providers: [ProductService], 9 | controllers: [ProductController] 10 | }) 11 | export class ProductsModule {} 12 | -------------------------------------------------------------------------------- /client/pages/auth/login.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Layout from "../../components/Layout"; 3 | import { NextPage } from "next"; 4 | import { LoginConnector } from "../../components/auth/login/loginConnector"; 5 | import withData from "../../lib/apollo"; 6 | const LoginPage: NextPage = () => { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | export default withData((props: any) => ); 15 | -------------------------------------------------------------------------------- /server/services/gateway/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": { 5 | "no-unused-expression": true 6 | }, 7 | "rules": { 8 | "quotemark": [true, "single"], 9 | "member-access": [false], 10 | "ordered-imports": [false], 11 | "max-line-length": [true, 150], 12 | "member-ordering": [false], 13 | "interface-name": [false], 14 | "arrow-parens": false, 15 | "object-literal-sort-keys": false 16 | }, 17 | "rulesDirectory": [] 18 | } 19 | -------------------------------------------------------------------------------- /server/services/orders/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": { 5 | "no-unused-expression": true 6 | }, 7 | "rules": { 8 | "quotemark": [true, "single"], 9 | "member-access": [false], 10 | "ordered-imports": [false], 11 | "max-line-length": [true, 150], 12 | "member-ordering": [false], 13 | "interface-name": [false], 14 | "arrow-parens": false, 15 | "object-literal-sort-keys": false 16 | }, 17 | "rulesDirectory": [] 18 | } 19 | -------------------------------------------------------------------------------- /server/services/users/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": { 5 | "no-unused-expression": true 6 | }, 7 | "rules": { 8 | "quotemark": [true, "single"], 9 | "member-access": [false], 10 | "ordered-imports": [false], 11 | "max-line-length": [true, 150], 12 | "member-ordering": [false], 13 | "interface-name": [false], 14 | "arrow-parens": false, 15 | "object-literal-sort-keys": false 16 | }, 17 | "rulesDirectory": [] 18 | } 19 | -------------------------------------------------------------------------------- /server/services/orders/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "allowSyntheticDefaultImports": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es6", 12 | "sourceMap": true, 13 | "outDir": "./dist", 14 | "baseUrl": "./src" 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules", "**/*.spec.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /server/services/gateway/src/filters/graphql-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { Catch, ArgumentsHost, HttpException } from "@nestjs/common"; 2 | import { GqlExceptionFilter, GqlArgumentsHost } from "@nestjs/graphql"; 3 | @Catch(HttpException) 4 | export class GraphQLErrorFilter implements GqlExceptionFilter { 5 | catch(exception: HttpException, host: ArgumentsHost) { 6 | const gqlHost = GqlArgumentsHost.create(host); 7 | console.log(gqlHost.getRoot(), gqlHost.getContext(), gqlHost.getInfo()); 8 | return exception; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /server/services/users/.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 -------------------------------------------------------------------------------- /server/services/payments/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended", "tslint-config-prettier"], 4 | "jsRules": { 5 | "no-unused-expression": true 6 | }, 7 | "rules": { 8 | "quotemark": [true, "single"], 9 | "member-access": [false], 10 | "ordered-imports": [false], 11 | "max-line-length": [true, 150], 12 | "member-ordering": [false], 13 | "interface-name": [false], 14 | "arrow-parens": false, 15 | "object-literal-sort-keys": false 16 | }, 17 | "rulesDirectory": [] 18 | } 19 | -------------------------------------------------------------------------------- /server/services/products/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended", "tslint-config-prettier"], 4 | "jsRules": { 5 | "no-unused-expression": true 6 | }, 7 | "rules": { 8 | "quotemark": [true, "single"], 9 | "member-access": [false], 10 | "ordered-imports": [false], 11 | "max-line-length": [true, 150], 12 | "member-ordering": [false], 13 | "interface-name": [false], 14 | "arrow-parens": false, 15 | "object-literal-sort-keys": false 16 | }, 17 | "rulesDirectory": [] 18 | } 19 | -------------------------------------------------------------------------------- /server/services/gateway/.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 | yarn.lock 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 -------------------------------------------------------------------------------- /server/services/orders/.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 | yarn.lock 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json -------------------------------------------------------------------------------- /server/services/payments/.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 | yarn.lock 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json -------------------------------------------------------------------------------- /server/services/products/.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 | yarn.lock 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json -------------------------------------------------------------------------------- /server/services/orders/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from "@nestjs/core"; 2 | import { AppModule } from "./app.module"; 3 | import { Transport } from "@nestjs/microservices"; 4 | import { config } from "@commerce/shared"; 5 | async function bootstrap() { 6 | const app = await NestFactory.createMicroservice(AppModule, { 7 | transport: Transport.REDIS, 8 | options: { 9 | url: `redis://${config.REDIS_URL}:${config.REDIS_PORT}` 10 | } 11 | }); 12 | await app.listen(() => console.log(`orders module is listening`)); 13 | } 14 | bootstrap(); 15 | -------------------------------------------------------------------------------- /server/services/users/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from "@nestjs/core"; 2 | import { AppModule } from "./app.module"; 3 | import { Transport } from "@nestjs/microservices"; 4 | import { config } from "@commerce/shared"; 5 | async function bootstrap() { 6 | const app = await NestFactory.createMicroservice(AppModule, { 7 | transport: Transport.REDIS, 8 | options: { 9 | url: `redis://${config.REDIS_URL}:${config.REDIS_PORT}` 10 | } 11 | }); 12 | await app.listen(() => console.log(`users module is listening`)); 13 | } 14 | bootstrap(); 15 | -------------------------------------------------------------------------------- /server/services/products/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from "@nestjs/core"; 2 | import { AppModule } from "./app.module"; 3 | import { Transport } from "@nestjs/microservices"; 4 | import { config } from "@commerce/shared"; 5 | async function bootstrap() { 6 | const app = await NestFactory.createMicroservice(AppModule, { 7 | transport: Transport.REDIS, 8 | options: { 9 | url: `redis://${config.REDIS_URL}:${config.REDIS_PORT}` 10 | } 11 | }); 12 | await app.listen(() => console.log(`products module is listening `)); 13 | } 14 | bootstrap(); 15 | -------------------------------------------------------------------------------- /server/services/shared/dist/config/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":";;AAAa,QAAA,MAAM,GAAG;IAClB,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,gBAAgB;IACpD,oBAAoB,EAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,IAAI;IAC9D,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,IAAI;IAC9C,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,WAAW;IACrD,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,aAAa;IAC7C,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,IAAI;IAC1C,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,WAAW;IAC/C,iBAAiB,EACb,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,kCAAkC;CAC1E,CAAC"} -------------------------------------------------------------------------------- /server/services/shared/src/config/index.ts: -------------------------------------------------------------------------------- 1 | export const config = { 2 | JWT_TOKEN: process.env.JWT_TOKEN || "ondoqnwdonqdwq", 3 | JWT_TOKEN_EXPIRATION: process.env.JWT_TOKEN_EXPIRATION || "7d", 4 | GATEWAY_PORT: process.env.GATEWAY_PORT || 8000, 5 | GATEWAY_HOST: process.env.GATEWAY_HOST || "127.0.0.1", 6 | APP_ENV: process.env.APP_ENV || "development", 7 | REDIS_PORT: process.env.REDIS_PORT || 6379, 8 | REDIS_URL: process.env.REDIS_URL || "127.0.0.1", 9 | STRIPE_SECRET_KEY: 10 | process.env.STRIPE_SECRET_KEY || "sk_test_J29GVsb1mCVACXR6LIz1wrW7" 11 | }; 12 | -------------------------------------------------------------------------------- /server/services/gateway/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "allowSyntheticDefaultImports": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es6", 12 | "sourceMap": true, 13 | "outDir": "./dist", 14 | "baseUrl": "./src" 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules", "**/*.spec.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /client/pages/checkout.tsx: -------------------------------------------------------------------------------- 1 | import StripeCheckout from "react-stripe-checkout"; 2 | 3 | export const checkout = () => { 4 | return ( 5 | { 7 | console.log(token); 8 | // const response = await mutate({ 9 | // variables: { source: token.id, ccLast4: token.card.last4 } 10 | // }); 11 | // console.log(response); 12 | }} 13 | stripeKey="pk_test_aIuqxcFC1ODFbiKmvbyPlNnl" 14 | amount={1000} 15 | /> 16 | ); 17 | }; 18 | export default checkout; 19 | -------------------------------------------------------------------------------- /server/services/gateway/src/orders/order.gql: -------------------------------------------------------------------------------- 1 | scalar DateTime 2 | type ProductWithQuantity { 3 | product: Product! 4 | quantity_ordered: Float! 5 | } 6 | input UUID { 7 | id: String! 8 | } 9 | input ProductInput { 10 | quantity: Float! 11 | id: String! 12 | } 13 | 14 | type Order { 15 | id: String! 16 | user: User! 17 | total_price: Float! 18 | products: [ProductWithQuantity!]! 19 | } 20 | type Query { 21 | orders: [Order!]! 22 | showOrder(id: String!): Order 23 | } 24 | type Mutation { 25 | createOrder(products: [ProductInput!]!): Order! 26 | deleteOrder(order: UUID!): Order! 27 | } 28 | -------------------------------------------------------------------------------- /server/services/users/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "allowSyntheticDefaultImports": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es6", 12 | "sourceMap": true, 13 | "outDir": "./dist", 14 | "resolveJsonModule": true, 15 | "baseUrl": "./src" 16 | }, 17 | "include": ["src/**/*"], 18 | "exclude": ["node_modules", "**/*.spec.ts"] 19 | } 20 | -------------------------------------------------------------------------------- /server/services/gateway/src/payments/create-payment-card.validation.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, Length, IsNumberString, IsBoolean } from "class-validator"; 2 | import { InputType, Field } from "type-graphql"; 3 | @InputType() 4 | export class CreatePaymentCard { 5 | @IsNotEmpty() 6 | @Field() 7 | provider_id: string; 8 | @IsNotEmpty() 9 | @Field() 10 | token_id: string; 11 | @IsNotEmpty() 12 | @Field() 13 | brand: string; 14 | @IsNumberString() 15 | @IsNotEmpty() 16 | @Field() 17 | @Length(4) 18 | last_four: string; 19 | @IsBoolean() 20 | @IsNotEmpty() 21 | default: boolean; 22 | } 23 | -------------------------------------------------------------------------------- /server/services/gateway/src/users/register-user.validation.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, MinLength, MaxLength, IsBoolean } from "class-validator"; 2 | import { LoginUser } from "./login-user.validation"; 3 | import { InputType, Field } from "type-graphql"; 4 | @InputType() 5 | export class RegisterUser extends LoginUser { 6 | @MinLength(8) 7 | @MaxLength(32) 8 | @IsNotEmpty() 9 | @Field() 10 | name: string; 11 | 12 | @MinLength(8) 13 | @MaxLength(32) 14 | @IsNotEmpty() 15 | @Field() 16 | password_confirmation: string; 17 | @IsNotEmpty() 18 | @IsBoolean() 19 | @Field() 20 | seller: boolean; 21 | } 22 | -------------------------------------------------------------------------------- /server/services/orders/ormconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "default", 4 | "type": "postgres", 5 | "host": "localhost", 6 | "port": 5432, 7 | "username": "mohammed", 8 | "password": "root", 9 | "database": "orders-module", 10 | "synchronize": true, 11 | "logging": true, 12 | "entities": ["dist/**/*.entity.js"] 13 | }, 14 | { 15 | "name": "production", 16 | "type": "mongodb", 17 | "host": "localhost", 18 | "database": "users-module", 19 | "entities": ["dist/**/*.entity.js"], 20 | "synchronize": true 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /server/services/users/ormconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "default", 4 | "type": "postgres", 5 | "host": "localhost", 6 | "port": 5432, 7 | "username": "mohammed", 8 | "password": "root", 9 | "database": "users-module", 10 | "synchronize": true, 11 | "logging": true, 12 | "entities": ["dist/**/*.entity.js"] 13 | }, 14 | { 15 | "name": "production", 16 | "type": "mongodb", 17 | "host": "localhost", 18 | "database": "users-module", 19 | "entities": ["dist/**/*.entity.js"], 20 | "synchronize": true 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /server/services/gateway/src/middlewares/seller.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CanActivate, 3 | ExecutionContext, 4 | HttpException, 5 | HttpStatus, 6 | Injectable 7 | } from "@nestjs/common"; 8 | import { GqlExecutionContext } from "@nestjs/graphql"; 9 | 10 | @Injectable() 11 | export class SellerGuard implements CanActivate { 12 | constructor() {} 13 | 14 | canActivate(context: ExecutionContext) { 15 | const ctx: any = GqlExecutionContext.create(context).getContext(); 16 | if (ctx.user && ctx.user.seller) { 17 | return true; 18 | } 19 | throw new HttpException("Unauthorized access", HttpStatus.UNAUTHORIZED); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /server/services/payments/ormconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "default", 4 | "type": "postgres", 5 | "host": "localhost", 6 | "port": 5432, 7 | "username": "mohammed", 8 | "password": "root", 9 | "database": "payments-module", 10 | "synchronize": true, 11 | "logging": true, 12 | "entities": ["dist/**/*.entity.js"] 13 | }, 14 | { 15 | "name": "production", 16 | "type": "mongodb", 17 | "host": "localhost", 18 | "database": "users-module", 19 | "entities": ["dist/**/*.entity.js"], 20 | "synchronize": true 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /server/services/products/ormconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "default", 4 | "type": "postgres", 5 | "host": "localhost", 6 | "port": 5432, 7 | "username": "mohammed", 8 | "password": "root", 9 | "database": "products-module", 10 | "synchronize": true, 11 | "logging": true, 12 | "entities": ["dist/**/*.entity.js"] 13 | }, 14 | { 15 | "name": "production", 16 | "type": "mongodb", 17 | "host": "localhost", 18 | "database": "users-module", 19 | "entities": ["dist/**/*.entity.js"], 20 | "synchronize": true 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /server/services/gateway/src/products/product.gql: -------------------------------------------------------------------------------- 1 | scalar DateTime 2 | type Product { 3 | id: String! 4 | user: User! 5 | title: String! 6 | description: String! 7 | image: String! 8 | price: Float! 9 | created_at: DateTime! 10 | } 11 | input CreateProduct { 12 | title: String! 13 | description: String! 14 | image: String! 15 | price: Float! 16 | } 17 | type Query { 18 | products: [Product!] 19 | showProduct(id: String!): Product! 20 | } 21 | type Mutation { 22 | createProduct(data: CreateProduct!): Product! 23 | updateProduct(data: CreateProduct!, id: String!): Product! 24 | deleteProduct(id: String!): Product! 25 | } 26 | -------------------------------------------------------------------------------- /server/services/shared/dist/config/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.config = { 4 | JWT_TOKEN: process.env.JWT_TOKEN || "ondoqnwdonqdwq", 5 | JWT_TOKEN_EXPIRATION: process.env.JWT_TOKEN_EXPIRATION || "7d", 6 | GATEWAY_PORT: process.env.GATEWAY_PORT || 8000, 7 | GATEWAY_HOST: process.env.GATEWAY_HOST || "127.0.0.1", 8 | APP_ENV: process.env.APP_ENV || "development", 9 | REDIS_PORT: process.env.REDIS_PORT || 6379, 10 | REDIS_URL: process.env.REDIS_URL || "127.0.0.1", 11 | STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY || "sk_test_J29GVsb1mCVACXR6LIz1wrW7" 12 | }; 13 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /server/services/gateway/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import * as request from 'supertest'; 3 | import { AppModule } from './../src/app.module'; 4 | 5 | describe('AppController (e2e)', () => { 6 | let app; 7 | 8 | beforeEach(async () => { 9 | const moduleFixture: TestingModule = await Test.createTestingModule({ 10 | imports: [AppModule], 11 | }).compile(); 12 | 13 | app = moduleFixture.createNestApplication(); 14 | await app.init(); 15 | }); 16 | 17 | it('/ (GET)', () => { 18 | return request(app.getHttpServer()) 19 | .get('/') 20 | .expect(200) 21 | .expect('Hello World!'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /server/services/orders/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import * as request from 'supertest'; 3 | import { AppModule } from './../src/app.module'; 4 | 5 | describe('AppController (e2e)', () => { 6 | let app; 7 | 8 | beforeEach(async () => { 9 | const moduleFixture: TestingModule = await Test.createTestingModule({ 10 | imports: [AppModule], 11 | }).compile(); 12 | 13 | app = moduleFixture.createNestApplication(); 14 | await app.init(); 15 | }); 16 | 17 | it('/ (GET)', () => { 18 | return request(app.getHttpServer()) 19 | .get('/') 20 | .expect(200) 21 | .expect('Hello World!'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /server/services/payments/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import * as request from 'supertest'; 3 | import { AppModule } from './../src/app.module'; 4 | 5 | describe('AppController (e2e)', () => { 6 | let app; 7 | 8 | beforeEach(async () => { 9 | const moduleFixture: TestingModule = await Test.createTestingModule({ 10 | imports: [AppModule], 11 | }).compile(); 12 | 13 | app = moduleFixture.createNestApplication(); 14 | await app.init(); 15 | }); 16 | 17 | it('/ (GET)', () => { 18 | return request(app.getHttpServer()) 19 | .get('/') 20 | .expect(200) 21 | .expect('Hello World!'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /server/services/products/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import * as request from 'supertest'; 3 | import { AppModule } from './../src/app.module'; 4 | 5 | describe('AppController (e2e)', () => { 6 | let app; 7 | 8 | beforeEach(async () => { 9 | const moduleFixture: TestingModule = await Test.createTestingModule({ 10 | imports: [AppModule], 11 | }).compile(); 12 | 13 | app = moduleFixture.createNestApplication(); 14 | await app.init(); 15 | }); 16 | 17 | it('/ (GET)', () => { 18 | return request(app.getHttpServer()) 19 | .get('/') 20 | .expect(200) 21 | .expect('Hello World!'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /server/services/users/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import * as request from 'supertest'; 3 | import { AppModule } from './../src/app.module'; 4 | 5 | describe('AppController (e2e)', () => { 6 | let app; 7 | 8 | beforeEach(async () => { 9 | const moduleFixture: TestingModule = await Test.createTestingModule({ 10 | imports: [AppModule], 11 | }).compile(); 12 | 13 | app = moduleFixture.createNestApplication(); 14 | await app.init(); 15 | }); 16 | 17 | it('/ (GET)', () => { 18 | return request(app.getHttpServer()) 19 | .get('/') 20 | .expect(200) 21 | .expect('Hello World!'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /client/components/partials/InputField.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { FieldProps } from "formik"; 3 | import { Form, Input } from "antd"; 4 | 5 | const FormItem = Form.Item; 6 | 7 | export const InputField: React.SFC< 8 | FieldProps & { prefix: React.ReactNode } 9 | > = ({ 10 | field, // { name, value, onChange, onBlur } 11 | form: { touched, errors }, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc. 12 | ...props 13 | }) => { 14 | const errorMsg = touched[field.name] && errors[field.name]; 15 | 16 | return ( 17 | 18 | 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /server/services/gateway/src/products/create-product.validation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsNotEmpty, 3 | MinLength, 4 | MaxLength, 5 | IsUrl, 6 | Min, 7 | Max, 8 | IsInt 9 | } from "class-validator"; 10 | import { InputType, Field } from "type-graphql"; 11 | @InputType() 12 | export class CreateProduct { 13 | @Min(1) 14 | @Max(999) 15 | @IsNotEmpty() 16 | @IsInt() 17 | @Field() 18 | price: number; 19 | 20 | @MinLength(8) 21 | @MaxLength(32) 22 | @IsNotEmpty() 23 | @Field() 24 | title: string; 25 | @MinLength(32) 26 | @MaxLength(255) 27 | @IsNotEmpty() 28 | @Field() 29 | description: string; 30 | @IsUrl() 31 | @IsNotEmpty() 32 | image: string; 33 | } 34 | -------------------------------------------------------------------------------- /server/services/gateway/src/payments/payment-card.gql: -------------------------------------------------------------------------------- 1 | scalar DateTime 2 | type PaymentCard { 3 | id: String! 4 | user: User! 5 | last_four: String! 6 | brand: String! 7 | default: Boolean! 8 | provider_id: String! 9 | created_at: DateTime! 10 | } 11 | input CreatePaymentCard { 12 | token_id: String! 13 | last_four: String! 14 | provider_id: String! 15 | brand: String! 16 | default: Boolean! 17 | } 18 | type Query { 19 | showPaymentCard(id: String!): PaymentCard 20 | indexUserPaymentCards: [PaymentCard!] 21 | } 22 | type Mutation { 23 | createPaymentCard(data: CreatePaymentCard!): PaymentCard! 24 | createChargeForUser(orderId: String!): Order! 25 | deletePaymentCard(id: String!): PaymentCard! 26 | } 27 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "alwaysStrict": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "isolatedModules": true, 8 | "jsx": "preserve", 9 | "lib": [ 10 | "dom", 11 | "es2017" 12 | ], 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "noEmit": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "resolveJsonModule": true, 20 | "skipLibCheck": true, 21 | "strict": true, 22 | "target": "esnext" 23 | }, 24 | "exclude": [ 25 | "node_modules" 26 | ], 27 | "include": [ 28 | "**/*.ts", 29 | "**/*.tsx" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /server/services/gateway/src/payments/payment.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, Scope } from "@nestjs/common"; 2 | 3 | import { PaymentCardResolver } from "./payment.resolver"; 4 | import { PaymentCardService } from "./payment.service"; 5 | import { UserDataLoader } from "../loaders/user.loader"; 6 | import { UserService } from "../users/user.service"; 7 | import { UsersModule } from "../users/users.module"; 8 | 9 | @Module({ 10 | providers: [ 11 | PaymentCardResolver, 12 | PaymentCardService, 13 | { 14 | inject: [UserService], 15 | useFactory: UserDataLoader.create, 16 | provide: UserDataLoader, 17 | scope: Scope.REQUEST 18 | } 19 | ], 20 | imports: [UsersModule] 21 | }) 22 | export class PaymentCardsModule {} 23 | -------------------------------------------------------------------------------- /server/services/gateway/src/utils/base-resolver.ts: -------------------------------------------------------------------------------- 1 | export class BaseResolver { 2 | fields(info) { 3 | const fields = info.fieldNodes[0].selectionSet.selections.filter( 4 | field => !field.selectionSet 5 | ); 6 | return fields.map(field => field.name.value); 7 | } 8 | relations(info) { 9 | const fields = info.fieldNodes[0].selectionSet.selections.filter( 10 | field => field.selectionSet 11 | ); 12 | return fields.map(field => { 13 | return { 14 | [field.name.value]: [ 15 | ...field.selectionSet.selections.map( 16 | field => field.name.value 17 | ) 18 | ] 19 | }; 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /server/services/gateway/src/products/products.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, Scope } from "@nestjs/common"; 2 | 3 | import { ProductResolver } from "./product.resolver"; 4 | import { ProductService } from "./product.service"; 5 | import { UserDataLoader } from "../loaders/user.loader"; 6 | import { UserService } from "../users/user.service"; 7 | import { UsersModule } from "../users/users.module"; 8 | 9 | @Module({ 10 | providers: [ 11 | ProductResolver, 12 | ProductService, 13 | { 14 | inject: [UserService], 15 | useFactory: UserDataLoader.create, 16 | provide: UserDataLoader, 17 | scope: Scope.REQUEST 18 | } 19 | ], 20 | imports: [UsersModule], 21 | exports: [ProductService] 22 | }) 23 | export class ProductsModule {} 24 | -------------------------------------------------------------------------------- /server/services/users/src/users/address.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | Entity, 4 | BaseEntity, 5 | OneToOne, 6 | JoinColumn, 7 | PrimaryGeneratedColumn 8 | } from "typeorm"; 9 | import { UserEntity } from "./user.entity"; 10 | 11 | @Entity("addresses") 12 | export class AddressEntity extends BaseEntity { 13 | @PrimaryGeneratedColumn("uuid") 14 | id: string; 15 | 16 | @Column("text") 17 | address_1: string; 18 | @Column("text", { nullable: true }) 19 | address_2: string; 20 | @Column("text") 21 | city: string; 22 | @Column("text") 23 | state: string; 24 | @Column("text") 25 | country: string; 26 | @Column("integer") 27 | zip: number; 28 | @OneToOne(() => UserEntity, user => user.address) 29 | @JoinColumn() 30 | user: UserEntity; 31 | } 32 | -------------------------------------------------------------------------------- /server/services/payments/src/payments/payment.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | Entity, 4 | BaseEntity, 5 | CreateDateColumn, 6 | UpdateDateColumn, 7 | PrimaryGeneratedColumn 8 | } from "typeorm"; 9 | 10 | @Entity("payments") 11 | export class PaymentEntity extends BaseEntity { 12 | @PrimaryGeneratedColumn("uuid") 13 | id: string; 14 | 15 | @PrimaryGeneratedColumn("uuid") 16 | user_id: string; 17 | @Column("text", { nullable: true }) 18 | brand: string; 19 | @Column("text", { nullable: true }) 20 | last_four: string; 21 | @Column("boolean", { default: true }) 22 | default: boolean; 23 | @Column("text", { unique: true }) 24 | provider_id: string; 25 | 26 | @CreateDateColumn() 27 | created_at: Date; 28 | @UpdateDateColumn() 29 | updated_at: Date; 30 | } 31 | -------------------------------------------------------------------------------- /server/services/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@commerce/shared", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "E-commerce using NestJS Microservices architecture.", 6 | "author": "Mohammed Osama", 7 | "license": "MIT", 8 | "main": "./dist/index.js", 9 | "typings": "./dist/index.d.ts", 10 | "scripts": { 11 | "build": "rm -rf ./dist && tsc" 12 | }, 13 | "devDependencies": { 14 | "@types/dotenv": "^6.1.1", 15 | "@types/node": "^12.7.1", 16 | "tslint": "^5.18.0", 17 | "tslint-config-prettier": "^1.18.0", 18 | "typescript": "^3.5.3" 19 | }, 20 | "dependencies": { 21 | "class-validator": "^0.10.0", 22 | "dotenv": "^8.1.0", 23 | "type-graphql": "^0.17.6", 24 | "typeorm": "^0.2.25" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/services/products/src/products/product.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | Entity, 4 | BaseEntity, 5 | CreateDateColumn, 6 | UpdateDateColumn, 7 | PrimaryGeneratedColumn 8 | } from "typeorm"; 9 | 10 | @Entity("products") 11 | export class ProductEntity extends BaseEntity { 12 | @PrimaryGeneratedColumn("uuid") 13 | id: string; 14 | 15 | @Column("integer") 16 | price: number; 17 | @PrimaryGeneratedColumn("uuid") 18 | user_id: string; 19 | @Column("integer", { default: 1 }) 20 | quantity: number; 21 | @Column("text", { unique: true }) 22 | title: string; 23 | @Column("text") 24 | description: string; 25 | 26 | @Column("text") 27 | image: string; 28 | 29 | @CreateDateColumn() 30 | created_at: Date; 31 | @UpdateDateColumn() 32 | updated_at: Date; 33 | } 34 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-typescript", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "dev": "next", 6 | "build": "next build", 7 | "start": "next start", 8 | "type-check": "tsc" 9 | }, 10 | "dependencies": { 11 | "@apollo/react-hooks": "^3.1.0", 12 | "@apollo/react-ssr": "^3.1.0", 13 | "antd": "^3.23.2", 14 | "apollo-boost": "^0.4.4", 15 | "formik": "^1.5.8", 16 | "graphql": "^14.5.4", 17 | "next": "^11.1.3", 18 | "next-apollo": "^3.1.4", 19 | "react": "^16.9.0", 20 | "react-dom": "^16.9.0", 21 | "react-stripe-checkout": "^2.6.3", 22 | "react-stripe-elements": "^5.0.0" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "^11.13.9", 26 | "@types/react": "^16.8.15", 27 | "@types/react-dom": "^16.0.11", 28 | "typescript": "3.5.2" 29 | }, 30 | "license": "ISC" 31 | } 32 | -------------------------------------------------------------------------------- /client/components/auth/login/loginController.tsx: -------------------------------------------------------------------------------- 1 | import LOGIN_MUTATION from "../../../graphql/user/mutations/login"; 2 | 3 | // @ts-ignore 4 | import * as React from "react"; 5 | import { useMutation } from "@apollo/react-hooks"; 6 | interface Props { 7 | children: (data: { 8 | submit: (values: any) => Promise; 9 | }) => JSX.Element | null; 10 | } 11 | export const LoginController = (props: Props) => { 12 | const [login] = useMutation(LOGIN_MUTATION); 13 | const submit = ({ email, password }: { email: string; password: string }) => { 14 | return login({ 15 | variables: { email, password } 16 | }) 17 | .then(({ login }: any) => { 18 | localStorage.setItem("token", login.token); 19 | return null; 20 | }) 21 | .catch(err => { 22 | return err; 23 | }); 24 | }; 25 | return props.children({ submit }); 26 | }; 27 | -------------------------------------------------------------------------------- /server/services/orders/src/orders/order.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | UpdateDateColumn, 3 | CreateDateColumn, 4 | Column, 5 | Entity, 6 | BaseEntity, 7 | PrimaryGeneratedColumn 8 | } from "typeorm"; 9 | 10 | @Entity("orders") 11 | export class OrderEntity extends BaseEntity { 12 | @PrimaryGeneratedColumn("uuid") 13 | id: string; 14 | 15 | @PrimaryGeneratedColumn("uuid") 16 | user_id: string; 17 | 18 | @Column("integer", { default: 0 }) 19 | total_price: number; 20 | 21 | @Column({ type: "simple-json" }) 22 | products: { 23 | id: string; 24 | quantity: number; 25 | }; 26 | @Column({ 27 | enum: ["pending", "failed", "succeeded"], 28 | default: "pending" 29 | }) 30 | status: string; 31 | @CreateDateColumn() 32 | created_at: Date; 33 | @UpdateDateColumn() 34 | updated_at: Date; 35 | } 36 | -------------------------------------------------------------------------------- /server/services/payments/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from "@nestjs/core"; 2 | import { AppModule } from "./app.module"; 3 | import { Transport } from "@nestjs/microservices"; 4 | import { config } from "@commerce/shared"; 5 | async function bootstrap() { 6 | const app = await NestFactory.createMicroservice(AppModule, { 7 | transport: Transport.REDIS, 8 | options: { 9 | url: `redis://${config.REDIS_URL}:${config.REDIS_PORT}` 10 | } 11 | }); 12 | await app.listen(() => console.log(`payments module is listening `)); 13 | // stripe.charges 14 | // .create({ 15 | // amount: 10000, 16 | // currency: "usd", 17 | // customer: "cus_Flokeye6LItio9", 18 | // source: "card_1FGH6qEESCiXtnb4oV5fB9ur" 19 | // }) 20 | // .then(charge => console.log(charge)); 21 | } 22 | bootstrap(); 23 | -------------------------------------------------------------------------------- /server/services/shared/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": { 5 | "no-unused-expression": true 6 | }, 7 | "rules": { 8 | "eofline": false, 9 | "quotemark": [true, "single"], 10 | "indent": false, 11 | "member-access": [false], 12 | "ordered-imports": [false], 13 | "max-line-length": [true, 150], 14 | "member-ordering": [false], 15 | "curly": false, 16 | "interface-name": [false], 17 | "array-type": [false], 18 | "no-empty-interface": false, 19 | "no-empty": false, 20 | "arrow-parens": false, 21 | "object-literal-sort-keys": false, 22 | "no-unused-expression": false, 23 | "max-classes-per-file": false, 24 | "variable-name": [false], 25 | "one-line": [false], 26 | "one-variable-per-declaration": [false] 27 | }, 28 | "rulesDirectory": [] 29 | } 30 | -------------------------------------------------------------------------------- /server/services/gateway/src/users/user.gql: -------------------------------------------------------------------------------- 1 | scalar DateTime 2 | type Address { 3 | address_1: String! 4 | address_2: String! 5 | city: String! 6 | state: String! 7 | country: String! 8 | zip: Float! 9 | } 10 | type User { 11 | id: String! 12 | name: String! 13 | seller: Boolean! 14 | address: Address 15 | created_at: DateTime! 16 | } 17 | type AuthToken { 18 | id: String! 19 | name: String! 20 | token: String! 21 | } 22 | input LoginUser { 23 | email: String! 24 | password: String! 25 | } 26 | input RegisterUser { 27 | email: String! 28 | password: String! 29 | password_confirmation: String! 30 | name: String! 31 | seller: Boolean! 32 | } 33 | type Query { 34 | users: [User!] 35 | me: User! 36 | } 37 | type Mutation { 38 | login(data: LoginUser!): AuthToken! 39 | register(data: RegisterUser!): User! 40 | } 41 | -------------------------------------------------------------------------------- /server/services/payments/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "lib": ["dom", "es6", "es2017", "esnext.asynciterable"], 5 | "declaration": true, 6 | "noImplicitAny": false, 7 | "removeComments": true, 8 | "noLib": false, 9 | "allowSyntheticDefaultImports": true, 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "es6", 13 | "sourceMap": true, 14 | "outDir": "./dist", 15 | "baseUrl": "./src", 16 | "noUnusedParameters": true, 17 | "noImplicitReturns": true, 18 | "strictNullChecks": true, 19 | "strictFunctionTypes": true, 20 | "incremental": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "moduleResolution": "node", 23 | "noImplicitThis": true, 24 | "noUnusedLocals": true 25 | }, 26 | "include": ["src/**/*"], 27 | "exclude": ["node_modules", "**/*.spec.ts", "dist"] 28 | } 29 | -------------------------------------------------------------------------------- /server/services/products/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "lib": ["dom", "es6", "es2017", "esnext.asynciterable"], 5 | "declaration": true, 6 | "noImplicitAny": false, 7 | "removeComments": true, 8 | "noLib": false, 9 | "allowSyntheticDefaultImports": true, 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "es6", 13 | "sourceMap": true, 14 | "outDir": "./dist", 15 | "baseUrl": "./src", 16 | "noUnusedParameters": true, 17 | "noImplicitReturns": true, 18 | "strictNullChecks": true, 19 | "strictFunctionTypes": true, 20 | "incremental": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "moduleResolution": "node", 23 | "noImplicitThis": true, 24 | "noUnusedLocals": true 25 | }, 26 | "include": ["src/**/*"], 27 | "exclude": ["node_modules", "**/*.spec.ts", "dist"] 28 | } 29 | -------------------------------------------------------------------------------- /client/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import Link from 'next/link' 3 | import Head from 'next/head' 4 | 5 | type Props = { 6 | title?: string 7 | } 8 | 9 | const Layout: React.FunctionComponent = ({ 10 | children, 11 | title = 'This is the default title', 12 | }) => ( 13 |
14 | 15 | {title} 16 | 17 | 18 | 19 |
20 | 33 |
34 | {children} 35 |
36 |
37 | I'm here to stay (Footer) 38 |
39 |
40 | ) 41 | 42 | export default Layout 43 | -------------------------------------------------------------------------------- /server/services/gateway/src/loaders/user.loader.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject, forwardRef } from "@nestjs/common"; 2 | import { ModuleRef } from "@nestjs/core"; 3 | import { UserDTO } from "@commerce/shared"; 4 | import DataLoader = require("dataloader"); // commonjs module 5 | 6 | import { IDataLoader } from "../contracts/nest-dataloader"; 7 | import { UserService } from "../users/user.service"; 8 | 9 | @Injectable() 10 | export class UserDataLoader implements IDataLoader { 11 | constructor(private readonly dataLoader: DataLoader) {} 12 | 13 | public static async create( 14 | userService: UserService 15 | ): Promise { 16 | const dataloader = new DataLoader(async ids => { 17 | let users = await userService.fetchUsersByIds(ids); 18 | return ids.map(key => users.find(entity => entity.id === key)); 19 | }); 20 | return new UserDataLoader(dataloader); 21 | } 22 | public async load(id: string) { 23 | return this.dataLoader.load(id); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /server/services/shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "lib": ["dom", "es6", "es2017", "esnext.asynciterable"], 6 | "sourceMap": true, 7 | "outDir": "./dist", 8 | "moduleResolution": "node", 9 | "declaration": false, 10 | 11 | "composite": false, 12 | "removeComments": true, 13 | "noImplicitAny": true, 14 | "strictNullChecks": true, 15 | "strictFunctionTypes": true, 16 | "noImplicitThis": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "noImplicitReturns": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "allowSyntheticDefaultImports": true, 22 | "esModuleInterop": true, 23 | "emitDecoratorMetadata": true, 24 | "experimentalDecorators": true, 25 | "skipLibCheck": true, 26 | "baseUrl": ".", 27 | "rootDir": "./src" 28 | }, 29 | "exclude": ["node_modules"], 30 | "include": ["./src/**/*.tsx", "./src/**/*.ts"] 31 | } 32 | -------------------------------------------------------------------------------- /server/services/orders/src/orders/order.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from "@nestjs/common"; 2 | import { OrderService } from "./order.service"; 3 | import { MessagePattern, EventPattern } from "@nestjs/microservices"; 4 | @Controller("orders") 5 | export class OrderController { 6 | constructor(private readonly orders: OrderService) {} 7 | @MessagePattern("index-orders") 8 | index(user_id: string) { 9 | return this.orders.get(user_id); 10 | } 11 | @MessagePattern("show-user-order") 12 | show({ id, user_id }: { id: string; user_id: string }) { 13 | console.log(id, user_id); 14 | return this.orders.findByIdAndUserId(id, user_id); 15 | } 16 | @MessagePattern("destroy-order-by-id") 17 | destroy({ id, user_id }: { user_id: string; id: string }) { 18 | return this.orders.destroy({ id, user_id }); 19 | } 20 | @MessagePattern("create_order") 21 | store(data: any) { 22 | return this.orders.create(data); 23 | } 24 | @EventPattern("order_charged") 25 | markOrderStatus({ id, status }) { 26 | return this.orders.markOrderStatus(id, status); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /server/services/gateway/src/orders/orders.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, Scope } from "@nestjs/common"; 2 | 3 | import { OrderResolver } from "./order.resolver"; 4 | import { OrderService } from "./order.service"; 5 | import { OrderProductDataLoader } from "../loaders/order-product.loader"; 6 | import { ProductService } from "../products/product.service"; 7 | import { ProductsModule } from "../products/products.module"; 8 | import { UserDataLoader } from "../loaders/user.loader"; 9 | import { UserService } from "../users/user.service"; 10 | import { UsersModule } from "../users/users.module"; 11 | 12 | @Module({ 13 | providers: [ 14 | OrderResolver, 15 | OrderService, 16 | { 17 | inject: [UserService], 18 | useFactory: UserDataLoader.create, 19 | provide: UserDataLoader, 20 | scope: Scope.REQUEST 21 | }, 22 | { 23 | inject: [ProductService], 24 | useFactory: OrderProductDataLoader.create, 25 | provide: OrderProductDataLoader, 26 | scope: Scope.REQUEST 27 | } 28 | ], 29 | imports: [UsersModule, ProductsModule] 30 | }) 31 | export class OrdersModule {} 32 | -------------------------------------------------------------------------------- /server/services/gateway/src/users/user.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Query, Resolver, Context, Mutation, Args } from "@nestjs/graphql"; 2 | import { UseGuards } from "@nestjs/common"; 3 | import { UserDTO } from "@commerce/shared"; 4 | import { LoginUser } from "./login-user.validation"; 5 | import { RegisterUser } from "./register-user.validation"; 6 | import { AuthGuard } from "../middlewares/auth.guard"; 7 | import { SellerGuard } from "../middlewares/seller.guard"; 8 | import { UserService } from "./user.service"; 9 | 10 | @Resolver("User") 11 | export class UserResolver { 12 | constructor(private readonly userService: UserService) {} 13 | 14 | @Query() 15 | users(): Promise { 16 | return this.userService.get(); 17 | } 18 | 19 | @Mutation() 20 | login( 21 | @Args("data") data: LoginUser 22 | ): Promise<{ token: string; id: string; name: string }> { 23 | return this.userService 24 | .login(data) 25 | .then(user => user) 26 | .catch(err => { 27 | console.log(err); 28 | }); 29 | } 30 | @Mutation() 31 | register(@Args("data") data: RegisterUser): Promise { 32 | return this.userService.register(data); 33 | } 34 | 35 | @Query() 36 | @UseGuards(new AuthGuard()) 37 | me(@Context("user") user: any) { 38 | return this.userService.me(user.id); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /server/services/users/src/users/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from "@nestjs/common"; 2 | import { LoginUser, RegisterUser } from "@commerce/shared"; 3 | import { MessagePattern, EventPattern } from "@nestjs/microservices"; 4 | import { ObjectID } from "typeorm"; 5 | import { UserService } from "./user.service"; 6 | 7 | @Controller("users") 8 | export class UserController { 9 | constructor(private readonly users: UserService) {} 10 | @MessagePattern("users") 11 | index() { 12 | return this.users.get(); 13 | } 14 | @MessagePattern("login-user") 15 | login(data: LoginUser) { 16 | return this.users.login(data); 17 | } 18 | @MessagePattern("register-user") 19 | register(data: RegisterUser) { 20 | return this.users.register(data); 21 | } 22 | @MessagePattern("current-loggedin-user") 23 | me(id: ObjectID) { 24 | return this.users.me({ id }); 25 | } 26 | @MessagePattern("fetch-user-by-id") 27 | fetchUserById(id: string) { 28 | return this.users.findById(id); 29 | } 30 | @MessagePattern("fetch-users-by-ids") 31 | fetchUsersByIds(ids: Array) { 32 | return this.users.fetchUsersByIds(ids); 33 | } 34 | @EventPattern("customer_created") 35 | handleCreatedCustomer({ user_id, gateway_customer_id }) { 36 | return this.users.updateToCustomer(user_id, gateway_customer_id); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /server/services/gateway/src/interceptors/logging.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NestInterceptor, 4 | ExecutionContext, 5 | Logger, 6 | CallHandler 7 | } from "@nestjs/common"; 8 | import { Observable } from "rxjs"; 9 | import { tap } from "rxjs/operators"; 10 | import { GqlExecutionContext } from "@nestjs/graphql"; 11 | @Injectable() 12 | export class LoggingInterceptor implements NestInterceptor { 13 | intercept( 14 | context: ExecutionContext, 15 | next: CallHandler 16 | ): Observable { 17 | const now = Date.now(); 18 | const req = context.switchToHttp().getRequest(); 19 | if (req) { 20 | const method = req.method; 21 | const url = req.url; 22 | 23 | return next 24 | .handle() 25 | .pipe( 26 | tap(() => 27 | Logger.log( 28 | `${method} ${url} ${Date.now() - now}ms`, 29 | context.getClass().name 30 | ) 31 | ) 32 | ); 33 | } else { 34 | const ctx: any = GqlExecutionContext.create(context); 35 | const resolverName = ctx.constructorRef.name; 36 | const info = ctx.getInfo(); 37 | 38 | return next 39 | .handle() 40 | .pipe( 41 | tap(() => 42 | Logger.log( 43 | `${info.parentType} "${info.fieldName}" ${Date.now() - now}ms`, 44 | resolverName 45 | ) 46 | ) 47 | ); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /server/services/gateway/src/middlewares/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | CanActivate, 4 | ExecutionContext, 5 | HttpStatus, 6 | HttpException 7 | } from "@nestjs/common"; 8 | import { GqlExecutionContext } from "@nestjs/graphql"; 9 | import { config } from "@commerce/shared"; 10 | import { verify } from "jsonwebtoken"; 11 | @Injectable() 12 | export class AuthGuard implements CanActivate { 13 | async canActivate(context: ExecutionContext): Promise { 14 | const request = context.switchToHttp().getRequest(); 15 | if (request) { 16 | if (!request.headers.authorization) { 17 | return false; 18 | } 19 | request.user = await this.validateToken(request.headers.authorization); 20 | return true; 21 | } else { 22 | const ctx: any = GqlExecutionContext.create(context).getContext(); 23 | if (!ctx.headers.authorization) { 24 | return false; 25 | } 26 | ctx.user = await this.validateToken(ctx.headers.authorization); 27 | return true; 28 | } 29 | } 30 | async validateToken(auth: string) { 31 | if (auth.split(" ")[0] !== "Bearer") { 32 | throw new HttpException( 33 | "Invalid Token has been passed", 34 | HttpStatus.FORBIDDEN 35 | ); 36 | } 37 | const token = auth.split(" ")[1]; 38 | try { 39 | const decodedToken = await verify(token, config.JWT_TOKEN); 40 | return decodedToken; 41 | } catch (err) { 42 | const message = "Token error:" + err.message; 43 | throw new HttpException(message, HttpStatus.FORBIDDEN); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /server/services/gateway/src/pipes/validation.pipe.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PipeTransform, 3 | Injectable, 4 | ArgumentMetadata, 5 | HttpException, 6 | HttpStatus 7 | } from "@nestjs/common"; 8 | import { validate } from "class-validator"; 9 | import { plainToClass } from "class-transformer"; 10 | 11 | @Injectable() 12 | export class ValidationPipe implements PipeTransform { 13 | async transform(value: any, { metatype }: ArgumentMetadata) { 14 | if (value instanceof Object && this.isEmpty(value)) { 15 | throw new HttpException( 16 | "Validation failed: No body submmited", 17 | HttpStatus.UNPROCESSABLE_ENTITY 18 | ); 19 | } 20 | if (!metatype || !this.toValidate(metatype)) { 21 | return value; 22 | } 23 | const object = plainToClass(metatype, value); 24 | const errors = await validate(object); 25 | if (errors.length > 0) { 26 | throw new HttpException( 27 | `Validation failed: ${this.formatErrors(errors)}`, 28 | HttpStatus.UNPROCESSABLE_ENTITY 29 | ); 30 | } 31 | return value; 32 | } 33 | private formatErrors(errors: any[]) { 34 | return errors 35 | .map(err => { 36 | for (let property in err.constraints) { 37 | return err.constraints[property]; 38 | } 39 | }) 40 | .join(", "); 41 | } 42 | private isEmpty(value: any) { 43 | return !(Object.keys(value).length > 0); 44 | } 45 | private toValidate(metatype: Function): boolean { 46 | const types: Function[] = [String, Boolean, Number, Array, Object]; 47 | return !types.includes(metatype); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /server/services/gateway/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { APP_INTERCEPTOR, APP_FILTER } from "@nestjs/core"; 2 | import { GraphQLModule } from "@nestjs/graphql"; 3 | import { Module } from "@nestjs/common"; 4 | import { config } from "@commerce/shared"; 5 | 6 | import { join } from "path"; 7 | 8 | import { GraphQLErrorFilter } from "./filters/graphql-exception.filter"; 9 | import { LoggingInterceptor } from "./interceptors/logging.interceptor"; 10 | import { OrdersModule } from "./orders/orders.module"; 11 | import { PaymentCardsModule } from "./payments/payment.module"; 12 | import { ProductsModule } from "./products/products.module"; 13 | import { UsersModule } from "./users/users.module"; 14 | 15 | @Module({ 16 | imports: [ 17 | config.APP_ENV === "production" 18 | ? GraphQLModule.forRoot({ 19 | typePaths: ["./**/*.gql"], 20 | context: ({ req, res }) => ({ headers: req.headers }), 21 | debug: true, 22 | installSubscriptionHandlers: true 23 | }) 24 | : GraphQLModule.forRoot({ 25 | typePaths: ["./**/*.gql"], 26 | definitions: { 27 | path: join(process.cwd(), "src/schemas/graphql.d.ts") 28 | }, 29 | context: ({ req, res }) => ({ headers: req.headers }), 30 | debug: true, 31 | installSubscriptionHandlers: true 32 | }), 33 | UsersModule, 34 | ProductsModule, 35 | OrdersModule, 36 | PaymentCardsModule 37 | ], 38 | providers: [ 39 | { 40 | provide: APP_FILTER, 41 | useClass: GraphQLErrorFilter 42 | }, 43 | { 44 | provide: APP_INTERCEPTOR, 45 | useClass: LoggingInterceptor 46 | } 47 | ] 48 | }) 49 | export class AppModule {} 50 | -------------------------------------------------------------------------------- /server/services/gateway/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from "@nestjs/core"; 2 | import { 3 | FastifyAdapter, 4 | NestFastifyApplication 5 | } from "@nestjs/platform-fastify"; 6 | import { AppModule } from "./app.module"; 7 | import { ValidationPipe } from "./pipes/validation.pipe"; 8 | import { config } from "@commerce/shared"; 9 | import * as helmet from "helmet"; 10 | import * as fastifyRateLimit from "fastify-rate-limit"; 11 | import { redis } from "./utils/redis"; 12 | import * as FastifyCompress from "fastify-compress"; 13 | import * as bodyParser from "body-parser"; 14 | import * as xssFilter from "x-xss-protection"; 15 | import * as hpp from "hpp"; 16 | 17 | async function bootstrap() { 18 | const fastify = new FastifyAdapter({ logger: true }); 19 | const app = await NestFactory.create( 20 | AppModule, 21 | fastify 22 | ); 23 | fastify.register(fastifyRateLimit, { 24 | max: 100, 25 | timeWindow: "1 minute", 26 | redis, 27 | whitelist: ["127.0.0.1"] 28 | }); 29 | app.enableCors(); 30 | app.use(helmet()); 31 | app.use(helmet.noSniff()); 32 | app.use(helmet.ieNoOpen()); 33 | app.use(bodyParser.urlencoded({ extended: true })); 34 | app.use(xssFilter()); 35 | app.use(hpp()); 36 | app.use( 37 | helmet.contentSecurityPolicy({ 38 | directives: { 39 | defaultSrc: ["'self'"], 40 | imgSrc: ["'self'"] 41 | }, 42 | disableAndroid: true, 43 | setAllHeaders: true 44 | }) 45 | ); 46 | 47 | app.useGlobalPipes(new ValidationPipe()); 48 | await app.listen(config.GATEWAY_PORT); 49 | } 50 | bootstrap(); 51 | -------------------------------------------------------------------------------- /server/services/gateway/src/users/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Client, ClientProxy, Transport } from "@nestjs/microservices"; 2 | import { Injectable } from "@nestjs/common"; 3 | import { UserDTO, RegisterUser, LoginUser } from "@commerce/shared"; 4 | import { ObjectID } from "typeorm"; 5 | import { config } from "@commerce/shared"; 6 | 7 | @Injectable() 8 | export class UserService { 9 | @Client({ 10 | transport: Transport.REDIS, 11 | options: { 12 | url: `redis://${config.REDIS_URL}:${config.REDIS_PORT}` 13 | } 14 | }) 15 | private client: ClientProxy; 16 | async get(): Promise { 17 | const response = await this.client.send("users", []); 18 | return response.toPromise(); 19 | } 20 | async login(data: LoginUser): Promise { 21 | return new Promise((resolve, reject) => { 22 | this.client 23 | .send("login-user", data) 24 | .subscribe(response => resolve(response), error => reject(error)); 25 | }); 26 | } 27 | async register(data: RegisterUser): Promise { 28 | const response = this.client.send("register-user", data); 29 | return response.toPromise(); 30 | } 31 | 32 | async fetchUsersByIds(ids: string[]): UserDTO { 33 | const user = await this.client 34 | .send("fetch-users-by-ids", ids) 35 | .toPromise(); 36 | return user; 37 | } 38 | me(id: ObjectID) { 39 | return new Promise((resolve, reject) => { 40 | this.client.send("current-loggedin-user", id).subscribe( 41 | response => { 42 | resolve(response); 43 | }, 44 | error => { 45 | reject(error); 46 | } 47 | ); 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /server/services/gateway/src/payments/payment.service.ts: -------------------------------------------------------------------------------- 1 | import { Client, ClientProxy, Transport } from "@nestjs/microservices"; 2 | import { Injectable } from "@nestjs/common"; 3 | import { PaymentCardDTO, UserDTO, config } from "@commerce/shared"; 4 | 5 | import { CreatePaymentCard } from "./create-payment-card.validation"; 6 | import { redis, redisProductsKey } from "../utils/redis"; 7 | @Injectable() 8 | export class PaymentCardService { 9 | @Client({ 10 | transport: Transport.REDIS, 11 | options: { 12 | url: `redis://${config.REDIS_URL}:${config.REDIS_PORT}` 13 | } 14 | }) 15 | private client: ClientProxy; 16 | async get(user_id: string): Promise { 17 | return this.client 18 | .send("index-user-payment-cards", user_id) 19 | .toPromise(); 20 | } 21 | async charge(order, user) { 22 | return this.client 23 | .send("create-charge", { 24 | total_price: order.total_price, 25 | user 26 | }) 27 | .toPromise(); 28 | } 29 | async store(data: CreatePaymentCard, user: UserDTO): Promise { 30 | return this.client 31 | .send( 32 | "create-payment", 33 | { 34 | data, 35 | user 36 | } 37 | ) 38 | .toPromise(); 39 | } 40 | async show(id, user_id) { 41 | return this.client 42 | .send("show-user-payment-card", { 43 | id, 44 | user_id 45 | }) 46 | .toPromise(); 47 | } 48 | destroy(id: string, user_id: string) { 49 | return this.client 50 | .send("delete-user-payment", { 51 | id, 52 | user_id 53 | }) 54 | .toPromise(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /server/services/gateway/src/loaders/order-product.loader.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject, forwardRef } from "@nestjs/common"; 2 | import { ModuleRef } from "@nestjs/core"; 3 | import { ProductDTO } from "@commerce/shared"; 4 | import DataLoader = require("dataloader"); // commonjs module 5 | 6 | import { IDataLoader } from "../contracts/nest-dataloader"; 7 | import { ProductService } from "../products/product.service"; 8 | 9 | @Injectable() 10 | export class OrderProductDataLoader implements IDataLoader { 11 | constructor(private readonly dataLoader: DataLoader) {} 12 | 13 | public static async create( 14 | productService: ProductService 15 | ): Promise { 16 | const dataloader = new DataLoader( 17 | async (products: any) => { 18 | const ids = products.map(product => product.id).flat(); 19 | let fetchedProducts = await productService.fetchProductsByIds( 20 | ids 21 | ); 22 | return products.map(product => { 23 | return { 24 | product: fetchedProducts.find( 25 | entity => entity.id === product.id 26 | ), 27 | quantity_ordered: products.find( 28 | p => p.id === product.id 29 | ).quantity 30 | }; 31 | }); 32 | } 33 | ); 34 | return new OrderProductDataLoader(dataloader); 35 | } 36 | public async load(id: string) { 37 | return this.dataLoader.load(id); 38 | } 39 | public async loadMany(products: any[]) { 40 | return this.dataLoader.loadMany(products); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /server/services/payments/src/payments/payment.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from "@nestjs/common"; 2 | import { MessagePattern } from "@nestjs/microservices"; 3 | 4 | // import { PaymentEntity } from "./payment.entity"; 5 | import { PaymentService } from "./payment.service"; 6 | import { stripe } from "../utils/stripe"; 7 | @Controller("payments") 8 | export class PaymentController { 9 | constructor(private readonly payments: PaymentService) {} 10 | @MessagePattern("index-user-payment-cards") 11 | index(user_id: string) { 12 | return this.payments.get(user_id); 13 | } 14 | @MessagePattern("show-user-payment-card") 15 | show({ id, user_id }) { 16 | return this.payments.show(id, user_id); 17 | } 18 | @MessagePattern("create-payment") 19 | store({ data, user }) { 20 | return this.payments.store(data, user); 21 | } 22 | @MessagePattern("delete-user-payment") 23 | destroy({ id, user_id }) { 24 | return this.payments.destroy(id, user_id); 25 | } 26 | @MessagePattern("create-charge") 27 | async createCharge({ total_price, user }) { 28 | const paymentCard = await this.payments.findByUserId(user.id); 29 | return stripe.charges.create({ 30 | amount: total_price, // in cents. 31 | currency: "usd", 32 | customer: user.gateway_customer_id, 33 | source: paymentCard.provider_id, // card_id 34 | shipping: { 35 | name: user.name, 36 | address: { 37 | line1: user.address.address_1, 38 | line2: user.address.address_2, 39 | city: user.address.city, 40 | state: user.address.state, 41 | country: user.address.country, 42 | postal_code: user.address.zip 43 | } 44 | } 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # TypeScript Next.js example 2 | 3 | This is a really simple project that show the usage of Next.js with TypeScript. 4 | 5 | ## How to use it? 6 | 7 | ### Using `create-next-app` 8 | 9 | Execute [`create-next-app`](https://github.com/segmentio/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example: 10 | 11 | ```bash 12 | npx create-next-app --example with-typescript with-typescript-app 13 | # or 14 | yarn create next-app --example with-typescript with-typescript-app 15 | ``` 16 | 17 | ### Download manually 18 | 19 | Download the example: 20 | 21 | ```bash 22 | curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-typescript 23 | cd with-typescript 24 | ``` 25 | 26 | Install it and run: 27 | 28 | ```bash 29 | npm install 30 | npm run dev 31 | # or 32 | yarn 33 | yarn dev 34 | ``` 35 | 36 | ## The idea behind the example 37 | 38 | This example shows how to integrate the TypeScript type system into Next.js. Since TypeScript is supported out of the box with Next.js, all we have to do is to install TypeScript. 39 | 40 | ``` 41 | npm install --save-dev typescript 42 | ``` 43 | 44 | To enable TypeScript's features, we install the type declaratons for React and Node. 45 | 46 | ``` 47 | npm install --save-dev @types/react @types/react-dom @types/node 48 | ``` 49 | 50 | When we run `next dev` the next time, Next.js will start looking for any `.ts` or `.tsx` files in our project and builds it. It even automatically creates a `tsconfig.json` file for our project with the recommended settings. 51 | 52 | Next.js has built-in TypeScript declarations, so we'll get autocompletion for Next.js' modules straight away. 53 | 54 | A `type-check` script is also added to `package.json`, which runs TypeScript's `tsc` CLI in `noEmit` mode to run type-checking separately. You can then include this, for example, in your `test` scripts. 55 | -------------------------------------------------------------------------------- /server/services/products/src/products/product.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from "@nestjs/common"; 2 | import { MessagePattern, EventPattern } from "@nestjs/microservices"; 3 | 4 | import { ProductEntity } from "./product.entity"; 5 | import { ProductService } from "./product.service"; 6 | 7 | @Controller("products") 8 | export class ProductController { 9 | constructor(private readonly products: ProductService) {} 10 | 11 | @MessagePattern("products") 12 | index(data: any = undefined): Promise { 13 | return this.products.get(data); 14 | } 15 | 16 | @MessagePattern("create-product") 17 | store(data: any): Promise { 18 | return this.products.store(data); 19 | } 20 | 21 | @MessagePattern("update-product") 22 | update({ 23 | id, 24 | title, 25 | description, 26 | image, 27 | price, 28 | user_id 29 | }: any): Promise { 30 | return this.products.update( 31 | id, 32 | { title, description, image, price }, 33 | user_id 34 | ); 35 | } 36 | 37 | @MessagePattern("show-product") 38 | show(id: string): Promise { 39 | return this.products.show(id); 40 | } 41 | @MessagePattern("fetch-products-by-ids") 42 | fetchProductsByIds(ids: Array) { 43 | return this.products.fetchProductsByIds(ids); 44 | } 45 | @EventPattern("order_deleted") 46 | async handleOrderDeleted( 47 | products: Array<{ id: string; quantity: number }> 48 | ) { 49 | this.products.incrementProductsStock(products); 50 | } 51 | @EventPattern("order_created") 52 | async handleOrderCreated( 53 | products: Array<{ id: string; quantity: number }> 54 | ) { 55 | this.products.decrementProductsStock(products); 56 | } 57 | 58 | @MessagePattern("delete-product") 59 | destroy({ id, user_id }: { id: string; user_id: string }) { 60 | return this.products.destroy(id, user_id); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /server/services/users/src/users/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | Entity, 4 | BaseEntity, 5 | PrimaryGeneratedColumn, 6 | OneToOne, 7 | CreateDateColumn, 8 | UpdateDateColumn, 9 | BeforeInsert 10 | } from "typeorm"; 11 | import { AddressEntity } from "./address.entity"; 12 | import { sign } from "jsonwebtoken"; 13 | import { config } from "@commerce/shared"; 14 | import { hash } from "bcryptjs"; 15 | @Entity("users") 16 | export class UserEntity extends BaseEntity { 17 | @PrimaryGeneratedColumn("uuid") 18 | id: string; 19 | @Column({ type: "boolean" }) 20 | seller: boolean; 21 | 22 | @Column("text") 23 | name: string; 24 | @Column("text", { unique: true }) 25 | email: string; 26 | @Column("text") 27 | password: string; 28 | @Column("text", { nullable: true }) 29 | gateway_customer_id: string; 30 | 31 | @CreateDateColumn() 32 | created_at: Date; 33 | @UpdateDateColumn() 34 | updated_at: Date; 35 | 36 | @OneToOne(() => AddressEntity, address => address.user) 37 | address: AddressEntity; 38 | @BeforeInsert() 39 | async hashPassword() { 40 | this.password = await hash(this.password, 12); 41 | } 42 | private get token() { 43 | const { id, seller } = this; 44 | return sign({ id, seller }, config.JWT_TOKEN, { 45 | expiresIn: config.JWT_TOKEN_EXPIRATION 46 | }); 47 | } 48 | toResponseObject(showToken: boolean = true) { 49 | const { 50 | id, 51 | created_at, 52 | name, 53 | email, 54 | token, 55 | updated_at, 56 | seller, 57 | address, 58 | gateway_customer_id 59 | } = this; 60 | let responseObject: any = { 61 | id, 62 | name, 63 | email, 64 | created_at, 65 | updated_at, 66 | seller, 67 | gateway_customer_id 68 | }; 69 | if (address) { 70 | responseObject.address = address; 71 | } 72 | if (showToken) { 73 | responseObject.token = token; 74 | } 75 | return responseObject; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /server/services/orders/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@commerce/orders", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "", 6 | "author": "", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "rimraf dist && tsc -p tsconfig.build.json", 10 | "format": "prettier --write \"src/**/*.ts\"", 11 | "start": "ts-node -r tsconfig-paths/register src/main.ts", 12 | "start:dev": "tsc-watch -p tsconfig.build.json --onSuccess \"node dist/main.js\"", 13 | "start:debug": "tsc-watch -p tsconfig.build.json --onSuccess \"node --inspect-brk dist/main.js\"", 14 | "start:prod": "node dist/main.js", 15 | "lint": "tslint -p tsconfig.json -c tslint.json", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@commerce/shared": "1.0.0", 24 | "@nestjs/common": "^6.0.0", 25 | "@nestjs/core": "^6.0.0", 26 | "@nestjs/microservices": "^6.5.3", 27 | "@nestjs/platform-express": "^6.0.0", 28 | "@nestjs/typeorm": "^6.1.3", 29 | "class-validator": "^0.10.0", 30 | "pg": "^7.12.1", 31 | "redis": "^3.1.1", 32 | "reflect-metadata": "^0.1.12", 33 | "rimraf": "^2.6.2", 34 | "rxjs": "^6.3.3", 35 | "typeorm": "^0.2.25" 36 | }, 37 | "devDependencies": { 38 | "@nestjs/testing": "^6.0.0", 39 | "@types/express": "4.16.1", 40 | "@types/jest": "24.0.11", 41 | "@types/node": "11.13.4", 42 | "@types/supertest": "2.0.7", 43 | "jest": "24.7.1", 44 | "prettier": "1.17.0", 45 | "supertest": "4.0.2", 46 | "ts-jest": "24.0.2", 47 | "ts-node": "8.1.0", 48 | "tsc-watch": "2.2.1", 49 | "tsconfig-paths": "3.8.0", 50 | "tslint": "5.16.0", 51 | "typescript": "3.4.3" 52 | }, 53 | "jest": { 54 | "moduleFileExtensions": [ 55 | "js", 56 | "json", 57 | "ts" 58 | ], 59 | "rootDir": "src", 60 | "testRegex": ".spec.ts$", 61 | "transform": { 62 | "^.+\\.(t|j)s$": "ts-jest" 63 | }, 64 | "coverageDirectory": "../coverage", 65 | "testEnvironment": "node" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /server/services/products/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@commerce/products", 3 | "private": true, 4 | "version": "0.0.1", 5 | "description": "", 6 | "author": "", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "rimraf dist && tsc -p tsconfig.build.json", 10 | "format": "prettier --write \"src/**/*.ts\"", 11 | "start": "ts-node -r tsconfig-paths/register src/main.ts", 12 | "start:dev": "tsc-watch -p tsconfig.build.json --onSuccess \"node dist/main.js\"", 13 | "start:debug": "tsc-watch -p tsconfig.build.json --onSuccess \"node --inspect-brk dist/main.js\"", 14 | "start:prod": "node dist/main.js", 15 | "lint": "tslint -p tsconfig.json -c tslint.json", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@commerce/shared": "1.0.0", 24 | "@nestjs/common": "^6.0.0", 25 | "@nestjs/core": "^6.0.0", 26 | "@nestjs/microservices": "^6.5.3", 27 | "@nestjs/platform-express": "^6.0.0", 28 | "@nestjs/typeorm": "^6.1.3", 29 | "class-validator": "^0.10.0", 30 | "pg": "^7.12.1", 31 | "redis": "^3.1.1", 32 | "reflect-metadata": "^0.1.12", 33 | "rimraf": "^2.6.2", 34 | "rxjs": "^6.3.3", 35 | "typeorm": "^0.2.25" 36 | }, 37 | "devDependencies": { 38 | "@nestjs/testing": "^6.0.0", 39 | "@types/express": "4.16.1", 40 | "@types/jest": "24.0.11", 41 | "@types/node": "11.13.4", 42 | "@types/supertest": "2.0.7", 43 | "jest": "24.7.1", 44 | "prettier": "1.17.0", 45 | "supertest": "4.0.2", 46 | "ts-jest": "24.0.2", 47 | "ts-node": "8.1.0", 48 | "tsc-watch": "2.2.1", 49 | "tsconfig-paths": "3.8.0", 50 | "tslint": "5.16.0", 51 | "typescript": "3.4.3" 52 | }, 53 | "jest": { 54 | "moduleFileExtensions": [ 55 | "js", 56 | "json", 57 | "ts" 58 | ], 59 | "rootDir": "src", 60 | "testRegex": ".spec.ts$", 61 | "transform": { 62 | "^.+\\.(t|j)s$": "ts-jest" 63 | }, 64 | "coverageDirectory": "../coverage", 65 | "testEnvironment": "node" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /server/services/orders/src/orders/order.service.ts: -------------------------------------------------------------------------------- 1 | import { InjectRepository } from "@nestjs/typeorm"; 2 | import { Injectable } from "@nestjs/common"; 3 | import { Repository } from "typeorm"; 4 | 5 | import { OrderEntity as Order } from "./order.entity"; 6 | import { OrderDTO } from "@commerce/shared"; 7 | @Injectable() 8 | export class OrderService { 9 | constructor( 10 | @InjectRepository(Order) 11 | private readonly orders: Repository 12 | ) {} 13 | async get(user_id: string): Promise { 14 | return this.orders.find({ user_id }); 15 | } 16 | async markOrderStatus(id, status) { 17 | return this.orders.update(id, { 18 | status 19 | }); 20 | } 21 | async findByIdAndUserId(id, user_id) { 22 | return this.orders.findOneOrFail({ id, user_id }); 23 | } 24 | async create({ products, user_id }): Promise { 25 | const INITIAL_VALUE = 0; 26 | const total_price = products.reduce( 27 | (accumulator, product) => 28 | accumulator + product.ordered_quantity * product.price, 29 | INITIAL_VALUE 30 | ); 31 | const databaseProducts = products.map(product => { 32 | return { id: product.id, quantity: product.ordered_quantity }; 33 | }); 34 | const actualProducts = products.map(product => { 35 | product.quantity = product.quantity - product.ordered_quantity; 36 | delete product.ordered_quantity; 37 | return { ...product }; 38 | }); 39 | products = databaseProducts; 40 | 41 | const order = await this.orders.create({ 42 | products, 43 | user_id, 44 | total_price 45 | }); 46 | await this.orders.save(order); 47 | order.products = actualProducts; 48 | return order; 49 | } 50 | async destroy({ id, user_id }) { 51 | // find the order. 52 | const order = await this.orders.findOneOrFail({ 53 | where: { id, user_id } 54 | }); 55 | await this.orders.delete({ id, user_id }); 56 | // return the order to fire an event increasing the stock of related products to this order at the gateway. 57 | return order; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /server/services/users/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@commerce/users", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "", 6 | "author": "", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "rimraf dist && tsc -p tsconfig.build.json", 10 | "format": "prettier --write \"src/**/*.ts\"", 11 | "start": "ts-node -r tsconfig-paths/register src/main.ts", 12 | "start:dev": "tsc-watch -p tsconfig.build.json --onSuccess \"node dist/main.js\"", 13 | "start:debug": "tsc-watch -p tsconfig.build.json --onSuccess \"node --inspect-brk dist/main.js\"", 14 | "start:prod": "node dist/main.js", 15 | "lint": "tslint -p tsconfig.json -c tslint.json", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@commerce/shared": "1.0.0", 24 | "@nestjs/common": "^6.0.0", 25 | "@nestjs/core": "^6.0.0", 26 | "@nestjs/microservices": "^6.5.3", 27 | "@nestjs/platform-express": "^6.0.0", 28 | "@nestjs/typeorm": "^6.1.3", 29 | "bcryptjs": "^2.4.3", 30 | "class-validator": "^0.10.0", 31 | "pg": "^7.12.1", 32 | "redis": "^3.1.1", 33 | "reflect-metadata": "^0.1.12", 34 | "rimraf": "^2.6.2", 35 | "rxjs": "^6.3.3", 36 | "typeorm": "^0.2.25" 37 | }, 38 | "devDependencies": { 39 | "@nestjs/testing": "^6.0.0", 40 | "@types/bcryptjs": "^2.4.2", 41 | "@types/express": "4.16.1", 42 | "@types/jest": "24.0.11", 43 | "@types/node": "11.13.4", 44 | "@types/supertest": "2.0.7", 45 | "jest": "24.7.1", 46 | "prettier": "1.17.0", 47 | "supertest": "4.0.2", 48 | "ts-jest": "24.0.2", 49 | "ts-node": "8.1.0", 50 | "tsc-watch": "2.2.1", 51 | "tsconfig-paths": "3.8.0", 52 | "tslint": "5.16.0", 53 | "typescript": "3.4.3" 54 | }, 55 | "jest": { 56 | "moduleFileExtensions": [ 57 | "js", 58 | "json", 59 | "ts" 60 | ], 61 | "rootDir": "src", 62 | "testRegex": ".spec.ts$", 63 | "transform": { 64 | "^.+\\.(t|j)s$": "ts-jest" 65 | }, 66 | "coverageDirectory": "../coverage", 67 | "testEnvironment": "node" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /server/services/payments/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@commerce/payments", 3 | "private": true, 4 | "version": "0.0.1", 5 | "description": "", 6 | "author": "", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "rimraf dist && tsc -p tsconfig.build.json", 10 | "format": "prettier --write \"src/**/*.ts\"", 11 | "start": "ts-node -r tsconfig-paths/register src/main.ts", 12 | "start:dev": "tsc-watch -p tsconfig.build.json --onSuccess \"node dist/main.js\"", 13 | "start:debug": "tsc-watch -p tsconfig.build.json --onSuccess \"node --inspect-brk dist/main.js\"", 14 | "start:prod": "node dist/main.js", 15 | "lint": "tslint -p tsconfig.json -c tslint.json", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@commerce/shared": "1.0.0", 24 | "@nestjs/common": "^6.0.0", 25 | "@nestjs/core": "^6.0.0", 26 | "@nestjs/microservices": "^6.5.3", 27 | "@nestjs/platform-express": "^6.0.0", 28 | "@nestjs/typeorm": "^6.1.3", 29 | "class-validator": "^0.10.0", 30 | "pg": "^7.12.1", 31 | "redis": "^3.1.1", 32 | "reflect-metadata": "^0.1.12", 33 | "rimraf": "^2.6.2", 34 | "rxjs": "^6.3.3", 35 | "stripe": "^7.8.0", 36 | "typeorm": "^0.2.25" 37 | }, 38 | "devDependencies": { 39 | "@nestjs/testing": "^6.0.0", 40 | "@types/express": "4.16.1", 41 | "@types/jest": "24.0.11", 42 | "@types/node": "11.13.4", 43 | "@types/stripe": "^6.31.27", 44 | "@types/supertest": "2.0.7", 45 | "jest": "24.7.1", 46 | "prettier": "1.17.0", 47 | "supertest": "4.0.2", 48 | "ts-jest": "24.0.2", 49 | "ts-node": "8.1.0", 50 | "tsc-watch": "2.2.1", 51 | "tsconfig-paths": "3.8.0", 52 | "tslint": "5.16.0", 53 | "typescript": "3.4.3" 54 | }, 55 | "jest": { 56 | "moduleFileExtensions": [ 57 | "js", 58 | "json", 59 | "ts" 60 | ], 61 | "rootDir": "src", 62 | "testRegex": ".spec.ts$", 63 | "transform": { 64 | "^.+\\.(t|j)s$": "ts-jest" 65 | }, 66 | "coverageDirectory": "../coverage", 67 | "testEnvironment": "node" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /server/services/gateway/src/products/product.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Client, ClientProxy, Transport } from "@nestjs/microservices"; 2 | import { ProductDTO, UserDTO, config } from "@commerce/shared"; 3 | import { 4 | Query, 5 | Resolver, 6 | Context, 7 | Mutation, 8 | Args, 9 | ResolveProperty, 10 | Parent 11 | } from "@nestjs/graphql"; 12 | import { UseGuards } from "@nestjs/common"; 13 | 14 | import { AuthGuard } from "../middlewares/auth.guard"; 15 | import { CreateProduct } from "./create-product.validation"; 16 | import { ProductService } from "./product.service"; 17 | import { SellerGuard } from "../middlewares/seller.guard"; 18 | import { UserDataLoader } from "../loaders/user.loader"; 19 | 20 | @Resolver("Product") 21 | export class ProductResolver { 22 | @Client({ 23 | transport: Transport.REDIS, 24 | options: { 25 | url: `redis://${config.REDIS_URL}:${config.REDIS_PORT}` 26 | } 27 | }) 28 | private client: ClientProxy; 29 | 30 | constructor( 31 | private readonly productService: ProductService, 32 | private readonly usersDataLoader: UserDataLoader 33 | ) {} 34 | @ResolveProperty("user", () => UserDTO) 35 | async user(@Parent() product: ProductDTO): Promise { 36 | return this.usersDataLoader.load(product.user_id); 37 | } 38 | @Query() 39 | products(): Promise { 40 | return this.productService.get(); 41 | } 42 | @Query() 43 | async showProduct(@Args("id") id: string) { 44 | return this.productService.show(id); 45 | } 46 | 47 | @Mutation() 48 | @UseGuards(new AuthGuard(), new SellerGuard()) 49 | async createProduct( 50 | @Args("data") data: CreateProduct, 51 | @Context("user") user: any 52 | ) { 53 | return this.productService.store(data, user.id); 54 | } 55 | @Mutation() 56 | @UseGuards(new AuthGuard(), new SellerGuard()) 57 | async updateProduct( 58 | @Args("data") data: CreateProduct, 59 | @Context("user") user: any, 60 | @Args("id") id: string 61 | ) { 62 | return this.productService.update(data, id, user.id); 63 | } 64 | @Mutation() 65 | @UseGuards(new AuthGuard(), new SellerGuard()) 66 | async deleteProduct(@Context("user") user: any, @Args("id") id: string) { 67 | return this.productService.destroy(id, user.id); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /client/components/auth/login/loginView.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as Antd from "antd"; 3 | import { withFormik, FormikErrors, FormikProps, Field, Form } from "formik"; 4 | import { InputField } from "../../partials/InputField"; 5 | 6 | const { Form: AntForm, Icon, Button } = Antd; 7 | const FormItem = AntForm.Item; 8 | 9 | interface FormValues { 10 | email: string; 11 | password: string; 12 | } 13 | 14 | interface Props { 15 | submit: (values: FormValues) => Promise | null>; 16 | } 17 | 18 | class C extends React.PureComponent & Props> { 19 | render() { 20 | return ( 21 |
22 |
23 | as any 28 | // tslint:disable-next-line:jsx-curly-spacing 29 | } 30 | placeholder="Email" 31 | component={InputField} 32 | /> 33 | as any 39 | // tslint:disable-next-line:jsx-curly-spacing 40 | } 41 | placeholder="Password" 42 | component={InputField} 43 | /> 44 | 45 | 46 | Forgot password 47 | 48 | 49 | 50 | 57 | 58 | 59 | Or login now! 60 | 61 |
62 |
63 | ); 64 | } 65 | } 66 | 67 | export const LoginView = withFormik({ 68 | mapPropsToValues: () => ({ email: "", password: "" }), 69 | handleSubmit: async (values, { props, setErrors }) => { 70 | const errors = await props.submit(values); 71 | if (errors) { 72 | setErrors(errors); 73 | } 74 | } 75 | })(C); 76 | -------------------------------------------------------------------------------- /server/services/gateway/src/orders/order.service.ts: -------------------------------------------------------------------------------- 1 | import { Client, ClientProxy, Transport } from "@nestjs/microservices"; 2 | import { Injectable } from "@nestjs/common"; 3 | import { UserDTO, ProductDTO, OrderDTO } from "@commerce/shared"; 4 | 5 | import { config } from "@commerce/shared"; 6 | import { redis, redisProductsKey } from "../utils/redis"; 7 | import { CreateProduct } from "@commerce/shared"; 8 | @Injectable() 9 | export class OrderService { 10 | @Client({ 11 | transport: Transport.REDIS, 12 | options: { 13 | url: `redis://${config.REDIS_URL}:${config.REDIS_PORT}` 14 | } 15 | }) 16 | private client: ClientProxy; 17 | indexOrdersByUser(user_id: string): Promise { 18 | return new Promise((resolve, reject) => { 19 | this.client.send("index-orders", user_id).subscribe(orders => { 20 | return resolve(orders); 21 | }); 22 | }); 23 | } 24 | async destroyUserOrder(order_id: any, user_id): Promise { 25 | return new Promise((resolve, reject) => { 26 | this.client 27 | .send("destroy-order-by-id", { 28 | id: order_id, 29 | user_id 30 | }) 31 | .subscribe(async order => { 32 | // fire an event that order is deleted to increase the product's quantity. 33 | this.client 34 | .emit("order_deleted", order.products) 35 | .subscribe(() => resolve(order)); 36 | }); 37 | }); 38 | } 39 | store(products: any, user_id, fetchedProducts): Promise { 40 | return new Promise((resolve, reject) => { 41 | const mappedProducts = fetchedProducts 42 | .map(product => { 43 | // find the product which user passed, to retrieve the ordered quantity. 44 | let p = products.find(p => p.id === product.id); 45 | if (p) { 46 | return { ...product, ordered_quantity: p.quantity }; 47 | } 48 | return product; 49 | }) 50 | .filter(product => !!product.ordered_quantity); 51 | this.client 52 | .send("create_order", { 53 | products: mappedProducts, 54 | user_id 55 | }) 56 | .subscribe( 57 | order => { 58 | // fire an event to reduce the quantity of the products. 59 | this.client 60 | .emit("order_created", products) 61 | .subscribe(() => {}, () => {}, () => resolve(order)); // resolve on completion 62 | }, 63 | error => reject(error) 64 | ); 65 | }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /server/services/products/src/products/product.service.ts: -------------------------------------------------------------------------------- 1 | import { InjectRepository } from "@nestjs/typeorm"; 2 | import { Injectable, NotFoundException } from "@nestjs/common"; 3 | import { Repository } from "typeorm"; 4 | import { RpcException } from "@nestjs/microservices"; 5 | 6 | import { ProductEntity } from "./product.entity"; 7 | 8 | @Injectable() 9 | export class ProductService { 10 | constructor( 11 | @InjectRepository(ProductEntity) 12 | private readonly products: Repository 13 | ) {} 14 | get(data: any = undefined): Promise { 15 | return this.products.find(data); 16 | } 17 | fetchProductsByIds(ids: Array) { 18 | return this.products 19 | .createQueryBuilder("products") 20 | .where(`products.id IN (:...ids)`, { ids }) 21 | .getMany(); 22 | } 23 | store(data: any): Promise { 24 | return this.products.save(data); 25 | } 26 | async update( 27 | id: string, 28 | data: any, 29 | user_id: string 30 | ): Promise { 31 | const product = await this.products.findOneOrFail({ id }); 32 | if (product.user_id === user_id) { 33 | await this.products.update({ id }, data); 34 | return this.products.findOneOrFail({ id }); 35 | } 36 | throw new RpcException( 37 | new NotFoundException("You cannot update what you don't own...") 38 | ); 39 | } 40 | async show(id: string): Promise { 41 | return this.products.findOneOrFail({ id }); 42 | } 43 | async destroy(id: string, user_id: string): Promise { 44 | const product = await this.products.findOneOrFail({ id }); 45 | if (product.user_id === user_id) { 46 | await this.products.delete({ id }); 47 | return product; 48 | } 49 | throw new RpcException( 50 | new NotFoundException("You cannot update what you don't own...") 51 | ); 52 | } 53 | async decrementProductsStock(products) { 54 | products.forEach(product => { 55 | this.products.decrement( 56 | { id: product.id }, 57 | "quantity", 58 | product.quantity 59 | ); 60 | }); 61 | } 62 | async incrementProductsStock(products) { 63 | products.forEach(product => { 64 | this.products.increment( 65 | { id: product.id }, 66 | "quantity", 67 | product.quantity 68 | ); 69 | }); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /server/services/gateway/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@commerce/gateway", 3 | "private": true, 4 | "version": "0.0.1", 5 | "description": "", 6 | "author": "", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "rimraf dist && tsc", 10 | "format": "prettier --write \"src/**/*.ts\"", 11 | "start": "ts-node -r tsconfig-paths/register src/main.ts", 12 | "start:dev": "tsc-watch --onSuccess \"node dist/main.js\"", 13 | "start:debug": "tsc-watch -p tsconfig.build.json --onSuccess \"node --inspect-brk dist/main.js\"", 14 | "start:prod": "node dist/main.js", 15 | "lint": "tslint -p tsconfig.json -c tslint.json", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@commerce/shared": "1.0.0", 24 | "@nestjs/common": "^6.0.0", 25 | "@nestjs/core": "^6.0.0", 26 | "@nestjs/graphql": "^6.4.2", 27 | "@nestjs/microservices": "^6.5.3", 28 | "@nestjs/platform-express": "^6.0.0", 29 | "@nestjs/platform-fastify": "^6.6.7", 30 | "@nestjs/typeorm": "^6.1.3", 31 | "@types/jsonwebtoken": "^8.3.3", 32 | "apollo-server-express": "^2.14.2", 33 | "apollo-server-fastify": "^2.14.2", 34 | "class-transformer": "^0.3.1", 35 | "class-validator": "^0.10.0", 36 | "dataloader": "^1.4.0", 37 | "dotenv": "^8.1.0", 38 | "fastify-compress": "^0.11.0", 39 | "fastify-rate-limit": "^2.2.0", 40 | "graphql": "^14.5.3", 41 | "graphql-tools": "^4.0.5", 42 | "helmet": "^3.21.0", 43 | "hpp": "^0.2.2", 44 | "ioredis": "^4.14.0", 45 | "jsonwebtoken": "^8.5.1", 46 | "mongodb": "^3.3.0", 47 | "reflect-metadata": "^0.1.12", 48 | "rimraf": "^2.6.2", 49 | "rxjs": "^6.3.3", 50 | "typeorm": "^0.2.25", 51 | "x-xss-protection": "^1.3.0" 52 | }, 53 | "devDependencies": { 54 | "@nestjs/testing": "^6.0.0", 55 | "@types/express": "4.16.1", 56 | "@types/ioredis": "^4.0.15", 57 | "@types/jest": "24.0.11", 58 | "@types/node": "11.13.4", 59 | "@types/supertest": "2.0.7", 60 | "jest": "24.7.1", 61 | "prettier": "1.17.0", 62 | "supertest": "4.0.2", 63 | "ts-jest": "24.0.2", 64 | "ts-node": "8.1.0", 65 | "tsc-watch": "2.2.1", 66 | "tsconfig-paths": "3.8.0", 67 | "tslint": "5.16.0", 68 | "typescript": "3.4.3" 69 | }, 70 | "jest": { 71 | "moduleFileExtensions": [ 72 | "js", 73 | "json", 74 | "ts" 75 | ], 76 | "rootDir": "src", 77 | "testRegex": ".spec.ts$", 78 | "transform": { 79 | "^.+\\.(t|j)s$": "ts-jest" 80 | }, 81 | "coverageDirectory": "../coverage", 82 | "testEnvironment": "node" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /server/services/users/src/users/user.service.ts: -------------------------------------------------------------------------------- 1 | import { InjectRepository } from "@nestjs/typeorm"; 2 | import { Injectable, NotFoundException } from "@nestjs/common"; 3 | import { LoginUser, RegisterUser, UserDTO } from "@commerce/shared"; 4 | import { Repository } from "typeorm"; 5 | import { UserEntity as User } from "./user.entity"; 6 | import { compareSync } from "bcryptjs"; 7 | import { RpcException } from "@nestjs/microservices"; 8 | 9 | @Injectable() 10 | export class UserService { 11 | constructor( 12 | @InjectRepository(User) 13 | private readonly users: Repository 14 | ) {} 15 | updateToCustomer(id, gateway_customer_id) { 16 | return this.users.update(id, { 17 | gateway_customer_id 18 | }); 19 | } 20 | findById(id: string) { 21 | return this.users.findOneOrFail(id); 22 | } 23 | fetchUsersByIds(ids: Array): Promise { 24 | return this.users.findByIds(ids); 25 | } 26 | async me({ id }: any): Promise { 27 | const user = await this.users.findOneOrFail(id, { 28 | relations: ["address"] 29 | }); 30 | return user.toResponseObject(false); 31 | } 32 | async get(page: number = 1): Promise { 33 | const options = { 34 | relations: ["address"], 35 | skip: 25 * (page - 1), 36 | take: 25 37 | }; 38 | return this.users.find(options); 39 | } 40 | async login({ email, password }: LoginUser): Promise { 41 | const user = await this.users.findOneOrFail({ 42 | where: { email } 43 | }); 44 | if (!compareSync(password, user.password)) { 45 | throw new RpcException( 46 | new NotFoundException("Invalid Credentials...") 47 | ); 48 | } 49 | return user.toResponseObject(); 50 | } 51 | async register({ 52 | email, 53 | password, 54 | password_confirmation, 55 | seller, 56 | name 57 | }: RegisterUser): Promise { 58 | if (password != password_confirmation) { 59 | throw new RpcException( 60 | new NotFoundException( 61 | "Password and password_confirmation should match" 62 | ) 63 | ); 64 | } 65 | 66 | const count = await this.users.count({ 67 | where: { 68 | email 69 | } 70 | }); 71 | if (count) { 72 | throw new RpcException( 73 | new NotFoundException( 74 | "email exists, please pick up another one." 75 | ) 76 | ); 77 | } 78 | let user = await this.users.create({ 79 | name, 80 | seller, 81 | email, 82 | password 83 | }); 84 | user = await this.users.save(user); 85 | return user.toResponseObject(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /server/services/gateway/src/payments/payment.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Client, ClientProxy, Transport } from "@nestjs/microservices"; 2 | import { ProductDTO, UserDTO, config } from "@commerce/shared"; 3 | import { 4 | Query, 5 | Resolver, 6 | Context, 7 | Mutation, 8 | Args, 9 | Parent 10 | } from "@nestjs/graphql"; 11 | import { UseGuards } from "@nestjs/common"; 12 | 13 | import { AuthGuard } from "../middlewares/auth.guard"; 14 | import { CreatePaymentCard } from "./create-payment-card.validation"; 15 | import { PaymentCardService } from "./payment.service"; 16 | import { UserDataLoader } from "../loaders/user.loader"; 17 | import { PaymentCardDTO } from "@commerce/shared"; 18 | @Resolver("PaymentCard") 19 | export class PaymentCardResolver { 20 | @Client({ 21 | transport: Transport.REDIS, 22 | options: { 23 | url: `redis://${config.REDIS_URL}:${config.REDIS_PORT}` 24 | } 25 | }) 26 | private client: ClientProxy; 27 | 28 | constructor(private readonly paymentCardsService: PaymentCardService) {} 29 | @Query() 30 | @UseGuards(new AuthGuard()) 31 | async indexUserPaymentCards(@Context("user") user: any) { 32 | return this.paymentCardsService.get(user.id); 33 | } 34 | @Query() 35 | @UseGuards(new AuthGuard()) 36 | async showPaymentCard(@Args("id") id: string, @Context("user") user: any) { 37 | return this.paymentCardsService.show(id, user.id); 38 | } 39 | @Mutation() 40 | @UseGuards(new AuthGuard()) 41 | async deletePaymentCard( 42 | @Args("id") id: string, 43 | @Context("user") user: any 44 | ) { 45 | return this.paymentCardsService.destroy(id, user.id); 46 | } 47 | @Mutation() 48 | @UseGuards(new AuthGuard()) 49 | async createPaymentCard( 50 | @Args("data") data: CreatePaymentCard, 51 | @Context("user") user: any 52 | ) { 53 | user = await this.client 54 | .send("current-loggedin-user", user.id) 55 | .toPromise(); 56 | return this.paymentCardsService.store(data, user); 57 | } 58 | @Mutation() 59 | @UseGuards(new AuthGuard()) 60 | async createChargeForUser( 61 | @Args("orderId") orderId: string, 62 | @Context("user") user: any 63 | ) { 64 | user = await this.client 65 | .send("current-loggedin-user", user.id) 66 | .toPromise(); 67 | let order = await this.client 68 | .send("show-user-order", { 69 | id: orderId, 70 | user_id: user.id 71 | }) 72 | .toPromise(); 73 | const charge = await this.paymentCardsService.charge(order, user); 74 | 75 | this.client 76 | .emit("order_charged", { 77 | id: orderId, 78 | status: charge.status 79 | }) 80 | .subscribe(() => {}); 81 | order.status = charge.status; 82 | order.user = user; 83 | return order; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /server/services/gateway/src/schemas/graphql.d.ts: -------------------------------------------------------------------------------- 1 | 2 | /** ------------------------------------------------------ 3 | * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) 4 | * ------------------------------------------------------- 5 | */ 6 | 7 | /* tslint:disable */ 8 | export interface CreatePaymentCard { 9 | token_id: string; 10 | last_four: string; 11 | provider_id: string; 12 | brand: string; 13 | default: boolean; 14 | } 15 | 16 | export interface CreateProduct { 17 | title: string; 18 | description: string; 19 | image: string; 20 | price: number; 21 | } 22 | 23 | export interface LoginUser { 24 | email: string; 25 | password: string; 26 | } 27 | 28 | export interface ProductInput { 29 | quantity: number; 30 | id: string; 31 | } 32 | 33 | export interface RegisterUser { 34 | email: string; 35 | password: string; 36 | password_confirmation: string; 37 | name: string; 38 | seller: boolean; 39 | } 40 | 41 | export interface UUID { 42 | id: string; 43 | } 44 | 45 | export interface Address { 46 | address_1: string; 47 | address_2: string; 48 | city: string; 49 | state: string; 50 | country: string; 51 | zip: number; 52 | } 53 | 54 | export interface AuthToken { 55 | id: string; 56 | name: string; 57 | token: string; 58 | } 59 | 60 | export interface IMutation { 61 | createOrder(products: ProductInput[]): Order | Promise; 62 | deleteOrder(order: UUID): Order | Promise; 63 | createPaymentCard(data: CreatePaymentCard): PaymentCard | Promise; 64 | createChargeForUser(orderId: string): Order | Promise; 65 | deletePaymentCard(id: string): PaymentCard | Promise; 66 | createProduct(data: CreateProduct): Product | Promise; 67 | updateProduct(data: CreateProduct, id: string): Product | Promise; 68 | deleteProduct(id: string): Product | Promise; 69 | login(data: LoginUser): AuthToken | Promise; 70 | register(data: RegisterUser): User | Promise; 71 | } 72 | 73 | export interface Order { 74 | id: string; 75 | user: User; 76 | total_price: number; 77 | products: ProductWithQuantity[]; 78 | } 79 | 80 | export interface PaymentCard { 81 | id: string; 82 | user: User; 83 | last_four: string; 84 | brand: string; 85 | default: boolean; 86 | provider_id: string; 87 | created_at: DateTime; 88 | } 89 | 90 | export interface Product { 91 | id: string; 92 | user: User; 93 | title: string; 94 | description: string; 95 | image: string; 96 | price: number; 97 | created_at: DateTime; 98 | } 99 | 100 | export interface ProductWithQuantity { 101 | product: Product; 102 | quantity_ordered: number; 103 | } 104 | 105 | export interface IQuery { 106 | orders(): Order[] | Promise; 107 | showOrder(id: string): Order | Promise; 108 | showPaymentCard(id: string): PaymentCard | Promise; 109 | indexUserPaymentCards(): PaymentCard[] | Promise; 110 | products(): Product[] | Promise; 111 | showProduct(id: string): Product | Promise; 112 | users(): User[] | Promise; 113 | me(): User | Promise; 114 | } 115 | 116 | export interface User { 117 | id: string; 118 | name: string; 119 | seller: boolean; 120 | address?: Address; 121 | created_at: DateTime; 122 | } 123 | 124 | export type DateTime = any; 125 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master 6 | [travis-url]: https://travis-ci.org/nestjs/nest 7 | [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux 8 | [linux-url]: https://travis-ci.org/nestjs/nest 9 | 10 |

A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.

11 |

12 | NPM Version 13 | Package License 14 | NPM Downloads 15 | Travis 16 | Linux 17 | Coverage 18 | Gitter 19 | Backers on Open Collective 20 | Sponsors on Open Collective 21 | 22 | 23 |

24 | 26 | 27 | ## Description 28 | 29 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 30 | 31 | ## Installation 32 | 33 | ```bash 34 | $ npm install 35 | ``` 36 | 37 | ## Running the app 38 | 39 | ```bash 40 | # development 41 | $ npm run start 42 | 43 | # watch mode 44 | $ npm run start:dev 45 | 46 | # production mode 47 | $ npm run start:prod 48 | ``` 49 | 50 | ## Test 51 | 52 | ```bash 53 | # unit tests 54 | $ npm run test 55 | 56 | # e2e tests 57 | $ npm run test:e2e 58 | 59 | # test coverage 60 | $ npm run test:cov 61 | ``` 62 | 63 | ## Support 64 | 65 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 66 | 67 | ## Stay in touch 68 | 69 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 70 | - Website - [https://nestjs.com](https://nestjs.com/) 71 | - Twitter - [@nestframework](https://twitter.com/nestframework) 72 | 73 | ## License 74 | 75 | Nest is [MIT licensed](LICENSE). 76 | -------------------------------------------------------------------------------- /server/services/gateway/src/products/product.service.ts: -------------------------------------------------------------------------------- 1 | import { Client, ClientProxy, Transport } from "@nestjs/microservices"; 2 | import { Injectable } from "@nestjs/common"; 3 | import { UserDTO, ProductDTO } from "@commerce/shared"; 4 | 5 | import { config } from "@commerce/shared"; 6 | import { redis, redisProductsKey } from "../utils/redis"; 7 | import { CreateProduct } from "@commerce/shared"; 8 | @Injectable() 9 | export class ProductService { 10 | @Client({ 11 | transport: Transport.REDIS, 12 | options: { 13 | url: `redis://${config.REDIS_URL}:${config.REDIS_PORT}` 14 | } 15 | }) 16 | private client: ClientProxy; 17 | async show(id: string): Promise { 18 | return new Promise((resolve, reject) => { 19 | this.client 20 | .send("show-product", id) 21 | .subscribe(product => resolve(product), error => reject(error)); 22 | }); 23 | } 24 | async get(): Promise { 25 | return new Promise((resolve, reject) => { 26 | // get products through cache. 27 | redis.get(redisProductsKey, (err, products) => { 28 | // if products don't persist, retrieve them, and store in redis. 29 | if (!products) { 30 | this.client.send("products", []).subscribe( 31 | products => { 32 | redis.set( 33 | redisProductsKey, 34 | JSON.stringify(products), 35 | "EX", 36 | 60 * 60 * 30 // 30 mins until expiration 37 | ); 38 | return resolve(products); 39 | }, 40 | error => reject(error) 41 | ); 42 | } 43 | // return the parsed products from cache. 44 | resolve(JSON.parse(products)); 45 | }); 46 | }); 47 | } 48 | store(data: CreateProduct, id: string): Promise { 49 | // TODO: handle the failure create produc 50 | return new Promise((resolve, reject) => { 51 | this.client 52 | .send("create-product", { 53 | ...data, 54 | user_id: id 55 | }) 56 | .subscribe( 57 | product => { 58 | redis.del(redisProductsKey); 59 | return resolve(product); 60 | }, 61 | error => reject(error) 62 | ); 63 | }); 64 | } 65 | update( 66 | data: CreateProduct, 67 | productId: string, 68 | id: string 69 | ): Promise { 70 | return new Promise((resolve, reject) => { 71 | this.client 72 | .send("update-product", { 73 | ...data, 74 | id: productId, 75 | user_id: id 76 | }) 77 | .subscribe( 78 | product => { 79 | redis.del(redisProductsKey); 80 | return resolve(product); 81 | }, 82 | error => reject(error) 83 | ); 84 | }); 85 | } 86 | async fetchProductsByIds(ids: string[]) { 87 | return this.client 88 | .send("fetch-products-by-ids", ids) 89 | .toPromise(); 90 | } 91 | destroy(productId: string, id: string) { 92 | return new Promise((resolve, reject) => { 93 | this.client 94 | .send("delete-product", { 95 | id: productId, 96 | user_id: id 97 | }) 98 | .subscribe( 99 | product => { 100 | redis.del(redisProductsKey); 101 | return resolve(product); 102 | }, 103 | error => reject(error) 104 | ); 105 | }); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /server/services/gateway/README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master 6 | [travis-url]: https://travis-ci.org/nestjs/nest 7 | [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux 8 | [linux-url]: https://travis-ci.org/nestjs/nest 9 | 10 |

A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.

11 |

12 | NPM Version 13 | Package License 14 | NPM Downloads 15 | Travis 16 | Linux 17 | Coverage 18 | Gitter 19 | Backers on Open Collective 20 | Sponsors on Open Collective 21 | 22 | 23 |

24 | 26 | 27 | ## Description 28 | 29 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 30 | 31 | ## Installation 32 | 33 | ```bash 34 | $ npm install 35 | ``` 36 | 37 | ## Running the app 38 | 39 | ```bash 40 | # development 41 | $ npm run start 42 | 43 | # watch mode 44 | $ npm run start:dev 45 | 46 | # production mode 47 | $ npm run start:prod 48 | ``` 49 | 50 | ## Test 51 | 52 | ```bash 53 | # unit tests 54 | $ npm run test 55 | 56 | # e2e tests 57 | $ npm run test:e2e 58 | 59 | # test coverage 60 | $ npm run test:cov 61 | ``` 62 | 63 | ## Support 64 | 65 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 66 | 67 | ## Stay in touch 68 | 69 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 70 | - Website - [https://nestjs.com](https://nestjs.com/) 71 | - Twitter - [@nestframework](https://twitter.com/nestframework) 72 | 73 | ## License 74 | 75 | Nest is [MIT licensed](LICENSE). 76 | -------------------------------------------------------------------------------- /server/services/orders/README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master 6 | [travis-url]: https://travis-ci.org/nestjs/nest 7 | [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux 8 | [linux-url]: https://travis-ci.org/nestjs/nest 9 | 10 |

A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.

11 |

12 | NPM Version 13 | Package License 14 | NPM Downloads 15 | Travis 16 | Linux 17 | Coverage 18 | Gitter 19 | Backers on Open Collective 20 | Sponsors on Open Collective 21 | 22 | 23 |

24 | 26 | 27 | ## Description 28 | 29 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 30 | 31 | ## Installation 32 | 33 | ```bash 34 | $ npm install 35 | ``` 36 | 37 | ## Running the app 38 | 39 | ```bash 40 | # development 41 | $ npm run start 42 | 43 | # watch mode 44 | $ npm run start:dev 45 | 46 | # production mode 47 | $ npm run start:prod 48 | ``` 49 | 50 | ## Test 51 | 52 | ```bash 53 | # unit tests 54 | $ npm run test 55 | 56 | # e2e tests 57 | $ npm run test:e2e 58 | 59 | # test coverage 60 | $ npm run test:cov 61 | ``` 62 | 63 | ## Support 64 | 65 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 66 | 67 | ## Stay in touch 68 | 69 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 70 | - Website - [https://nestjs.com](https://nestjs.com/) 71 | - Twitter - [@nestframework](https://twitter.com/nestframework) 72 | 73 | ## License 74 | 75 | Nest is [MIT licensed](LICENSE). 76 | -------------------------------------------------------------------------------- /server/services/users/README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master 6 | [travis-url]: https://travis-ci.org/nestjs/nest 7 | [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux 8 | [linux-url]: https://travis-ci.org/nestjs/nest 9 | 10 |

A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.

11 |

12 | NPM Version 13 | Package License 14 | NPM Downloads 15 | Travis 16 | Linux 17 | Coverage 18 | Gitter 19 | Backers on Open Collective 20 | Sponsors on Open Collective 21 | 22 | 23 |

24 | 26 | 27 | ## Description 28 | 29 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 30 | 31 | ## Installation 32 | 33 | ```bash 34 | $ npm install 35 | ``` 36 | 37 | ## Running the app 38 | 39 | ```bash 40 | # development 41 | $ npm run start 42 | 43 | # watch mode 44 | $ npm run start:dev 45 | 46 | # production mode 47 | $ npm run start:prod 48 | ``` 49 | 50 | ## Test 51 | 52 | ```bash 53 | # unit tests 54 | $ npm run test 55 | 56 | # e2e tests 57 | $ npm run test:e2e 58 | 59 | # test coverage 60 | $ npm run test:cov 61 | ``` 62 | 63 | ## Support 64 | 65 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 66 | 67 | ## Stay in touch 68 | 69 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 70 | - Website - [https://nestjs.com](https://nestjs.com/) 71 | - Twitter - [@nestframework](https://twitter.com/nestframework) 72 | 73 | ## License 74 | 75 | Nest is [MIT licensed](LICENSE). 76 | -------------------------------------------------------------------------------- /server/services/payments/README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master 6 | [travis-url]: https://travis-ci.org/nestjs/nest 7 | [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux 8 | [linux-url]: https://travis-ci.org/nestjs/nest 9 | 10 |

A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.

11 |

12 | NPM Version 13 | Package License 14 | NPM Downloads 15 | Travis 16 | Linux 17 | Coverage 18 | Gitter 19 | Backers on Open Collective 20 | Sponsors on Open Collective 21 | 22 | 23 |

24 | 26 | 27 | ## Description 28 | 29 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 30 | 31 | ## Installation 32 | 33 | ```bash 34 | $ npm install 35 | ``` 36 | 37 | ## Running the app 38 | 39 | ```bash 40 | # development 41 | $ npm run start 42 | 43 | # watch mode 44 | $ npm run start:dev 45 | 46 | # production mode 47 | $ npm run start:prod 48 | ``` 49 | 50 | ## Test 51 | 52 | ```bash 53 | # unit tests 54 | $ npm run test 55 | 56 | # e2e tests 57 | $ npm run test:e2e 58 | 59 | # test coverage 60 | $ npm run test:cov 61 | ``` 62 | 63 | ## Support 64 | 65 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 66 | 67 | ## Stay in touch 68 | 69 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 70 | - Website - [https://nestjs.com](https://nestjs.com/) 71 | - Twitter - [@nestframework](https://twitter.com/nestframework) 72 | 73 | ## License 74 | 75 | Nest is [MIT licensed](LICENSE). 76 | -------------------------------------------------------------------------------- /server/services/products/README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master 6 | [travis-url]: https://travis-ci.org/nestjs/nest 7 | [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux 8 | [linux-url]: https://travis-ci.org/nestjs/nest 9 | 10 |

A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.

11 |

12 | NPM Version 13 | Package License 14 | NPM Downloads 15 | Travis 16 | Linux 17 | Coverage 18 | Gitter 19 | Backers on Open Collective 20 | Sponsors on Open Collective 21 | 22 | 23 |

24 | 26 | 27 | ## Description 28 | 29 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 30 | 31 | ## Installation 32 | 33 | ```bash 34 | $ npm install 35 | ``` 36 | 37 | ## Running the app 38 | 39 | ```bash 40 | # development 41 | $ npm run start 42 | 43 | # watch mode 44 | $ npm run start:dev 45 | 46 | # production mode 47 | $ npm run start:prod 48 | ``` 49 | 50 | ## Test 51 | 52 | ```bash 53 | # unit tests 54 | $ npm run test 55 | 56 | # e2e tests 57 | $ npm run test:e2e 58 | 59 | # test coverage 60 | $ npm run test:cov 61 | ``` 62 | 63 | ## Support 64 | 65 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 66 | 67 | ## Stay in touch 68 | 69 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 70 | - Website - [https://nestjs.com](https://nestjs.com/) 71 | - Twitter - [@nestframework](https://twitter.com/nestframework) 72 | 73 | ## License 74 | 75 | Nest is [MIT licensed](LICENSE). 76 | -------------------------------------------------------------------------------- /server/services/gateway/src/orders/order.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Client, ClientProxy, Transport } from "@nestjs/microservices"; 2 | import { 3 | Resolver, 4 | Context, 5 | Mutation, 6 | Args, 7 | Query, 8 | ResolveProperty, 9 | Parent 10 | } from "@nestjs/graphql"; 11 | import { UseGuards } from "@nestjs/common"; 12 | import { config, ProductDTO, OrderDTO, UserDTO } from "@commerce/shared"; 13 | 14 | import { AuthGuard } from "../middlewares/auth.guard"; 15 | import { CreateOrder } from "./create-order.validation"; 16 | import { OrderProductDataLoader } from "../loaders/order-product.loader"; 17 | import { OrderService } from "./order.service"; 18 | import { UUID } from "../shared/validation/uuid.validation"; 19 | import { UserDataLoader } from "../loaders/user.loader"; 20 | 21 | @Resolver("Order") 22 | export class OrderResolver { 23 | @Client({ 24 | transport: Transport.REDIS, 25 | options: { 26 | url: `redis://${config.REDIS_URL}:${config.REDIS_PORT}` 27 | } 28 | }) 29 | private client: ClientProxy; 30 | 31 | constructor( 32 | private readonly orderService: OrderService, 33 | private readonly usersDataLoader: UserDataLoader, 34 | private readonly orderProductLoader: OrderProductDataLoader 35 | ) {} 36 | @ResolveProperty("user", () => UserDTO) 37 | async user(@Parent() order: OrderDTO): Promise { 38 | return this.usersDataLoader.load(order.user_id); 39 | } 40 | @ResolveProperty("products", () => ProductDTO) 41 | async products(@Parent() order): Promise { 42 | return this.orderProductLoader.loadMany(order.products); 43 | } 44 | @Query() 45 | @UseGuards(new AuthGuard()) 46 | orders(@Context("user") user: any): Promise { 47 | return this.orderService.indexOrdersByUser(user.id); 48 | } 49 | @Mutation() 50 | @UseGuards(new AuthGuard()) 51 | deleteOrder(@Args("order") { id }: UUID, @Context("user") user: any) { 52 | return this.orderService.destroyUserOrder(id, user.id); 53 | } 54 | @Mutation() 55 | @UseGuards(new AuthGuard()) 56 | createOrder( 57 | @Args("products") products: CreateOrder[], 58 | @Context("user") user: any 59 | ): Promise { 60 | return new Promise((resolve, reject) => { 61 | // fetch products user is trying to purchase to check on the quantity. 62 | this.client 63 | .send( 64 | "fetch-products-by-ids", 65 | products.map(product => product.id) 66 | ) 67 | .subscribe( 68 | async fetchedProducts => { 69 | const filteredProducts = products.filter(product => { 70 | const p = fetchedProducts.find( 71 | p => p.id === product.id 72 | ); 73 | return p.quantity >= product.quantity; 74 | }); 75 | // there is something wrong with the quantity of passed products. 76 | if (filteredProducts.length != products.length) { 77 | return reject( 78 | "Products are out of stock at the moment, try with lower stock." 79 | ); 80 | } 81 | return resolve( 82 | await this.orderService.store( 83 | products, 84 | user.id, 85 | fetchedProducts 86 | ) 87 | ); 88 | }, 89 | error => reject(error) 90 | ); 91 | }); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /server/services/payments/src/payments/payment.service.ts: -------------------------------------------------------------------------------- 1 | import { InjectRepository } from "@nestjs/typeorm"; 2 | import { Injectable, NotFoundException } from "@nestjs/common"; 3 | import { Repository } from "typeorm"; 4 | import { 5 | RpcException, 6 | Client, 7 | Transport, 8 | ClientProxy 9 | } from "@nestjs/microservices"; 10 | import { PaymentCardDTO, config } from "@commerce/shared"; 11 | 12 | import { PaymentEntity } from "./payment.entity"; 13 | import { stripe } from "../utils/stripe"; 14 | 15 | @Injectable() 16 | export class PaymentService { 17 | @Client({ 18 | transport: Transport.REDIS, 19 | options: { 20 | url: `redis://${config.REDIS_URL}:${config.REDIS_PORT}` 21 | } 22 | }) 23 | private client: ClientProxy; 24 | 25 | constructor( 26 | @InjectRepository(PaymentEntity) 27 | private readonly payments: Repository 28 | ) {} 29 | async findByUserId(user_id) { 30 | return this.payments.findOneOrFail({ where: { user_id } }); 31 | } 32 | async store( 33 | data: { 34 | token_id: string; 35 | last_four: string; 36 | provider_id: string; 37 | brand: string; 38 | default: boolean; 39 | }, 40 | user 41 | ): Promise { 42 | // if user sets the payment card we are about to store to default, set others to false. 43 | if (data.default) { 44 | await this.payments.update( 45 | { 46 | user_id: user.id 47 | }, 48 | { default: false } 49 | ); 50 | } 51 | const paymentData = { 52 | last_four: data.last_four, 53 | provider_id: data.provider_id, 54 | default: data.default, 55 | brad: data.brand, 56 | user_id: user.id 57 | }; 58 | // store the payment method to our database alongside with token. 59 | const payment = await this.payments.save(paymentData); 60 | 61 | // check if user has gateway_customer_id, if user doesn't have one, create one using stripe. 62 | if (!user.gateway_customer_id) { 63 | let address = { 64 | line1: user.address.address_1, 65 | line2: user.address.address_2, 66 | city: user.address.city, 67 | state: user.address.state, 68 | country: user.address.country, 69 | postal_code: user.address.zip 70 | }; 71 | // fetch user addresses, email, provider_id, name and create a customer. 72 | const customer = await stripe.customers.create({ 73 | email: user.email, 74 | name: user.name, 75 | source: data.token_id, 76 | address 77 | }); 78 | // attach customer id to the user.gateway_customer_id 79 | this.client 80 | .emit("customer_created", { 81 | user_id: user.id, 82 | gateway_customer_id: customer.id 83 | }) 84 | .subscribe(() => {}); 85 | } 86 | delete payment.user_id; 87 | // @ts-ignore 88 | payment.user = user; 89 | return payment; 90 | } 91 | async get(user_id: string): Promise { 92 | return this.payments.find({ user_id }); 93 | } 94 | async show(id: string, user_id: string): Promise { 95 | return this.payments.findOneOrFail({ id, user_id }); 96 | } 97 | async destroy(id: string, user_id: string): Promise { 98 | const paymentCard = await this.payments.findOneOrFail({ id }); 99 | if (paymentCard.user_id === user_id) { 100 | await this.payments.delete({ id }); 101 | return paymentCard; 102 | } 103 | throw new RpcException( 104 | new NotFoundException("You cannot update what you don't own...") 105 | ); 106 | } 107 | } 108 | --------------------------------------------------------------------------------