├── .nvmrc ├── packages ├── postgresql │ ├── .prettierrc.json │ ├── .lintstagedrc.json │ ├── Makefile │ ├── src │ │ ├── utils │ │ │ ├── types.ts │ │ │ └── transformers │ │ │ │ └── int8-multiplier.transformer.ts │ │ ├── db │ │ │ └── migrations │ │ │ │ ├── 1660937199912-siteadmin.ts │ │ │ │ ├── 1659663840662-decimalQuantity.ts │ │ │ │ ├── 1660667316904-numbuysdecimal.ts │ │ │ │ ├── 1659722004232-isactiveposition.ts │ │ │ │ ├── 1658173270596-fixquantity-position.ts │ │ │ │ ├── 1657899007309-gamestatusandpurchase.ts │ │ │ │ ├── 1658518500917-priceChange.ts │ │ │ │ └── 1658391520030-kickeduser.ts │ │ └── entities │ │ │ ├── game │ │ │ └── kicked-user.entity.ts │ │ │ ├── stock │ │ │ ├── stock-category-mapping.entity.ts │ │ │ └── stock-category.entity.ts │ │ │ └── user │ │ │ ├── user.entity.ts │ │ │ └── social-app.entity.ts │ ├── .config.env │ ├── .gitignore │ ├── bin │ │ └── dev │ │ │ └── restart.sh │ ├── .prettierignore │ ├── .eslintrc.json │ ├── tsconfig.json │ ├── package.json │ └── ormconfig.ts ├── scripts │ ├── .prettierrc.json │ ├── .lintstagedrc.json │ ├── .gitignore │ ├── .prettierignore │ ├── .eslintrc.json │ ├── tsconfig.json │ ├── package.json │ ├── src │ │ ├── actions │ │ │ ├── ci-test.ts │ │ │ ├── utils │ │ │ │ └── from-dir.ts │ │ │ ├── pre-deploy-frontend.ts │ │ │ ├── utils.ts │ │ │ └── pre-deploy.ts │ │ └── index.ts │ └── README.md ├── real-time-collector │ ├── .prettierrc.json │ ├── deploy │ │ ├── start.sh │ │ ├── appspec-real-time-collector-dev.yaml │ │ ├── appspec-real-time-collector-prod.yaml │ │ └── README.md │ ├── .lintstagedrc.json │ ├── tsconfig.build.json │ ├── nest-cli.json │ ├── src │ │ ├── app │ │ │ ├── app.service.ts │ │ │ ├── app.controller.ts │ │ │ ├── minutes │ │ │ │ ├── crypto │ │ │ │ │ ├── crypto.service.ts │ │ │ │ │ └── dto │ │ │ │ │ │ └── minute-bar-data.dto.ts │ │ │ │ ├── minutes.module.ts │ │ │ │ └── stock │ │ │ │ │ └── dto │ │ │ │ │ └── minute-bar-data.dto.ts │ │ │ └── app.module.ts │ │ ├── testing │ │ │ ├── jest-e2e.json │ │ │ ├── setup.testconfig.ts │ │ │ └── factory │ │ │ │ └── external │ │ │ │ └── stock-minute-bar.factory.ts │ │ ├── utils │ │ │ ├── types.ts │ │ │ └── typeorm │ │ │ │ └── map-stream.ts │ │ ├── system │ │ │ ├── ConfigurationManager.ts │ │ │ ├── exceptions │ │ │ │ ├── BloomExceptionType.ts │ │ │ │ ├── BloomExceptionFilter.ts │ │ │ │ └── BloomException.ts │ │ │ ├── logger.middleware.ts │ │ │ └── OrmConfig.ts │ │ ├── config.ts │ │ └── main.ts │ ├── .env │ ├── .gitignore │ ├── Dockerfile │ ├── .prettierignore │ ├── README.md │ ├── tsconfig.json │ └── .eslintrc.json ├── api │ ├── deploy │ │ ├── start.sh │ │ ├── appspec-api-dev.yaml │ │ ├── appspec-api-prod.yaml │ │ ├── appspec-api-staging.yaml │ │ └── README.md │ ├── .lintstagedrc.json │ ├── tsconfig.build.json │ ├── .dockerignore │ ├── nest-cli.json │ ├── src │ │ ├── app │ │ │ ├── auth │ │ │ │ ├── dto │ │ │ │ │ ├── providers.dto.ts │ │ │ │ │ ├── jwt-pair.dto.ts │ │ │ │ │ ├── email-checker.dto.ts │ │ │ │ │ ├── verify-oauth.dto.ts │ │ │ │ │ ├── login.dto.ts │ │ │ │ │ ├── forgot-password.dto.ts │ │ │ │ │ └── create-user.dto.ts │ │ │ │ └── auth.module.ts │ │ │ ├── admin │ │ │ │ ├── dto │ │ │ │ │ ├── login-as.dto.ts │ │ │ │ │ └── login-as-body.dto.ts │ │ │ │ ├── admin.module.ts │ │ │ │ ├── admin.controller.ts │ │ │ │ └── admin.service.ts │ │ │ ├── app.service.ts │ │ │ ├── game │ │ │ │ ├── dto │ │ │ │ │ ├── holdings-value.dto.ts │ │ │ │ │ ├── latest.enum.ts │ │ │ │ │ ├── holdings-change.dto.ts │ │ │ │ │ ├── player-names.dto.ts │ │ │ │ │ ├── add-player.dto.ts │ │ │ │ │ ├── historical-agg-position.dto.ts │ │ │ │ │ ├── popular-asset.dto.ts │ │ │ │ │ ├── get-user-games-query.dto.ts │ │ │ │ │ ├── order-history-query.dto.ts │ │ │ │ │ ├── historical-position.dto.ts │ │ │ │ │ ├── player.dto.ts │ │ │ │ │ └── create-game.dto.ts │ │ │ │ └── core-game.controller.ts │ │ │ ├── categories │ │ │ │ ├── dto │ │ │ │ │ ├── category-asset.dto.ts │ │ │ │ │ ├── category-assets.dto.ts │ │ │ │ │ └── category.dto.ts │ │ │ │ ├── categories.module.ts │ │ │ │ └── categories.controller.ts │ │ │ ├── market │ │ │ │ ├── dto │ │ │ │ │ ├── alpaca-trading-day.dto.ts │ │ │ │ │ ├── get-latest-deposit-date.dto.ts │ │ │ │ │ ├── fetch-last-market-date.dto.ts │ │ │ │ │ └── get-trading-days.dto.ts │ │ │ │ └── market.module.ts │ │ │ ├── assets │ │ │ │ ├── dto │ │ │ │ │ ├── related-asset.dto.ts │ │ │ │ │ ├── asset-basic-info.dto.ts │ │ │ │ │ ├── get-analyst-ratings.dto.ts │ │ │ │ │ ├── Assets.ts │ │ │ │ │ ├── get-price-by-date.dto.ts │ │ │ │ │ ├── gainer-loser.dto.ts │ │ │ │ │ ├── get-quote.dto.ts │ │ │ │ │ ├── market-data-with-latest-quotes.dto.ts │ │ │ │ │ └── historical-bars.dto.ts │ │ │ │ ├── regenerate │ │ │ │ │ ├── dto │ │ │ │ │ │ ├── categories-json.dto.ts │ │ │ │ │ │ └── parent-app-server-stock-json.dto.ts │ │ │ │ │ └── regenerate.controller.ts │ │ │ │ ├── models │ │ │ │ │ └── Holidays.ts │ │ │ │ └── assets.module.ts │ │ │ ├── orders │ │ │ │ └── dto │ │ │ │ │ ├── sell-all-body.dto.ts │ │ │ │ │ ├── transaction.dto.ts │ │ │ │ │ ├── order-history.dto.ts │ │ │ │ │ └── order-history-query.dto.ts │ │ │ ├── news │ │ │ │ ├── dto │ │ │ │ │ ├── array-limit.dto.ts │ │ │ │ │ ├── news-content.dto.ts │ │ │ │ │ └── limit-query.dto.ts │ │ │ │ ├── news.module.ts │ │ │ │ └── news.controller.ts │ │ │ ├── user │ │ │ │ ├── user.module.ts │ │ │ │ ├── dto │ │ │ │ │ ├── full-user-info.dto.ts │ │ │ │ │ └── update-user.dto.ts │ │ │ │ └── user.controller.ts │ │ │ ├── app.controller.ts │ │ │ └── search │ │ │ │ ├── search.module.ts │ │ │ │ ├── dto │ │ │ │ ├── search-query.dto.ts │ │ │ │ └── asset-search.dto.ts │ │ │ │ └── search.controller.ts │ │ ├── clients │ │ │ ├── courier │ │ │ │ └── dto │ │ │ │ │ ├── signup.dto.ts │ │ │ │ │ ├── password-reset.dto.ts │ │ │ │ │ ├── create-game.dto.ts │ │ │ │ │ ├── start-game.dto.ts │ │ │ │ │ └── end-game.dto.ts │ │ │ ├── Alpaca │ │ │ │ └── types.ts │ │ │ ├── StockNews │ │ │ │ └── types.ts │ │ │ └── YHFinance │ │ │ │ └── index.ts │ │ ├── testing │ │ │ ├── jest-e2e.json │ │ │ ├── global.testconfig.ts │ │ │ └── setup.testconfig.ts │ │ ├── utils │ │ │ ├── types.ts │ │ │ ├── validator │ │ │ │ ├── index.ts │ │ │ │ └── is-integer-string.validator.ts │ │ │ ├── strings.ts │ │ │ └── auth.ts │ │ ├── system │ │ │ ├── ConfigurationManager.ts │ │ │ ├── exceptions │ │ │ │ ├── BloomExceptionType.ts │ │ │ │ ├── BloomExceptionFilter.ts │ │ │ │ └── BloomException.ts │ │ │ ├── OrmConfig.ts │ │ │ └── logger.middleware.ts │ │ ├── models │ │ │ └── Holidays.ts │ │ ├── config.ts │ │ └── types │ │ │ └── Request.ts │ ├── Dockerfile │ ├── .gitignore │ ├── .prettierignore │ ├── tsconfig.json │ ├── .eslintrc.json │ └── README.md └── frontend │ ├── .eslintignore │ ├── .lintstagedrc.json │ ├── src │ ├── assets │ │ ├── images │ │ │ ├── bloom.png │ │ │ ├── polka-bg.png │ │ │ ├── leaderboard.png │ │ │ ├── favicons │ │ │ │ ├── favicon.ico │ │ │ │ └── apple-touch-icon.png │ │ │ └── purple-polka-bg.png │ │ └── fonts │ │ │ ├── SF-Pro-Rounded-Bold.ttf │ │ │ ├── SF-Pro-Rounded-Medium.ttf │ │ │ ├── SF-Pro-Rounded-Regular.ttf │ │ │ └── SF-Pro-Rounded-Semibold.ttf │ ├── vite-env.d.ts │ ├── modules │ │ ├── Dashboard │ │ │ ├── OrderHistory │ │ │ │ └── index.tsx │ │ │ ├── Navigation │ │ │ │ ├── index.tsx │ │ │ │ └── utils.tsx │ │ │ ├── MobileHeader │ │ │ │ └── index.tsx │ │ │ ├── SiteAdminHeader │ │ │ │ └── index.tsx │ │ │ ├── IndividualStock │ │ │ │ └── IndividualStockGraph │ │ │ │ │ └── index.tsx │ │ │ ├── Leaderboard │ │ │ │ └── utils.ts │ │ │ └── Portfolio │ │ │ │ └── utils.ts │ │ ├── NotFound │ │ │ └── index.tsx │ │ ├── PasswordReset │ │ │ └── index.tsx │ │ └── GameCreation │ │ │ └── index.tsx │ ├── system │ │ └── Analytics │ │ │ ├── events │ │ │ ├── BrowseEvents.ts │ │ │ ├── OrderEvents.ts │ │ │ ├── IndividualAssetEvents.ts │ │ │ ├── ProfileEvents.ts │ │ │ ├── PortfolioEvents.ts │ │ │ ├── GameEvents.ts │ │ │ └── OnboardingEvents.ts │ │ │ └── index.ts │ ├── services │ │ ├── News │ │ │ ├── types.ts │ │ │ └── index.ts │ │ ├── Search │ │ │ ├── types.ts │ │ │ └── index.ts │ │ ├── Auth │ │ │ └── types.ts │ │ ├── User │ │ │ ├── types.ts │ │ │ └── index.ts │ │ ├── Admin │ │ │ └── index.ts │ │ ├── Categories │ │ │ ├── types.ts │ │ │ └── index.ts │ │ ├── Order │ │ │ └── types.ts │ │ ├── Market │ │ │ └── index.ts │ │ └── index.ts │ ├── common │ │ ├── LoadingScreen │ │ │ └── index.tsx │ │ ├── Graph │ │ │ ├── types.ts │ │ │ ├── PeriodTabs │ │ │ │ └── index.tsx │ │ │ └── utils.ts │ │ ├── PrivateRoute │ │ │ └── index.tsx │ │ ├── MobileGamesToggle │ │ │ └── index.tsx │ │ └── ChatwootWidget │ │ │ └── index.tsx │ ├── main.tsx │ ├── utils │ │ ├── DateTimeUtils.ts │ │ ├── ArrayUtils.ts │ │ ├── MathUtils.ts │ │ ├── StyleUtils.tsx │ │ └── ClipboardUtils.ts │ ├── hooks │ │ ├── useMobile.ts │ │ ├── useDashboardNavigate.ts │ │ └── useCountdown.ts │ ├── components │ │ ├── Badge │ │ │ └── index.tsx │ │ ├── Loader │ │ │ └── index.tsx │ │ └── CountdownTimer │ │ │ └── index.tsx │ └── App.css │ ├── README.md │ ├── .env │ ├── tsconfig.node.json │ ├── postcss.config.js │ ├── vite.config.ts │ ├── .gitignore │ ├── .prettierrc.js │ ├── .prettierignore │ ├── tsconfig.json │ ├── .eslintrc.js │ ├── tailwind.config.js │ └── package.json ├── .husky └── pre-commit ├── CODEOWNERS ├── lerna.json ├── package.json ├── .gitignore └── CONTRIBUTING.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 17.6.0 2 | -------------------------------------------------------------------------------- /packages/postgresql/.prettierrc.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /packages/scripts/.prettierrc.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /packages/real-time-collector/.prettierrc.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /packages/api/deploy/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn start:prod 4 | -------------------------------------------------------------------------------- /packages/frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | tailwind.config.js 3 | -------------------------------------------------------------------------------- /packages/real-time-collector/deploy/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn start:prod 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | yarn pre-commit 5 | -------------------------------------------------------------------------------- /packages/frontend/.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,ts,tsx}": ["prettier --write --list-different"] 3 | } 4 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | @joinbloomapp/smg 2 | 3 | scripts/* @joinbloomapp/devops 4 | .github/workflows/* @joinbloomapp/devops 5 | -------------------------------------------------------------------------------- /packages/api/.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,ts,tsx}": ["prettier --write --list-different", "eslint --fix"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/scripts/.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,ts,tsx}": ["prettier --write --list-different", "eslint --fix"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/postgresql/.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,ts,tsx}": ["prettier --write --list-different", "eslint --fix"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/real-time-collector/.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,ts,tsx}": ["prettier --write --list-different", "eslint --fix"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/api/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "yarn", 3 | "useWorkspaces": true, 4 | "packages": [ 5 | "packages/*" 6 | ], 7 | "version": "0.0.0" 8 | } 9 | -------------------------------------------------------------------------------- /packages/api/.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !./package.json 3 | !./packages/api/package.json 4 | !./packages/postgresql/package.json 5 | !./packages/api/.env 6 | -------------------------------------------------------------------------------- /packages/frontend/src/assets/images/bloom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joinbloomapp/stock-market-game/HEAD/packages/frontend/src/assets/images/bloom.png -------------------------------------------------------------------------------- /packages/frontend/src/assets/images/polka-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joinbloomapp/stock-market-game/HEAD/packages/frontend/src/assets/images/polka-bg.png -------------------------------------------------------------------------------- /packages/real-time-collector/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/frontend/README.md: -------------------------------------------------------------------------------- 1 | # smg 2 | 3 | Stock Market Game Front-End 4 | 5 | 1. Run `yarn` 6 | 2. Run `yarn dev` to run dev app and open in http://localhost:3000 7 | -------------------------------------------------------------------------------- /packages/frontend/src/assets/images/leaderboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joinbloomapp/stock-market-game/HEAD/packages/frontend/src/assets/images/leaderboard.png -------------------------------------------------------------------------------- /packages/frontend/src/assets/images/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joinbloomapp/stock-market-game/HEAD/packages/frontend/src/assets/images/favicons/favicon.ico -------------------------------------------------------------------------------- /packages/frontend/src/assets/images/purple-polka-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joinbloomapp/stock-market-game/HEAD/packages/frontend/src/assets/images/purple-polka-bg.png -------------------------------------------------------------------------------- /packages/api/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src", 4 | "compilerOptions": { 5 | "plugins": ["@nestjs/swagger"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/frontend/src/assets/fonts/SF-Pro-Rounded-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joinbloomapp/stock-market-game/HEAD/packages/frontend/src/assets/fonts/SF-Pro-Rounded-Bold.ttf -------------------------------------------------------------------------------- /packages/real-time-collector/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src" 5 | } 6 | -------------------------------------------------------------------------------- /packages/frontend/src/assets/fonts/SF-Pro-Rounded-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joinbloomapp/stock-market-game/HEAD/packages/frontend/src/assets/fonts/SF-Pro-Rounded-Medium.ttf -------------------------------------------------------------------------------- /packages/frontend/src/assets/fonts/SF-Pro-Rounded-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joinbloomapp/stock-market-game/HEAD/packages/frontend/src/assets/fonts/SF-Pro-Rounded-Regular.ttf -------------------------------------------------------------------------------- /packages/frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | /// 7 | -------------------------------------------------------------------------------- /packages/frontend/src/assets/fonts/SF-Pro-Rounded-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joinbloomapp/stock-market-game/HEAD/packages/frontend/src/assets/fonts/SF-Pro-Rounded-Semibold.ttf -------------------------------------------------------------------------------- /packages/api/src/app/auth/dto/providers.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export type Providers = "google"; 7 | -------------------------------------------------------------------------------- /packages/frontend/src/assets/images/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joinbloomapp/stock-market-game/HEAD/packages/frontend/src/assets/images/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /packages/frontend/.env: -------------------------------------------------------------------------------- 1 | VITE_BASE_API_URL=http://localhost:8000 2 | VITE_APP_URL=http://localhost:3000 3 | # VITE_BASE_API_URL=https://game-api.joinbloom.co 4 | # VITE_APP_URL=https://game.joinbloom.co 5 | -------------------------------------------------------------------------------- /packages/api/src/app/auth/dto/jwt-pair.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export class JwtRetDto { 7 | access: string; 8 | } 9 | -------------------------------------------------------------------------------- /packages/frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/api/src/app/admin/dto/login-as.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export class LoginAsDto { 7 | access: string; 8 | } 9 | -------------------------------------------------------------------------------- /packages/api/src/app/auth/dto/email-checker.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export class EmailCheckerDto { 7 | email: string; 8 | } 9 | -------------------------------------------------------------------------------- /packages/api/src/clients/courier/dto/signup.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface SignupDto { 7 | name: string; 8 | } 9 | -------------------------------------------------------------------------------- /packages/postgresql/Makefile: -------------------------------------------------------------------------------- 1 | YARN ?= yarn 2 | 3 | .PHONY: all restart lint 4 | all: lint restart 5 | 6 | restart: # Ensures that we are only restarting locally 7 | sh bin/dev/restart.sh 8 | 9 | lint: 10 | lint-staged 11 | -------------------------------------------------------------------------------- /packages/api/src/app/app.service.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { Injectable } from "@nestjs/common"; 7 | 8 | @Injectable() 9 | export class AppService {} 10 | -------------------------------------------------------------------------------- /packages/api/src/testing/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 | -------------------------------------------------------------------------------- /packages/frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | module.exports = { 7 | plugins: { 8 | tailwindcss: {}, 9 | autoprefixer: {}, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/api/src/clients/courier/dto/password-reset.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface PasswordResetDto { 7 | name: string; 8 | passwordResetLink: string; 9 | } 10 | -------------------------------------------------------------------------------- /packages/real-time-collector/src/app/app.service.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { Injectable } from "@nestjs/common"; 7 | 8 | @Injectable() 9 | export class AppService {} 10 | -------------------------------------------------------------------------------- /packages/real-time-collector/src/testing/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": "../", 4 | "testEnvironment": "node", 5 | "testRegex": ".test.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/api/src/app/game/dto/holdings-value.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export class PlayerHoldingsValueDto { 7 | currentPortfolioValue: number; 8 | currentBuyingPower: number; 9 | } 10 | -------------------------------------------------------------------------------- /packages/api/src/clients/courier/dto/create-game.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface CreateGameDto { 7 | name: string; 8 | gameName: string; 9 | inviteCode: string; 10 | } 11 | -------------------------------------------------------------------------------- /packages/api/src/app/categories/dto/category-asset.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface CategoryAssetDto { 7 | name: string; 8 | ticker: string; 9 | image: string; 10 | latestPrice: number; 11 | } 12 | -------------------------------------------------------------------------------- /packages/api/src/utils/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | type JSONValue = 7 | | string 8 | | number 9 | | boolean 10 | | { [x: string]: JSONValue } 11 | | Array; 12 | 13 | export { JSONValue }; 14 | -------------------------------------------------------------------------------- /packages/postgresql/src/utils/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | type JSONValue = 7 | | string 8 | | number 9 | | boolean 10 | | { [x: string]: JSONValue } 11 | | Array; 12 | 13 | export { JSONValue }; 14 | -------------------------------------------------------------------------------- /packages/postgresql/.config.env: -------------------------------------------------------------------------------- 1 | # If you want to develop locally and use bin/dev/restart.sh, 2 | # use the below credentials in .user.env 3 | PG_USERNAME=ULOORL5TSX57ZFDRCDXKCZZ5P6NE7PD5 4 | PG_PASSWORD=NFEGHIK4HEQBWRCFHRHJZQINO3KMJR7Q7HTX7YCVTS3V2MTOUL 5 | PG_HOST=localhost 6 | PG_PORT=5432 7 | PG_DATABASE=stockmarketgame 8 | -------------------------------------------------------------------------------- /packages/real-time-collector/src/utils/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | type JSONValue = 7 | | string 8 | | number 9 | | boolean 10 | | { [x: string]: JSONValue } 11 | | Array; 12 | 13 | export { JSONValue }; 14 | -------------------------------------------------------------------------------- /packages/api/src/app/admin/dto/login-as-body.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { IsEmail, IsNotEmpty } from "class-validator"; 7 | 8 | export class LoginAsBodyDto { 9 | @IsEmail() 10 | @IsNotEmpty() 11 | email: string; 12 | } 13 | -------------------------------------------------------------------------------- /packages/api/src/clients/courier/dto/start-game.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface StartGameDto { 7 | name: string; // user's first name 8 | gameName: string; // game name 9 | inviteCode: string; // game invite code 10 | } 11 | -------------------------------------------------------------------------------- /packages/api/src/app/market/dto/alpaca-trading-day.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface AlpacaTradingDay { 7 | date: string; 8 | open: string; 9 | close: string; 10 | session_open: string; 11 | session_close: string; 12 | } 13 | -------------------------------------------------------------------------------- /packages/frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { defineConfig } from 'vite'; 7 | import react from '@vitejs/plugin-react'; 8 | 9 | // https://vitejs.dev/config/ 10 | export default defineConfig({ 11 | plugins: [react()], 12 | }); 13 | -------------------------------------------------------------------------------- /packages/api/deploy/appspec-api-dev.yaml: -------------------------------------------------------------------------------- 1 | version: 0.0 2 | 3 | Resources: 4 | - TargetService: 5 | Type: AWS::ECS::Service 6 | Properties: 7 | TaskDefinition: "arn::ecs:us-east-1:968542545167:task-definition/smg-api-dev:*" 8 | LoadBalancerInfo: 9 | ContainerName: "app" 10 | ContainerPort: 80 11 | -------------------------------------------------------------------------------- /packages/api/deploy/appspec-api-prod.yaml: -------------------------------------------------------------------------------- 1 | version: 0.0 2 | 3 | Resources: 4 | - TargetService: 5 | Type: AWS::ECS::Service 6 | Properties: 7 | TaskDefinition: "arn::ecs:us-east-1:968542545167:task-definition/smg-api-prod:*" 8 | LoadBalancerInfo: 9 | ContainerName: "app" 10 | ContainerPort: 80 11 | -------------------------------------------------------------------------------- /packages/frontend/src/modules/Dashboard/OrderHistory/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import OrderHistoryCard from '../../../common/OrderHistoryCard'; 7 | 8 | export default function OrderHistory() { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /packages/api/src/app/assets/dto/related-asset.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export class RelatedAssetDto { 7 | ticker: string; 8 | name: string; 9 | image: string; 10 | latestPrice?: number; 11 | change?: number; 12 | changePercent?: number; 13 | } 14 | -------------------------------------------------------------------------------- /packages/api/deploy/appspec-api-staging.yaml: -------------------------------------------------------------------------------- 1 | version: 0.0 2 | 3 | Resources: 4 | - TargetService: 5 | Type: AWS::ECS::Service 6 | Properties: 7 | TaskDefinition: "arn::ecs:us-east-1:968542545167:task-definition/smg-api-staging:*" 8 | LoadBalancerInfo: 9 | ContainerName: "app" 10 | ContainerPort: 80 11 | -------------------------------------------------------------------------------- /packages/api/src/clients/Alpaca/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface CryptoAggBar { 7 | t: string; 8 | x: string; 9 | o: number; 10 | h: number; 11 | l: number; 12 | c: number; 13 | v: number; 14 | n: number; 15 | vw: number; 16 | } 17 | -------------------------------------------------------------------------------- /packages/api/src/app/assets/regenerate/dto/categories-json.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface CategoriesJsonDto { 7 | id: string; 8 | name: string; 9 | stocks: string[]; 10 | description: string; 11 | image: string; 12 | snippet: string; 13 | } 14 | -------------------------------------------------------------------------------- /packages/api/src/app/game/dto/latest.enum.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export enum LatestEnum { 7 | ONE_DAY = "ONE_DAY", 8 | ONE_WEEK = "ONE_WEEK", 9 | ONE_MONTH = "ONE_MONTH", 10 | THREE_MONTHS = "THREE_MONTHS", 11 | ONE_YEAR = "ONE_YEAR", 12 | ALL = "ALL", 13 | } 14 | -------------------------------------------------------------------------------- /packages/frontend/src/system/Analytics/events/BrowseEvents.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export enum BrowseEvents { 7 | BROWSE_QUERY = 'BROWSE:browse_query', 8 | NAVIGATE_TO_ASSET = 'BROWSE:navigate_to_asset', 9 | NAVIGATE_TO_CATEGORY = 'BROWSE:navigate_to_category', 10 | } 11 | -------------------------------------------------------------------------------- /packages/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .idea 17 | .DS_Store 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | .env 24 | .env.development 25 | -------------------------------------------------------------------------------- /packages/api/deploy/README.md: -------------------------------------------------------------------------------- 1 | ## Notes 2 | 3 | - Side note: why not combine AWS task definitions since all 4 4 | files use the same environment variables? In the scenario 5 | that they don't have the same ARNs, or you want separate configurations 6 | for each, then thank me :) It's a hassle, but for customization and explicit 7 | purposes, it's better to have this duplication of code 8 | -------------------------------------------------------------------------------- /packages/api/src/app/assets/regenerate/dto/parent-app-server-stock-json.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | interface _ParentAppServerStockJsonDto { 7 | name: string; 8 | image: string; 9 | } 10 | 11 | export type ParentAppServerStockJsonDto = { 12 | [key: string]: _ParentAppServerStockJsonDto; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/real-time-collector/deploy/appspec-real-time-collector-dev.yaml: -------------------------------------------------------------------------------- 1 | version: 0.0 2 | 3 | Resources: 4 | - TargetService: 5 | Type: AWS::ECS::Service 6 | Properties: 7 | TaskDefinition: "arn::ecs:us-east-1:968542545167:task-definition/smg-real-time-collector-dev:*" 8 | LoadBalancerInfo: 9 | ContainerName: "app" 10 | ContainerPort: 80 11 | -------------------------------------------------------------------------------- /packages/real-time-collector/deploy/appspec-real-time-collector-prod.yaml: -------------------------------------------------------------------------------- 1 | version: 0.0 2 | 3 | Resources: 4 | - TargetService: 5 | Type: AWS::ECS::Service 6 | Properties: 7 | TaskDefinition: "arn::ecs:us-east-1:968542545167:task-definition/smg-real-time-collector-prod:*" 8 | LoadBalancerInfo: 9 | ContainerName: "app" 10 | ContainerPort: 80 11 | -------------------------------------------------------------------------------- /packages/frontend/src/services/News/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface NewsItem { 7 | sourceName: string; 8 | date: number; // unix time, 9 | imageSource?: string; 10 | url?: string; 11 | snippet: string; 12 | snippetLineLimit?: number; // line limit 13 | tickers?: string[]; 14 | } 15 | -------------------------------------------------------------------------------- /packages/real-time-collector/.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | TZ=UTC 3 | IS_AWS=false 4 | 5 | # API Keys and Credentials 6 | 7 | POLYGON_API_KEY=a2Q46pulxLzY6CIRYCCRlhfbdMUAFtaO 8 | 9 | # 10 | # POSTGRESQL 11 | # 12 | PG_USERNAME=ULOORL5TSX57ZFDRCDXKCZZ5P6NE7PD5 13 | PG_PASSWORD=NFEGHIK4HEQBWRCFHRHJZQINO3KMJR7Q7HTX7YCVTS3V2MTOUL 14 | PG_HOST=localhost 15 | PG_PORT=5432 16 | PG_DATABASE=stockmarketgame -------------------------------------------------------------------------------- /packages/real-time-collector/deploy/README.md: -------------------------------------------------------------------------------- 1 | ## Notes 2 | 3 | - Side note: why not combine AWS task definitions since all 4 4 | files use the same environment variables? In the scenario 5 | that they don't have the same ARNs, or you want separate configurations 6 | for each, then thank me :) It's a hassle, but for customization and explicit 7 | purposes, it's better to have this duplication of code 8 | -------------------------------------------------------------------------------- /packages/frontend/src/system/Analytics/events/OrderEvents.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export enum OrderEvents { 7 | STARTED_ORDER = 'ORDER:started_order', 8 | ORDER_SUCCESS = 'ORDER:order_success', 9 | ORDER_ERROR = 'ORDER:order_error', 10 | CLICKED_ORDER_HISTORY_ITEM = 'ORDER:clicked_order_history_item', 11 | } 12 | -------------------------------------------------------------------------------- /packages/api/src/app/game/dto/holdings-change.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export class PlayerHoldingsChangeDto { 7 | totalChange: number; // in dollars 8 | totalChangePercent: number; // percent change 9 | todayChange: number; // in today's dollars change 10 | todayChangePercent: number; // today's percent change 11 | } 12 | -------------------------------------------------------------------------------- /packages/api/src/app/orders/dto/sell-all-body.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { IsNotEmpty, IsString } from "class-validator"; 7 | import { ApiProperty } from "@nestjs/swagger"; 8 | 9 | export class SellAllBodyDto { 10 | @ApiProperty({ description: "Stock id" }) 11 | @IsString() 12 | @IsNotEmpty() 13 | stockId: string; 14 | } 15 | -------------------------------------------------------------------------------- /packages/frontend/src/services/Search/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface AssetSearchResult { 7 | description: string; 8 | name: string; 9 | image: string; 10 | latestPrice: number; 11 | ticker: string; 12 | } 13 | 14 | export interface AssetSearch { 15 | assets: AssetSearchResult[]; 16 | count: number; 17 | } 18 | -------------------------------------------------------------------------------- /packages/api/src/system/ConfigurationManager.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export default () => { 7 | const config = { 8 | storeLogsToLogServer: false, 9 | }; 10 | 11 | config.storeLogsToLogServer = 12 | process.env.LOG_LEVEL.toLowerCase() !== "development" || 13 | process.env.IS_AWS === "1"; 14 | 15 | return config; 16 | }; 17 | -------------------------------------------------------------------------------- /packages/api/src/clients/courier/dto/end-game.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface EndGameDto { 7 | name: string; // user first name 8 | gameName: string; // game name 9 | inviteCode: string; // invite code 10 | rank: string; // final game rank (i.e. 3rd, 4th, etc.) 11 | numPlayers: number; // number of players in the game 12 | } 13 | -------------------------------------------------------------------------------- /packages/api/src/utils/validator/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { IsBigSerialString } from "./is-big-serial.validator"; 7 | import { IsIntegerString } from "./is-integer-string.validator"; 8 | import { ValidateBigSerialPipe } from "./validate-big-serial.pipe"; 9 | 10 | export { IsBigSerialString, IsIntegerString, ValidateBigSerialPipe }; 11 | -------------------------------------------------------------------------------- /packages/api/src/app/market/dto/get-latest-deposit-date.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ApiPropertyOptional } from "@nestjs/swagger"; 7 | import { IsOptional } from "class-validator"; 8 | 9 | export class GetLatestDepositDateDto { 10 | @ApiPropertyOptional({ description: "Format", default: "ll" }) 11 | @IsOptional() 12 | format: string = "ll"; 13 | } 14 | -------------------------------------------------------------------------------- /packages/frontend/src/services/Auth/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface SignupUser { 7 | email: string; 8 | password: string; 9 | firstName: string; 10 | lastName: string; 11 | } 12 | 13 | export interface LoginUser { 14 | email: string; 15 | password: string; 16 | } 17 | 18 | export interface AuthResponse { 19 | access: string; 20 | } 21 | -------------------------------------------------------------------------------- /packages/frontend/src/services/User/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface User { 7 | id: string; 8 | name: string; 9 | email: string; 10 | firstName: string; 11 | lastName: string; 12 | isSiteAdmin?: boolean; 13 | } 14 | 15 | export interface UpdateUser { 16 | firstName?: string; 17 | lastName?: string; 18 | email?: string; 19 | } 20 | -------------------------------------------------------------------------------- /packages/frontend/src/common/LoadingScreen/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export default function LoadingScreen() { 7 | return ( 8 |
9 |
10 |
11 |
12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /packages/api/src/app/game/dto/player-names.dto.ts: -------------------------------------------------------------------------------- 1 | import { PlayerEntity } from "@bloom-smg/postgresql"; 2 | 3 | export class PlayerNamesDto { 4 | playerId: string; 5 | userId: string; 6 | name: string; 7 | isGameAdmin: boolean; 8 | 9 | constructor(player: PlayerEntity) { 10 | this.playerId = player.id; 11 | this.userId = player.userId; 12 | this.name = player.user.name; 13 | this.isGameAdmin = player.isGameAdmin; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/api/src/clients/StockNews/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface StockNewsRecord { 7 | news_url: string; 8 | image_url: string; 9 | title: string; 10 | text: string; 11 | source_name: string; 12 | date: string; 13 | topics: string[]; 14 | sentiment: "Neutral" | "Positive" | "Negative"; 15 | type: string; 16 | tickers: string[]; 17 | } 18 | -------------------------------------------------------------------------------- /packages/frontend/.prettierrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | module.exports = { 7 | arrowParens: 'always', 8 | bracketSpacing: true, 9 | bracketSameLine: false, 10 | jsxSingleQuote: false, 11 | quoteProps: 'as-needed', 12 | singleQuote: true, 13 | semi: true, 14 | printWidth: 100, 15 | useTabs: false, 16 | tabWidth: 2, 17 | trailingComma: 'es5', 18 | }; 19 | -------------------------------------------------------------------------------- /packages/frontend/src/main.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | /* eslint-disable */ 7 | 8 | import React from 'react'; 9 | import ReactDOM from 'react-dom/client'; 10 | import App from './App'; 11 | import './index.css'; 12 | 13 | // @ts-ignore 14 | ReactDOM.createRoot(document.getElementById('root')).render( 15 | 16 | 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /packages/real-time-collector/src/system/ConfigurationManager.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export default () => { 7 | const config = { 8 | storeLogsToLogServer: false, 9 | }; 10 | 11 | config.storeLogsToLogServer = 12 | (process.env.LOG_LEVEL ?? "development").toLowerCase() !== "development" || 13 | process.env.IS_AWS === "1"; 14 | 15 | return config; 16 | }; 17 | -------------------------------------------------------------------------------- /packages/api/src/app/auth/dto/verify-oauth.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { IsNotEmpty, IsString } from "class-validator"; 7 | import { Providers } from "./providers.dto"; 8 | 9 | export class VerifyOAuthDto { 10 | @IsNotEmpty() 11 | @IsString() 12 | provider: Providers; 13 | 14 | @IsNotEmpty() 15 | refreshToken: string; 16 | 17 | data: { [key: string]: any }; 18 | } 19 | -------------------------------------------------------------------------------- /packages/frontend/src/services/Admin/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import client from '..'; 7 | 8 | namespace AdminService { 9 | export async function loginAs(email: string): Promise { 10 | const res = await client.post('/admin/login-as', { email }); 11 | localStorage.setItem('userAuthToken', res?.data?.access); 12 | } 13 | } 14 | 15 | export default AdminService; 16 | -------------------------------------------------------------------------------- /packages/api/src/utils/strings.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export function capitalizeAllWords(sentence: string): string { 7 | const separateWord = sentence.toLowerCase().split(" "); 8 | for (let i = 0; i < separateWord.length; i++) { 9 | separateWord[i] = 10 | separateWord[i].charAt(0).toUpperCase() + separateWord[i].substring(1); 11 | } 12 | return separateWord.join(" "); 13 | } 14 | -------------------------------------------------------------------------------- /packages/api/src/app/assets/dto/asset-basic-info.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface AssetBasicInfoDto { 7 | address?: string; 8 | city?: string; 9 | country?: string; 10 | employees?: number; 11 | image?: string; 12 | industry?: string; 13 | isEtf?: boolean; 14 | name: string; 15 | shortable?: boolean; 16 | state?: string; 17 | ticker?: string; 18 | description?: string; 19 | } 20 | -------------------------------------------------------------------------------- /packages/api/src/models/Holidays.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import * as holidays from "./holidays.json"; 7 | 8 | export default class Holidays { 9 | public static holidays: Set = new Set(holidays.data); 10 | 11 | /** 12 | * 13 | * @param date date formatted as "YYYY-MM-DD" 14 | */ 15 | public static isHoliday(date: string): boolean { 16 | return this.holidays.has(date); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/api/src/utils/auth.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { SetMetadata } from "@nestjs/common"; 7 | 8 | /** 9 | * Decorator to remove any JWT auth protection. 10 | * Use it in controller methods like 11 | * 12 | * ```typescript 13 | * @NoAuth() 14 | * @Get() 15 | * aRoute(@Body() param: ADto): Promise {} 16 | * ``` 17 | */ 18 | export const NoAuth = () => SetMetadata("no-auth", true); 19 | -------------------------------------------------------------------------------- /packages/frontend/src/system/Analytics/events/IndividualAssetEvents.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export enum IndividualAssetEvents { 7 | CLICKED_SHOW_MORE_OR_LESS = 'INDIVIDUAL_ASSET:clicked_show_more_or_less', 8 | CLICKED_NEWS_ITEM = 'INDIVIDUAL_ASSET:clicked_news_item', 9 | CLICKED_RELATED_ASSET = 'INDIVIDUAL_ASSET:clicked_related_asset', 10 | SWITCH_GRAPH_PERIOD = 'INDIVIDUAL_ASSET:switch_graph_period', 11 | } 12 | -------------------------------------------------------------------------------- /packages/api/src/app/market/market.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { Module } from "@nestjs/common"; 7 | import { MarketController } from "./market.controller"; 8 | import { MarketService } from "./market.service"; 9 | 10 | const providers = [MarketService]; 11 | 12 | @Module({ 13 | controllers: [MarketController], 14 | providers: providers, 15 | exports: providers, 16 | }) 17 | export class MarketModule {} 18 | -------------------------------------------------------------------------------- /packages/api/src/app/assets/models/Holidays.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import * as holidays from "./holidays.json"; 7 | 8 | export default class Holidays { 9 | public static holidays: Set = new Set(holidays.data); 10 | 11 | /** 12 | * 13 | * @param date date formatted as "YYYY-MM-DD" 14 | */ 15 | public static isHoliday(date: string): boolean { 16 | return this.holidays.has(date); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/api/src/app/auth/dto/login.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { IsEmail, IsNotEmpty } from "class-validator"; 7 | import { ApiProperty } from "@nestjs/swagger"; 8 | 9 | export class LoginDto { 10 | @ApiProperty({ description: "Email address" }) 11 | @IsNotEmpty() 12 | @IsEmail() 13 | email: string; 14 | 15 | @ApiProperty({ description: "Password" }) 16 | @IsNotEmpty() 17 | password: string; 18 | } 19 | -------------------------------------------------------------------------------- /packages/api/src/app/game/dto/add-player.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ApiProperty } from "@nestjs/swagger"; 7 | import { IsInt, IsNotEmpty, MaxLength, MinLength } from "class-validator"; 8 | import { Type } from "class-transformer"; 9 | 10 | export class AddPlayerDto { 11 | @ApiProperty({ description: "Invite code" }) 12 | @IsNotEmpty() 13 | @MinLength(6) 14 | @MaxLength(6) 15 | inviteCode: string; 16 | } 17 | -------------------------------------------------------------------------------- /packages/frontend/src/modules/Dashboard/Navigation/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import MobileBottomNavbar from './MobileBottomNavbar'; 7 | import Sidebar from './Sidebar'; 8 | 9 | export default function Navigation() { 10 | return ( 11 |
12 |
13 | 14 |
15 | 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "scripts": { 5 | "script": "node packages/scripts/dist/index.js", 6 | "prepare": "husky install", 7 | "pre-commit": "lint-staged" 8 | }, 9 | "devDependencies": { 10 | "husky": "^8.0.1", 11 | "lerna": "^3.22.1", 12 | "lint-staged": "^13.0.3" 13 | }, 14 | "workspaces": [ 15 | "packages/*" 16 | ], 17 | "resolutions": { 18 | "@types/eslint": "8.4.3" 19 | }, 20 | "overrides": { 21 | "@types/eslint": "8.4.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/api/src/app/news/dto/array-limit.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ApiProperty } from "@nestjs/swagger"; 7 | import { IsArray, IsNotEmpty } from "class-validator"; 8 | import { LimitDto } from "./limit-query.dto"; 9 | 10 | export class ArrayLimitDto extends LimitDto { 11 | @ApiProperty({ 12 | description: "A comma-delimited array of tickers", 13 | }) 14 | @IsNotEmpty() 15 | @IsArray() 16 | tickers: string[]; 17 | } 18 | -------------------------------------------------------------------------------- /packages/api/src/testing/global.testconfig.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import "../config"; 7 | 8 | const originalWarn = console.warn.bind(console.warn); 9 | 10 | // Disable warning for js files in dist/ 11 | module.exports = async () => { 12 | console.warn = (msg) => { 13 | for (const x of ["postgresql/dist/"]) { 14 | if (msg.toString().includes(x)) { 15 | return; 16 | } 17 | } 18 | originalWarn(msg); 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /packages/frontend/src/common/Graph/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export enum PeriodType { 7 | _1D = '1D', 8 | _1W = '1W', 9 | _1M = '1M', 10 | _3M = '3M', 11 | _1A = '1A', 12 | ALL = '5Y', 13 | } 14 | 15 | export interface Period { 16 | value: PeriodType; 17 | displayVal: string; 18 | dayjsTimestampFormat: string; 19 | tooltipOffset: number; 20 | } 21 | 22 | export interface Point { 23 | x: number; 24 | y: number; 25 | } 26 | -------------------------------------------------------------------------------- /packages/frontend/src/system/Analytics/events/ProfileEvents.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export enum ProfileEvents { 7 | CLICKED_MY_PROFILE = 'PROFILE:clicked_my_profile', 8 | UPDATE_USER_SUCCESS = 'PROFILE:update_user_success', 9 | UPDATE_USER_ERROR = 'PROFILE:update_user_error', 10 | RESET_PASSWORD_SUCCESS = 'PROFILE:reset_password_success', 11 | RESET_PASSWORD_ERROR = 'PROFILE:reset_password_error', 12 | SWITCH_TABS = 'PROFILE:switch_tabs', 13 | } 14 | -------------------------------------------------------------------------------- /packages/real-time-collector/src/utils/typeorm/map-stream.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { through } from "through"; 7 | 8 | export function mapSync(sync) { 9 | return through(function write(data) { 10 | let mappedData; 11 | try { 12 | mappedData = sync(data); 13 | } catch (e) { 14 | return this.emit("error", e); 15 | } 16 | if (mappedData !== undefined) { 17 | this.emit("data", mappedData); 18 | } 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /packages/api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:17.6.0-alpine 2 | 3 | WORKDIR /server 4 | 5 | COPY ./package.json . 6 | COPY ./lerna.json . 7 | COPY ./yarn.lock . 8 | ADD ./packages/api/ ./packages/api/ 9 | ADD ./packages/postgresql/ ./packages/postgresql/ 10 | 11 | RUN yarn install --production 12 | RUN yarn global add lerna@^3.22.1 13 | RUN npx lerna bootstrap 14 | 15 | WORKDIR /server/packages/postgresql/ 16 | 17 | RUN yarn build 18 | 19 | WORKDIR /server/packages/api/ 20 | 21 | RUN yarn build 22 | 23 | EXPOSE 80 24 | 25 | CMD ["sh", "deploy/start.sh"] 26 | -------------------------------------------------------------------------------- /packages/api/src/app/news/dto/news-content.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface TickerPrice { 7 | ticker: string; 8 | latestPrice: number; 9 | changePercent: number; 10 | change: number; 11 | } 12 | 13 | export interface NewsContentDto { 14 | sourceName: string; 15 | date: number; // unix time, 16 | imageSource?: string; 17 | url?: string; 18 | snippet: string; 19 | snippetLineLimit?: number; // line limit 20 | tickers: TickerPrice[]; 21 | } 22 | -------------------------------------------------------------------------------- /packages/api/src/app/market/dto/fetch-last-market-date.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ApiProperty } from "@nestjs/swagger"; 7 | import { IsInt, IsNotEmpty } from "class-validator"; 8 | import { Type } from "class-transformer"; 9 | 10 | export class FetchLastMarketDateDto { 11 | @ApiProperty({ 12 | description: "Number of days from today", 13 | type: Number, 14 | }) 15 | @Type(() => Number) 16 | @IsNotEmpty() 17 | @IsInt() 18 | numDaysAgo: number; 19 | } 20 | -------------------------------------------------------------------------------- /packages/api/src/testing/setup.testconfig.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | const originalWarn = console.warn.bind(console.warn); 7 | 8 | // Disable warning for js files in dist/ 9 | module.exports = () => { 10 | console.warn = (msg) => { 11 | for (const x of ["/dist/"]) { 12 | if (msg.toString().includes(x)) { 13 | return; 14 | } 15 | } 16 | originalWarn(msg); 17 | }; 18 | }; 19 | 20 | if (!process.env.CI) { 21 | jest.setTimeout(1000000); 22 | } 23 | -------------------------------------------------------------------------------- /packages/api/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 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 36 | -------------------------------------------------------------------------------- /packages/postgresql/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 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 -------------------------------------------------------------------------------- /packages/scripts/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 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 -------------------------------------------------------------------------------- /packages/api/src/app/assets/dto/get-analyst-ratings.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ApiProperty } from "@nestjs/swagger"; 7 | import { IsInt, IsNotEmpty, IsOptional, Max, Min } from "class-validator"; 8 | import { Type } from "class-transformer"; 9 | 10 | export class LimitDto { 11 | @ApiProperty({ 12 | description: "Limit, max 20 and default 3", 13 | }) 14 | @IsOptional() 15 | @Type(() => Number) 16 | @Min(1) 17 | @Max(20) 18 | @IsInt() 19 | limit = 3; 20 | } 21 | -------------------------------------------------------------------------------- /packages/frontend/src/utils/DateTimeUtils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import dayjs from 'dayjs'; 7 | 8 | namespace DateTimeUtils { 9 | export function getCurrentTimeOfDayMessage() { 10 | const curHours = dayjs().local().hour(); 11 | 12 | if (curHours < 12) { 13 | return 'Good morning'; 14 | } else if (curHours < 18) { 15 | return 'Good afternoon'; 16 | } else { 17 | return 'Good evening'; 18 | } 19 | } 20 | } 21 | 22 | export default DateTimeUtils; 23 | -------------------------------------------------------------------------------- /packages/api/src/app/news/dto/limit-query.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ApiProperty } from "@nestjs/swagger"; 7 | import { IsInt, IsOptional, Max, Min } from "class-validator"; 8 | import { Type } from "class-transformer"; 9 | 10 | export class LimitDto { 11 | @ApiProperty({ 12 | description: "Limit, max and default 50", 13 | type: Number, 14 | }) 15 | @IsOptional() 16 | @Type(() => Number) 17 | @Max(50) 18 | @Min(1) 19 | @IsInt() 20 | limit: number = 50; 21 | } 22 | -------------------------------------------------------------------------------- /packages/postgresql/bin/dev/restart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright (c) 2022 Contour Labs, Inc. 5 | # SPDX-License-Identifier: AGPL-3.0-only 6 | # 7 | 8 | # Restarts a local postgres instance 9 | 10 | psql postgres << EOF 11 | DROP DATABASE stockmarketgame; 12 | CREATE DATABASE stockmarketgame; 13 | CREATE ROLE "ULOORL5TSX57ZFDRCDXKCZZ5P6NE7PD5" WITH LOGIN SUPERUSER CREATEDB ENCRYPTED PASSWORD 'NFEGHIK4HEQBWRCFHRHJZQINO3KMJR7Q7HTX7YCVTS3V2MTOUL'; 14 | GRANT ALL PRIVILEGES ON DATABASE stockmarketgame TO "ULOORL5TSX57ZFDRCDXKCZZ5P6NE7PD5"; 15 | EOF 16 | yarn migration:run 17 | -------------------------------------------------------------------------------- /packages/real-time-collector/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 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 -------------------------------------------------------------------------------- /packages/real-time-collector/src/testing/setup.testconfig.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | const originalWarn = console.warn.bind(console.warn); 7 | 8 | // Disable warning for js files in dist/ 9 | module.exports = () => { 10 | console.warn = (msg) => { 11 | for (const x of ["/dist/"]) { 12 | if (msg.toString().includes(x)) { 13 | return; 14 | } 15 | } 16 | originalWarn(msg); 17 | }; 18 | }; 19 | 20 | if (!process.env.CI) { 21 | jest.setTimeout(1000000); 22 | } 23 | -------------------------------------------------------------------------------- /packages/api/src/app/categories/dto/category-assets.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { CategoryAssetDto } from "./category-asset.dto"; 7 | 8 | export class CategoryAssetsResponseDto { 9 | assets: CategoryAssetDto[]; 10 | count: number; // total count of assets 11 | cursor: string; // base64 cursor 12 | 13 | constructor(assets: CategoryAssetDto[], count: number, cursor: string) { 14 | this.assets = assets; 15 | this.count = count; 16 | this.cursor = cursor; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/api/src/app/game/dto/historical-agg-position.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { HistoricalAggregatePositionEntity } from "@bloom-smg/postgresql"; 7 | 8 | export class HistoricalAggPositionDto { 9 | value: number; 10 | createdAt: Date; 11 | playerId: string; 12 | 13 | constructor(position: HistoricalAggregatePositionEntity, playerId: string) { 14 | this.value = position.value; 15 | this.createdAt = position.createdAt; 16 | this.playerId = playerId; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/real-time-collector/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:17.6.0-alpine 2 | 3 | WORKDIR /server 4 | 5 | COPY ./package.json . 6 | COPY ./lerna.json . 7 | COPY ./yarn.lock . 8 | ADD ./packages/real-time-collector/ ./packages/real-time-collector/ 9 | ADD ./packages/postgresql/ ./packages/postgresql/ 10 | 11 | RUN yarn install --production 12 | RUN yarn global add lerna 13 | RUN npx lerna bootstrap 14 | 15 | WORKDIR /server/packages/postgresql/ 16 | 17 | RUN yarn build 18 | 19 | WORKDIR /server/packages/real-time-collector/ 20 | 21 | RUN yarn build 22 | 23 | EXPOSE 80 24 | 25 | CMD ["sh", "deploy/start.sh"] 26 | -------------------------------------------------------------------------------- /packages/frontend/src/services/Categories/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface Category { 7 | id: string; 8 | name: string; 9 | image: string; 10 | numAssets: number; 11 | description: string; 12 | } 13 | 14 | export interface CategoryAsset { 15 | name: string; 16 | ticker: string; 17 | image: string; 18 | latestPrice: number; 19 | } 20 | 21 | export interface CategoryAssetsResponse { 22 | assets: CategoryAsset[]; 23 | count: number; // total count of assets 24 | cursor: string; // base64 cursor 25 | } 26 | -------------------------------------------------------------------------------- /packages/api/.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | yarn.lock 3 | package-lock.json 4 | 5 | # compiled output 6 | /dist 7 | /node_modules 8 | 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | pnpm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | lerna-debug.log* 17 | 18 | # OS 19 | .DS_Store 20 | 21 | # Tests 22 | /coverage 23 | /.nyc_output 24 | 25 | # IDEs and editors 26 | /.idea 27 | .project 28 | .classpath 29 | .c9/ 30 | *.launch 31 | .settings/ 32 | *.sublime-workspace 33 | 34 | # IDE - VSCode 35 | .vscode/* 36 | !.vscode/settings.json 37 | !.vscode/tasks.json 38 | !.vscode/launch.json 39 | !.vscode/extensions.json -------------------------------------------------------------------------------- /packages/frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | yarn.lock 3 | package-lock.json 4 | 5 | # compiled output 6 | /dist 7 | /node_modules 8 | 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | pnpm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | lerna-debug.log* 17 | 18 | # OS 19 | .DS_Store 20 | 21 | # Tests 22 | /coverage 23 | /.nyc_output 24 | 25 | # IDEs and editors 26 | /.idea 27 | .project 28 | .classpath 29 | .c9/ 30 | *.launch 31 | .settings/ 32 | *.sublime-workspace 33 | 34 | # IDE - VSCode 35 | .vscode/* 36 | !.vscode/settings.json 37 | !.vscode/tasks.json 38 | !.vscode/launch.json 39 | !.vscode/extensions.json -------------------------------------------------------------------------------- /packages/scripts/.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | yarn.lock 3 | package-lock.json 4 | 5 | # compiled output 6 | /dist 7 | /node_modules 8 | 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | pnpm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | lerna-debug.log* 17 | 18 | # OS 19 | .DS_Store 20 | 21 | # Tests 22 | /coverage 23 | /.nyc_output 24 | 25 | # IDEs and editors 26 | /.idea 27 | .project 28 | .classpath 29 | .c9/ 30 | *.launch 31 | .settings/ 32 | *.sublime-workspace 33 | 34 | # IDE - VSCode 35 | .vscode/* 36 | !.vscode/settings.json 37 | !.vscode/tasks.json 38 | !.vscode/launch.json 39 | !.vscode/extensions.json -------------------------------------------------------------------------------- /packages/postgresql/.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | yarn.lock 3 | package-lock.json 4 | 5 | # compiled output 6 | /dist 7 | /node_modules 8 | 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | pnpm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | lerna-debug.log* 17 | 18 | # OS 19 | .DS_Store 20 | 21 | # Tests 22 | /coverage 23 | /.nyc_output 24 | 25 | # IDEs and editors 26 | /.idea 27 | .project 28 | .classpath 29 | .c9/ 30 | *.launch 31 | .settings/ 32 | *.sublime-workspace 33 | 34 | # IDE - VSCode 35 | .vscode/* 36 | !.vscode/settings.json 37 | !.vscode/tasks.json 38 | !.vscode/launch.json 39 | !.vscode/extensions.json -------------------------------------------------------------------------------- /packages/api/src/app/categories/dto/category.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { StockCategoryEntity } from "@bloom-smg/postgresql"; 7 | 8 | export class CategoryDto { 9 | id: string; 10 | name: string; 11 | image: string; 12 | numAssets: number; 13 | description: string; 14 | 15 | constructor(category: StockCategoryEntity, count: number) { 16 | this.id = category.id; 17 | this.name = category.name; 18 | this.image = category.image; 19 | this.numAssets = count; 20 | this.description = category.description; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/real-time-collector/.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | yarn.lock 3 | package-lock.json 4 | 5 | # compiled output 6 | /dist 7 | /node_modules 8 | 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | pnpm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | lerna-debug.log* 17 | 18 | # OS 19 | .DS_Store 20 | 21 | # Tests 22 | /coverage 23 | /.nyc_output 24 | 25 | # IDEs and editors 26 | /.idea 27 | .project 28 | .classpath 29 | .c9/ 30 | *.launch 31 | .settings/ 32 | *.sublime-workspace 33 | 34 | # IDE - VSCode 35 | .vscode/* 36 | !.vscode/settings.json 37 | !.vscode/tasks.json 38 | !.vscode/launch.json 39 | !.vscode/extensions.json -------------------------------------------------------------------------------- /packages/api/src/app/assets/dto/Assets.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface CryptoStockStyles { 7 | primaryColor: string; 8 | } 9 | 10 | export interface CryptoStock { 11 | isEtf?: boolean; 12 | name: string; 13 | symbol?: string; 14 | isCrypto: boolean; 15 | description?: string; 16 | exchange?: string; 17 | styles: Record; 18 | industry: string; 19 | founded: string; 20 | marketRank: number; 21 | minimumAmount: number; 22 | minimumQty: number; 23 | qtyIncrement: number; 24 | priceIncrement: number; 25 | } 26 | -------------------------------------------------------------------------------- /packages/real-time-collector/src/app/app.controller.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { Controller, Get } from "@nestjs/common"; 7 | import { AppService } from "./app.service"; 8 | 9 | @Controller() 10 | export class AppController { 11 | constructor(private readonly appService: AppService) {} 12 | 13 | @Get() 14 | /** 15 | * For health checks, we need a root path. This will not interfere 16 | * with Swagger Docs 17 | */ 18 | async root(): Promise { 19 | return "We are improving the financial future of the next generation!"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Secrets 2 | **/.user.env 3 | 4 | # compiled output 5 | **/dist/ 6 | **/node_modules/ 7 | 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | pnpm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | lerna-debug.log* 16 | 17 | # OS 18 | .DS_Store 19 | 20 | # Tests 21 | /coverage 22 | /.nyc_output 23 | 24 | # IDEs and editors 25 | .project 26 | .classpath 27 | .c9/ 28 | *.launch 29 | .settings/ 30 | *.sublime-workspace 31 | 32 | # IDE - VSCode 33 | **/.vscode/ 34 | !.vscode/settings.json 35 | !.vscode/tasks.json 36 | !.vscode/launch.json 37 | !.vscode/extensions.json 38 | 39 | # IDE - JetBrains 40 | **/.idea/ 41 | !.idea/copyright 42 | -------------------------------------------------------------------------------- /packages/api/src/app/user/user.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { UserEntity } from "@bloom-smg/postgresql"; 7 | import { Module } from "@nestjs/common"; 8 | import { TypeOrmModule } from "@nestjs/typeorm"; 9 | import { AuthModule } from "./../auth/auth.module"; 10 | import { UserController } from "./user.controller"; 11 | import { UserService } from "./user.service"; 12 | 13 | @Module({ 14 | imports: [TypeOrmModule.forFeature([UserEntity]), AuthModule], 15 | controllers: [UserController], 16 | providers: [UserService], 17 | }) 18 | export class UserModule {} 19 | -------------------------------------------------------------------------------- /packages/frontend/src/hooks/useMobile.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { useEffect, useState } from 'react'; 7 | 8 | export default function useMobile() { 9 | const [width, setWidth] = useState(window.innerWidth); 10 | 11 | function handleWindowSizeChange() { 12 | setWidth(window.innerWidth); 13 | } 14 | 15 | useEffect(() => { 16 | window.addEventListener('resize', handleWindowSizeChange); 17 | return () => { 18 | window.removeEventListener('resize', handleWindowSizeChange); 19 | }; 20 | }, []); 21 | 22 | return width <= 768; 23 | } 24 | -------------------------------------------------------------------------------- /packages/api/src/app/game/dto/popular-asset.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { StockPriceEntity } from "@bloom-smg/postgresql"; 7 | 8 | export class PopularAssetDto { 9 | latestPrice: number; 10 | id: string; // stock id 11 | ticker: string; 12 | name: string; 13 | image: string; 14 | 15 | constructor(stockPrice: StockPriceEntity) { 16 | this.latestPrice = stockPrice.price; 17 | this.id = stockPrice.stockId; 18 | this.ticker = stockPrice.stock.ticker; 19 | this.name = stockPrice.stock.name; 20 | this.image = stockPrice.stock.image; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/api/src/app/news/news.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { Module } from "@nestjs/common"; 7 | import { NewsController } from "./news.controller"; 8 | import { NewsService } from "./news.service"; 9 | import { TypeOrmModule } from "@nestjs/typeorm"; 10 | import { StockPriceEntity } from "@bloom-smg/postgresql"; 11 | 12 | const providers = [NewsService]; 13 | 14 | @Module({ 15 | imports: [TypeOrmModule.forFeature([StockPriceEntity])], 16 | controllers: [NewsController], 17 | providers: providers, 18 | exports: providers, 19 | }) 20 | export class NewsModule {} 21 | -------------------------------------------------------------------------------- /packages/frontend/src/modules/NotFound/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import Header from '../Header'; 7 | 8 | export default function NotFound() { 9 | return ( 10 |
11 |
12 |
13 |
14 |

Sorry, could not find the page!

15 |
16 |
17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /packages/frontend/src/components/Badge/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import cls from 'classnames'; 7 | import { twMerge } from 'tailwind-merge'; 8 | 9 | interface IBadgeProps { 10 | children: React.ReactNode; 11 | className?: string; 12 | } 13 | 14 | export default function Badge({ children, className }: IBadgeProps) { 15 | return ( 16 |
22 | {children} 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /packages/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /packages/api/src/app/app.controller.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { Controller, Get } from "@nestjs/common"; 7 | import { AppService } from "./app.service"; 8 | import { NoAuth } from "../utils/auth"; 9 | 10 | @Controller() 11 | export class AppController { 12 | constructor(private readonly appService: AppService) {} 13 | 14 | @NoAuth() 15 | @Get() 16 | /** 17 | * For health checks, we need a root path. This will not interfere 18 | * with Swagger Docs 19 | */ 20 | async root(): Promise { 21 | return "We are improving the financial future of the next generation!"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/api/src/app/orders/dto/transaction.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ApiPropertyOptional } from "@nestjs/swagger"; 7 | import { IsOptional } from "class-validator"; 8 | import { IsBigSerialString } from "src/utils/validator"; 9 | 10 | export class TransactionDto { 11 | @IsOptional() 12 | @IsBigSerialString() 13 | stockId?: string; 14 | 15 | ticker?: string; 16 | 17 | @ApiPropertyOptional({ 18 | description: "Amount of stock to sell", 19 | }) 20 | quantity?: number; 21 | 22 | @ApiPropertyOptional({ 23 | description: "Amount in dollars to sell", 24 | }) 25 | notional?: number; 26 | } 27 | -------------------------------------------------------------------------------- /packages/frontend/src/modules/Dashboard/MobileHeader/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { Link } from 'react-router-dom'; 7 | import Logo from '../../../assets/images/bloom.png'; 8 | import MobileGamesToggle from '../../../common/MobileGamesToggle'; 9 | 10 | export default function MobileHeader() { 11 | return ( 12 |
13 | 14 | Bloom logo 15 | 16 | 17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /packages/api/src/app/assets/dto/get-price-by-date.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ApiProperty } from "@nestjs/swagger"; 7 | import { Transform, Type } from "class-transformer"; 8 | import * as moment from "moment"; 9 | import { Moment } from "moment"; 10 | import { IsNotEmpty } from "class-validator"; 11 | 12 | export class GetPriceByDateDto { 13 | @ApiProperty({ 14 | description: "Date formatted", 15 | type: String, 16 | default: new Date().toISOString(), 17 | }) 18 | @IsNotEmpty() 19 | @Type(() => Date) 20 | @Transform(({ value }) => moment(value), { toClassOnly: true }) 21 | date: Moment; 22 | } 23 | -------------------------------------------------------------------------------- /packages/api/src/system/exceptions/BloomExceptionType.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { JSONValue } from "src/utils/types"; 7 | 8 | enum BloomExcpetionType { 9 | InternalServerError = "InternalServerError", 10 | NotEnoughCoins = "NotEnoughCoins", 11 | } 12 | 13 | /* 14 | * WARNING: keep AllMessagesForUsers key equal to BloomExcpetionType so we can do AllMessagesForUsers[type] and find the right message 15 | */ 16 | export const AllMessagesForUsers: JSONValue = { 17 | InternalServerError: "We had a problem, please try again", 18 | NotEnoughCoins: "Not enough coins", 19 | }; 20 | 21 | export default BloomExcpetionType; 22 | -------------------------------------------------------------------------------- /packages/api/src/app/assets/dto/gainer-loser.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ApiPropertyOptional } from "@nestjs/swagger"; 7 | import { IsInt, IsOptional, Max, Min } from "class-validator"; 8 | import { Type } from "class-transformer"; 9 | 10 | export class GainerLoserDto { 11 | @ApiPropertyOptional({ description: "Source to get the data" }) 12 | @IsOptional() 13 | source: "iex" | "polygon" = "iex"; 14 | 15 | @ApiPropertyOptional({ 16 | description: "Max number to return. 20 is the max.", 17 | }) 18 | @IsOptional() 19 | @Type(() => Number) 20 | @IsInt() 21 | @Min(1) 22 | @Max(20) 23 | limit = 20; 24 | } 25 | -------------------------------------------------------------------------------- /packages/real-time-collector/src/system/exceptions/BloomExceptionType.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { JSONValue } from "src/utils/types"; 7 | 8 | enum BloomExcpetionType { 9 | InternalServerError = "InternalServerError", 10 | NotEnoughCoins = "NotEnoughCoins", 11 | } 12 | 13 | /* 14 | * WARNING: keep AllMessagesForUsers key equal to BloomExcpetionType so we can do AllMessagesForUsers[type] and find the right message 15 | */ 16 | export const AllMessagesForUsers: JSONValue = { 17 | InternalServerError: "We had a problem, please try again", 18 | NotEnoughCoins: "Not enough coins", 19 | }; 20 | 21 | export default BloomExcpetionType; 22 | -------------------------------------------------------------------------------- /packages/scripts/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es2021": true, 4 | "node": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "prettier" 10 | ], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "ecmaVersion": "latest", 14 | "sourceType": "module" 15 | }, 16 | "plugins": ["@typescript-eslint"], 17 | "rules": { 18 | "@typescript-eslint/interface-name-prefix": "off", 19 | "@typescript-eslint/explicit-function-return-type": "off", 20 | "@typescript-eslint/explicit-module-boundary-types": "off", 21 | "@typescript-eslint/no-explicit-any": "off", 22 | "@typescript-eslint/no-namespace": "off" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | module.exports = { 7 | env: { 8 | browser: true, 9 | es6: true, 10 | }, 11 | extends: ['airbnb', 'prettier'], 12 | globals: { 13 | Atomics: 'readonly', 14 | SharedArrayBuffer: 'readonly', 15 | }, 16 | parserOptions: { 17 | ecmaFeatures: { 18 | jsx: true, 19 | }, 20 | ecmaVersion: 2018, 21 | sourceType: 'module', 22 | }, 23 | plugins: ['react', 'prettier'], 24 | rules: { 25 | 'react/jsx-filename-extension': [2, { extensions: ['.js', '.jsx', '.ts', '.tsx'] }], 26 | 'react/jsx-uses-react': 0, 27 | 'react/react-in-jsx-scope': 0, 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /packages/frontend/src/services/Search/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import client from '..'; 7 | import { AssetSearch } from './types'; 8 | 9 | namespace SearchService { 10 | /** 11 | * Performs a back-end search based on asset names, tickers, and descriptions based on a query string 12 | * 13 | * @param query 14 | * @returns asset search results 15 | */ 16 | export async function searchAssets(query: string): Promise { 17 | const res = await client.get('/search', { 18 | params: { 19 | q: query, 20 | }, 21 | }); 22 | 23 | return res?.data; 24 | } 25 | } 26 | 27 | export default SearchService; 28 | -------------------------------------------------------------------------------- /packages/frontend/src/utils/ArrayUtils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | namespace ArrayUtils { 7 | /** 8 | * Returns an empty array if the given array is falsey 9 | * 10 | * @param arr array to check 11 | * @returns array 12 | */ 13 | export function orEmptyArray(arr: any[] | undefined): any[] { 14 | return arr || []; 15 | } 16 | 17 | /** 18 | * Sums up all numbers in an array 19 | * 20 | * @param arr an array of numbers 21 | * @returns sum of all numbers in the array 22 | */ 23 | export function sum(arr: number[]): number { 24 | return arr.reduce((a, b) => a + b, 0); 25 | } 26 | } 27 | 28 | export default ArrayUtils; 29 | -------------------------------------------------------------------------------- /packages/postgresql/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es2021": true, 4 | "node": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "prettier" 10 | ], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "ecmaVersion": "latest", 14 | "sourceType": "module" 15 | }, 16 | "plugins": ["@typescript-eslint"], 17 | "rules": { 18 | "@typescript-eslint/interface-name-prefix": "off", 19 | "@typescript-eslint/explicit-function-return-type": "off", 20 | "@typescript-eslint/explicit-module-boundary-types": "off", 21 | "@typescript-eslint/no-explicit-any": "off", 22 | "@typescript-eslint/no-namespace": "off" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/postgresql/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | }, 21 | "include": ["src/**/*"], 22 | "exclude": ["node_modules"], 23 | "compileOnSave": true 24 | } 25 | -------------------------------------------------------------------------------- /packages/scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | }, 21 | "include": ["src/**/*"], 22 | "exclude": ["node_modules"], 23 | "compileOnSave": true 24 | } 25 | -------------------------------------------------------------------------------- /packages/api/src/app/search/search.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { Module } from "@nestjs/common"; 7 | import { SearchController } from "./search.controller"; 8 | import { SearchService } from "./search.service"; 9 | import { TypeOrmModule } from "@nestjs/typeorm"; 10 | import { 11 | StockEntity, 12 | StockCategoryEntity, 13 | StockPriceEntity, 14 | } from "@bloom-smg/postgresql"; 15 | 16 | @Module({ 17 | imports: [ 18 | TypeOrmModule.forFeature([ 19 | StockEntity, 20 | StockCategoryEntity, 21 | StockPriceEntity, 22 | ]), 23 | ], 24 | controllers: [SearchController], 25 | providers: [SearchService], 26 | }) 27 | export class SearchModule {} 28 | -------------------------------------------------------------------------------- /packages/real-time-collector/README.md: -------------------------------------------------------------------------------- 1 | ## Price Alerts 2 | 3 | Head to [src/socket.ts](./src/socket.ts) to view how we deal with notifying users 4 | of price changes. 5 | 6 | At 9:30am EST, we start collect minute bars from Alpaca to determine whether certain 7 | thresholds have been passed based on the given open and current price. 8 | 9 | ## Installation 10 | 11 | Follow monorepo lerna command. 12 | 13 | ## Running the app 14 | 15 | ```bash 16 | # development 17 | $ npm run start 18 | 19 | # watch mode 20 | $ npm run start:dev 21 | 22 | # production mode 23 | $ npm run start:prod 24 | ``` 25 | 26 | ## Test 27 | 28 | ```bash 29 | # unit tests 30 | $ npm run test 31 | 32 | # e2e tests 33 | $ npm run test:e2e 34 | 35 | # test coverage 36 | $ npm run test:cov 37 | ``` 38 | -------------------------------------------------------------------------------- /packages/api/src/app/user/dto/full-user-info.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { UserEntity } from "@bloom-smg/postgresql"; 7 | 8 | export class FullUserInfoDto { 9 | id: string; 10 | name: string; 11 | firstName: string; 12 | lastName: string; 13 | email: string; 14 | hasPassword: boolean; 15 | isSiteAdmin?: boolean; 16 | 17 | constructor(user: UserEntity) { 18 | this.id = user.id; 19 | this.name = user.name; 20 | this.firstName = user.firstName; 21 | this.lastName = user.lastName; 22 | this.email = user.email; 23 | this.hasPassword = user.extra?.hasPassword; 24 | if (user.isSiteAdmin) { 25 | this.isSiteAdmin = true; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/api/src/app/admin/admin.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { UserEntity } from "@bloom-smg/postgresql"; 7 | import { Module } from "@nestjs/common"; 8 | import { JwtService } from "@nestjs/jwt"; 9 | import { TypeOrmModule } from "@nestjs/typeorm"; 10 | import { AuthModule } from "../auth/auth.module"; 11 | import { AdminController } from "./admin.controller"; 12 | import { AdminService } from "./admin.service"; 13 | 14 | const providers = [AdminService, JwtService]; 15 | 16 | @Module({ 17 | imports: [TypeOrmModule.forFeature([UserEntity]), AuthModule], 18 | controllers: [AdminController], 19 | providers: providers, 20 | exports: providers, 21 | }) 22 | export class AdminModule {} 23 | -------------------------------------------------------------------------------- /packages/real-time-collector/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false, 20 | "paths": { 21 | "@bloom/api/*": ["../api/"], 22 | "@bloom/postgresql/*": ["../postgresql/"] 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/api/src/app/admin/admin.controller.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { Controller, Post, Body, Request } from "@nestjs/common"; 7 | import { ApiTags } from "@nestjs/swagger"; 8 | import { AdminService } from "./admin.service"; 9 | import { LoginAsBodyDto } from "./dto/login-as-body.dto"; 10 | import { LoginAsDto } from "./dto/login-as.dto"; 11 | 12 | @ApiTags("Admin") 13 | @Controller() 14 | export class AdminController { 15 | constructor(private readonly adminService: AdminService) {} 16 | 17 | @Post("login-as") 18 | getBasicInfo( 19 | @Request() req, 20 | @Body() body: LoginAsBodyDto 21 | ): Promise { 22 | return this.adminService.loginAs(req, body.email); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "resolveJsonModule": true, 10 | "allowJs": true, 11 | "target": "es2017", 12 | "sourceMap": true, 13 | "outDir": "./dist", 14 | "baseUrl": "./", 15 | "incremental": true, 16 | "skipLibCheck": true, 17 | "strictNullChecks": false, 18 | "noImplicitAny": false, 19 | "strictBindCallApply": false, 20 | "forceConsistentCasingInFileNames": false, 21 | "noFallthroughCasesInSwitch": false, 22 | "paths": { 23 | "@bloom-smg/postgresql/*": ["../postgresql/"] 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/real-time-collector/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es2021": true, 4 | "node": true, 5 | "jest": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "prettier" 11 | ], 12 | "parser": "@typescript-eslint/parser", 13 | "parserOptions": { 14 | "ecmaVersion": "latest", 15 | "sourceType": "module" 16 | }, 17 | "plugins": ["@typescript-eslint"], 18 | "rules": { 19 | "@typescript-eslint/interface-name-prefix": "off", 20 | "@typescript-eslint/explicit-function-return-type": "off", 21 | "@typescript-eslint/explicit-module-boundary-types": "off", 22 | "@typescript-eslint/no-explicit-any": "off", 23 | "@typescript-eslint/ban-ts-comment": "off", 24 | "@typescript-eslint/no-namespace": "off" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/api/src/config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { parse as parseDotenv } from "dotenv"; 7 | import * as path from "path"; 8 | import { existsSync, readFileSync } from "fs"; 9 | 10 | (() => { 11 | const env = {}; 12 | for (const envFile of [ 13 | path.resolve(process.cwd(), ".env"), 14 | path.resolve(process.cwd(), ".user.env"), 15 | ]) { 16 | if (existsSync(envFile)) { 17 | for (const [key, value] of Object.entries( 18 | parseDotenv(readFileSync(envFile)) 19 | )) { 20 | if (!process.env[key]) { 21 | env[key] = value; 22 | } 23 | } 24 | } 25 | } 26 | for (const [key, value] of Object.entries(env)) { 27 | process.env[key] = value.toString(); 28 | } 29 | })(); 30 | -------------------------------------------------------------------------------- /packages/api/src/app/game/dto/get-user-games-query.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ApiPropertyOptional } from "@nestjs/swagger"; 7 | import { GameStatusEnum } from "@bloom-smg/postgresql"; 8 | import { IsEnum, IsInt, IsOptional, Max, Min } from "class-validator"; 9 | import { Type } from "class-transformer"; 10 | 11 | export class GetUserGamesQueryDto { 12 | @ApiPropertyOptional({ description: "Limit number of games returned" }) 13 | @Type(() => Number) 14 | @IsInt() 15 | @IsOptional() 16 | @Min(1) 17 | @Max(50) 18 | limit?: number = 50; 19 | 20 | @ApiPropertyOptional({ 21 | description: "Game status type", 22 | enum: GameStatusEnum, 23 | }) 24 | @IsOptional() 25 | @IsEnum(GameStatusEnum) 26 | status?: GameStatusEnum; 27 | } 28 | -------------------------------------------------------------------------------- /packages/api/src/app/search/dto/search-query.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; 7 | import { IsInt, IsNotEmpty, IsOptional, Max, Min } from "class-validator"; 8 | import { Type } from "class-transformer"; 9 | 10 | export class SearchQueryDto { 11 | @IsOptional() 12 | @Type(() => Number) 13 | @IsInt() 14 | @IsInt() 15 | @Min(0) 16 | offset?: number; 17 | 18 | @ApiPropertyOptional({ 19 | description: "Limit the number of results", 20 | default: 30, 21 | }) 22 | @Type(() => Number) 23 | @IsInt() 24 | @IsOptional() 25 | @Max(50) 26 | @Min(1) 27 | limit?: number = 30; 28 | 29 | @ApiProperty({ description: "Search query" }) 30 | @IsNotEmpty() 31 | q: string; 32 | } 33 | -------------------------------------------------------------------------------- /packages/real-time-collector/src/config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { parse as parseDotenv } from "dotenv"; 7 | import * as path from "path"; 8 | import { existsSync, readFileSync } from "fs"; 9 | 10 | (() => { 11 | const env = {}; 12 | for (const envFile of [ 13 | path.resolve(process.cwd(), ".env"), 14 | path.resolve(process.cwd(), ".user.env"), 15 | ]) { 16 | if (existsSync(envFile)) { 17 | for (const [key, value] of Object.entries( 18 | parseDotenv(readFileSync(envFile)) 19 | )) { 20 | if (!process.env[key]) { 21 | env[key] = value; 22 | } 23 | } 24 | } 25 | } 26 | for (const [key, value] of Object.entries(env)) { 27 | process.env[key] = value.toString(); 28 | } 29 | })(); 30 | -------------------------------------------------------------------------------- /packages/frontend/src/system/Analytics/events/PortfolioEvents.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export enum PortfolioEvents { 7 | SWITCH_GRAPH_PERIOD = 'PORTFOLIO:switch_graph_period', 8 | CLICKED_BUY_FIRST_ASSET = 'PORTFOLIO:clicked_buy_first_asset', 9 | CLICKED_BUY_ASSET = 'PORTFOLIO:clicked_buy_asset', 10 | CLICKED_LEADERBOARD_SHOW_ALL = 'PORTFOLIO:clicked_leaderboard_show_all', 11 | CLICKED_GET_BLOOM_APP = 'PORTFOLIO:clicked_get_bloom_app', 12 | CLICKED_HOST_NEW_GAME = 'PORTFOLIO:clicked_host_new_game', 13 | CLICKED_POSITION = 'PORTFOLIO:clicked_position', 14 | CLICKED_POPULAR_ASSET = 'PORTFOLIO:clicked_popular_asset', 15 | CLICKED_JOIN_NEW_GAME = 'PORTFOLIO:clicked_join_new_game', 16 | CLICKED_CREATE_NEW_GAME = 'PORTFOLIO:clicked_create_new_game', 17 | } 18 | -------------------------------------------------------------------------------- /packages/postgresql/src/db/migrations/1660937199912-siteadmin.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class siteadmin1660937199912 implements MigrationInterface { 4 | name = "siteadmin1660937199912"; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "User" ADD "isSiteAdmin" boolean NOT NULL DEFAULT false` 9 | ); 10 | await queryRunner.query( 11 | `ALTER TABLE "AverageTodayPrice" ALTER COLUMN "numBuys" DROP DEFAULT` 12 | ); 13 | } 14 | 15 | public async down(queryRunner: QueryRunner): Promise { 16 | await queryRunner.query( 17 | `ALTER TABLE "AverageTodayPrice" ALTER COLUMN "numBuys" SET DEFAULT '0'` 18 | ); 19 | await queryRunner.query(`ALTER TABLE "User" DROP COLUMN "isSiteAdmin"`); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/frontend/src/components/Loader/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { twMerge } from 'tailwind-merge'; 7 | 8 | interface ILoaderProps { 9 | className?: string; 10 | color?: string; // tailwind color, i.e. text-t-1 11 | } 12 | 13 | export default function Loader({ className, color }: ILoaderProps) { 14 | return ( 15 | 16 | 17 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /packages/api/src/app/search/search.controller.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { Controller, Get, Query } from "@nestjs/common"; 7 | import { ApiOperation, ApiTags } from "@nestjs/swagger"; 8 | import { SearchService } from "./search.service"; 9 | import { SearchPageDto } from "./dto/asset-search.dto"; 10 | import { SearchQueryDto } from "./dto/search-query.dto"; 11 | 12 | @ApiTags("Search") 13 | @Controller() 14 | export class SearchController { 15 | constructor(private readonly searchService: SearchService) {} 16 | 17 | @ApiOperation({ 18 | summary: "Looks up the query string in stock and category records", 19 | }) 20 | @Get() 21 | search(@Query() query: SearchQueryDto): Promise { 22 | return this.searchService.search(query.q, query.offset, query.limit); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/postgresql/src/db/migrations/1659663840662-decimalQuantity.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class decimalQuantity1659663840662 implements MigrationInterface { 4 | name = "decimalQuantity1659663840662"; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "CurrentPosition" ALTER COLUMN "quantity" type numeric(40,20)` 9 | ); 10 | await queryRunner.query( 11 | `UPDATE "CurrentPosition" SET "quantity" = "quantity" / 1000` 12 | ); 13 | } 14 | 15 | public async down(queryRunner: QueryRunner): Promise { 16 | await queryRunner.query( 17 | `ALTER TABLE "CurrentPosition" DROP COLUMN "quantity"` 18 | ); 19 | await queryRunner.query( 20 | `ALTER TABLE "CurrentPosition" ADD "quantity" bigint NOT NULL` 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/api/src/app/assets/regenerate/regenerate.controller.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { Controller, ForbiddenException, Post, Query } from "@nestjs/common"; 7 | import { ApiTags } from "@nestjs/swagger"; 8 | import { RegenerateStocksService } from "./regenerate.service"; 9 | import { NoAuth } from "../../../utils/auth"; 10 | 11 | @ApiTags("Assets: Regenerate") 12 | @Controller("regenerate") 13 | export class RegenerateStockInfoController { 14 | constructor(private readonly regenerateService: RegenerateStocksService) {} 15 | 16 | @Post() 17 | @NoAuth() 18 | regenerate(@Query("token") token: string): Promise { 19 | if (token !== process.env.REGENERATE_TOKEN) { 20 | throw new ForbiddenException("Token incorrect"); 21 | } 22 | return this.regenerateService.writeStocks(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/frontend/src/common/PrivateRoute/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { useContext } from 'react'; 7 | import { Navigate, useLocation, useMatch } from 'react-router-dom'; 8 | import { UserContext } from '../../App'; 9 | 10 | interface IPrivateRouteProps { 11 | Component: any; 12 | } 13 | 14 | // Use this HOC to specify if a route requires auth 15 | export default function PrivateRoute({ Component }: IPrivateRouteProps) { 16 | const { user } = useContext(UserContext); 17 | let location = useLocation(); 18 | const createGame = useMatch('/game/create'); 19 | 20 | if (!user) { 21 | return ( 22 | 27 | ); 28 | } 29 | 30 | return ; 31 | } 32 | -------------------------------------------------------------------------------- /packages/postgresql/src/db/migrations/1660667316904-numbuysdecimal.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class numbuysdecimal1660667316904 implements MigrationInterface { 4 | name = "numbuysdecimal1660667316904"; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "AverageTodayPrice" ALTER COLUMN "numBuys" type numeric(40,20)` 9 | ); 10 | await queryRunner.query( 11 | `ALTER TABLE "AverageTotalPrice" ALTER COLUMN "numBuys" type numeric(40,20)` 12 | ); 13 | } 14 | public async down(queryRunner: QueryRunner): Promise { 15 | await queryRunner.query( 16 | `ALTER TABLE "AverageTotalPrice" ALTER COLUMN "numBuys" type int4` 17 | ); 18 | await queryRunner.query( 19 | `ALTER TABLE "AverageTodayPrice" ALTER COLUMN "numBuys" type int4` 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scripts", 3 | "version": "0.0.0", 4 | "description": "Helper script files", 5 | "private": true, 6 | "main": "dist/index.js", 7 | "types": "dist/index.d.ts", 8 | "files": [ 9 | "/dist" 10 | ], 11 | "scripts": { 12 | "build": "npx tsc", 13 | "start:prod": "node dist/index", 14 | "format:check": "prettier --check .", 15 | "format": "prettier --write .", 16 | "lint:check": "eslint \"src/**/*.ts\"", 17 | "lint": "yarn lint:check --fix" 18 | }, 19 | "dependencies": { 20 | "@actions/core": "^1.8.2", 21 | "@actions/github": "^5.0.3", 22 | "aws-sdk": "^2.1138.0", 23 | "node-fetch": "^2.6.7", 24 | "set-interval-async": "^2.0.3" 25 | }, 26 | "devDependencies": { 27 | "eslint": "^8.17.0", 28 | "eslint-config-prettier": "^8.5.0", 29 | "prettier": "2.6.2", 30 | "typescript": "^4.6.4" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/api/src/app/assets/dto/get-quote.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; 7 | import { IsArray, IsBoolean, IsNotEmpty, IsOptional } from "class-validator"; 8 | 9 | export class GetQuoteDto { 10 | @ApiProperty({ 11 | description: "Defines whether ticker is crypto", 12 | }) 13 | @IsNotEmpty() 14 | @IsBoolean() 15 | isCrypto = false; 16 | } 17 | 18 | export class TickersDto { 19 | @ApiProperty({ 20 | description: "Stock tickers", 21 | }) 22 | @IsNotEmpty() 23 | @IsArray() 24 | tickers: string[]; 25 | } 26 | 27 | export class GetBatchQuoteDto extends TickersDto { 28 | @ApiPropertyOptional({ 29 | description: "Defines whether ticker is crypto. Default false.", 30 | }) 31 | @IsBoolean() 32 | @IsOptional() 33 | isCrypto = false; 34 | } 35 | -------------------------------------------------------------------------------- /packages/api/src/app/assets/dto/market-data-with-latest-quotes.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ApiPropertyOptional } from "@nestjs/swagger"; 7 | import { IsArray, IsBoolean, IsOptional } from "class-validator"; 8 | 9 | export class MarketDataWithLatestQuotesDto { 10 | @ApiPropertyOptional({ 11 | description: "A comma-delimited array of stock tickers", 12 | }) 13 | @IsArray() 14 | @IsOptional() 15 | stocks: string[] = []; 16 | 17 | @ApiPropertyOptional({ 18 | description: "A comma-delimited array of crypto tickers", 19 | }) 20 | @IsArray() 21 | @IsOptional() 22 | crypto: string[] = []; 23 | 24 | @ApiPropertyOptional({ 25 | description: 26 | "If set to true, it will append crypto data before stock data. Default false", 27 | }) 28 | @IsOptional() 29 | @IsBoolean() 30 | reverseOrder = false; 31 | } 32 | -------------------------------------------------------------------------------- /packages/frontend/src/App.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | .App { 7 | text-align: center; 8 | } 9 | 10 | .App-logo { 11 | height: 40vmin; 12 | pointer-events: none; 13 | } 14 | 15 | @media (prefers-reduced-motion: no-preference) { 16 | .App-logo { 17 | animation: App-logo-spin infinite 20s linear; 18 | } 19 | } 20 | 21 | .App-header { 22 | background-color: #282c34; 23 | min-height: 100vh; 24 | display: flex; 25 | flex-direction: column; 26 | align-items: center; 27 | justify-content: center; 28 | font-size: calc(10px + 2vmin); 29 | color: white; 30 | } 31 | 32 | .App-link { 33 | color: #61dafb; 34 | } 35 | 36 | @keyframes App-logo-spin { 37 | from { 38 | transform: rotate(0deg); 39 | } 40 | to { 41 | transform: rotate(360deg); 42 | } 43 | } 44 | 45 | button { 46 | font-size: calc(10px + 2vmin); 47 | } 48 | -------------------------------------------------------------------------------- /packages/real-time-collector/src/testing/factory/external/stock-minute-bar.factory.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { MinuteBarData } from "../../../app/minutes/stock/dto/minute-bar-data.dto"; 7 | 8 | export function stockMinuteBarFactory( 9 | data?: Partial 10 | ): MinuteBarData { 11 | if (!data) { 12 | data = {}; 13 | } 14 | return { 15 | ev: "AM", 16 | sym: "AAPL", 17 | v: Math.random() * 100, 18 | av: Math.random() * 100, 19 | vw: Math.random() * 100, 20 | o: Math.random() * 100, 21 | c: Math.random() * 100, 22 | h: Math.random() * 100, 23 | l: Math.random() * 100, 24 | a: Math.random() * 100, 25 | z: Math.random() * 100, 26 | s: Math.random() * 100, 27 | e: Math.random() * 100, 28 | ...data, 29 | }; 30 | } 31 | 32 | export { stockMinuteBarFactory as default }; 33 | -------------------------------------------------------------------------------- /packages/api/src/app/game/dto/order-history-query.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ApiPropertyOptional } from "@nestjs/swagger"; 7 | import { IsInt, IsOptional, Max, Min } from "class-validator"; 8 | import { Type } from "class-transformer"; 9 | 10 | export class OrderHistoryQueryDto { 11 | @ApiPropertyOptional({ 12 | description: "Pass in cursor in query parameter to get next page", 13 | }) 14 | @IsOptional() 15 | after?: string; 16 | 17 | @ApiPropertyOptional({ 18 | description: "Pass in cursor in query parameter to get next page", 19 | }) 20 | @IsOptional() 21 | before?: string; 22 | 23 | @ApiPropertyOptional({ 24 | description: "Limit the number of results", 25 | default: 30, 26 | }) 27 | @IsOptional() 28 | @Type(() => Number) 29 | @IsInt() 30 | @Max(50) 31 | @Min(1) 32 | limit: number = 30; 33 | } 34 | -------------------------------------------------------------------------------- /packages/frontend/src/utils/MathUtils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | namespace MathUtils { 7 | /** 8 | * Rounds a number to whatever number of places you pass in 9 | * 10 | * @param value number to round 11 | * @param places number of places to round to 12 | * @returns 13 | */ 14 | export function round(value: number, places: number): number { 15 | return +(Math.round(Number(String(value) + `e+${places}`)) + `e-${places}`); 16 | } 17 | 18 | /** 19 | * Calculates in percent, the change between 2 numbers. 20 | * e.g from 1000 to 500 = 50% 21 | * 22 | * @param oldVal The initial value 23 | * @param newVal The value that changed 24 | */ 25 | export function percentChange(oldVal: number, newVal: number): number { 26 | return ((newVal - oldVal) / oldVal) * 100; 27 | } 28 | } 29 | 30 | export default MathUtils; 31 | -------------------------------------------------------------------------------- /packages/postgresql/src/db/migrations/1659722004232-isactiveposition.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | export class isactiveposition1659722004232 implements MigrationInterface { 4 | name = "isactiveposition1659722004232"; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | `ALTER TABLE "CurrentPosition" ADD "isActive" boolean NOT NULL DEFAULT true` 9 | ); 10 | await queryRunner.query( 11 | `CREATE INDEX "current_position_is_active_position_index" ON "CurrentPosition" ("stockId", "isActive") ` 12 | ); 13 | } 14 | 15 | public async down(queryRunner: QueryRunner): Promise { 16 | await queryRunner.query( 17 | `DROP INDEX "public"."current_position_is_active_position_index"` 18 | ); 19 | await queryRunner.query( 20 | `ALTER TABLE "CurrentPosition" DROP COLUMN "isActive"` 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/api/src/app/categories/categories.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { Module } from "@nestjs/common"; 7 | import { TypeOrmModule } from "@nestjs/typeorm"; 8 | import { 9 | StockCategoryEntity, 10 | StockCategoryMappingEntity, 11 | StockEntity, 12 | StockPriceEntity, 13 | } from "@bloom-smg/postgresql"; 14 | import { CategoriesService } from "./categories.service"; 15 | import { CategoriesController } from "./categories.controller"; 16 | 17 | const providers = [CategoriesService]; 18 | 19 | @Module({ 20 | imports: [ 21 | TypeOrmModule.forFeature([ 22 | StockCategoryMappingEntity, 23 | StockCategoryEntity, 24 | StockPriceEntity, 25 | StockEntity, 26 | ]), 27 | ], 28 | controllers: [CategoriesController], 29 | providers: providers, 30 | exports: providers, 31 | }) 32 | export class CategoriesModule {} 33 | -------------------------------------------------------------------------------- /packages/scripts/src/actions/ci-test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | /** 7 | * This file is run in GitHub Actions ci.yml 8 | */ 9 | 10 | import * as core from "@actions/core"; 11 | import { checkDockerfiles } from "./ci/check/dockerfile"; 12 | import { checkEnv } from "./ci/check/env"; 13 | import { checkInitPackages } from "./ci/check/init-packages"; 14 | 15 | async function run() { 16 | const tasks: (() => Promise)[] = [ 17 | checkEnv, 18 | checkDockerfiles, 19 | checkInitPackages, 20 | ]; 21 | let shouldExit = false; 22 | for (const task of tasks) { 23 | const taskShouldExit = await task(); 24 | if (!shouldExit) { 25 | shouldExit = taskShouldExit; 26 | } 27 | } 28 | if (shouldExit) { 29 | core.setFailed("Failed"); 30 | } else { 31 | core.info("No errors!"); 32 | } 33 | } 34 | 35 | export default run; 36 | -------------------------------------------------------------------------------- /packages/api/src/app/game/dto/historical-position.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { 7 | HistoricalPositionEntity, 8 | StockPriceEntity, 9 | } from "@bloom-smg/postgresql"; 10 | 11 | export class HistoricalPositionDto { 12 | createdAt: Date; 13 | value: number; 14 | stockId: string; 15 | ticker: string; 16 | stockPrice: number; 17 | playerId: string; 18 | 19 | constructor( 20 | position: HistoricalPositionEntity, 21 | stockPrices: StockPriceEntity[], 22 | playerId: string 23 | ) { 24 | const stockPrice = stockPrices.find((x) => x.id === position.stockId); 25 | this.value = position.value / 1000; 26 | this.createdAt = position.createdAt; 27 | this.stockPrice = stockPrice.price / 1000; 28 | this.stockId = position.stockId; 29 | this.ticker = stockPrice.ticker; 30 | this.playerId = playerId; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/api/src/system/exceptions/BloomExceptionFilter.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ExceptionFilter, Catch, ArgumentsHost, Logger } from "@nestjs/common"; 7 | import { Response } from "express"; 8 | import BloomException from "./BloomException"; 9 | 10 | @Catch(BloomException) 11 | export class BloomExceptionFilter implements ExceptionFilter { 12 | private readonly logger = new Logger(BloomExceptionFilter.name); 13 | 14 | catch(exception: BloomException, host: ArgumentsHost) { 15 | const ctx = host.switchToHttp(); 16 | const response = ctx.getResponse(); 17 | const status = exception.getStatus(); 18 | 19 | const res: any = exception.getResponse(); 20 | this.logger.error( 21 | res.message + " (ID: " + res.uniqueId + ") (type: " + res.type + ")" 22 | ); 23 | 24 | response.status(status).json(res); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/api/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es2021": true, 4 | "node": true, 5 | "jest": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "prettier" 11 | ], 12 | "parser": "@typescript-eslint/parser", 13 | "parserOptions": { 14 | "ecmaVersion": "latest", 15 | "sourceType": "module" 16 | }, 17 | "plugins": ["@typescript-eslint"], 18 | "rules": { 19 | "@typescript-eslint/interface-name-prefix": "off", 20 | "@typescript-eslint/explicit-function-return-type": "off", 21 | "@typescript-eslint/explicit-module-boundary-types": "off", 22 | "@typescript-eslint/no-explicit-any": "off", 23 | "@typescript-eslint/no-namespace": "off", 24 | "@typescript-eslint/ban-ts-comment": "off", 25 | "@typescript-eslint/no-unused-vars": "off", 26 | "@typescript-eslint/no-inferrable-types": "off", 27 | "no-inner-declarations": "off" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/scripts/src/actions/utils/from-dir.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { promises as fs } from "fs"; 7 | import * as path from "path"; 8 | 9 | /** 10 | * @param filter {string} 11 | * @param startPath {string} 12 | * @return {Promise>} returns a list of files 13 | */ 14 | export async function fromDir(filter, startPath) { 15 | if (!(await fs.stat(startPath))) { 16 | return []; 17 | } 18 | 19 | const files = await fs.readdir(startPath); 20 | const fileList = []; 21 | for (let i = 0; i < files.length; i++) { 22 | const filename = path.join(startPath, files[i]); 23 | const stat = await fs.lstat(filename); 24 | if (stat.isDirectory()) { 25 | fileList.push(...(await fromDir(filter, filename))); 26 | } else if (filename.endsWith(filter)) { 27 | fileList.push(filename); 28 | } 29 | } 30 | return fileList; 31 | } 32 | -------------------------------------------------------------------------------- /packages/real-time-collector/src/system/exceptions/BloomExceptionFilter.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ExceptionFilter, Catch, ArgumentsHost, Logger } from "@nestjs/common"; 7 | import { Response } from "express"; 8 | import BloomException from "./BloomException"; 9 | 10 | @Catch(BloomException) 11 | export class BloomExceptionFilter implements ExceptionFilter { 12 | private readonly logger = new Logger(BloomExceptionFilter.name); 13 | 14 | catch(exception: BloomException, host: ArgumentsHost) { 15 | const ctx = host.switchToHttp(); 16 | const response = ctx.getResponse(); 17 | const status = exception.getStatus(); 18 | 19 | const res: any = exception.getResponse(); 20 | this.logger.error( 21 | res.message + " (ID: " + res.uniqueId + ") (type: " + res.type + ")" 22 | ); 23 | 24 | response.status(status).json(res); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/frontend/src/components/CountdownTimer/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import useCountdown from '../../hooks/useCountdown'; 7 | 8 | interface ICountdownTimerProps { 9 | targetDate: string | number | Date; 10 | expiredMessage?: string; 11 | onExpire?: () => Promise; 12 | } 13 | export default function CountdownTimer({ 14 | targetDate, 15 | expiredMessage, 16 | onExpire, 17 | }: ICountdownTimerProps) { 18 | const [days, hours, minutes, seconds] = useCountdown(targetDate, onExpire); 19 | 20 | if (days + hours + minutes + seconds <= 0) { 21 | return
{expiredMessage || 'Expired'}
; 22 | } 23 | 24 | return ( 25 |
26 | {days > 0 ? `${days} days, ` : ''} 27 | {hours > 0 ? `${hours} hours, ` : ''} 28 | {minutes > 0 ? `${minutes} minutes, ` : ''} 29 | {seconds > 0 ? `${seconds} seconds` : ''} 30 |
31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /packages/api/src/app/auth/dto/forgot-password.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ApiProperty } from "@nestjs/swagger"; 7 | import { 8 | IsEmail, 9 | IsNotEmpty, 10 | Matches, 11 | MaxLength, 12 | MinLength, 13 | } from "class-validator"; 14 | 15 | export class CreateForgotPasswordResetTokenDto { 16 | @ApiProperty({ description: "Email address" }) 17 | @IsNotEmpty() 18 | @IsEmail() 19 | email: string; 20 | } 21 | 22 | export class ValidateForgotPasswordResetTokenDto { 23 | @ApiProperty({ description: "Password reset token" }) 24 | @IsNotEmpty() 25 | token: string; 26 | 27 | @ApiProperty({ description: "New password for the user" }) 28 | @IsNotEmpty() 29 | @MinLength(8) 30 | @MaxLength(150) 31 | @Matches(/^(?=.*[A-Za-z])(?=.*\d)[a-zA-Z\d\w\W]{8,}$/, { 32 | message: "Password must contain at least one letter and one number", 33 | }) 34 | newPassword: string; 35 | } 36 | -------------------------------------------------------------------------------- /packages/frontend/src/services/Order/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export enum OrderType { 7 | BUY = 'BUY', 8 | SELL = 'SELL', 9 | } 10 | 11 | export interface CreateOrder { 12 | quantity?: number; 13 | notional?: number; 14 | stockId?: string; // Just need to pass in one or the other (either stockId or ticker, if stockId is passed in it will be prioritized) 15 | ticker?: string; 16 | } 17 | 18 | export interface Order { 19 | name: string; 20 | ticker: string; 21 | image: string; 22 | quantity: number; 23 | boughtAt: number; 24 | currentBuyingPower: number; 25 | id: string; 26 | createdAt: string; 27 | type: OrderType; 28 | status: string; 29 | notional: number; 30 | } 31 | 32 | export interface OrderHistory { 33 | cursor: string; 34 | data: Order[]; 35 | } 36 | 37 | export interface GetOrdersQueryParams { 38 | ticker?: string; 39 | limit?: number; // defaults to 30 40 | } 41 | -------------------------------------------------------------------------------- /packages/api/README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | ## Description 6 | 7 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 8 | 9 | ## Installation 10 | 11 | ```bash 12 | $ yarn install 13 | ``` 14 | 15 | ## Hacking 16 | 17 | You need to start two things: 18 | 19 | The api: 20 | 21 | ```bash 22 | yarn run start:dev 23 | ``` 24 | 25 | You can also use `.user.env` as a gitignored .env file that won't 26 | be checked into VCS. 27 | 28 | ## Test 29 | 30 | ```bash 31 | # unit tests 32 | $ yarn run test 33 | 34 | # e2e tests 35 | $ yarn run test:e2e 36 | 37 | # test coverage 38 | $ yarn run test:cov 39 | ``` 40 | 41 | ## To Test the Production Dockerfile 42 | 43 | If you're in the root directory: 44 | 45 | ```shell 46 | docker build -t bloom/api:latest -f packages/api/Dockerfile . 47 | docker run -p 80:80 -t bloom/api:latest 48 | ``` 49 | -------------------------------------------------------------------------------- /packages/postgresql/src/db/migrations/1658173270596-fixquantity-position.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { MigrationInterface, QueryRunner } from "typeorm"; 7 | 8 | export class fixquantityPosition1658173270596 implements MigrationInterface { 9 | name = "fixquantityPosition1658173270596"; 10 | 11 | public async up(queryRunner: QueryRunner): Promise { 12 | await queryRunner.query( 13 | `ALTER TABLE "CurrentPosition" DROP COLUMN "quantity"` 14 | ); 15 | await queryRunner.query( 16 | `ALTER TABLE "CurrentPosition" ADD "quantity" bigint NOT NULL` 17 | ); 18 | } 19 | 20 | public async down(queryRunner: QueryRunner): Promise { 21 | await queryRunner.query( 22 | `ALTER TABLE "CurrentPosition" DROP COLUMN "quantity"` 23 | ); 24 | await queryRunner.query( 25 | `ALTER TABLE "CurrentPosition" ADD "quantity" double precision NOT NULL` 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/api/src/app/market/dto/get-trading-days.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ApiProperty } from "@nestjs/swagger"; 7 | import { Transform, Type } from "class-transformer"; 8 | import * as moment from "moment"; 9 | import { Moment } from "moment"; 10 | import { IsNotEmpty } from "class-validator"; 11 | 12 | export class GetTradingDaysDto { 13 | @ApiProperty({ 14 | description: "Date formatted", 15 | type: String, 16 | default: new Date(new Date().getTime() - 100000).toISOString(), 17 | }) 18 | @IsNotEmpty() 19 | @Type(() => Date) 20 | @Transform(({ value }) => moment(value), { toClassOnly: true }) 21 | start: Moment; 22 | 23 | @ApiProperty({ 24 | description: "Date formatted", 25 | type: String, 26 | default: new Date().toISOString(), 27 | }) 28 | @IsNotEmpty() 29 | @Type(() => Date) 30 | @Transform(({ value }) => moment(value), { toClassOnly: true }) 31 | end: Moment; 32 | } 33 | -------------------------------------------------------------------------------- /packages/api/src/system/OrmConfig.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { PostgresConnectionOptions } from "typeorm/driver/postgres/PostgresConnectionOptions"; 7 | import AllEntities from "@bloom-smg/postgresql"; 8 | import DbQueryLogger from "./DbQueryLogger"; 9 | 10 | const isCI = process.env.CI === "true"; 11 | const isLocalTest = process.env.NODE_ENV === "test" && !isCI; 12 | 13 | const ormConfig: PostgresConnectionOptions = { 14 | type: "postgres", 15 | username: process.env.PG_USERNAME, 16 | host: process.env.PG_HOST, 17 | database: isLocalTest 18 | ? `test_${process.env.PG_DATABASE}` 19 | : process.env.PG_DATABASE, 20 | password: process.env.PG_PASSWORD, 21 | port: parseInt(process.env.PG_PORT), 22 | synchronize: false, 23 | entities: [...AllEntities], 24 | logger: new DbQueryLogger(), 25 | maxQueryExecutionTime: 300, //it will log all queries that take more than Xms 26 | }; 27 | 28 | export default ormConfig; 29 | -------------------------------------------------------------------------------- /packages/real-time-collector/src/app/minutes/crypto/crypto.service.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { Injectable, Logger } from "@nestjs/common"; 7 | import { AbstractMinutesService } from "../abstract-minutes.service"; 8 | import { MinuteBarData } from "./dto/minute-bar-data.dto"; 9 | 10 | @Injectable() 11 | export class CryptoMinutesService extends AbstractMinutesService { 12 | logger = new Logger(CryptoMinutesService.name); 13 | url = "wss://delayed.polygon.io/crypto"; 14 | 15 | async onConnect(): Promise { 16 | this.socket.send( 17 | JSON.stringify({ 18 | action: "subscribe", 19 | params: "XA.*", 20 | }) 21 | ); 22 | } 23 | 24 | async receive(data) { 25 | for (const x of data) { 26 | if (x.ev === "XA") { 27 | await this.handleNewMinute(x); 28 | } 29 | } 30 | } 31 | 32 | async handleNewMinute(data: MinuteBarData) { 33 | return data; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/api/src/app/orders/dto/order-history.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ApiProperty } from "@nestjs/swagger"; 7 | import { OrderHistoryEntity } from "@bloom-smg/postgresql"; 8 | import { OrderDto } from "./order.dto"; 9 | import { StockPriceEntity } from "@bloom-smg/postgresql"; 10 | 11 | export class OrderHistoryPageDto { 12 | @ApiProperty({ 13 | description: 14 | "Pass in `next` or `before` in query parameter to get next or previous page", 15 | }) 16 | cursor: string; 17 | 18 | data: OrderDto[]; 19 | 20 | constructor( 21 | data: OrderHistoryEntity[], 22 | cursor: string, 23 | stockPrices: StockPriceEntity[] 24 | ) { 25 | this.data = data.map((x) => { 26 | const stockPrice = stockPrices.find( 27 | (price) => price.stockId === x.stockId 28 | ); 29 | return OrderDto.Init(x, x.stock, stockPrice.price, x.value, 0); 30 | }); 31 | this.cursor = cursor; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/api/src/app/search/dto/asset-search.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { StockEntity } from "@bloom-smg/postgresql"; 7 | 8 | class SearchDto { 9 | ticker: string; 10 | name: string; 11 | description: string; 12 | image: string; 13 | latestPrice?: number; 14 | 15 | constructor(stock: StockEntity, prices: { [key: string]: number }) { 16 | this.ticker = stock.ticker; 17 | this.name = stock.name; 18 | this.description = stock.description; 19 | this.image = stock.image; 20 | this.latestPrice = prices[stock.id] || null; // possibility it might not exist yet 21 | } 22 | } 23 | 24 | export class SearchPageDto { 25 | assets: SearchDto[]; 26 | count: number; 27 | 28 | constructor( 29 | stocks: StockEntity[], 30 | prices: { [key: string]: number }, 31 | count: number 32 | ) { 33 | this.assets = stocks.map((stock) => new SearchDto(stock, prices)); 34 | this.count = count; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/real-time-collector/src/app/minutes/crypto/dto/minute-bar-data.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | export interface MinuteBarData { 7 | // https://polygon.io/docs/crypto/ws_crypto_xa 8 | ev: "XA"; 9 | // The crypto pair. 10 | pair: string; 11 | // The open price for this aggregate window. 12 | o: number; 13 | // The close price for this aggregate window. 14 | c: number; 15 | // The high price for this aggregate window. 16 | h: number; 17 | // The low price for this aggregate window. 18 | l: number; 19 | // The volume of trades for this aggregate window. 20 | v: number; 21 | // The timestamp of the starting tick for this aggregate window in Unix Milliseconds. 22 | s: number; 23 | // The timestamp of the ending tick for this aggregate window in Unix Milliseconds. 24 | e: number; 25 | // The volume weighted average price for this aggregate window. 26 | vw: number; 27 | // The average trade size for this aggregate window. 28 | z: number; 29 | } 30 | -------------------------------------------------------------------------------- /packages/frontend/src/hooks/useDashboardNavigate.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { useContext } from 'react'; 7 | import { NavigationItem } from '../modules/Dashboard/Navigation/utils'; 8 | import { useEffect, useState } from 'react'; 9 | import { DashboardContext } from '../modules/Dashboard'; 10 | import { getNavigationItems } from '../modules/Dashboard/Navigation/utils'; 11 | 12 | export default function useDashboardNavigate(): [number, (val: number) => void, NavigationItem[]] { 13 | const { game } = useContext(DashboardContext); 14 | const [active, setActive] = useState(0); 15 | const items = getNavigationItems(game?.isGameAdmin as boolean); 16 | 17 | useEffect(() => { 18 | let navPath = location.pathname.substring(location.pathname.lastIndexOf('/') + 1); 19 | const idx = items.findIndex((item) => { 20 | return item.to === navPath; 21 | }); 22 | setActive(idx); 23 | }, [location.pathname]); 24 | 25 | return [active, setActive, items]; 26 | } 27 | -------------------------------------------------------------------------------- /packages/frontend/src/modules/PasswordReset/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { Navigate, Route, Routes, useMatch } from 'react-router-dom'; 7 | import Header from '../Header'; 8 | import ForgotPassword from './ForgotPassword'; 9 | import ResetPassword from './ResetPassword'; 10 | 11 | export default function PasswordReset() { 12 | const empty = useMatch('/password'); 13 | 14 | if (empty) { 15 | return ; 16 | } 17 | 18 | return ( 19 |
20 |
21 |
22 | 23 | } /> 24 | } /> 25 | } /> 26 | 27 |
28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /packages/api/src/app/orders/dto/order-history-query.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import { ApiPropertyOptional } from "@nestjs/swagger"; 7 | import { IsInt, IsOptional, Max, Min } from "class-validator"; 8 | import { Type } from "class-transformer"; 9 | 10 | export class OrderHistoryQueryDto { 11 | @ApiPropertyOptional({ 12 | description: "Pass in cursor in query parameter to get next page", 13 | }) 14 | @IsOptional() 15 | after?: string; 16 | 17 | @ApiPropertyOptional({ 18 | description: "Pass in cursor in query parameter to get next page", 19 | }) 20 | @IsOptional() 21 | before?: string; 22 | 23 | @ApiPropertyOptional({ 24 | description: "Limit the number of results", 25 | default: 30, 26 | }) 27 | @IsOptional() 28 | @Type(() => Number) 29 | @IsInt() 30 | @Max(80) 31 | @Min(1) 32 | limit: number = 30; 33 | 34 | @IsOptional() 35 | @ApiPropertyOptional({ 36 | description: "Pass in ticker to search order history by", 37 | }) 38 | ticker?: string; 39 | } 40 | -------------------------------------------------------------------------------- /packages/scripts/src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Contour Labs, Inc. 3 | * SPDX-License-Identifier: AGPL-3.0-only 4 | */ 5 | 6 | import * as core from "@actions/core"; 7 | import CITest from "./actions/ci-test"; 8 | import PreDeploy from "./actions/pre-deploy"; 9 | import PreDeployFrontend from "./actions/pre-deploy-frontend"; 10 | import Deploy from "./actions/deploy"; 11 | import PostDeploy from "./actions/post-deploy"; 12 | import CopyParameterStoreValues from "./aws/copy.parameterstore"; 13 | 14 | async function main() { 15 | const args = process.argv.slice(2); 16 | if (args.length === 0) { 17 | return; 18 | } 19 | const actions = { 20 | "ci-test": CITest, 21 | "pre-deploy": PreDeploy, 22 | "pre-deploy-frontend": PreDeployFrontend, 23 | deploy: Deploy, 24 | "post-deploy": PostDeploy, 25 | "aws-parameter-store-copy": CopyParameterStoreValues, 26 | }; 27 | const fn = actions[args[0]]; 28 | if (fn === null || fn === undefined) { 29 | core.setFailed("Invalid script name"); 30 | return; 31 | } 32 | await fn(...args.slice(1)); 33 | } 34 | 35 | main(); 36 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Getting Started 2 | 3 | 1. Environment Setup 4 | 5 | This project requires node version `17.6.0` (our legacy repo needs `14.x.x`) + `yarn`. If you are using `nvm`: 6 | 7 | ```shell 8 | nvm install 9 | nvm use 10 | # close and reopen shell 11 | npm install --global yarn 12 | ``` 13 | 14 | 2. Lerna Setup 15 | 16 | ```shell 17 | yarn global add lerna 18 | lerna bootstrap 19 | lerna run build 20 | ``` 21 | 22 | The command `lerna run