├── startup.sh ├── data-layer ├── tests │ ├── __init__.py │ └── test_data_layer.py ├── data_layer │ ├── __init__.py │ ├── models │ │ ├── __init__.py │ │ ├── base.py │ │ └── notes.py │ └── settings.py ├── migrations │ ├── README │ ├── script.py.mako │ ├── versions │ │ └── 0514e3418c6b_added_notes_table.py │ └── env.py ├── .gitignore ├── poetry.toml ├── README.rst ├── pyproject.toml └── alembic.ini ├── packages ├── components │ ├── .gitignore │ ├── types │ │ ├── ui-elements │ │ │ └── icons.d.ts │ │ ├── bootstrap.d.ts │ │ ├── index.d.ts │ │ └── theme.d.ts │ ├── src │ │ ├── utils │ │ │ ├── index.d.ts │ │ │ └── functions.ts │ │ ├── stories │ │ │ └── button.stories.tsx │ │ ├── bootstrap.tsx │ │ ├── index.ts │ │ ├── ui-elements │ │ │ └── SizeDetector.ts │ │ └── theme.ts │ ├── tsconfig.json │ ├── package.json │ ├── tests │ │ └── index.ts │ └── readme.md ├── common │ ├── src │ │ ├── testing │ │ │ ├── index.ts │ │ │ └── db-suite.ts │ │ └── middleware │ │ │ ├── index.ts │ │ │ └── logging.ts │ ├── tsconfig.json │ └── package.json └── www │ ├── next-env.d.ts │ ├── public │ ├── favicon.ico │ └── vercel.svg │ ├── pages │ ├── api │ │ └── hello.js │ └── index.tsx │ ├── package.json │ ├── tsconfig.json │ ├── .gitignore │ ├── src │ └── index.ts │ ├── README.md │ ├── utils │ └── autotrader.ts │ └── tests │ └── index.ts ├── .dockerignore ├── .gitmodules ├── .huskyrc ├── services └── example │ ├── src │ ├── env.ts │ ├── index.ts │ ├── app.ts │ └── notes.ts │ ├── tsconfig.json │ ├── prisma │ └── schema.prisma │ ├── package.json │ └── tests │ └── notes.ts ├── .storybook ├── main.js └── preview.tsx ├── .lintstagedrc ├── lerna.json ├── .changeset ├── eighty-pets-yawn.md ├── config.json └── README.md ├── .editorconfig ├── .vscode ├── settings.json └── launch.json ├── service-tsconfig.json ├── tsconfig.json ├── docker-compose.yml ├── .eslintrc ├── ops └── Dockerfile-example ├── .gitignore ├── .github └── workflows │ └── ci.yml ├── server-package.json ├── package.json └── README.md /startup.sh: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data-layer/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/components/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules? 2 | */*/dist 3 | dist? 4 | -------------------------------------------------------------------------------- /data-layer/data_layer/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.0' 2 | -------------------------------------------------------------------------------- /data-layer/data_layer/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .notes import * 2 | -------------------------------------------------------------------------------- /data-layer/migrations/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /data-layer/.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.pyc 3 | *__pycache__ 4 | startup.sh -------------------------------------------------------------------------------- /data-layer/poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | create = true 3 | in_project = true -------------------------------------------------------------------------------- /packages/common/src/testing/index.ts: -------------------------------------------------------------------------------- 1 | export { dbSuite } from "./db-suite"; 2 | -------------------------------------------------------------------------------- /packages/common/src/middleware/index.ts: -------------------------------------------------------------------------------- 1 | export { logging } from "./logging"; 2 | -------------------------------------------------------------------------------- /data-layer/README.rst: -------------------------------------------------------------------------------- 1 | Data Layer Documentation 2 | ======================== 3 | 4 | -------------------------------------------------------------------------------- /packages/www/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /data-layer/data_layer/models/base.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.ext.declarative import declarative_base 2 | 3 | Base = declarative_base() -------------------------------------------------------------------------------- /packages/www/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tuteria/Frontend-Assessment/HEAD/packages/www/public/favicon.ico -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "enhanced_savings"] 2 | path = enhanced_savings 3 | url = git@github.com:gbozee/enhanced_savings.git 4 | -------------------------------------------------------------------------------- /.huskyrc: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", 4 | "pre-commit": "lint-staged" 5 | } 6 | } -------------------------------------------------------------------------------- /data-layer/tests/test_data_layer.py: -------------------------------------------------------------------------------- 1 | from data_layer import __version__ 2 | 3 | 4 | def test_version(): 5 | assert __version__ == '0.1.0' 6 | -------------------------------------------------------------------------------- /services/example/src/env.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | 3 | import { config } from "dotenv"; 4 | 5 | config({ path: resolve(__dirname, "../.env") }); 6 | -------------------------------------------------------------------------------- /packages/components/types/ui-elements/icons.d.ts: -------------------------------------------------------------------------------- 1 | interface Icons { 2 | [key: string]: any; 3 | } 4 | declare const icons: Icons; 5 | export default icons; 6 | -------------------------------------------------------------------------------- /packages/components/types/bootstrap.d.ts: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from "react"; 2 | declare const ZetaProvider: FunctionComponent; 3 | export default ZetaProvider; 4 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ["../packages/**/*.stories.tsx"], 3 | addons: [ 4 | "@storybook/addon-actions", 5 | "@storybook/addon-links", 6 | ], 7 | }; 8 | -------------------------------------------------------------------------------- /packages/components/src/utils/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "json-groupby" { 2 | function groupBy(array: Array, key: Array): any {} 3 | export default groupBy; 4 | } 5 | -------------------------------------------------------------------------------- /packages/www/pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | export default (req, res) => { 4 | res.statusCode = 200 5 | res.json({ name: 'John Doe' }) 6 | } 7 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "**/*.{ts,tsx,js,jsx}": [ 3 | "prettier --write", 4 | "eslint --fix", 5 | "tsc --noEmit" 6 | ], 7 | "**/*.{md,yml,json,babelrc,eslintrc,prettierrc}": [ 8 | "prettier --write" 9 | ] 10 | } -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["packages/*", "services/*"], 3 | "version": "0.0.0", 4 | "npmClient": "yarn", 5 | "useWorkspaces": true, 6 | "command": { 7 | "run": { 8 | "npmClient": "yarn" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.changeset/eighty-pets-yawn.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@tuteria/components": major 3 | "@tuteria/video-mobile-app": minor 4 | "@tuteria/video-web-app": minor 5 | --- 6 | 7 | The initial setup and structure of the repository for frontend and mobile development. 8 | -------------------------------------------------------------------------------- /data-layer/data_layer/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 4 | ROOT_DIR = os.path.join(BASE_DIR, "..", "..", "example.db") 5 | DATABASE_URL = os.getenv("DATABASE_URL", f"sqlite:///{ROOT_DIR}") 6 | print(DATABASE_URL) 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = tab 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.{json,yml,md}] 13 | indent_style = space 14 | -------------------------------------------------------------------------------- /packages/components/types/index.d.ts: -------------------------------------------------------------------------------- 1 | export declare function sum(...nums: number[]): number; 2 | export declare function substract(...nums: number[]): number; 3 | export declare function average(...nums: number[]): number; 4 | export { default as ZetaProvider } from "./bootstrap"; 5 | -------------------------------------------------------------------------------- /.storybook/preview.tsx: -------------------------------------------------------------------------------- 1 | import { ZetaProvider } from "@tuteria/components"; 2 | import { addDecorator } from "@storybook/react"; 3 | import * as React from "react"; 4 | 5 | addDecorator((StoryFn: Function) => ( 6 | 7 | 8 | 9 | )); 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.formatting.provider": "black", 3 | "restructuredtext.confPath": "", 4 | "editor.tabSize": 2, 5 | "files.watcherExclude": { 6 | "**/.git/objects/**": true, 7 | "**/.git/subtree-cache/**": true, 8 | "**/.hg/store/**": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.3.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "linked": [], 6 | "access": "restricted", 7 | "baseBranch": "scaffold", 8 | "updateInternalDependencies": "patch", 9 | "ignore": [] 10 | } 11 | -------------------------------------------------------------------------------- /packages/www/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tuteria/www", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "test": "uvu -r ts-node/register test" 10 | }, 11 | "dependencies": { 12 | "next": "9.4.4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../service-tsconfig.json", 3 | "ts-node": { 4 | "transpileOnly": true, 5 | "compilerOptions": { 6 | "module": "commonjs", 7 | "sourceMap": true 8 | }, 9 | "include": ["tests/**/*"] 10 | }, 11 | "include": ["@types", "src"], 12 | "exclude": ["node_modules"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/components/types/theme.d.ts: -------------------------------------------------------------------------------- 1 | import { DefaultTheme } from "@chakra-ui/core"; 2 | export interface Theme extends DefaultTheme { 3 | borders: any; 4 | shadows: any; 5 | radii: any; 6 | colors: any; 7 | borderWidths: any; 8 | borderRadius: any; 9 | } 10 | declare const theme: Theme; 11 | export default theme; 12 | -------------------------------------------------------------------------------- /packages/components/src/stories/button.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { action } from "@storybook/addon-actions"; 3 | import { Button } from "@storybook/react/demo"; 4 | 5 | export default { 6 | title: "Components/Button", 7 | }; 8 | 9 | export const Text = () => ( 10 | 11 | ); 12 | 13 | -------------------------------------------------------------------------------- /services/example/src/index.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import notes from "./notes"; 3 | import * as bodyParser from "body-parser"; 4 | import { logging as httpLogger } from "@tuteria/common/src/middleware"; 5 | 6 | const app = express(); 7 | app.use(bodyParser.json()); 8 | app.use(httpLogger); 9 | 10 | app.use("/notes", notes); 11 | 12 | export default app; 13 | -------------------------------------------------------------------------------- /packages/www/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "strict": false, 6 | "noEmit": true, 7 | "isolatedModules": true, 8 | "jsx": "preserve" 9 | }, 10 | "exclude": ["node_modules", "**/**/*.stories.tsx", "src/stories"], 11 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"] 12 | } 13 | -------------------------------------------------------------------------------- /services/example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../service-tsconfig.json", 3 | "ts-node": { 4 | "transpileOnly": true, 5 | "compilerOptions": { 6 | "module": "commonjs", 7 | "sourceMap": true 8 | }, 9 | "include": ["tests/**/*"] 10 | }, 11 | "include": ["@types", "src","../../node_modules/@tuteria/common"], 12 | "exclude": ["tests"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/components/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "ts-node": { 4 | "transpileOnly": true, 5 | "compilerOptions": { 6 | "module": "commonjs", 7 | "sourceMap": true 8 | }, 9 | "include": ["tests/**/*"] 10 | }, 11 | "include": ["@types", "src"], 12 | "exclude": ["node_modules", "**/**/*.stories.tsx", "src/stories"] 13 | } 14 | -------------------------------------------------------------------------------- /services/example/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "sqlite" 7 | url = "file:./../../../example.db" 8 | } 9 | 10 | model alembic_version { 11 | version_num String @id 12 | } 13 | 14 | model notes { 15 | description String? 16 | id Int @default(autoincrement()) @id 17 | title String? 18 | } 19 | -------------------------------------------------------------------------------- /data-layer/data_layer/models/notes.py: -------------------------------------------------------------------------------- 1 | from data_layer.models.base import Base 2 | import sqlalchemy as sql 3 | 4 | 5 | class Notes(Base): 6 | __tablename__ = "notes" 7 | id = sql.Column(sql.Integer, primary_key=True) 8 | title = sql.Column(sql.String) 9 | description = sql.Column(sql.Text) 10 | 11 | def __repr__(self): 12 | return f"" 13 | 14 | -------------------------------------------------------------------------------- /services/example/src/app.ts: -------------------------------------------------------------------------------- 1 | import "./env"; 2 | import { PrismaClient } from "@prisma/client"; 3 | import app from "."; 4 | 5 | const prisma = new PrismaClient(); 6 | 7 | app.locals.prisma = prisma; 8 | let PORT = 3000; 9 | if (process.env.PORT !== undefined) { 10 | PORT = parseInt(process.env.PORT, 10); 11 | } 12 | app.listen(PORT, (err: Error) => { 13 | if (err) throw err; 14 | console.log(`> Running on localhost:${PORT}`); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.1", 3 | "name": "@tuteria/common", 4 | "umd:name": "@tuteria/common", 5 | "scripts": { 6 | "build": "tsc --module commonjs --outDir dist --declaration false", 7 | "lint:src": "eslint src --ext .ts,.tsx --config ../../.eslintrc", 8 | "lint:types": "tsc --noEmit", 9 | "lint": "npm-run-all lint:src lint:types", 10 | "test": "uvu -r ts-node/register test" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/components/src/bootstrap.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { FunctionComponent } from "react"; 3 | import theme from "./theme"; 4 | import { ThemeProvider, CSSReset } from "@chakra-ui/core"; 5 | 6 | const ZetaProvider: FunctionComponent = ({ children }) => { 7 | return ( 8 | 9 | 10 | {children} 11 | 12 | ); 13 | }; 14 | export default ZetaProvider; 15 | -------------------------------------------------------------------------------- /service-tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "skipLibCheck": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true, 9 | "resolveJsonModule": true, 10 | "moduleResolution": "node", 11 | "esModuleInterop": true, 12 | "sourceMap": true 13 | }, 14 | 15 | "exclude": ["node_modules", "typings/*"] 16 | } 17 | -------------------------------------------------------------------------------- /services/example/src/notes.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | import { Router } from "express"; 3 | 4 | const router = Router(); 5 | 6 | router.post("/create", async (req, res) => { 7 | const prisma: PrismaClient = req.app.locals.prisma; 8 | const { description, title } = req.body; 9 | const result = await prisma.notes.create({ 10 | data: { description, title }, 11 | }); 12 | res.status(200).json(result); 13 | }); 14 | 15 | export default router; 16 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Attach", 9 | "port": 9229, 10 | "request": "attach", 11 | "skipFiles": ["/**"], 12 | "type": "pwa-node" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "esnext", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "allowSyntheticDefaultImports": true, 9 | "resolveJsonModule": true, 10 | "moduleResolution": "node", 11 | "strict": true, 12 | "jsx": "react", 13 | "lib": ["dom", "dom.iterable", "esnext"], 14 | "esModuleInterop": true 15 | }, 16 | "exclude": ["node_modules"] 17 | } 18 | -------------------------------------------------------------------------------- /packages/www/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # local env files 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | -------------------------------------------------------------------------------- /data-layer/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "data-layer" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Beesama "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.6" 9 | sqlalchemy = "^1.3.18" 10 | alembic = "^1.4.2" 11 | psycopg2-binary = "^2.8.5" 12 | 13 | [tool.poetry.dev-dependencies] 14 | pytest = "^4.6" 15 | black = "^19.10b0" 16 | ipython = "^7.16.1" 17 | pdbpp = "^0.10.2" 18 | 19 | [build-system] 20 | requires = ["poetry>=0.12"] 21 | build-backend = "poetry.masonry.api" 22 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /packages/www/src/index.ts: -------------------------------------------------------------------------------- 1 | export function sum(...nums: number[]): number { 2 | let i = 0, 3 | total = 0; 4 | for (; i < nums.length; i++) total += nums[i]; 5 | return total; 6 | } 7 | 8 | export function substract(...nums: number[]): number { 9 | let i = 0, 10 | total = nums[i++] | 0; 11 | for (; i < nums.length; i++) total -= nums[i]; 12 | return total; 13 | } 14 | 15 | export function average(...nums: number[]): number { 16 | let i = 0, 17 | len = nums.length, 18 | total = 0; 19 | for (; i < len; i++) total += nums[i]; 20 | return total / len; 21 | } 22 | -------------------------------------------------------------------------------- /data-layer/migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /packages/components/src/index.ts: -------------------------------------------------------------------------------- 1 | export function sum(...nums: number[]): number { 2 | let i = 0, 3 | total = 0; 4 | for (; i < nums.length; i++) total += nums[i]; 5 | return total; 6 | } 7 | 8 | export function substract(...nums: number[]): number { 9 | let i = 0, 10 | total = nums[i++] | 0; 11 | for (; i < nums.length; i++) total -= nums[i]; 12 | return total; 13 | } 14 | 15 | export function average(...nums: number[]): number { 16 | const len = nums.length; 17 | let i = 0, 18 | total = 0; 19 | for (; i < len; i++) total += nums[i]; 20 | return total / len; 21 | } 22 | export { default as ZetaProvider } from "./bootstrap"; 23 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.1" 2 | 3 | volumes: 4 | postgres_data_dev: {} 5 | postgres_backup_dev: {} 6 | webserver_vol: {} 7 | 8 | services: 9 | app: 10 | build: 11 | context: . 12 | dockerfile: ops/Dockerfile-example 13 | ports: 14 | - "0.0.0.0:9007:3000" 15 | # command: uvicorn index:app 16 | 17 | postgres: 18 | image: sameersbn/postgresql:9.6-2 19 | volumes: 20 | - postgres_data_dev:/var/lib/postgresql/data 21 | - postgres_backup_dev:/backups 22 | restart: always 23 | environment: 24 | DB_USER: test_db 25 | DB_PASS: test_db 26 | DB_NAME: test_db 27 | ports: 28 | - "5529:5432" 29 | 30 | 31 | -------------------------------------------------------------------------------- /packages/components/src/ui-elements/SizeDetector.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | const isServer = typeof window === "undefined"; 3 | function defaultMatch(query: string): boolean | undefined { 4 | if (isServer) { 5 | return undefined; 6 | } 7 | return window.matchMedia(query).matches; 8 | } 9 | export const useMedia = (query: string) => { 10 | const [matches, setMatches] = useState(defaultMatch(query)); 11 | 12 | useEffect(() => { 13 | if (!isServer) { 14 | const media = window.matchMedia(query); 15 | const listener = () => setMatches(media.matches); 16 | media.addListener(listener); 17 | listener(); 18 | return () => media.removeListener(listener); 19 | } 20 | }, [query]); 21 | 22 | return matches; 23 | }; 24 | -------------------------------------------------------------------------------- /services/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.1", 3 | "name": "@tuteria/example-service", 4 | "scripts": { 5 | "build": "tsc --module commonjs --outDir dist --declaration false", 6 | "watch": "tsc --module commonjs --outDir dist --declaration false --watch", 7 | "start:dev": "cross-env NODE_ENV=development nodemon dist/app.js", 8 | "test": "cross-env DATABASE_URL=$TEST_DATABASE_URL uvu -r ts-node/register tests", 9 | "test:raw": "uvu -r ts-node/register", 10 | "test:debug": "node --inspect ../../node_modules/uvu/bin.js -r ts-node/register tests", 11 | "db:update": "prisma introspect", 12 | "db:generate": "prisma generate", 13 | "lint:src": "eslint src --ext .ts --config ../../.eslintrc", 14 | "lint:types": "tsc --noEmit", 15 | "lint": "npm-run-all lint:src lint:types" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/common/src/testing/db-suite.ts: -------------------------------------------------------------------------------- 1 | import { suite } from "uvu"; 2 | import * as core from "express-serve-static-core"; 3 | import { PrismaClient } from "@prisma/client"; 4 | 5 | function getDataSource( 6 | name: string, 7 | url: string, 8 | provider: string | null | undefined = "sqlite" 9 | ) { 10 | return { 11 | [name]: { 12 | provider, 13 | url, 14 | }, 15 | }; 16 | } 17 | export function dbSuite( 18 | text: string, 19 | app: core.Express, 20 | db_path: string, 21 | provider: string | null | undefined 22 | ) { 23 | const suiteName = suite(text); 24 | async function beforeCallback() { 25 | const prisma = new PrismaClient({ 26 | datasources: getDataSource("db", db_path, provider), 27 | }); 28 | app.locals.prisma = prisma; 29 | return prisma; 30 | } 31 | 32 | return { suiteName, beforeCallback }; 33 | } 34 | -------------------------------------------------------------------------------- /data-layer/migrations/versions/0514e3418c6b_added_notes_table.py: -------------------------------------------------------------------------------- 1 | """Added notes table 2 | 3 | Revision ID: 0514e3418c6b 4 | Revises: 5 | Create Date: 2020-08-20 10:17:31.453707 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '0514e3418c6b' 14 | down_revision = None 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('notes', 22 | sa.Column('id', sa.Integer(), nullable=False), 23 | sa.Column('title', sa.String(), nullable=True), 24 | sa.Column('description', sa.Text(), nullable=True), 25 | sa.PrimaryKeyConstraint('id') 26 | ) 27 | # ### end Alembic commands ### 28 | 29 | 30 | def downgrade(): 31 | # ### commands auto generated by Alembic - please adjust! ### 32 | op.drop_table('notes') 33 | # ### end Alembic commands ### 34 | -------------------------------------------------------------------------------- /packages/www/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /packages/common/src/middleware/logging.ts: -------------------------------------------------------------------------------- 1 | import { createLogger, transports, format } from "winston"; 2 | import morgan from "morgan"; 3 | import { FileTransportOptions } from "winston/lib/winston/transports"; 4 | 5 | interface InfoConfig { 6 | timestamp?: string; 7 | level?: string; 8 | message?: string; 9 | } 10 | interface Extender extends FileTransportOptions { 11 | json?: boolean; 12 | } 13 | const logger = createLogger({ 14 | format: format.combine( 15 | format.timestamp({ format: "YYYY-MM-DD HH:mm:ss:ms" }), 16 | format.printf( 17 | (info: InfoConfig) => `${info.timestamp} ${info.level}: ${info.message}` 18 | ) 19 | ), 20 | transports: [ 21 | new transports.File({ 22 | filename: "./logs/all-logs.log", 23 | json: false, 24 | maxsize: 5242880, 25 | maxFiles: 5, 26 | } as Extender), 27 | new transports.Console(), 28 | ], 29 | }); 30 | 31 | logger.stream = { 32 | write: (message: string) => 33 | logger.info(message.substring(0, message.lastIndexOf("\n"))), 34 | } as any; 35 | 36 | export const logging = morgan( 37 | ":method :url :status :response-time ms - :res[content-length]", 38 | { stream: (logger.stream as unknown) as morgan.StreamOptions } 39 | ); 40 | -------------------------------------------------------------------------------- /packages/www/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 16 | 17 | ## Learn More 18 | 19 | To learn more about Next.js, take a look at the following resources: 20 | 21 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 22 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 23 | 24 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 25 | 26 | ## Deploy on Vercel 27 | 28 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 29 | 30 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 31 | -------------------------------------------------------------------------------- /packages/www/utils/autotrader.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | const API_URL = "http://backup.tuteria.com:5020"; 3 | 4 | interface Position { 5 | symbol?: string; 6 | entry?: number; 7 | margin?: number; 8 | leverage: number; 9 | margin_type: "isolated" | "cross"; 10 | kind: "long" | "short"; 11 | isolatedMargin: number; 12 | markPrice: number; 13 | pnl: number; 14 | [key: string]: any; 15 | } 16 | 17 | class Fetcher { 18 | owner: string; 19 | constructor(owner: string) { 20 | this.owner = owner; 21 | } 22 | buildUrl(path: string) { 23 | return `${API_URL}/ft/${this.owner}${path}`; 24 | } 25 | async getOpenPositions(): Promise> { 26 | const url = this.buildUrl("/active-markets"); 27 | const response = await fetch(url); 28 | if (response.status < 400) { 29 | const result = await response.json(); 30 | return result.data; 31 | } 32 | } 33 | async getOpenOrders() {} 34 | } 35 | class Market { 36 | position: Position; 37 | orders: Array = []; 38 | constructor(position: Position) { 39 | this.position = position; 40 | } 41 | } 42 | class Trader { 43 | symbol: string; 44 | fetcher: Fetcher; 45 | interval: number; // interval to check current position. 46 | constructor(symbol: string, fetcher: Fetcher, updateInterval = 20) { 47 | this.symbol = symbol; 48 | this.fetcher = fetcher; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.1", 3 | "name": "@tuteria/components", 4 | "umd:name": "@tuteria/components", 5 | "description": "Npm package housing internal components used in building the application", 6 | "module": "dist/esm/index.js", 7 | "main": "dist/cjs/index.js", 8 | "types": "dist/types/index.d.ts", 9 | "license": "MIT", 10 | "author": { 11 | "name": "Tuteria", 12 | "email": "dev@tuteria.com", 13 | "url": "https://www.tuteria.com" 14 | }, 15 | "files": [ 16 | "dist", 17 | "types" 18 | ], 19 | "engines": { 20 | "node": ">= 10" 21 | }, 22 | "scripts": { 23 | "build:esm": "tsc --module es2015 --outDir dist/esm --declaration false", 24 | "build:cjs": "tsc --module commonjs --outDir dist/cjs --declaration false", 25 | "build:types": "tsc --emitDeclarationOnly --declaration true --declarationDir dist/types", 26 | "build": "npm-run-all build:esm build:cjs build:types", 27 | "lint:src": "eslint src --ext .ts,.tsx --config ../../.eslintrc", 28 | "lint:types": "tsc --noEmit", 29 | "lint": "npm-run-all lint:src lint:types", 30 | "test": "uvu -r ts-node/register test" 31 | }, 32 | "keywords": [ 33 | "TODO", 34 | "module", 35 | "keywords" 36 | ], 37 | "peerDependencies": { 38 | "react": "16.13.1", 39 | "react-dom": "16.13.1" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /services/example/tests/notes.ts: -------------------------------------------------------------------------------- 1 | import App from "../src"; 2 | import "../src/env"; 3 | import request from "supertest"; 4 | import * as assert from "uvu/assert"; 5 | import "hard-rejection/register"; 6 | import { PrismaClient } from "@prisma/client"; 7 | import { suite } from "uvu"; 8 | 9 | 10 | console.log(process.env.TEST_DATABASE_URL); 11 | const Notes = suite("Notes API"); 12 | 13 | 14 | Notes.before(async (context) => { 15 | // context.prisma = await beforeCallback(); 16 | context.prisma = new PrismaClient(); 17 | App.locals.prisma = context.prisma; 18 | await context.prisma.queryRaw("DELETE from notes;"); 19 | }); 20 | 21 | Notes.after(async (context) => { 22 | await context.prisma.queryRaw("DELETE from notes;"); 23 | const count = await context.prisma.notes.count(); 24 | assert.is(count, 0); 25 | }); 26 | 27 | Notes("Create endpoint works as expected", async (context) => { 28 | await request(App) 29 | .post("/notes/create") 30 | .send({ 31 | title: "Sample notes", 32 | description: "This is a sample description", 33 | }) 34 | .set("Accept", "application/json") 35 | .expect("Content-Type", /json/) 36 | .then((response) => { 37 | assert.is(response.body.title, "Sample notes"); 38 | }); 39 | const count = await context.prisma.notes.count(); 40 | assert.is(count, 1); 41 | }); 42 | 43 | Notes.run(); 44 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaFeatures": { 5 | "jsx": true 6 | } 7 | }, 8 | "plugins": ["@typescript-eslint", "react-hooks"], 9 | "extends": [ 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended" 12 | ], 13 | "rules": { 14 | "@typescript-eslint/explicit-module-boundary-types": "off", 15 | "@typescript-eslint/explicit-function-return-type": "off", 16 | "@typescript-eslint/no-empty-interface": "off", 17 | "@typescript-eslint/camelcase": "off", 18 | "@typescript-eslint/no-explicit-any": "off", 19 | "@typescript-eslint/ban-ts-ignore": "off", 20 | "@typescript-eslint/no-unused-vars": "off", 21 | "@typescript-eslint/no-use-before-define": "off", 22 | "@typescript-eslint/no-empty-function": "off", 23 | "@typescript-eslint/indent": "off", 24 | "@typescript-eslint/array-callback-return": "off", 25 | "@typescript-eslint/array-type": "off", 26 | "react-hooks/rules-of-hooks": "error", 27 | "react-hooks/exhaustive-deps": "warn", 28 | "@typescript-eslint/member-delimiter-style": "off", 29 | "@typescript-eslint/no-var-requires": "off", 30 | "@typescript-eslint/ban-types": "off", 31 | "@typescript-eslint/ban-ts-comment": "off" 32 | } 33 | } -------------------------------------------------------------------------------- /ops/Dockerfile-example: -------------------------------------------------------------------------------- 1 | FROM node:12-buster-slim AS base 2 | 3 | RUN apt-get update && apt-get install --no-install-recommends --yes openssl 4 | 5 | WORKDIR /app 6 | 7 | ### BUILDER ### 8 | FROM base AS builder 9 | 10 | # Install production dependencies 11 | ADD server-package.json ./package.json 12 | COPY tsconfig.json service-tsconfig.json lerna.json yarn.lock ./ 13 | COPY packages/common/*.json ./packages/common/ 14 | COPY services/example/*.json ./packages/example/ 15 | 16 | RUN yarn install --production --pure-lockfile 17 | 18 | RUN cp -RL node_modules/ /tmp/node_modules/ 19 | 20 | # Install all dependencies 21 | RUN yarn install --pure-lockfile 22 | 23 | # Copy source files 24 | COPY packages/common/ ./packages/common/ 25 | COPY services/example/ ./packages/example/ 26 | 27 | # Build 28 | RUN yarn --cwd ./packages/common/ build 29 | RUN yarn --cwd ./packages/example/ db:generate 30 | RUN yarn --cwd ./packages/example/ build 31 | 32 | ### RUNNER ### 33 | FROM base 34 | 35 | # Copy runtime dependencies 36 | COPY --from=builder /tmp/node_modules/ ./node_modules/ 37 | # COPY --from=builder /app/packages/backend/node_modules/@prisma/client/ ./node_modules/@prisma/client/ 38 | COPY --from=builder /app/node_modules/.prisma/client/ ./node_modules/.prisma/client/ 39 | # COPY --from=builder /app/packages/example/node_modules/.prisma/client/ ./node_modules/.prisma/client/ 40 | COPY --from=builder /app/packages/common/dist/ ./node_modules/@tuteria/common/src/ 41 | 42 | # Copy runtime project 43 | COPY --from=builder /app/packages/example/dist/ ./src/ 44 | COPY services/example/package.json ./ 45 | RUN mkdir logs 46 | 47 | USER node 48 | 49 | CMD ["node", "src/app.js"] 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | node_modules 107 | .DS_Store 108 | *-lock.* 109 | *.lock 110 | *.log 111 | 112 | /dist 113 | /types 114 | *.env 115 | *.db -------------------------------------------------------------------------------- /data-layer/alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # path to migration scripts 5 | script_location = migrations 6 | 7 | # template used to generate migration files 8 | # file_template = %%(rev)s_%%(slug)s 9 | 10 | # timezone to use when rendering the date 11 | # within the migration file as well as the filename. 12 | # string value is passed to dateutil.tz.gettz() 13 | # leave blank for localtime 14 | # timezone = 15 | 16 | # max length of characters to apply to the 17 | # "slug" field 18 | # truncate_slug_length = 40 19 | 20 | # set to 'true' to run the environment during 21 | # the 'revision' command, regardless of autogenerate 22 | # revision_environment = false 23 | 24 | # set to 'true' to allow .pyc and .pyo files without 25 | # a source .py file to be detected as revisions in the 26 | # versions/ directory 27 | # sourceless = false 28 | 29 | # version location specification; this defaults 30 | # to migrations/versions. When using multiple version 31 | # directories, initial revisions must be specified with --version-path 32 | # version_locations = %(here)s/bar %(here)s/bat migrations/versions 33 | 34 | # the output encoding used when revision files 35 | # are written from script.py.mako 36 | # output_encoding = utf-8 37 | 38 | # sqlalchemy.url = driver://user:pass@localhost/dbname 39 | 40 | 41 | [post_write_hooks] 42 | # post_write_hooks defines scripts or Python functions that are run 43 | # on newly generated revision scripts. See the documentation for further 44 | # detail and examples 45 | 46 | # format using "black" - use the console_scripts runner, against the "black" entrypoint 47 | # hooks=black 48 | # black.type=console_scripts 49 | # black.entrypoint=black 50 | # black.options=-l 79 51 | 52 | # Logging configuration 53 | [loggers] 54 | keys = root,sqlalchemy,alembic 55 | 56 | [handlers] 57 | keys = console 58 | 59 | [formatters] 60 | keys = generic 61 | 62 | [logger_root] 63 | level = WARN 64 | handlers = console 65 | qualname = 66 | 67 | [logger_sqlalchemy] 68 | level = WARN 69 | handlers = 70 | qualname = sqlalchemy.engine 71 | 72 | [logger_alembic] 73 | level = INFO 74 | handlers = 75 | qualname = alembic 76 | 77 | [handler_console] 78 | class = StreamHandler 79 | args = (sys.stderr,) 80 | level = NOTSET 81 | formatter = generic 82 | 83 | [formatter_generic] 84 | format = %(levelname)-5.5s [%(name)s] %(message)s 85 | datefmt = %H:%M:%S 86 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: 7 | - develop 8 | - release/* 9 | 10 | jobs: 11 | test: 12 | name: Building codebase on Node v${{ matrix.nodejs }} 13 | runs-on: ubuntu-latest 14 | services: 15 | postgres: 16 | image: sameersbn/postgresql:12-20200524 17 | env: 18 | DB_USER: qa_user 19 | DB_PASS: qa_password 20 | DB_NAME: ci_db 21 | PG_PASSWORD: password 22 | options: >- 23 | --health-cmd pg_isready 24 | --health-interval 10s 25 | --health-timeout 5s 26 | --health-retries 5 27 | ports: 28 | # Maps tcp port 5432 on service container to the host 29 | - 5432:5432 30 | strategy: 31 | matrix: 32 | nodejs: [12] 33 | python_version: [3.7] 34 | steps: 35 | - uses: actions/checkout@v2 36 | - uses: actions/setup-node@v1 37 | with: 38 | node-version: ${{ matrix.nodejs }} 39 | # rep-token: ${{secrets.GITHUB_TOKEN}} 40 | - uses: actions/setup-python@v2 41 | with: 42 | python-version: ${{ matrix.python_version }} 43 | 44 | - name: Install Python Dependencies and update migrations 45 | run: | 46 | cd enhanced_savings 47 | curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python 48 | $HOME/.poetry/bin/poetry install 49 | $HOME/.poetry/bin/poetry run alembic upgrade head 50 | env: 51 | DATABASE_URL: postgresql://qa_user:qa_password@localhost:5432/ci_db 52 | 53 | - name: Install 54 | run: | 55 | yarn install 56 | npm install -g nyc 57 | yarn bootstrap 58 | 59 | - name: Lint 60 | run: yarn lint 61 | 62 | - name: Test all Services and components 63 | run: | 64 | yarn db:generate-all 65 | nyc yarn test 66 | env: 67 | DATABASE_URL: postgresql://qa_user:qa_password@localhost:5432/ci_db?schema=public 68 | DB_PROVIDER: postgresql 69 | # - name: Report 70 | # if: matrix.nodejs >= 14 71 | # run: | 72 | # nyc report --reporter=text-lcov > coverage.lcov 73 | # bash <(curl -s https://codecov.io/bash) 74 | # env: 75 | # CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 76 | -------------------------------------------------------------------------------- /data-layer/migrations/env.py: -------------------------------------------------------------------------------- 1 | from logging.config import fileConfig 2 | 3 | from sqlalchemy import engine_from_config 4 | from sqlalchemy import pool 5 | 6 | from alembic import context 7 | 8 | # this is the Alembic Config object, which provides 9 | # access to the values within the .ini file in use. 10 | config = context.config 11 | 12 | # Interpret the config file for Python logging. 13 | # This line sets up loggers basically. 14 | fileConfig(config.config_file_name) 15 | 16 | # add your model's MetaData object here 17 | # for 'autogenerate' support 18 | from data_layer.models import Base 19 | from data_layer import settings 20 | 21 | # from myapp import mymodel 22 | config.set_main_option("sqlalchemy.url", str(settings.DATABASE_URL)) 23 | target_metadata = Base.metadata 24 | # target_metadata = None 25 | 26 | # other values from the config, defined by the needs of env.py, 27 | # can be acquired: 28 | # my_important_option = config.get_main_option("my_important_option") 29 | # ... etc. 30 | 31 | 32 | def run_migrations_offline(): 33 | """Run migrations in 'offline' mode. 34 | 35 | This configures the context with just a URL 36 | and not an Engine, though an Engine is acceptable 37 | here as well. By skipping the Engine creation 38 | we don't even need a DBAPI to be available. 39 | 40 | Calls to context.execute() here emit the given string to the 41 | script output. 42 | 43 | """ 44 | url = config.get_main_option("sqlalchemy.url") 45 | context.configure( 46 | url=url, 47 | target_metadata=target_metadata, 48 | literal_binds=True, 49 | dialect_opts={"paramstyle": "named"}, 50 | ) 51 | 52 | with context.begin_transaction(): 53 | context.run_migrations() 54 | 55 | 56 | def run_migrations_online(): 57 | """Run migrations in 'online' mode. 58 | 59 | In this scenario we need to create an Engine 60 | and associate a connection with the context. 61 | 62 | """ 63 | connectable = engine_from_config( 64 | config.get_section(config.config_ini_section), 65 | prefix="sqlalchemy.", 66 | poolclass=pool.NullPool, 67 | ) 68 | 69 | with connectable.connect() as connection: 70 | context.configure(connection=connection, target_metadata=target_metadata) 71 | 72 | with context.begin_transaction(): 73 | context.run_migrations() 74 | 75 | 76 | if context.is_offline_mode(): 77 | run_migrations_offline() 78 | else: 79 | run_migrations_online() 80 | -------------------------------------------------------------------------------- /server-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend-application", 3 | "version": "0.0.1", 4 | "description": "The repository housing all projects to be built by Tuteria backend Team including web and mobile", 5 | "main": "index.js", 6 | "repository": "git@github.com:Tuteria/frontend-application.git", 7 | "author": "Beesama ", 8 | "license": "None", 9 | "private": true, 10 | "workspaces": ["packages/*", "services/*"], 11 | "scripts": { 12 | "bootstrap": "lerna bootstrap", 13 | "changeset": "changeset", 14 | "prettier": "prettier --ignore-path .gitignore \"**/*.+(js|jsx|ts|tsx|json|css)\"", 15 | "format": "yarn prettier --write", 16 | "check-format": "yarn prettier --list-different", 17 | "lint": "lerna run lint", 18 | "components": "yarn workspace @tuteria/components", 19 | "web": "yarn workspace @tuteria/video-web-app", 20 | "mobile": "yarn workspace @tuteria/video-mobile-app", 21 | "build": "lerna build", 22 | "validate": "npm-run-all --parallel check-format lint build", 23 | "storybook": "start-storybook -p 9009 -s public --ci", 24 | "build-storybook": "build-storybook -s public", 25 | "clean": "lerna clean --yes && rm -rf node_modules", 26 | "test": "lerna run test", 27 | "service:auth": "yarn workspace @tuteria/authentication-service", 28 | "service:marketing": "yarn workspace @tuteria/marketing-service", 29 | "service:media": "yarn workspace @tuteria/media-service", 30 | "service:notification": "yarn workspace @tuteria/notification-service", 31 | "service:quiz": "yarn workspace @tuteria/quiz-service", 32 | "service:referral": "yarn workspace @tuteria/referral-service", 33 | "service:subscription": "yarn workspace @tuteria/subscription-service", 34 | "service:example": "yarn workspace @tuteria/example-service" 35 | }, 36 | "devDependencies": { 37 | "@prisma/cli": "^2.2.2", 38 | "@types/dotenv": "^8.2.0", 39 | "@types/express": "^4.17.7", 40 | "@types/node": "^12.12.24", 41 | "@types/supertest": "^2.0.10", 42 | "@types/morgan": "^1.9.1", 43 | "lerna": "^3.20.2", 44 | "supertest": "^4.0.2", 45 | "ts-node": "8.10.2", 46 | "uvu": "0.0.19" 47 | }, 48 | "dependencies": { 49 | "npm-run-all": "^4.1.5", 50 | "@prisma/client": "^2.2.2", 51 | "body-parser": "^1.19.0", 52 | "dotenv": "^8.2.0", 53 | "nodemon": "^2.0.4", 54 | "express": "^4.17.1", 55 | "typescript": "3.9.6", 56 | "morgan": "^1.10.0", 57 | "winston": "^3.3.3" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/www/tests/index.ts: -------------------------------------------------------------------------------- 1 | import { suite } from "uvu"; 2 | import * as assert from "uvu/assert"; 3 | import * as math from "../src"; 4 | import "hard-rejection/register"; 5 | 6 | const API = suite("exports"); 7 | 8 | API("should export an object", () => { 9 | assert.type(math, "object"); 10 | }); 11 | 12 | API.run(); 13 | 14 | // --- 15 | 16 | const sum = suite("sum"); 17 | 18 | sum("should be a function", () => { 19 | assert.type(math.sum, "function"); 20 | }); 21 | 22 | sum("should default to 0", () => { 23 | assert.is(math.sum(), 0); 24 | }); 25 | 26 | sum("should handle one argument", () => { 27 | assert.is(math.sum(5), 5); 28 | assert.is(math.sum(-12), -12); 29 | }); 30 | 31 | sum("should handle two arguments", () => { 32 | assert.is(math.sum(1, 2), 3); 33 | assert.is(math.sum(11, 12), 23); 34 | 35 | assert.is(math.sum(-1, -2), -3); 36 | assert.is(math.sum(-11, -12), -23); 37 | 38 | assert.is(math.sum(1, -2), -1); 39 | assert.is(math.sum(-11, 12), 1); 40 | }); 41 | 42 | sum("should handle multiple arguments", () => { 43 | assert.is(math.sum(0, 0, -1, -2, 4, 9, 10), 20); 44 | assert.is(math.sum(1, 2, 3, 4, 5, 6), 21); 45 | assert.is(math.sum(10, 20, 30), 60); 46 | }); 47 | 48 | sum.run(); 49 | 50 | // --- 51 | 52 | const substract = suite("substract"); 53 | 54 | substract("should be a function", () => { 55 | assert.type(math.substract, "function"); 56 | }); 57 | 58 | substract("should default to 0", () => { 59 | assert.is(math.substract(), 0); 60 | }); 61 | 62 | substract("should handle one argument", () => { 63 | assert.is(math.substract(5), 5); 64 | assert.is(math.substract(-12), -12); 65 | }); 66 | 67 | substract("should handle two arguments", () => { 68 | assert.is(math.substract(1, 2), -1); 69 | assert.is(math.substract(11, 12), -1); 70 | 71 | assert.is(math.substract(-1, -2), 1); 72 | assert.is(math.substract(-11, -12), 1); 73 | 74 | assert.is(math.substract(1, -2), 3); 75 | assert.is(math.substract(-11, 12), -23); 76 | }); 77 | 78 | substract("should handle multiple arguments", () => { 79 | assert.is(math.substract(0, 0, -1, -2, 4, 9, 10), -20); 80 | assert.is(math.substract(1, 2, 3, 4, 5, 6), -19); 81 | assert.is(math.substract(10, 20, 30), -40); 82 | }); 83 | 84 | substract.run(); 85 | 86 | // --- 87 | 88 | const average = suite("average"); 89 | 90 | average("should be a function", () => { 91 | assert.type(math.average, "function"); 92 | }); 93 | 94 | average("should default to NaN", () => { 95 | assert.equal(math.average(), NaN); 96 | }); 97 | 98 | average("should handle one argument", () => { 99 | assert.is(math.average(5), 5); 100 | assert.is(math.average(-12), -12); 101 | }); 102 | 103 | average("should handle two arguments", () => { 104 | assert.is(math.average(1, 2), 1.5); 105 | assert.is(math.average(11, 12), 11.5); 106 | 107 | assert.is(math.average(-1, -2), -1.5); 108 | assert.is(math.average(-11, -12), -11.5); 109 | 110 | assert.is(math.average(1, -2), -0.5); 111 | assert.is(math.average(-11, 12), 0.5); 112 | }); 113 | 114 | average("should handle multiple arguments", () => { 115 | assert.is(math.average(0, 0, -1, -2, 4, 9, 10), 20 / 7); 116 | assert.is(math.average(1, 2, 3, 4, 5, 6), 21 / 6); 117 | assert.is(math.average(10, 20, 30), 20); 118 | }); 119 | 120 | average.run(); 121 | -------------------------------------------------------------------------------- /packages/components/tests/index.ts: -------------------------------------------------------------------------------- 1 | import { suite } from "uvu"; 2 | import * as assert from "uvu/assert"; 3 | import * as math from "../src"; 4 | import "hard-rejection/register"; 5 | 6 | const API = suite("exports"); 7 | 8 | API("should export an object", () => { 9 | assert.type(math, "object"); 10 | }); 11 | 12 | API.run(); 13 | 14 | // --- 15 | 16 | const sum = suite("sum"); 17 | 18 | sum("should be a function", () => { 19 | assert.type(math.sum, "function"); 20 | }); 21 | 22 | sum("should default to 0", () => { 23 | assert.is(math.sum(), 0); 24 | }); 25 | 26 | sum("should handle one argument", () => { 27 | assert.is(math.sum(5), 5); 28 | assert.is(math.sum(-12), -12); 29 | }); 30 | 31 | sum("should handle two arguments", () => { 32 | assert.is(math.sum(1, 2), 3); 33 | assert.is(math.sum(11, 12), 23); 34 | 35 | assert.is(math.sum(-1, -2), -3); 36 | assert.is(math.sum(-11, -12), -23); 37 | 38 | assert.is(math.sum(1, -2), -1); 39 | assert.is(math.sum(-11, 12), 1); 40 | }); 41 | 42 | sum("should handle multiple arguments", () => { 43 | assert.is(math.sum(0, 0, -1, -2, 4, 9, 10), 20); 44 | assert.is(math.sum(1, 2, 3, 4, 5, 6), 21); 45 | assert.is(math.sum(10, 20, 30), 60); 46 | }); 47 | 48 | sum.run(); 49 | 50 | // --- 51 | 52 | const substract = suite("substract"); 53 | 54 | substract("should be a function", () => { 55 | assert.type(math.substract, "function"); 56 | }); 57 | 58 | substract("should default to 0", () => { 59 | assert.is(math.substract(), 0); 60 | }); 61 | 62 | substract("should handle one argument", () => { 63 | assert.is(math.substract(5), 5); 64 | assert.is(math.substract(-12), -12); 65 | }); 66 | 67 | substract("should handle two arguments", () => { 68 | assert.is(math.substract(1, 2), -1); 69 | assert.is(math.substract(11, 12), -1); 70 | 71 | assert.is(math.substract(-1, -2), 1); 72 | assert.is(math.substract(-11, -12), 1); 73 | 74 | assert.is(math.substract(1, -2), 3); 75 | assert.is(math.substract(-11, 12), -23); 76 | }); 77 | 78 | substract("should handle multiple arguments", () => { 79 | assert.is(math.substract(0, 0, -1, -2, 4, 9, 10), -20); 80 | assert.is(math.substract(1, 2, 3, 4, 5, 6), -19); 81 | assert.is(math.substract(10, 20, 30), -40); 82 | }); 83 | 84 | substract.run(); 85 | 86 | // --- 87 | 88 | const average = suite("average"); 89 | 90 | average("should be a function", () => { 91 | assert.type(math.average, "function"); 92 | }); 93 | 94 | average("should default to NaN", () => { 95 | assert.equal(math.average(), NaN); 96 | }); 97 | 98 | average("should handle one argument", () => { 99 | assert.is(math.average(5), 5); 100 | assert.is(math.average(-12), -12); 101 | }); 102 | 103 | average("should handle two arguments", () => { 104 | assert.is(math.average(1, 2), 1.5); 105 | assert.is(math.average(11, 12), 11.5); 106 | 107 | assert.is(math.average(-1, -2), -1.5); 108 | assert.is(math.average(-11, -12), -11.5); 109 | 110 | assert.is(math.average(1, -2), -0.5); 111 | assert.is(math.average(-11, 12), 0.5); 112 | }); 113 | 114 | average("should handle multiple arguments", () => { 115 | assert.is(math.average(0, 0, -1, -2, 4, 9, 10), 20 / 7); 116 | assert.is(math.average(1, 2, 3, 4, 5, 6), 21 / 6); 117 | assert.is(math.average(10, 20, 30), 20); 118 | }); 119 | 120 | average.run(); 121 | -------------------------------------------------------------------------------- /packages/components/readme.md: -------------------------------------------------------------------------------- 1 | # Template: TypeScript Module [![CI](https://github.com/lukeed/typescript-module/workflows/CI/badge.svg)](https://github.com/lukeed/typescript-module/actions) [![codecov](https://badgen.now.sh/codecov/c/github/lukeed/typescript-module)](https://codecov.io/gh/lukeed/typescript-module) 2 | 3 | This is a [clonable template repository](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template) for authoring a `npm` module with TypeScript. Out of the box, it: 4 | 5 | * Provides minimally-viable `tsconfig.json` settings 6 | * Scaffolds a silly arithmetic module (`src/index.ts`) 7 | * Scaffolds test suites for full test coverage (`test/index.ts`) 8 | * Scaffolds a GitHub Action for Code Integration (CI) that: 9 | * checks if compilation is successful 10 | * runs the test suite(s) 11 | * reports test coverage 12 | * Generates type definitions (`types/*.d.ts`) 13 | * Generates multiple distribution formats: 14 | * ES Module (`dist/index.mjs`) 15 | * CommonJS (`dist/index.js`) 16 | * UMD (`dist/index.min.js`) 17 | 18 | All configuration is accessible via the `rollup.config.js` and a few `package.json` keys: 19 | 20 | * `name` — the name of your module 21 | * `main` — the destination file for your CommonJS build 22 | * `module` — the destination file for your ESM build (optional but recommended) 23 | * `unpkg` — the destination file for your UMD build (optional for [unpkg.com](https://unpkg.com/)) 24 | * `umd:name` — the UMD global name for your module (optional) 25 | 26 | ## Setup 27 | 28 | 1. [Clone this template](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template) 29 | 2. Replace all instances of `TODO` within the `license` and `package.json` files 30 | 3. Create [CodeCov](https://codecov.io) account (free for OSS) 31 | 4. Copy the provided CodeCov token as the `CODECOV_TOKEN` [repository secret](https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets-for-a-repository) (for CI reporting) 32 | 5. Replace `src/index.ts` and `test/index.ts` with your own code! 🎉 33 | 34 | ## Commands 35 | 36 | ### build 37 | 38 | Builds your module for distribution in multiple formats (ESM, CommonJS, and UMD). 39 | 40 | ```sh 41 | $ npm run build 42 | ``` 43 | 44 | ### test 45 | 46 | Runs your test suite(s) (`/tests/**`) against your source code (`/src/**`).
Doing so allows for accurate code coverage. 47 | 48 | > **Note:** Coverage is only collected and reported through the "CI" Github Action (`.github/workflows/ci.yml`). 49 | 50 | ```sh 51 | $ npm test 52 | ``` 53 | 54 | ## Publishing 55 | 56 | > **Important:** Please finish [Setup](#setup) before continuing! 57 | 58 | Once all `TODO` notes have been updated & your new module is ready to be shared, all that's left to do is decide its new version — AKA, do the changes consitute a `patch`, `minor`, or `major` release? 59 | 60 | Once decided, you can run the following: 61 | 62 | ```sh 63 | $ npm version && git push origin master --tags && npm publish 64 | # Example: 65 | # npm version patch && git push origin master --tags && npm publish 66 | ``` 67 | 68 | This command sequence will: 69 | * version your module, updating the `package.json` "version" 70 | * create and push a `git` tag (matching the new version) to your repository 71 | * build your module (via the `prepublishOnly` script) 72 | * publish the module to the npm registry 73 | 74 | ## License 75 | 76 | MIT © [Luke Edwards](https://lukeed.com) 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend-application", 3 | "version": "0.0.1", 4 | "description": "The repository housing all trading application", 5 | "main": "index.js", 6 | "repository": "git@github.com:gbozee/the-trader-guide.git", 7 | "author": "Beesama ", 8 | "license": "None", 9 | "private": true, 10 | "workspaces": [ 11 | "packages/*", 12 | "services/*" 13 | ], 14 | "scripts": { 15 | "bootstrap": "lerna bootstrap", 16 | "changeset": "changeset", 17 | "prettier": "prettier --ignore-path .gitignore \"**/*.+(js|jsx|ts|tsx|json|css)\"", 18 | "format": "yarn prettier --write", 19 | "check-format": "yarn prettier --list-different", 20 | "lint": "lerna run lint", 21 | "components": "yarn workspace @tuteria/components", 22 | "web": "yarn workspace @tuteria/www", 23 | "build": "lerna build", 24 | "validate": "npm-run-all --parallel check-format lint build", 25 | "storybook": "start-storybook -p 9010 -s public --ci", 26 | "build-storybook": "build-storybook -s public", 27 | "clean": "lerna clean --yes && rm -rf node_modules", 28 | "test": "lerna run test", 29 | "service:common": "yarn workspace @tuteria/common", 30 | "service:example": "yarn workspace @tuteria/example-service", 31 | "db:generate-all": "yarn service:example db:generate" 32 | }, 33 | "devDependencies": { 34 | "@babel/core": "^7.10.5", 35 | "@prisma/cli": "^2.2.2", 36 | "@storybook/addon-actions": "6.0.0-beta.38", 37 | "@storybook/addon-links": "6.0.0-beta.38", 38 | "@storybook/addons": "6.0.0-beta.38", 39 | "@storybook/preset-create-react-app": "^3.1.4", 40 | "@storybook/preset-typescript": "^3.0.0", 41 | "@storybook/react": "6.0.0-beta.38", 42 | "@testing-library/jest-dom": "^4.2.4", 43 | "@testing-library/react": "^9.4.0", 44 | "@testing-library/user-event": "^8.0.3", 45 | "@types/dotenv": "^8.2.0", 46 | "@types/express": "^4.17.7", 47 | "@types/jest": "^24.0.25", 48 | "@types/katex": "^0.11.0", 49 | "@types/morgan": "^1.9.1", 50 | "@types/node": "^12.12.24", 51 | "@types/pluralize": "^0.0.29", 52 | "@types/react": "^16.9.17", 53 | "@types/react-dom": "^16.9.4", 54 | "@types/supertest": "^2.0.10", 55 | "@types/testing-library__dom": "^7.5.0", 56 | "@typescript-eslint/eslint-plugin": "^3.4.0", 57 | "@typescript-eslint/parser": "^3.4.0", 58 | "babel-eslint": "10.1.0", 59 | "babel-loader": "^8.1.0", 60 | "eslint-plugin-jest-dom": "^3.0.1", 61 | "eslint-plugin-prettier": "^3.1.4", 62 | "eslint-plugin-react": "^7.20.3", 63 | "eslint-plugin-react-hooks": "^4.0.5", 64 | "eslint-plugin-testing-library": "^3.3.1", 65 | "hard-rejection": "^2.1.0", 66 | "husky": "^4.2.5", 67 | "lerna": "^3.20.2", 68 | "lint-staged": "^10.2.11", 69 | "next-transpile-modules": "^4.0.2", 70 | "nodemon": "^2.0.4", 71 | "prettier": "^2.0.5", 72 | "supertest": "^4.0.2", 73 | "ts-node": "8.10.2", 74 | "uvu": "0.3.0" 75 | }, 76 | "dependencies": { 77 | "@chakra-ui/core": "^0.8.0", 78 | "@changesets/cli": "^2.9.2", 79 | "@emotion/core": "^10.0.28", 80 | "@emotion/styled": "^10.0.27", 81 | "@prisma/client": "^2.2.2", 82 | "body-parser": "^1.19.0", 83 | "cross-env": "^7.0.2", 84 | "dotenv": "^8.2.0", 85 | "emotion-theming": "^10.0.27", 86 | "express": "^4.17.1", 87 | "json-groupby": "^1.1.0", 88 | "morgan": "^1.10.0", 89 | "npm-run-all": "^4.1.5", 90 | "pluralize": "^8.0.0", 91 | "react": "^16.13.1", 92 | "react-dom": "^16.13.1", 93 | "react-scripts": "3.4.1", 94 | "typescript": "3.9.6", 95 | "winston": "^3.3.3" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /packages/components/src/theme.ts: -------------------------------------------------------------------------------- 1 | import { theme as ChakraTheme, DefaultTheme } from "@chakra-ui/core"; 2 | import icons from "./ui-elements/icons"; 3 | 4 | export interface Theme extends DefaultTheme { 5 | borders: any; 6 | shadows: any; 7 | radii: any; 8 | colors: any; 9 | borderWidths: any; 10 | borderRadius: any; 11 | } 12 | 13 | const theme: Theme = { 14 | ...ChakraTheme, 15 | borders: { 16 | ...ChakraTheme.borders, 17 | none: "none", 18 | normal: "1px solid", 19 | }, 20 | borderWidths: { 21 | xl: "2rem", 22 | lg: "1rem", 23 | md: "0.5rem", 24 | sm: "0.25rem", 25 | xs: "0.125rem", 26 | "2xs": "0.0625rem", 27 | "3xs": "0.03125rem", 28 | none: 0, 29 | }, 30 | icons: { 31 | ...ChakraTheme.icons, 32 | ...icons, 33 | }, 34 | fonts: { 35 | ...ChakraTheme.fonts, 36 | heading: "Larsseit, Helvetica Neue, Open Sans, sans-serif", 37 | body: "Inter, Helvetica Neue, Open Sans, sans-serif", 38 | }, 39 | shadows: { 40 | ...ChakraTheme.shadows, 41 | subtle: "rgba(0, 0, 0, 0.12) 0px 12px 12px", 42 | around: 43 | "rgba(0, 0, 0, 0.04) 0px 0px 0px 1px, rgba(0, 0, 0, 0.08) 0px 3px 6px", 44 | }, 45 | radii: { 46 | ...ChakraTheme.radii, 47 | xl: "1rem", 48 | noLeftRadius: `0 4px 4px 0`, 49 | noRightRadius: `4px 0 0 4px`, 50 | noTopRadius: `0 0 4px 4px`, 51 | noBottomRadius: `4px 4px 0 0`, 52 | }, 53 | colors: { 54 | ...ChakraTheme.colors, 55 | blue: { 56 | 50: "#E7EFFC", 57 | 100: "#B9D0F7", 58 | 200: "#8BB1F3", 59 | 300: "#5C92EE", 60 | 400: "#2E73E9", 61 | 500: "#0054E5", 62 | 600: "#0045BC", 63 | 700: "#003692", 64 | 800: "#002769", 65 | 900: "#00173F", 66 | }, 67 | teal: { 68 | 50: "#E7F3F7", 69 | 100: "#B9DB37", 70 | 200: "#8BC3D7", 71 | 300: "#5CABC7", 72 | 400: "#2E93B7", 73 | 500: "#007BA7", 74 | 600: "#006589", 75 | 700: "#004F6B", 76 | 800: "#00384C", 77 | 900: "#00222E", 78 | }, 79 | coral: { 80 | 50: "#FFF3EF", 81 | 100: "#FFDCCF", 82 | 200: "#FFC4AF", 83 | 300: "#FFAD8F", 84 | 400: "#FF966F", 85 | 500: "#FF7F50", 86 | 600: "#D16842", 87 | 700: "#A35133", 88 | 800: "#743A25", 89 | 900: "#462316", 90 | }, 91 | brown: { 92 | 50: "#F6EBEB", 93 | 100: "#E6C4C4", 94 | 200: "#D69E9E", 95 | 300: "#C57777", 96 | 400: "#5BB050", 97 | 500: "#A52A2A", 98 | 600: "#882323", 99 | 700: "#6A1B1B", 100 | 800: "#4B1414", 101 | 900: "#2D0C0C", 102 | }, 103 | green: { 104 | 50: "#EDF8F4", 105 | 100: "#CAEADE", 106 | 200: "#A6DDC8", 107 | 300: "#83CFB2", 108 | 400: "#60C29C", 109 | 500: "#3DB586", 110 | 600: "#32956E", 111 | 700: "#277456", 112 | 800: "#1C533D", 113 | 900: "#113225", 114 | }, 115 | red: { 116 | 50: "#FCEFEC", 117 | 100: "#F6D0C8", 118 | 200: "#F1B2A3", 119 | 300: "#EB937F", 120 | 400: "#E6745A", 121 | 500: "#E15636", 122 | 600: "#B9472D", 123 | 700: "#903723", 124 | 800: "#672819", 125 | 900: "#3E180F", 126 | }, 127 | crimson: { 128 | 50: "#FDE9ED", 129 | 100: "#FABECA", 130 | 200: "#F694A6", 131 | 300: "#F36983", 132 | 400: "#F03E60", 133 | 500: "#ED143D", 134 | 600: "#C21132", 135 | 700: "#970D27", 136 | 800: "#6C0A1C", 137 | 900: "#410611", 138 | }, 139 | purple: { 140 | 50: "#F2E8FF", 141 | 100: "#D9BCFF", 142 | 200: "#BF8FFF", 143 | 300: "#A663FF", 144 | 400: "#8D36FF", 145 | 500: "#740AFF", 146 | 600: "#5F09D1", 147 | 700: "#4A07A3", 148 | 800: "#350574", 149 | 900: "#200346", 150 | }, 151 | magenta: { 152 | 50: "#F3EBF2", 153 | 100: "#DCC3D8", 154 | 200: "#C69BBE", 155 | 300: "#AF73A4", 156 | 400: "#984B8A", 157 | 500: "#822470", 158 | 600: "#6B1E5C", 159 | 700: "#531748", 160 | 800: "#3C1133", 161 | 900: "#240A1F", 162 | }, 163 | orange: { 164 | 50: "#FCF0E8", 165 | 100: "#F7D3BB", 166 | 200: "#F3B78F", 167 | 300: "#EE9A62", 168 | 400: "#E97D35", 169 | 500: "#E56109", 170 | 600: "#BC5008", 171 | 700: "#923E06", 172 | 800: "#692D05", 173 | 900: "#3F1B03", 174 | }, 175 | yellow: { 176 | 50: "#FEFCE7", 177 | 100: "#FDF6B9", 178 | 200: "#FCF18B", 179 | 300: "#FBEB5C", 180 | 400: "#FAE62E", 181 | 500: "#FAE100", 182 | 600: "#CDB900", 183 | 700: "#A09000", 184 | 800: "#726700", 185 | 900: "#453E00", 186 | }, 187 | gold: { 188 | 50: "#FFF8E7", 189 | 100: "#FFEBB9", 190 | 200: "#FFDD8B", 191 | 300: "#FFD05C", 192 | 400: "#FFC32E", 193 | 500: "#FFB600", 194 | 600: "#D19500", 195 | 700: "#A37400", 196 | 800: "#745300", 197 | 900: "#463200", 198 | }, 199 | gray: { 200 | 50: "#F7FAFC", 201 | 100: "#EDF2F7", 202 | 200: "#E3E7ED", 203 | 300: "#BABEC2", 204 | 400: "#A0AEC0", 205 | 500: "#718096", 206 | 600: "#4A5568", 207 | 700: "#2D3748", 208 | 800: "#1A202C", 209 | 900: "#171923", 210 | }, 211 | gray2: { 212 | 50: "#EBECED", 213 | 100: "#EDEFF3", 214 | 200: "#E3E7ED", 215 | 300: "#747A83", 216 | 400: "#4C5460", 217 | 500: "#252E3D", 218 | 600: "#1F2632", 219 | 700: "#181E27", 220 | 800: "#11151C", 221 | 900: "#0B0D11", 222 | }, 223 | neutral: { 224 | 50: "#FCFCFD", 225 | 100: "#F7F8FA", 226 | 200: "#F2F4F6", 227 | 300: "#EDEFF3", 228 | 400: "#E8EBF0", 229 | 500: "#E3E7ED", 230 | 600: "#BABEC2", 231 | 700: "#919397", 232 | 800: "#68696C", 233 | 900: "#3E3F41", 234 | }, 235 | }, 236 | borderRadius: { 237 | none: 0, 238 | small: 2, 239 | regular: 4, 240 | big: 8, 241 | bigger: 16, 242 | biggest: 24, 243 | round: 9999, 244 | }, 245 | }; 246 | 247 | export default theme; 248 | -------------------------------------------------------------------------------- /packages/www/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | 3 | export default function Home() { 4 | return ( 5 |
6 | 7 | Create Next App 8 | 9 | 10 | 11 |
12 |

13 | Welcome to Next.js! 14 |

15 | 16 |

17 | Get started by editing pages/index.js 18 |

19 | 20 | 49 |
50 | 51 | 61 | 62 | 192 | 193 | 207 |
208 | ) 209 | } 210 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Tuteria Fullstack Engineering Assessment 2 | 3 | First of all, let me thank you for taking this time to learn about us, we are honored that you’re considering joining our team. 4 | 5 | I’m Biola, Tuteria's CTO & Co-founder. Before we get into the assessment, I’ll like to give a bit more context: 6 | 7 | 1. This test was previously administered after an initial round of interviews, but we're making it public so that anyone can make an attempt in hopes of working with us. 8 | 2. We don't care about experience, but about **your ability to solve problems**. We'll be in touch with everyone who attempts this test and attempt to hire those who do well. If you're seeing this, it means we're still hiring ;) 9 | 3. Presently, we'll pay in the range of **N150,000 - N350,000 monthly,** depending on your performance on this test. This is in addition to other benefits like Learning Stipends, Housing Grant, Medical Insurance, Stock Options to exceptional teammates, etc. If you live outside Nigeria, we can only pay the dollar equivalent of the above amount using official rates. 10 | 4. We use TypeScript, Nodejs ([Expressjs](https://expressjs.com/), or [Featherjs](https://feathersjs.com/)) and Python ([Starlette](https://www.starlette.io/)) on the backend and Reactjs (Nextjs) on the frontend. We prefer Fullstack Javascript Engineers. 11 | 5. We are open to a fully remote engagement, as well as a partially remote one. If you would like to work from the office at any time, we're in Gbagada Phase 2, Lagos, Nigeria. 12 | 13 | If you'd like to work with us, please give this a shot. You don't have to do everything, we just want to see how you solve problems and where we may need to support you, should you work with us. After this test would be the rest of the interviews via video chat. 14 | 15 | All the very best! 16 | 17 | ### Taking and submitting the assessment 18 | 19 | 1. Clone the repository and provide your implementation to the tasks there. 20 | 2. When done, submit a pull request of your implementation to the base repository. 21 | 22 | **_Bonus Points:_** If you make use of the [changesets](https://github.com/atlassian/changesets) when creating the PR and also provide a live link of the hosted application. 23 | 24 | 3. These tasks require using [TypeScript](https://www.typescriptlang.org/). 25 | 4. To ensure consistent commit messages, visit [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for commit conventions. 26 | 5. We recommend you complete this assessment **within 4 days** from when you fork the project 27 | 6. If you have any questions, please reach out to me on Telegram [@Beee_sama](https://t.me/Beee_sama) or send an email to [biola@tuteria.com](mailto:biola@tuteria.com) 28 | 29 | ### The assessment 30 | 31 | This is the repository to be used in solving the assessment. It is a mono-repository for a Note-taking app managed by [Lerna](https://github.com/lerna/lerna) in combination with [Yarn Workspace](https://github.com/Tuteria/Frontend-Assessment/blob/master). 32 | 33 | Frontend packages are located in [/packages](https://github.com/Tuteria/Frontend-Assessment/blob/master), while the backend packages are in [/services](https://github.com/Tuteria/Frontend-Assessment/blob/master). There's a sample backend service called `example` located in the `services` directory. 34 | 35 | To get the project up and running, you need to have [Node](https://nodejs.org/en/), [Lerna](https://github.com/lerna/lerna), and [npm](https://www.npmjs.com/get-npm) or [yarn](https://classic.yarnpkg.com/en/docs/install) installed globally. [Python3](https://www.python.org/downloads/) and [poetry](https://python-poetry.org/) which is the package manager used by [python](https://github.com/Tuteria/Frontend-Assessment/blob/master) also need to be installed globally. 36 | 37 | Once you've cloned the repo, simply run the below command to install all the dependencies 38 | 39 | `> yarn bootstrap` or `npm run bootstrap` (yarn preferably) 40 | 41 | ### First task 42 | 43 | The first task is to ensure that all tests pass when you run `yarn test` from the root package. In order for this to happen, the following steps need to occur: 44 | 45 | 1. Navigate to the `data-layer` directory and run `poetry install` *(You should have installed poetry and python)* 46 | 2. Run `poetry run alembic revision --autogenerate -m "Added notes table"` to create the database migration for the the table specified in `data-layer/data_layer/models/notes.py` (The ORM used is [SQLAlchemy](https://docs.sqlalchemy.org/en/13/orm/tutorial.html)) 47 | 3. Run `poetry run alembic upgrade head` to apply the migration to the database. 48 | 4. Navigate back to the root of the project and run `yarn service:example db:update` to create [Prisma](https://www.prisma.io/docs/) model schema 49 | 5. Run `yarn service:example db:generate` to generate [Prisma](https://www.prisma.io/docs/) definitions that would be used in the project. 50 | 6. Finally run `yarn service:example test`. All the tests located in `services/example/tests` should pass. 51 | 52 | **You get bonus points if:** 53 | 54 | Instead of using `sqlite` as the database of choice, make use of [PostgreSQL](https://www.postgresql.org/). There is a [docker-compose.yml](https://github.com/Tuteria/Frontend-Assessment/blob/master) file provided to ease the creation of the database. Also, ensure that the connection string for PostgresSQL is read as an environmental variable in 55 | 56 | i. The `data-layer` python project 57 | 58 | ii. The `services/example/prisma` settings. 59 | 60 | You may need to go through the Prisma documentation to figure out how to switch from `sqlite` to `postgresql`. 61 | 62 | ### Second task 63 | 64 | Based on the work done in the first task, create a CRUD API that consists of the following endpoints 65 | 66 | 1. `POST /notes/create` *Anonymous note creation* 67 | 2. `GET /notes` *Fetching the list of anonymous notes created* 68 | 3. `PUT /notes/:note-id` *The ability to update a specific anonymous note* 69 | 4. `DELETE /notes/:note-id` *The ability to delete a specific anonymous note* 70 | 5. `POST /users/create` *Endpoint to create a user* 71 | 6. `GET /users/:username/notes` *Fetching the notes of a particular user* 72 | 73 | Create the corresponding tables using the convention already specified in the first task. Ensure to implement automated testing on each of these endpoints. 74 | 75 | **You get bonus points if:** 76 | 77 | 1. Instead of the `service/example` package, you create a different [NextJS](https://nextjs.org/docs) application `package` to house these endpoints. 78 | 2. You use a different database when running the tests since each test run will lead to deleting and recreating records. 79 | 3. You write a script that can pre-populate the database with sample records. 80 | 81 | ### Third task 82 | 83 | Building on the work done in the second task, create a [NextJS](https://nextjs.org/docs) application that consists of the following pages: 84 | 85 | 1. The Home page which consists of a list of notes already created that do not belong to any user with the ability to read the detail of each note when clicked. 86 | 2. A User page that displays the list of notes belonging to the user whose username is passed in the route, using [NextJS](https://nextjs.org/docs)' routing system to pull the relevant username from the URL. The ability to create notes, view notes, and delete notes should be provided. 87 | 3. A Protected page `/admin` that would be used to create users and to view the list of users and notes created by each of them. You're free to decide how you would handle access to this page. The only requirement is that it is protected, i.e. can't be accessed without some sort of credentials provided. 88 | 89 | **You get bonus points if you:** 90 | 91 | 1. Create [Storybook](https://storybook.js.org/) for the components used in creating the pages. *The project already has `storybook` setup.* 92 | 2. Use [Chakra-UI](https://chakra-ui.com/getting-started) as the component library of choice *This is already installed in the project.* 93 | 3. Use `packages/components` to house the components used in the project. 94 | 4. Add relevant tests where necessary for the frontend. 95 | 5. Host the whole application on a production URL e.g. Heroku, Vercel, Netlify, AWS, Digitalocean e.t.c 96 | -------------------------------------------------------------------------------- /packages/components/src/utils/functions.ts: -------------------------------------------------------------------------------- 1 | import groupBy from "json-groupby"; 2 | //This function removes duplicates from an array of objects 3 | //using the object key 4 | type StringType = string | number; 5 | export function getUnique(arr: Array<{ [K in StringType]: any }>, key: string) { 6 | const unique = arr 7 | .map((object: { [x: string]: any }) => object[key]) 8 | 9 | // store the keys of the unique objects 10 | .map((e, i, final) => final.indexOf(e) === i && i) 11 | 12 | // eliminate the dead keys & store unique objects 13 | .filter((e) => typeof e == "number" && arr[e]) 14 | .map((e) => typeof e == "number" && arr[e]); 15 | 16 | return unique; 17 | } 18 | 19 | export const isDefined = (elem: any) => typeof elem !== "undefined"; 20 | 21 | /** 22 | * Breaks an array into 23 | * @param {Array[*]} array The array that is to be grouped into smaller arrays 24 | * @param {String} size The maximum number of elements in each chunked array 25 | * 26 | * @returns {Array[Arrays]} An array of smaller, chunked arrays 27 | * 28 | * @example 29 | * chunkArray(["a","b","c","d"], 2) 30 | * //=> [["a","b"],["c","d"]] 31 | */ 32 | export function chunkArray(array: Array, size: number) { 33 | const results = []; 34 | 35 | while (array.length > 0) results.push(array.splice(0, size)); 36 | 37 | return results; 38 | } 39 | 40 | export function shuffle(array: Array) { 41 | let currentIndex = array.length, 42 | temporaryValue, 43 | randomIndex; 44 | 45 | // While there remain elements to shuffle... 46 | while (0 !== currentIndex) { 47 | // Pick a remaining element... 48 | randomIndex = Math.floor(Math.random() * currentIndex); 49 | currentIndex -= 1; 50 | 51 | // And swap it with the current element. 52 | temporaryValue = array[currentIndex]; 53 | array[currentIndex] = array[randomIndex]; 54 | array[randomIndex] = temporaryValue; 55 | } 56 | 57 | return array; 58 | } 59 | 60 | /** 61 | * Returns the duplicate elements in an array 62 | * @param {Array} arr Array that has duplicate elements 63 | * @returns {Array} Duplicate elements 64 | */ 65 | export function getArrayDuplicates(arr: Array) { 66 | return arr.filter((elem, index, array) => array.indexOf(elem) !== index); 67 | } 68 | 69 | /** 70 | * Checks if an array contains nested arrays or not. 71 | * @param {Array[]} arr 72 | * @returns {Boolean} 73 | * @example 74 | * isNestedArray([["a","b"],["c","d"]]) 75 | * //=> true 76 | */ 77 | export function isNestedArray(arr: Array) { 78 | const nestedArrays = arr.filter((item) => Array.isArray(item)); 79 | return nestedArrays.length > 0; 80 | } 81 | 82 | export function scrollToTop(style: "auto" | "smooth" | undefined) { 83 | return window.scroll({ 84 | top: 0, 85 | behavior: style ? style : "auto", 86 | }); 87 | } 88 | 89 | export function capFirstLetter(string: string) { 90 | return string.charAt(0).toUpperCase() + string.slice(1); 91 | } 92 | 93 | // This component renders a title, description, icon, badge, and hide/show link 94 | // as well as a renderElement on the Right and Left 95 | 96 | export function removeClassPrefix(word: string) { 97 | return word 98 | .replace(/Primary /g, "") 99 | .replace(/JSS /g, "") 100 | .replace(/SSS /g, ""); 101 | } 102 | 103 | export const toTitleCase = (string: string) => 104 | string 105 | .split(" ") 106 | .map((w) => w.substring(0, 1).toUpperCase() + w.substring(1)) 107 | .join(" ") 108 | .split(",") 109 | .map((w) => w.substring(0, 1).toUpperCase() + w.substring(1)) 110 | .join(", ") 111 | .split(".") 112 | .map((w) => w.substring(0, 1).toUpperCase() + w.substring(1)) 113 | .join(". "); 114 | 115 | /** 116 | * Sorts an array of objects in alphabetical order using the supplied `key` 117 | * @param {Array[{}]} array Array of objects to be ordered alphabetically 118 | * @param {String} key the object key to be used in the sorting 119 | * @returns A sorted Array 120 | * @example 121 | * orderAlphabetically([{food: "Yam", price: 500},{food: "Egg", price: 100}], "food") 122 | * //=> [{food: "Egg", price: 100}, {food: "Yam", price: 500}] 123 | */ 124 | export const orderAlphabetically = (array: Array, key: string) => { 125 | return array.sort((a, b) => { 126 | if (a[key] < b[key]) return -1; 127 | if (a[key] > b[key]) return 1; 128 | return 0; 129 | }); 130 | }; 131 | 132 | /** 133 | * Checks if all the properties of an object have values 134 | * Applies to boolean, string, array or object values 135 | * @param {Object} obj - Object to be checked 136 | * @param {Object} deleteKey1 - A key to be disregarded 137 | * @param {Object} deleteKey2 - A key to be disregarded 138 | * @returns Boolean 139 | */ 140 | 141 | export function isValidObject( 142 | obj: { [key: string]: any }, 143 | deleteKey1 = "", 144 | deleteKey2 = "" 145 | ): boolean { 146 | const objKeyValue = Object.entries(obj) 147 | .filter((keyValue) => keyValue[0] !== deleteKey1) 148 | .filter((keyValue) => keyValue[0] !== deleteKey2) 149 | .map((keyValue) => keyValue[1]); 150 | return objKeyValue.length > 0 151 | ? objKeyValue.every((item) => 152 | Array.isArray(item) 153 | ? item.length > 0 154 | : typeof item === "object" 155 | ? isValidObject(item) 156 | : Boolean(item) || typeof item === "boolean" 157 | ) 158 | : false; 159 | } 160 | /** 161 | * Groups an array of objects using a key into two arrays 162 | * one containing objects that have the key, and the other containing 163 | * objects that do not have the key. 164 | * @param {Array} array An array of objects containing properties to be grouped 165 | * @param {String} key Name of the object key that will be used to group arrays 166 | * @example 167 | * groupArrayByKey([{food: "Yam", type: "Carbohydrate"},{food: "Beans", type: "Protein"},{food: "Rice", type: "Carbohydrate"},{food: "Vegetable", type: "vitamins"}],"Carbohydrate") 168 | * //=> {arrayWithKey: [{food: "Yam", type: "Carbohydrate"}, {food: "Rice", type: "Carbohydrate"}], arrayWithoutKey: [{food: "Beans", type: "Protein"}, {food: "Vegetable", type: "vitamins"}]} 169 | */ 170 | export function groupArrayByKey(array: Array, key: string) { 171 | const firstKey = array[0][key]; 172 | const arrayWithKey = array.filter((item) => item[key] === firstKey); 173 | const arrayWithoutKey = array.filter((item) => !arrayWithKey.includes(item)); 174 | return { arrayWithKey, arrayWithoutKey }; 175 | } 176 | 177 | /** 178 | * Chunks an array of objects into smaller arrays with similiar object keys 179 | * @param {Array} array 180 | * @param {String} key 181 | * @example 182 | * chunkArrayByKey([{food: "Yam", type: "Carbohydrate"},{food: "Beans", type: "Protein"},{food: "Rice", type: "Carbohydrate"},{food: "Vegetable", type: "vitamins"}],"Carbohydrate") 183 | * //=> [[{food: "Yam", type: "Carbohydrate"}, {food: "Rice", type: "Carbohydrate"}],[{food: "Beans", type: "Protein"}],[{food: "Vegetable", type: "vitamins"}]] 184 | */ 185 | export function chunkArrayByKey(array: Array, key: string) { 186 | const result = []; 187 | let continueLoop = true; 188 | let startArr = array; 189 | 190 | const rr = groupBy(array, [key]); 191 | return Object.values(rr); 192 | console.log(rr); 193 | console.log(array); 194 | console.log(key); 195 | while (continueLoop) { 196 | const { arrayWithKey, arrayWithoutKey } = groupArrayByKey(startArr, key); 197 | if (arrayWithKey.length > 0) result.push(arrayWithKey); 198 | startArr = arrayWithoutKey; 199 | if (arrayWithoutKey.length === 0) continueLoop = false; 200 | } 201 | console.log(result); 202 | return result; 203 | } 204 | 205 | export function getRandomElemFromArray(array: Array) { 206 | return array[Math.floor(Math.random() * array.length)]; 207 | } 208 | 209 | export function newChunkArrayByKey(array: Array, key: string) { 210 | const result = groupBy(array, [key]); 211 | return Object.entries(result); 212 | } 213 | 214 | /** 215 | * This combines an array of strings into a string output inserting 216 | * "," and "and" where appropriate 217 | * @param {Array} array 218 | * 219 | * @example 220 | * arrayToString(["a","b","c"]) 221 | * //=>"a, b and c" 222 | */ 223 | export function arrayToString(array: Array, separator = "and") { 224 | let string = ""; 225 | 226 | array.forEach((x, i) => { 227 | if (i < array.length - 2) string = string.concat(x, ", "); 228 | if (i === array.length - 2) string = string.concat(x, ` ${separator} `); 229 | if (i > array.length - 2) string = string.concat(x); 230 | }); 231 | 232 | return string; 233 | } 234 | 235 | /* Given a start date, end date and day name, return 236 | ** an array of dates between the two dates for the 237 | ** given day inclusive 238 | ** @param {Date} start - date to start from 239 | ** @param {Date} end - date to end on 240 | ** @param {string} dayName - name of day 241 | ** @returns {Array} array of Dates 242 | */ 243 | export function getDaysBetweenDates( 244 | start: string | number | Date, 245 | end: number | string | Date, 246 | dayName: string 247 | ) { 248 | const result = []; 249 | const days: { [key: string]: number } = { 250 | sunday: 0, 251 | monday: 1, 252 | tuesday: 2, 253 | wednesday: 3, 254 | thursday: 4, 255 | friday: 5, 256 | saturday: 6, 257 | }; 258 | const day = days[dayName.toLowerCase()]; 259 | // Copy start date 260 | const current = new Date(start); 261 | // Shift to next of required days 262 | current.setDate(current.getDate() + ((day - current.getDay() + 7) % 7)); 263 | // While less than end date, add dates to result array 264 | while (current < end) { 265 | result.push(+current); 266 | current.setDate(current.getDate() + 7); 267 | } 268 | return result; 269 | } 270 | 271 | export const scrollToId = (id: string, scrollProps = {}) => { 272 | const section = document.getElementById(id); 273 | const scrollObj: ScrollIntoViewOptions = { 274 | block: "start", 275 | inline: "start", 276 | behavior: "smooth", 277 | ...scrollProps, 278 | }; 279 | if (section) { 280 | section.scrollIntoView(scrollObj); 281 | } 282 | }; 283 | --------------------------------------------------------------------------------