├── FROSTED ├── bin ├── detect ├── release └── compile ├── workspaces ├── lib │ ├── src │ │ ├── config │ │ │ └── clients.json │ │ ├── index.ts │ │ ├── types │ │ │ ├── page.ts │ │ │ ├── category.ts │ │ │ ├── chapter.ts │ │ │ ├── item.ts │ │ │ ├── product.ts │ │ │ ├── cart.ts │ │ │ ├── blab.ts │ │ │ ├── wallet.ts │ │ │ ├── subscription.ts │ │ │ ├── index.ts │ │ │ ├── user.ts │ │ │ ├── auth.ts │ │ │ ├── drawing.ts │ │ │ └── order.ts │ │ └── util.ts │ ├── tsconfig.json │ └── package.json ├── server │ ├── app.yaml │ ├── tests │ │ ├── helpers.ts │ │ ├── tsconfig.json │ │ ├── server.tests.ts │ │ └── db │ │ │ └── migrations.tests.ts │ ├── Dockerfile │ ├── src │ │ ├── shared │ │ │ ├── socket │ │ │ │ ├── handlers │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ ├── types │ │ │ │ ├── index.ts │ │ │ │ └── models │ │ │ │ │ ├── category.ts │ │ │ │ │ ├── setting.ts │ │ │ │ │ ├── drawing.ts │ │ │ │ │ ├── subscription.ts │ │ │ │ │ ├── product.ts │ │ │ │ │ ├── wallet.ts │ │ │ │ │ ├── cart.ts │ │ │ │ │ ├── item.ts │ │ │ │ │ ├── order.ts │ │ │ │ │ └── user.ts │ │ │ ├── logger.ts │ │ │ ├── db │ │ │ │ ├── template.ts │ │ │ │ └── check.ts │ │ │ ├── secrets.ts │ │ │ ├── firebase.ts │ │ │ ├── auth │ │ │ │ └── auth0.ts │ │ │ └── trace.ts │ │ ├── routes │ │ │ ├── index.ts │ │ │ ├── main │ │ │ │ └── index.ts │ │ │ ├── shop │ │ │ │ ├── index.ts │ │ │ │ └── paypal.ts │ │ │ ├── stripe │ │ │ │ └── index.ts │ │ │ └── swagger.yaml │ │ ├── config │ │ │ └── app.json │ │ ├── index.ts │ │ └── app.ts │ ├── .eslintrc.json │ ├── jest.config.js │ └── tsconfig.json ├── client │ ├── .firebaserc │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ ├── index.html │ │ └── 404.html │ ├── src │ │ ├── features │ │ │ ├── app │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ ├── App.test.tsx │ │ │ │ ├── LanguageProvider.tsx │ │ │ │ ├── ConfigProvider.tsx │ │ │ │ ├── App.tsx │ │ │ │ ├── slice.ts │ │ │ │ └── SocketListener.tsx │ │ │ ├── languages │ │ │ │ ├── es.json │ │ │ │ ├── en.json │ │ │ │ └── extract.json │ │ │ ├── ui │ │ │ │ ├── Spacer.tsx │ │ │ │ ├── Consent.tsx │ │ │ │ ├── BackToTop.tsx │ │ │ │ ├── StyledFab.tsx │ │ │ │ ├── Card │ │ │ │ │ ├── Skeleton │ │ │ │ │ │ ├── ImagePlaceholder.tsx │ │ │ │ │ │ ├── EarningCard.tsx │ │ │ │ │ │ ├── TotalIncomeCard.tsx │ │ │ │ │ │ ├── TotalGrowthBarChart.tsx │ │ │ │ │ │ └── ProductPlaceholder.tsx │ │ │ │ │ ├── SubCard.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── LoadingLine.tsx │ │ │ │ ├── BlurBackdrop.tsx │ │ │ │ ├── Theme │ │ │ │ │ └── index.tsx │ │ │ │ ├── Dialogs.tsx │ │ │ │ ├── TabPanel.tsx │ │ │ │ ├── CircularProgressWithLabel.tsx │ │ │ │ ├── PasswordField.tsx │ │ │ │ ├── MainLayout.tsx │ │ │ │ ├── Drawer.tsx │ │ │ │ ├── Routing.tsx │ │ │ │ ├── Footer.tsx │ │ │ │ ├── StyledSlider.tsx │ │ │ │ ├── AlertDialog.tsx │ │ │ │ ├── Notifications.tsx │ │ │ │ └── extended │ │ │ │ │ ├── Avatar.tsx │ │ │ │ │ └── AnimateButton.tsx │ │ │ ├── admin │ │ │ │ ├── Vertical.tsx │ │ │ │ ├── Active.tsx │ │ │ │ ├── Users │ │ │ │ │ ├── DialogWrap.tsx │ │ │ │ │ └── UserOrders.tsx │ │ │ │ ├── Orders.tsx │ │ │ │ ├── thunks.ts │ │ │ │ ├── Subscriptions │ │ │ │ │ └── hooks.ts │ │ │ │ ├── slice.ts │ │ │ │ ├── Dashboard │ │ │ │ │ ├── images │ │ │ │ │ │ ├── earning.svg │ │ │ │ │ │ └── social-google.svg │ │ │ │ │ ├── chart-data │ │ │ │ │ │ ├── bajaj-area-chart.ts │ │ │ │ │ │ ├── total-order-month-line-chart.ts │ │ │ │ │ │ ├── total-order-year-line-chart.ts │ │ │ │ │ │ └── total-growth-bar-chart.ts │ │ │ │ │ ├── BajajAreaChartCard.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── Footer.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── MenuItem.tsx │ │ │ ├── pages │ │ │ │ ├── Terms1.tsx │ │ │ │ ├── 404.tsx │ │ │ │ └── Maintenance.tsx │ │ │ ├── shop │ │ │ │ ├── images │ │ │ │ │ └── stripe.png │ │ │ │ ├── FakeCheckout.tsx │ │ │ │ ├── Receipt.tsx │ │ │ │ ├── slice.ts │ │ │ │ ├── OrderAddress.tsx │ │ │ │ └── Payment.tsx │ │ │ ├── profile │ │ │ │ ├── AuthProviders.tsx │ │ │ │ ├── AuthCheck.tsx │ │ │ │ ├── Orders.tsx │ │ │ │ └── Callback.tsx │ │ │ ├── canvas │ │ │ │ ├── LoadingCanvas.tsx │ │ │ │ ├── Player.tsx │ │ │ │ ├── LineSize.tsx │ │ │ │ ├── NameEdit.tsx │ │ │ │ ├── Toolbar.tsx │ │ │ │ ├── slice.ts │ │ │ │ ├── Color.tsx │ │ │ │ └── worker.ts │ │ │ └── home │ │ │ │ ├── images │ │ │ │ ├── github.svg │ │ │ │ ├── redux.svg │ │ │ │ ├── ts.svg │ │ │ │ └── nodejs.svg │ │ │ │ ├── index.tsx │ │ │ │ ├── Subscribe.tsx │ │ │ │ ├── HeroSection.tsx │ │ │ │ └── Logos.tsx │ │ ├── shared │ │ │ ├── constant.ts │ │ │ ├── debouncer.ts │ │ │ ├── selectFile.ts │ │ │ ├── config.ts │ │ │ ├── loadConfig.ts │ │ │ ├── store.ts │ │ │ └── testing.tsx │ │ ├── index.tsx │ │ ├── reportWebVitals.ts │ │ ├── setupTests.ts │ │ └── react-app-env.d.ts │ ├── tools │ │ ├── style-mock.js │ │ ├── webpack │ │ │ └── persistentCache │ │ │ │ └── createEnvironmentHash.js │ │ ├── jest │ │ │ ├── cssTransform.js │ │ │ ├── babelTransform.js │ │ │ └── fileTransform.js │ │ ├── react-intl-sync.js │ │ └── getHttpsConfig.js │ ├── .env.example │ ├── firebase.json │ ├── tsconfig.json │ ├── .eslintrc.json │ ├── jest.config.js │ ├── scripts │ │ └── test.js │ └── .gitignore ├── tsconfig.shared.json └── function-gkill │ ├── tsconfig.json │ └── package.json ├── Procfile ├── docs ├── images │ ├── 4Pane.png │ └── lighthouse.png ├── dev.md ├── README.md └── deploy.md ├── .husky ├── pre-commit └── pre-push ├── .editorconfig ├── jest.config.js ├── prettier.config.js ├── app.json ├── docker-compose.yml ├── tools └── createEnvironmentHash.js ├── .yarnrc.yml ├── .github └── workflows │ ├── tests.yml │ ├── firebase-hosting-pull-request.yml │ ├── firebase-hosting-live.yml │ ├── client-deploy-ghpages.yml │ └── deploy-google-server.yml ├── amplify.yml ├── .vscode ├── settings.json ├── launch.json └── tasks.json ├── .eslintrc.json ├── .gitignore ├── README.md └── package.json /FROSTED: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/detect: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/release: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /workspaces/lib/src/config/clients.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /workspaces/server/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: nodejs18 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: PORT=$PORT node workspaces/server/dist/index.js 2 | -------------------------------------------------------------------------------- /workspaces/lib/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './util' 3 | -------------------------------------------------------------------------------- /docs/images/4Pane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruyd/fullstack-monorepo/HEAD/docs/images/4Pane.png -------------------------------------------------------------------------------- /workspaces/client/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "drawspace-6c652" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | yarn precommit 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/images/lighthouse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruyd/fullstack-monorepo/HEAD/docs/images/lighthouse.png -------------------------------------------------------------------------------- /workspaces/client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /workspaces/client/src/features/app/index.ts: -------------------------------------------------------------------------------- 1 | export * from './slice' 2 | export * from './thunks' 3 | export * from './types' 4 | -------------------------------------------------------------------------------- /workspaces/client/src/features/languages/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "home.button": "Pruebalo", 3 | "home.title": "Bazaar de Arte" 4 | } -------------------------------------------------------------------------------- /workspaces/client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruyd/fullstack-monorepo/HEAD/workspaces/client/public/favicon.ico -------------------------------------------------------------------------------- /workspaces/client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruyd/fullstack-monorepo/HEAD/workspaces/client/public/logo192.png -------------------------------------------------------------------------------- /workspaces/client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruyd/fullstack-monorepo/HEAD/workspaces/client/public/logo512.png -------------------------------------------------------------------------------- /workspaces/client/tools/style-mock.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process() { 3 | return 'module.exports = {};' 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /workspaces/client/src/features/ui/Spacer.tsx: -------------------------------------------------------------------------------- 1 | export default function Spacer() { 2 | return
3 | } 4 | -------------------------------------------------------------------------------- /workspaces/server/tests/helpers.ts: -------------------------------------------------------------------------------- 1 | jest.mock('../src/shared/logger') 2 | 3 | export function beforeAllHook() { 4 | // ... 5 | } 6 | -------------------------------------------------------------------------------- /workspaces/client/.env.example: -------------------------------------------------------------------------------- 1 | BACKEND=http://localhost:3001 2 | GOOGLE_CLIENT_ID= 3 | AUTH_REDIRECT_URL=http://localhost:3000/callback 4 | -------------------------------------------------------------------------------- /workspaces/client/src/features/languages/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "home.button": "Take it for a spin", 3 | "home.title": "Drawings Marketplace" 4 | } -------------------------------------------------------------------------------- /workspaces/client/src/shared/constant.ts: -------------------------------------------------------------------------------- 1 | export const gridSpacing = 3 2 | export const drawerWidth = 260 3 | export const appDrawerWidth = 320 4 | -------------------------------------------------------------------------------- /workspaces/client/src/features/admin/Vertical.tsx: -------------------------------------------------------------------------------- 1 | export default function VerticalPlayer() { 2 | return
3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | projects: ['/workspaces/*/jest.config.js'], 4 | } 5 | -------------------------------------------------------------------------------- /workspaces/client/src/features/pages/Terms1.tsx: -------------------------------------------------------------------------------- 1 | export default function Terms() { 2 | return ( 3 |
4 |

Terms

5 |
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /workspaces/client/src/features/shop/images/stripe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruyd/fullstack-monorepo/HEAD/workspaces/client/src/features/shop/images/stripe.png -------------------------------------------------------------------------------- /workspaces/server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | WORKDIR /app 3 | ADD ./dist . 4 | RUN npm install --production 5 | EXPOSE 80 8080 443 6 | CMD ["node", "index.js"] 7 | 8 | -------------------------------------------------------------------------------- /workspaces/server/tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /workspaces/client/src/features/admin/Active.tsx: -------------------------------------------------------------------------------- 1 | export default function Active(): JSX.Element { 2 | return ( 3 |
4 |
Active
5 |
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | singleQuote: true, 4 | trailingComma: 'none', 5 | arrowParens: 'avoid', 6 | semi: false, 7 | tabWidth: 2, 8 | } 9 | -------------------------------------------------------------------------------- /workspaces/client/src/features/ui/Consent.tsx: -------------------------------------------------------------------------------- 1 | export default function Consent(): JSX.Element { 2 | return ( 3 |
4 |

Consent Banner

5 |
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildpacks": [ 3 | { 4 | "url": "heroku/nodejs" 5 | }, 6 | { 7 | "url": "https://github.com/heroku/heroku-buildpack-inline.git" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | database: 3 | image: postgres:14.5 4 | ports: 5 | - 5432:5432 6 | environment: 7 | POSTGRES_USER: postgres 8 | POSTGRES_PASSWORD: postgrespass 9 | -------------------------------------------------------------------------------- /workspaces/client/src/features/ui/BackToTop.tsx: -------------------------------------------------------------------------------- 1 | import Fab from '@mui/material/Fab' 2 | 3 | export default function FabBackToTop() { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /workspaces/lib/src/types/page.ts: -------------------------------------------------------------------------------- 1 | import { Blab } from './blab' 2 | 3 | export interface Page { 4 | pageId?: string 5 | path?: string 6 | title?: string 7 | roles?: string[] 8 | blabs?: Blab[] 9 | } 10 | -------------------------------------------------------------------------------- /workspaces/client/src/features/profile/AuthProviders.tsx: -------------------------------------------------------------------------------- 1 | import { GoogleOneTap } from '../profile/GoogleOneTap' 2 | export default function AuthProviderInjections() { 3 | return ( 4 | <> 5 | 6 | 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /workspaces/lib/src/types/category.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Item } from '.' 2 | 3 | export interface Category extends Entity { 4 | categoryId?: string 5 | title?: string 6 | imageUrl: string 7 | urlName?: string 8 | items?: Item[] 9 | } 10 | -------------------------------------------------------------------------------- /tools/createEnvironmentHash.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { createHash } = require('crypto'); 3 | 4 | module.exports = env => { 5 | const hash = createHash('md5'); 6 | hash.update(JSON.stringify(env)); 7 | 8 | return hash.digest('hex'); 9 | }; 10 | -------------------------------------------------------------------------------- /workspaces/client/src/features/admin/Users/DialogWrap.tsx: -------------------------------------------------------------------------------- 1 | export default function DialogWrap({ children }: { children: React.ReactNode }): JSX.Element { 2 | return ( 3 |
4 |

DialogWrap

5 | {children} 6 |
7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /workspaces/lib/src/types/chapter.ts: -------------------------------------------------------------------------------- 1 | import { Blab } from './blab' 2 | 3 | export interface Chapter { 4 | chapterId?: string 5 | titleId?: number 6 | chapterNumber?: number 7 | title?: string 8 | chapter?: string 9 | blabs?: Blab[] 10 | } 11 | -------------------------------------------------------------------------------- /workspaces/lib/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.shared.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "baseUrl": "." 6 | }, 7 | "include": ["src", ".eslintrc.js", "package.json"], 8 | "exclude": ["node_modules", "dist"] 9 | } 10 | -------------------------------------------------------------------------------- /workspaces/client/tools/webpack/persistentCache/createEnvironmentHash.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const { createHash } = require('crypto') 3 | 4 | module.exports = env => { 5 | const hash = createHash('md5') 6 | hash.update(JSON.stringify(env)) 7 | 8 | return hash.digest('hex') 9 | } 10 | -------------------------------------------------------------------------------- /workspaces/server/src/shared/socket/handlers/index.ts: -------------------------------------------------------------------------------- 1 | import { SocketHandler } from '..' 2 | 3 | export const userHandler: SocketHandler = (io, socket) => { 4 | socket.on('user:message', data => { 5 | console.log('user', data) 6 | }) 7 | } 8 | 9 | export default [userHandler] 10 | -------------------------------------------------------------------------------- /workspaces/client/src/features/languages/extract.json: -------------------------------------------------------------------------------- 1 | { 2 | "home.button": { 3 | "defaultMessage": "Take it for a spin", 4 | "description": "Hero Button Caption" 5 | }, 6 | "home.title": { 7 | "defaultMessage": "Drawings Marketplace", 8 | "description": "Hero Title" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | plugins: 4 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs 5 | spec: "@yarnpkg/plugin-workspace-tools" 6 | - path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs 7 | spec: "@yarnpkg/plugin-typescript" 8 | 9 | yarnPath: .yarn/releases/yarn-3.3.0.cjs 10 | -------------------------------------------------------------------------------- /workspaces/server/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "@typescript-eslint/no-unused-vars": "error", 4 | "no-console": "warn" 5 | }, 6 | "settings": { 7 | "import/resolver": { 8 | "node": true, 9 | "typescript": { 10 | "project": "./tsconfig.json" 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /workspaces/client/src/features/app/types.ts: -------------------------------------------------------------------------------- 1 | export enum NotificationSeverity { 2 | info = 'info', 3 | success = 'success', 4 | warning = 'warning', 5 | error = 'error', 6 | } 7 | export interface AppNotification { 8 | id: string 9 | message: string 10 | severity?: NotificationSeverity 11 | closed?: boolean 12 | } 13 | -------------------------------------------------------------------------------- /workspaces/client/src/features/admin/Orders.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Orders needs special view, we want to see oneline status as well as certain actions 4 | * maybe we can enrich generic editor via props, think 5 | */ 6 | export default function Orders() { 7 | return ( 8 |
9 |

Orders

10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /workspaces/server/src/routes/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import main from './main' 3 | import profile from './profile' 4 | import shop from './shop' 5 | import stripe from './stripe' 6 | 7 | const router = express.Router() 8 | router.use(main) 9 | router.use(profile) 10 | router.use(shop) 11 | router.use(stripe) 12 | export default router 13 | -------------------------------------------------------------------------------- /workspaces/client/src/features/ui/StyledFab.tsx: -------------------------------------------------------------------------------- 1 | import Fab from '@mui/material/Fab' 2 | import { styled } from '@mui/material/styles' 3 | 4 | export const StyledFab = styled(Fab)(({ theme }) => ({ 5 | [theme.breakpoints.up('md')]: {}, 6 | position: 'absolute', 7 | right: '1em', 8 | bottom: '1em', 9 | zIndex: 1 10 | })) 11 | 12 | export default StyledFab 13 | -------------------------------------------------------------------------------- /workspaces/client/src/shared/debouncer.ts: -------------------------------------------------------------------------------- 1 | const cache: Record = {} 2 | export const debouncer = (key: string, callback: () => void, delay = 1000) => { 3 | if (cache[key]) { 4 | window.clearTimeout(cache[key]) 5 | } 6 | const timer = window.setTimeout(() => callback(), delay) 7 | cache[key] = timer 8 | } 9 | 10 | export default debouncer 11 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | tests: 8 | runs-on: 'ubuntu-latest' 9 | steps: 10 | - uses: 'actions/checkout@v3' 11 | 12 | - uses: actions/checkout@v3 13 | 14 | - name: Install 15 | run: yarn install --immutable 16 | 17 | - name: Tests 18 | 19 | run: yarn test 20 | -------------------------------------------------------------------------------- /workspaces/client/src/features/ui/Card/Skeleton/ImagePlaceholder.tsx: -------------------------------------------------------------------------------- 1 | // material-ui 2 | import Skeleton from '@mui/material/Skeleton'; 3 | 4 | // ==============================|| SKELETON IMAGE CARD ||============================== // 5 | 6 | const ImagePlaceholder = ({ ...others }) => ; 7 | 8 | export default ImagePlaceholder; 9 | -------------------------------------------------------------------------------- /workspaces/client/src/features/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import Container from '@mui/material/Container' 2 | import image from './images/404.svg' 3 | import Typography from '@mui/material/Typography' 4 | 5 | export default function Error404() { 6 | return ( 7 | 8 | 9 | Oops! 10 | 11 | 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /workspaces/client/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import './styles/index.scss' 4 | import App from './features/app/App' 5 | 6 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 7 | const container = document.getElementById('root')! 8 | const root = createRoot(container) 9 | root.render( 10 | 11 | 12 | 13 | ) 14 | -------------------------------------------------------------------------------- /amplify.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | applications: 3 | - frontend: 4 | phases: 5 | preBuild: 6 | commands: 7 | - yarn install 8 | build: 9 | commands: 10 | - yarn workspace client build 11 | artifacts: 12 | baseDirectory: build 13 | files: 14 | - '**/*' 15 | cache: 16 | paths: 17 | - node_modules/**/* 18 | appRoot: workspaces/client 19 | -------------------------------------------------------------------------------- /workspaces/server/src/routes/main/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import { checkFirebase, gallery, sendClientConfigSettings, start } from './controller' 3 | 4 | const router = express.Router() 5 | 6 | router.get(['/gallery', '/gallery/:userId'], gallery) 7 | 8 | router.get('/config', sendClientConfigSettings) 9 | 10 | router.post('/start', start) 11 | 12 | router.post('/firebase/check', checkFirebase) 13 | 14 | export default router 15 | -------------------------------------------------------------------------------- /workspaces/client/src/features/ui/LoadingLine.tsx: -------------------------------------------------------------------------------- 1 | import LinearProgress from '@mui/material/LinearProgress' 2 | import { useAppSelector } from '../../shared/store' 3 | 4 | export default function LoadingLine() { 5 | const loading = useAppSelector(store => store.app.loading) 6 | return ( 7 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /workspaces/lib/src/types/item.ts: -------------------------------------------------------------------------------- 1 | import { Entity } from '.' 2 | import { Blab } from './blab' 3 | import { Chapter } from './chapter' 4 | 5 | export interface Item extends Entity { 6 | itemId?: string 7 | title?: string 8 | urlName?: string 9 | subscriptions?: string[] 10 | tokens?: number 11 | price?: number 12 | currency?: string 13 | inventory?: number 14 | blabs?: Blab[] 15 | paywall?: boolean 16 | tags?: string[] 17 | chapters?: Chapter[] 18 | } 19 | -------------------------------------------------------------------------------- /workspaces/lib/src/types/product.ts: -------------------------------------------------------------------------------- 1 | export interface Price { 2 | id: string 3 | amount: number 4 | currency: string 5 | interval?: string 6 | intervalCount?: number 7 | freeTrialDays?: number 8 | divide_by?: number 9 | } 10 | 11 | export interface Product { 12 | productId: string 13 | title: string 14 | description: string 15 | imageUrl?: string 16 | images?: string[] 17 | keywords?: string 18 | prices?: Price[] 19 | shippable?: boolean 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsserver.experimental.enableProjectDiagnostics": true, 3 | "eslint.useESLintClass": true, 4 | "files.exclude": { 5 | "amplify/.config": true, 6 | "amplify/**/*-parameters.json": true, 7 | "amplify/**/amplify.state": true, 8 | "amplify/**/transform.conf.json": true, 9 | "amplify/#current-cloud-backend": true, 10 | "amplify/backend/amplify-meta.json": true, 11 | "amplify/backend/awscloudformation": true 12 | } 13 | } -------------------------------------------------------------------------------- /workspaces/tsconfig.shared.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "composite": true, 5 | "declaration": true, 6 | "strict": true, 7 | "target": "ES2021", 8 | "module": "commonjs", 9 | "lib": ["ES2021"], 10 | "moduleResolution": "node", 11 | "sourceMap": true, 12 | "preserveConstEnums": true, 13 | "resolveJsonModule": true, 14 | "esModuleInterop": true, 15 | "skipLibCheck": true, 16 | "outDir": "dist" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /workspaces/client/src/features/canvas/LoadingCanvas.tsx: -------------------------------------------------------------------------------- 1 | import CircularProgress from '@mui/material/CircularProgress' 2 | import { useAppSelector } from '../../shared/store' 3 | 4 | export default function LoadingCanvas() { 5 | const loading = useAppSelector(store => store.canvas.loading) 6 | return ( 7 | <> 8 | 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /workspaces/client/src/features/profile/AuthCheck.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useAppSelector } from '../../shared/store' 3 | import Login from './Login' 4 | 5 | export default function AuthCheck({ 6 | children, 7 | secure, 8 | }: { 9 | children: React.ReactNode 10 | secure?: boolean 11 | }) { 12 | const token = useAppSelector(state => state.app.token) 13 | const denied = secure && !token 14 | if (denied) { 15 | return 16 | } 17 | return children as JSX.Element 18 | } 19 | -------------------------------------------------------------------------------- /workspaces/client/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals' 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry) 7 | getFID(onPerfEntry) 8 | getFCP(onPerfEntry) 9 | getLCP(onPerfEntry) 10 | getTTFB(onPerfEntry) 11 | }) 12 | } 13 | } 14 | 15 | export default reportWebVitals 16 | -------------------------------------------------------------------------------- /workspaces/server/src/routes/shop/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import { addSubscriptionToCart, checkout } from './controller' 3 | import { capturePaymentHandler, createOrderHandler } from './paypal' 4 | 5 | const router = express.Router() 6 | 7 | router.post('/shop/checkout', checkout) 8 | 9 | router.post('/shop/subscribe', addSubscriptionToCart) 10 | 11 | router.post('/paypal/order', createOrderHandler) 12 | 13 | router.post('/paypal/orders/:orderID/capture', capturePaymentHandler) 14 | 15 | export default router 16 | -------------------------------------------------------------------------------- /workspaces/client/tools/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | 4 | // This is a custom Jest transformer turning style imports into empty objects. 5 | // http://facebook.github.io/jest/docs/en/webpack.html 6 | 7 | module.exports = { 8 | process(sourceText, sourcePath, options) { 9 | return { 10 | code: `module.exports = ${JSON.stringify(path.basename(sourcePath))};`, 11 | } 12 | }, 13 | getCacheKey() { 14 | // The output is always the same. 15 | return 'cssTransform' 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /workspaces/client/src/features/ui/BlurBackdrop.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@mui/material/styles/styled' 2 | 3 | const StyledDiv = styled('div')` 4 | backdrop-filter: blur(6px); 5 | position: absolute; 6 | right: 0; 7 | left: 0; 8 | ` 9 | export function BlurBackdrop({ 10 | height, 11 | bottom 12 | }: { 13 | height?: string | number 14 | bottom?: string | number 15 | }): JSX.Element { 16 | return ( 17 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /workspaces/server/src/shared/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './models/drawing' 2 | export * from './models/user' 3 | export * from './models/setting' 4 | export * from './models/cart' 5 | export * from './models/order' 6 | export * from './models/subscription' 7 | export * from './models/category' 8 | export * from './models/item' 9 | 10 | import express from 'express' 11 | import { EntityConfig } from '../db' 12 | import { AppAccessToken } from '@lib' 13 | 14 | export type EnrichedRequest = express.Request & { 15 | auth: AppAccessToken 16 | config?: EntityConfig 17 | } 18 | -------------------------------------------------------------------------------- /workspaces/client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Drawspace", 3 | "name": "Drawspace", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /workspaces/client/src/features/admin/thunks.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | import { AnyAction, createAsyncThunk, ThunkDispatch } from '@reduxjs/toolkit' 4 | import { GridPatchProps, PagedResult } from '@lib' 5 | import { get } from '../app' 6 | import { patch } from './slice' 7 | 8 | export const loadDataAsync = createAsyncThunk( 9 | 'admin/data/read', 10 | async (name: string, { dispatch, getState }) => { 11 | const response = await get(`${name}`) 12 | dispatch(patch({ data: { [name]: { ...response.data } } })) 13 | }, 14 | ) 15 | -------------------------------------------------------------------------------- /workspaces/lib/src/types/cart.ts: -------------------------------------------------------------------------------- 1 | import { Drawing } from './drawing' 2 | import { Price, Product } from './product' 3 | 4 | export const CartType = { 5 | PRODUCT: 'product', 6 | SUBSCRIPTION: 'subscription', 7 | DRAWING: 'drawing', 8 | TOKENS: 'tokens' 9 | } as const 10 | 11 | export type CartType = typeof CartType[keyof typeof CartType] 12 | 13 | export interface Cart { 14 | cartId: string 15 | userId: string 16 | quantity: number 17 | cartType?: CartType 18 | drawingId?: string 19 | productId?: string 20 | priceId?: string 21 | drawing?: Drawing 22 | product?: Partial 23 | } 24 | -------------------------------------------------------------------------------- /workspaces/client/src/features/ui/Theme/index.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from '@mui/material/styles' 2 | import React from 'react' 3 | import { useAppSelector } from '../../../shared/store' 4 | import { getTheme } from './getTheme' 5 | 6 | export default function ThemeSwitch({ children }: React.PropsWithChildren): JSX.Element { 7 | const darkTheme = useAppSelector(store => store.app.darkMode) 8 | const state = useAppSelector(store => store.app.ui) 9 | const theme = React.useMemo(() => getTheme(darkTheme, state), [darkTheme, state]) 10 | return {children} 11 | } 12 | -------------------------------------------------------------------------------- /workspaces/server/src/shared/types/models/category.ts: -------------------------------------------------------------------------------- 1 | import { Category } from '@lib' 2 | import { DataTypes } from 'sequelize' 3 | import { EntityDefinition } from '../../db' 4 | import { addModel } from '../../db/util' 5 | 6 | export const CategoryDefinition: EntityDefinition = { 7 | categoryId: { 8 | type: DataTypes.STRING, 9 | primaryKey: true 10 | }, 11 | title: { 12 | type: DataTypes.STRING 13 | }, 14 | imageUrl: { 15 | type: DataTypes.STRING 16 | } 17 | } 18 | 19 | export const CategoryModel = addModel({ 20 | name: 'category', 21 | attributes: CategoryDefinition 22 | }) 23 | -------------------------------------------------------------------------------- /workspaces/lib/src/types/blab.ts: -------------------------------------------------------------------------------- 1 | export const BlabTypes = { 2 | Text: 'text', 3 | Image: 'image', 4 | Video: 'video', 5 | Audio: 'audio', 6 | File: 'file' 7 | } as const 8 | 9 | export type BlabType = typeof BlabTypes[keyof typeof BlabTypes] 10 | 11 | export interface Blab { 12 | BlabId?: string 13 | BlabType?: BlabType 14 | Blab?: string 15 | BlabName?: string 16 | BlabDescription?: string 17 | BlabUrl?: string 18 | BlabSize?: number 19 | BlabWidth?: number 20 | BlabHeight?: number 21 | BlabDuration?: number 22 | BlabEncoding?: string 23 | BlabMimeType?: string 24 | BlabExtension?: string 25 | } 26 | -------------------------------------------------------------------------------- /workspaces/server/src/routes/stripe/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import { 3 | stripeCreatePaymentIntent, 4 | stripeCreateVerifyIdentitySession, 5 | stripeWebHook, 6 | syncProductsHandler 7 | } from '../stripe/controller' 8 | 9 | const router = express.Router() 10 | 11 | router.post('/stripe/payment/intent', stripeCreatePaymentIntent) 12 | 13 | router.post('/stripe/identity/start', stripeCreateVerifyIdentitySession) 14 | 15 | router.post('/stripe/webhook', express.raw({ type: 'application/json' }), stripeWebHook) 16 | 17 | router.get('/stripe/products/sync', syncProductsHandler) 18 | 19 | export default router 20 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | declare HAS_CLIENT_CHANGE=$(git show --name-only -r --stat --oneline HEAD^^..HEAD | grep 'workspaces/client') 5 | declare HAS_SERVER_CHANGE=$(git show --name-only -r --stat --oneline HEAD^^..HEAD | grep 'workspaces/server') 6 | 7 | if [ -n "$HAS_CLIENT_CHANGE" ]; then 8 | echo "Client changed, testing client build" 9 | yarn jest --selectProjects client --maxWorkers=50% 10 | fi 11 | 12 | if [ -n "$HAS_SERVER_CHANGE" ]; then 13 | echo "Server changed, testing server build" 14 | yarn docker && yarn jest --selectProjects server --runInBand --bail 15 | fi 16 | 17 | -------------------------------------------------------------------------------- /workspaces/lib/src/types/wallet.ts: -------------------------------------------------------------------------------- 1 | import { Entity } from '.' 2 | 3 | export interface WalletTransaction extends Entity { 4 | transactionId?: string 5 | userId?: string 6 | amount?: number 7 | orderId?: string 8 | } 9 | 10 | export const WalletStatus = { 11 | Active: 'active', 12 | Flagged: 'flagged', 13 | Frozen: 'frozen', 14 | Closed: 'closed' 15 | } as const 16 | 17 | export type WalletStatus = typeof WalletStatus[keyof typeof WalletStatus] 18 | 19 | export interface Wallet extends Entity { 20 | walletId?: string 21 | balance?: number 22 | currency?: string 23 | status?: WalletStatus 24 | transactions?: WalletTransaction[] 25 | } 26 | -------------------------------------------------------------------------------- /workspaces/server/src/shared/types/models/setting.ts: -------------------------------------------------------------------------------- 1 | import { Setting } from '@lib' 2 | import { DataTypes } from 'sequelize' 3 | import { sendConfig } from '../../../shared/socket' 4 | import { addModel } from '../../../shared/db/util' 5 | 6 | export const SettingModel = addModel({ 7 | name: 'setting', 8 | attributes: { 9 | name: { 10 | primaryKey: true, 11 | type: DataTypes.STRING 12 | }, 13 | data: { 14 | type: DataTypes.JSONB 15 | } 16 | }, 17 | joins: [], 18 | roles: ['admin'], 19 | publicRead: false, 20 | publicWrite: false, 21 | onChanges: sendConfig 22 | }) 23 | 24 | export default SettingModel 25 | -------------------------------------------------------------------------------- /workspaces/server/jest.config.js: -------------------------------------------------------------------------------- 1 | const { pathsToModuleNameMapper } = require('ts-jest') 2 | const { compilerOptions } = require('./tsconfig.json') 3 | const paths = compilerOptions.paths || {} 4 | const rootPath = compilerOptions.baseUrl || '.' 5 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 6 | module.exports = { 7 | preset: 'ts-jest', 8 | displayName: 'server', 9 | testEnvironment: 'node', 10 | modulePathIgnorePatterns: ['/build/', '/dist/'], 11 | roots: [''], 12 | testMatch: ['/tests/**/*.tests.ts'], 13 | modulePaths: [rootPath], 14 | moduleNameMapper: pathsToModuleNameMapper(paths, { prefix: '/' }), 15 | } 16 | -------------------------------------------------------------------------------- /workspaces/server/src/shared/logger.ts: -------------------------------------------------------------------------------- 1 | import winston from 'winston' 2 | 3 | const format = winston.format.combine(winston.format.timestamp(), winston.format.simple()) 4 | const logger = winston.createLogger({ 5 | level: 'info', 6 | transports: [ 7 | new winston.transports.Console({ 8 | format: winston.format.combine(winston.format.colorize(), winston.format.simple()), 9 | }), 10 | new winston.transports.File({ 11 | filename: '_error.log', 12 | level: 'error', 13 | format, 14 | }), 15 | new winston.transports.File({ 16 | filename: '_trace.log', 17 | format, 18 | }), 19 | ], 20 | }) 21 | 22 | export default logger 23 | -------------------------------------------------------------------------------- /workspaces/client/tools/jest/babelTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const babelJest = require('babel-jest').default 4 | 5 | const hasJsxRuntime = (() => { 6 | if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') { 7 | return false 8 | } 9 | 10 | try { 11 | require.resolve('react/jsx-runtime') 12 | return true 13 | } catch (e) { 14 | return false 15 | } 16 | })() 17 | 18 | module.exports = babelJest.createTransformer({ 19 | presets: [ 20 | [ 21 | require.resolve('babel-preset-react-app'), 22 | { 23 | runtime: hasJsxRuntime ? 'automatic' : 'classic', 24 | }, 25 | ], 26 | ], 27 | babelrc: false, 28 | configFile: false, 29 | }) 30 | -------------------------------------------------------------------------------- /workspaces/client/src/features/ui/Dialogs.tsx: -------------------------------------------------------------------------------- 1 | import { routes } from '../../shared/routes' 2 | import OnboardingDialog from '../profile/OnboardingDialog' 3 | const dialogs = routes.filter(route => route.dialog && !route.dialog?.includes('onboard')) 4 | 5 | /** 6 | * Multi component dialogs that return empty if not open 7 | * Dev Note: Single component level dialogs -> inside component 8 | * @returns 9 | */ 10 | export default function Dialogs() { 11 | return ( 12 | <> 13 | {dialogs.map(dialog => { 14 | const DialogComponent = dialog.component 15 | return 16 | })} 17 | 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /workspaces/function-gkill/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowUnreachableCode": false, 4 | "allowUnusedLabels": false, 5 | "declaration": false, 6 | "forceConsistentCasingInFileNames": true, 7 | "lib": ["es2018"], 8 | "module": "commonjs", 9 | "noEmitOnError": true, 10 | "esModuleInterop": true, 11 | "resolveJsonModule": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "noImplicitReturns": true, 14 | "sourceMap": true, 15 | "strict": true, 16 | "allowJs": false, 17 | "target": "es2018", 18 | "rootDir": ".", 19 | "outDir": "dist" 20 | }, 21 | "include": ["src", "package.json"], 22 | "exclude": ["node_modules"] 23 | } 24 | -------------------------------------------------------------------------------- /workspaces/server/tests/server.tests.ts: -------------------------------------------------------------------------------- 1 | import request from 'supertest' 2 | import createBackendApp from '../src/app' 3 | import { getRoutesFromApp } from '../src/shared/server' 4 | import { beforeAllHook } from './helpers' 5 | 6 | beforeAll(() => beforeAllHook()) 7 | describe('server route checks', () => { 8 | const app = createBackendApp({ checks: false, trace: false }) 9 | const routes = getRoutesFromApp(app) 10 | 11 | test('should have at least one route', () => { 12 | expect(routes.length).toBeGreaterThan(0) 13 | }) 14 | test('should return a 200 status code', async () => { 15 | const response = await request(app).get('/') 16 | expect(response.status).toBe(200) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /workspaces/client/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "build", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "headers": [ 10 | { 11 | "source": "**", 12 | "headers": [ 13 | { 14 | "key": "Access-Control-Allow-Origin", 15 | "value": "*" 16 | }, 17 | { 18 | "key": "Access-Control-Allow-Headers", 19 | "value": "Access-Control-Allow-Origin" 20 | } 21 | ] 22 | } 23 | ], 24 | "rewrites": [ 25 | { 26 | "source": "**", 27 | "destination": "/index.html" 28 | } 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /workspaces/client/src/features/admin/Subscriptions/hooks.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { useQuery } from 'react-query' 3 | import config from 'src/shared/config' 4 | 5 | export function useProducts() { 6 | const stripeProducts = useQuery( 7 | 'stripe.products', 8 | async () => { 9 | await axios.get('https://api.stripe.com/v1/products', { 10 | auth: { 11 | username: config.settings.system?.paymentMethods?.stripe?.publishableKey || '', 12 | password: '', 13 | }, 14 | }) 15 | }, 16 | { 17 | enabled: false, 18 | }, 19 | ) 20 | // eslint-disable-next-line no-console 21 | console.log(stripeProducts) 22 | return stripeProducts 23 | } 24 | -------------------------------------------------------------------------------- /workspaces/client/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect' 6 | 7 | Object.defineProperty(window, 'matchMedia', { 8 | writable: true, 9 | value: jest.fn().mockImplementation(query => ({ 10 | matches: false, 11 | media: query, 12 | onchange: null, 13 | addListener: jest.fn(), // Deprecated 14 | removeListener: jest.fn(), // Deprecated 15 | addEventListener: jest.fn(), 16 | removeEventListener: jest.fn(), 17 | dispatchEvent: jest.fn(), 18 | })), 19 | }) 20 | -------------------------------------------------------------------------------- /workspaces/server/tests/db/migrations.tests.ts: -------------------------------------------------------------------------------- 1 | import { checkMigrations } from '../../src/shared/db/migrator' 2 | import { beforeAllHook } from 'tests/helpers' 3 | import createBackendApp from '../../src/app' 4 | import { Connection } from '../../src/shared/db' 5 | 6 | beforeAll(() => beforeAllHook()) 7 | afterAll(() => { 8 | Connection.db.close() 9 | }) 10 | describe('database migrations', () => { 11 | const app = createBackendApp({ checks: true, trace: false }) 12 | test('startup', async () => { 13 | const checks = await app.onStartupCompletePromise 14 | expect(checks[0]).toBeTruthy() 15 | }) 16 | 17 | test('migrate', async () => { 18 | const result = await checkMigrations() 19 | expect(result).toBeTruthy() 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /workspaces/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2021", 4 | "module": "commonjs", 5 | "lib": ["ES2021"], 6 | "removeComments": false, 7 | "moduleResolution": "node", 8 | "esModuleInterop": true, 9 | "resolveJsonModule": true, 10 | "strict": true, 11 | "sourceMap": true, 12 | "skipLibCheck": true, 13 | "baseUrl": ".", 14 | "noEmit": true, 15 | "outDir": "build", 16 | "paths": { 17 | "@lib": ["../lib/src"] 18 | } 19 | }, 20 | "include": ["src", ".eslintrc.js", "package.json", "src/config/*.json"], 21 | "exclude": ["dist", "node_modules", "../../node_modules"], 22 | "references": [ 23 | { 24 | "path": "../lib/tsconfig.json" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /workspaces/server/src/shared/db/template.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize } from 'sequelize' 2 | 3 | // Sync() will create any new objects but will not alter existing ones 4 | // Migrations used for changes to existing objects to prevent data loss 5 | 6 | export const up = async ({ context }: { context: Sequelize }) => { 7 | const qi = context.getQueryInterface() 8 | const transaction = await context.transaction() 9 | try { 10 | // changes 11 | qi.changeColumn 12 | await transaction.commit() 13 | } catch (err) { 14 | await transaction.rollback() 15 | throw err 16 | } 17 | } 18 | 19 | export const down = async ({ context }: { context: Sequelize }) => { 20 | context.getQueryInterface 21 | //await context.getQueryInterface().removeX 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/firebase-hosting-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: 'Deploy: Client: Firebase > Review App' 2 | on: 3 | pull_request: 4 | branches: ['master'] 5 | paths: 6 | - 'workspaces/client/**' 7 | 8 | jobs: 9 | build_and_preview: 10 | if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}' 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - run: yarn workspace client build 15 | - uses: FirebaseExtended/action-hosting-deploy@v0 16 | with: 17 | repoToken: '${{ secrets.GITHUB_TOKEN }}' 18 | firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_DRAWSPACE_6C652 }}' 19 | projectId: drawspace-6c652 20 | entryPoint: 'workspaces/client' 21 | -------------------------------------------------------------------------------- /workspaces/client/src/shared/selectFile.ts: -------------------------------------------------------------------------------- 1 | export function selectFile(contentType: string, multiple = false): Promise { 2 | return new Promise(resolve => { 3 | const input = document.createElement('input') 4 | input.type = 'file' 5 | input.multiple = multiple 6 | input.accept = contentType 7 | input.onchange = () => { 8 | const files = Array.from(input.files || []) 9 | resolve(files) 10 | } 11 | input.click() 12 | }) 13 | } 14 | 15 | export async function selectJsonFile>(): Promise { 16 | const result = await selectFile('.json') 17 | if (!result[0]) { 18 | return null 19 | } 20 | const file = await result[0].text() 21 | const obj = JSON.parse(file) 22 | return obj as T 23 | } 24 | -------------------------------------------------------------------------------- /workspaces/client/src/features/app/App.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen, waitFor } from '@testing-library/react' 2 | import App from './App' 3 | import { config } from '../../shared/config' 4 | import axios from 'axios' 5 | import MockAdapter from 'axios-mock-adapter' 6 | 7 | describe('App', () => { 8 | test('Root render', async () => { 9 | const axiosMock = new MockAdapter(axios) 10 | axiosMock.onGet('/config').reply(200, { ready: true }) 11 | axiosMock.onGet('/gallery').reply(200, { mocked: true }) 12 | const { findAllByRole } = render() 13 | await waitFor(() => findAllByRole('progressbar'), { 14 | timeout: 5000 15 | }) 16 | const appLinks = screen.getAllByText(config.defaultTitle) 17 | expect(appLinks.length).toBeGreaterThan(0) 18 | }, 10000) 19 | }) 20 | -------------------------------------------------------------------------------- /workspaces/client/src/features/admin/slice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit' 2 | import { PagedResult } from '../../../../lib/src/types' 3 | 4 | export interface AdminState { 5 | activeMenuItem?: string 6 | menuOpen?: boolean 7 | loading?: boolean 8 | loaded?: boolean 9 | data: { 10 | [key: string]: PagedResult 11 | } 12 | } 13 | 14 | const initialState: AdminState = { 15 | data: {}, 16 | } 17 | 18 | const slice = createSlice({ 19 | name: 'admin', 20 | initialState, 21 | reducers: { 22 | patch: (state, action: PayloadAction>) => { 23 | return { ...state, ...action.payload } 24 | }, 25 | }, 26 | }) 27 | 28 | export const { patch } = slice.actions 29 | 30 | export const actions = slice.actions 31 | 32 | export default slice.reducer 33 | -------------------------------------------------------------------------------- /workspaces/client/src/features/shop/FakeCheckout.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | 3 | import { PaymentIntentResult, PaymentIntent } from '@stripe/stripe-js' 4 | import { useAppDispatch } from '../../shared/store' 5 | 6 | import CircularProgress from '@mui/material/CircularProgress' 7 | 8 | import { checkoutAsync } from './thunks' 9 | 10 | export default function FakeCheckout({}: React.PropsWithChildren<{ 11 | onApproval?: (result: PaymentIntentResult) => void 12 | onLoading?: () => void 13 | }>): JSX.Element { 14 | const dispatch = useAppDispatch() 15 | 16 | useEffect((): void => { 17 | dispatch( 18 | checkoutAsync({ 19 | paymentIntent: { 20 | amount: 111 21 | } as PaymentIntent 22 | }) 23 | ) 24 | }, [dispatch]) 25 | 26 | return 27 | } 28 | -------------------------------------------------------------------------------- /workspaces/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": false, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "target": "ES2021", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "outDir": "build", 18 | "jsx": "react-jsx", 19 | "baseUrl": ".", 20 | "paths": { 21 | "@lib": ["../lib/src"] 22 | } 23 | }, 24 | "include": ["src", ".eslintrc.*"], 25 | "exclude": ["node_modules", "build"], 26 | "references": [{ "path": "../lib" }] 27 | } 28 | -------------------------------------------------------------------------------- /workspaces/client/src/features/admin/Dashboard/images/earning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /workspaces/client/src/features/ui/TabPanel.tsx: -------------------------------------------------------------------------------- 1 | import Box from '@mui/material/Box' 2 | 3 | interface TabPanelProps extends React.HTMLAttributes { 4 | children?: React.ReactNode 5 | index?: number 6 | value?: number 7 | tabs?: string 8 | keepMounted?: boolean 9 | } 10 | 11 | export function TabPanel(props: TabPanelProps = { tabs: 'tabs' }) { 12 | const { children, value, index, tabs, keepMounted, ...other } = props 13 | const id = tabs?.toLowerCase().replace(/ /g, '-') 14 | 15 | return ( 16 | 25 | ) 26 | } 27 | 28 | export default TabPanel 29 | -------------------------------------------------------------------------------- /workspaces/server/src/shared/types/models/drawing.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes } from 'sequelize' 2 | import { Drawing } from '@lib' 3 | import { addModel } from '../../db/util' 4 | 5 | export const DrawingModel = addModel({ 6 | name: 'drawing', 7 | attributes: { 8 | drawingId: { 9 | type: DataTypes.UUID, 10 | primaryKey: true, 11 | defaultValue: DataTypes.UUIDV4 12 | }, 13 | userId: { 14 | type: DataTypes.UUID 15 | }, 16 | name: { 17 | type: DataTypes.STRING 18 | }, 19 | history: { 20 | type: DataTypes.JSONB 21 | }, 22 | thumbnail: { 23 | type: DataTypes.TEXT 24 | }, 25 | private: { 26 | type: DataTypes.BOOLEAN 27 | }, 28 | sell: { 29 | type: DataTypes.BOOLEAN 30 | }, 31 | price: { 32 | type: DataTypes.DECIMAL(10, 2) 33 | } 34 | } 35 | }) 36 | -------------------------------------------------------------------------------- /workspaces/server/src/shared/types/models/subscription.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes } from 'sequelize' 2 | import { addModel } from '../../db/util' 3 | import { Subscription } from '@lib' 4 | 5 | export const SubscriptionModel = addModel({ 6 | name: 'subscription', 7 | attributes: { 8 | subscriptionId: { 9 | type: DataTypes.UUID, 10 | primaryKey: true, 11 | defaultValue: DataTypes.UUIDV4 12 | }, 13 | userId: { 14 | type: DataTypes.UUID 15 | }, 16 | orderId: { 17 | type: DataTypes.UUID 18 | }, 19 | priceId: { 20 | type: DataTypes.STRING 21 | }, 22 | status: { 23 | type: DataTypes.STRING 24 | }, 25 | title: { 26 | type: DataTypes.STRING 27 | }, 28 | canceledAt: { 29 | type: DataTypes.DATE 30 | }, 31 | cancelationReason: { 32 | type: DataTypes.STRING 33 | } 34 | } 35 | }) 36 | -------------------------------------------------------------------------------- /workspaces/function-gkill/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gkill", 3 | "main": "src/index.js", 4 | "version": "0.0.1", 5 | "description": "Billing kill switch", 6 | "dependencies": { 7 | "@google-cloud/functions-framework": "^3.1.2", 8 | "@google-cloud/pubsub": "^3.2.1", 9 | "google-auth-library": "^8.8.0", 10 | "googleapis": "^118.0.0" 11 | }, 12 | "devDependencies": { 13 | "@types/eslint": "^8", 14 | "@types/node": "^14.11.2", 15 | "@typescript-eslint/eslint-plugin": "^5.48.2", 16 | "eslint": "^8.42.0", 17 | "eslint-import-resolver-typescript": "^3.5.5", 18 | "eslint-plugin-import": "^2.27.5", 19 | "gts": "^3.1.1", 20 | "typescript": "^5.1.3" 21 | }, 22 | "scripts": { 23 | "lint": "gts lint", 24 | "clean": "gts clean", 25 | "build": "yarn tsc --build", 26 | "gcp-build": "" 27 | }, 28 | "engines": { 29 | "yarn": ">=3.30.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /workspaces/client/src/features/admin/Dashboard/chart-data/bajaj-area-chart.ts: -------------------------------------------------------------------------------- 1 | // ===========================|| DASHBOARD - BAJAJ AREA CHART ||=========================== // 2 | 3 | const chartData = { 4 | type: 'area', 5 | height: 95, 6 | options: { 7 | chart: { 8 | id: 'support-chart', 9 | sparkline: { 10 | enabled: true, 11 | }, 12 | }, 13 | dataLabels: { 14 | enabled: false, 15 | }, 16 | stroke: { 17 | curve: 'smooth', 18 | width: 1, 19 | }, 20 | tooltip: { 21 | fixed: { 22 | enabled: false, 23 | }, 24 | x: { 25 | show: false, 26 | }, 27 | y: { 28 | title: 'Ticket ', 29 | }, 30 | marker: { 31 | show: false, 32 | }, 33 | }, 34 | }, 35 | series: [ 36 | { 37 | data: [0, 15, 10, 50, 30, 40, 25], 38 | }, 39 | ], 40 | } 41 | 42 | export default chartData 43 | -------------------------------------------------------------------------------- /bin/compile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # bin/compile 3 | 4 | # used for inline heroku buildpack 5 | 6 | BUILD_DIR=${1:-} 7 | CACHE_DIR=${2:-} 8 | ENV_DIR=${3:-} 9 | 10 | PROJECT_PATH=src 11 | 12 | echo "-----> monorepo post cleanup buildpack: path is $PROJECT_PATH" 13 | # npm list 14 | # echo " creating cache: $CACHE_DIR" 15 | # mkdir -p $CACHE_DIR 16 | # TMP_DIR=`mktemp -d $CACHE_DIR/subdirXXXXX` 17 | # echo " created tmp dir: $TMP_DIR" 18 | 19 | # echo " moving working dir: $PROJECT_PATH to $TMP_DIR" 20 | # cp -R $BUILD_DIR/$PROJECT_PATH/. $TMP_DIR/ 21 | 22 | # echo " cleaning build dir $BUILD_DIR" 23 | # rm -rf $BUILD_DIR 24 | # echo " recreating $BUILD_DIR" 25 | # mkdir -p $BUILD_DIR 26 | # echo " copying work dir from cache $TMP_DIR to build dir $BUILD_DIR" 27 | # cp -R $TMP_DIR/. $BUILD_DIR/ 28 | # echo " cleaning tmp dir $TMP_DIR" 29 | # rm -rf $TMP_DIR 30 | exit 0 31 | -------------------------------------------------------------------------------- /workspaces/client/src/features/canvas/Player.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { DrawAction } from '@lib' 3 | // import { useAppSelector } from '../../shared/store' 4 | import StyledSlider from '../ui/StyledSlider' 5 | import Box from '@mui/material/Box' 6 | 7 | export default function Player({ buffer }: { buffer: React.RefObject }) { 8 | // const active = useAppSelector((state) => state.canvas?.active) 9 | const max = buffer?.current?.length || 1 10 | const [marks, setMarks] = React.useState<{ value: number; label: string }[]>([]) 11 | 12 | useEffect(() => { 13 | const m = [{ value: 0, label: '0' }] 14 | if (max > 0) { 15 | m.push({ value: max, label: `${max}` }) 16 | } 17 | setMarks(m) 18 | }, [max]) 19 | 20 | return ( 21 | 22 | 23 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /workspaces/server/src/shared/types/models/product.ts: -------------------------------------------------------------------------------- 1 | import { Product } from '@lib' 2 | import { DataTypes } from 'sequelize' 3 | import { addModel } from '../../db/util' 4 | // import { CategoryModel } from './category' 5 | 6 | export const ProductDefinition = { 7 | productId: { 8 | type: DataTypes.STRING, 9 | primaryKey: true 10 | }, 11 | title: { 12 | type: DataTypes.STRING, 13 | required: true 14 | }, 15 | description: { 16 | type: DataTypes.STRING 17 | }, 18 | imageUrl: { 19 | type: DataTypes.STRING 20 | }, 21 | images: { 22 | type: DataTypes.ARRAY(DataTypes.STRING) 23 | }, 24 | keywords: { 25 | type: DataTypes.STRING 26 | }, 27 | prices: { 28 | type: DataTypes.JSONB 29 | }, 30 | shippable: { 31 | type: DataTypes.BOOLEAN, 32 | defaultValue: false 33 | } 34 | } 35 | 36 | export const ProductModel = addModel({ name: 'product', attributes: ProductDefinition }) 37 | -------------------------------------------------------------------------------- /docs/dev.md: -------------------------------------------------------------------------------- 1 | ## debug 2 | 3 | 4 | ## Commit Flow 5 | `epic | feature -> develop -> master` 6 | - Feature branches are created from, merged to, develop 7 | - Pre-commit compile check 8 | 9 | ## Push Flow 10 | - Pre-push tests against docker environment, push if okay 11 | - Github Action Tests are run against testing environment 12 | - If tests pass, build container with branch tag 13 | - Push built container image to registry 14 | - Trigger environment redeployment 15 | 16 | | Branch | Environment | 17 | | --- | --- | 18 | | master | Production | 19 | | develop | Testing | 20 | | feature | Review App | 21 | | epic | Review App | 22 | | epic-feature | none | 23 | 24 | 25 | ## Container Flow 26 | 27 | Image Tags are based on the branch name and not hash, with latest being environment source 28 | 29 | `Branch -> Tests -> Container -> Environment` 30 | 31 | Github Actions can use the branch name hints like `review` for review app and web hook creation 32 | 33 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | ## State Management 4 | 5 | - Redux for app state machine 6 | - React Query for data retrieval and caching at component level 7 | - useState as normal 8 | 9 | ## Space 10 | 11 | - Not storing canvas image data directly, rather an array[] buffer of CanvasAction(s) 12 | - A Lean object made to be stored in database as well as consumed by worker to redraw image in the background 13 | - Thumbnails stored as data-image/png 14 | 15 | ## Data Flow 16 | 17 | - API requests centralized via request() function with typings, side effects and errors 18 | - Components use custom useGet hook for data retrievals 19 | - Patching slice state done via generic actions.patch({ prop: value }) 20 | 21 | ## Layout 22 | 23 | - Flexbox strategy for responsiveness via css 24 | - Flexed routed section for MaterialUI content without margins 25 | 26 | ## Styles 27 | 28 | - Global: index.css 29 | - Small: sx 30 | - Medium: const 31 | - Big: Component.styles.ts 32 | -------------------------------------------------------------------------------- /workspaces/client/src/features/home/images/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /workspaces/server/src/routes/swagger.yaml: -------------------------------------------------------------------------------- 1 | components: 2 | securitySchemes: 3 | BearerAuth: 4 | type: http 5 | scheme: bearer 6 | in: header 7 | responses: 8 | 403Unauthorized: 9 | description: You do not have permission to access this 10 | content: 11 | application/json: 12 | schema: 13 | type: object 14 | properties: 15 | statusCode: 16 | type: integer 17 | example: 403 18 | message: 19 | type: string 20 | example: Unauthorized 21 | 500Error: 22 | description: Unable to process the request 23 | content: 24 | application/json: 25 | schema: 26 | type: object 27 | properties: 28 | statusCode: 29 | type: integer 30 | example: 500 31 | message: 32 | type: string 33 | example: Internal Server Error 34 | -------------------------------------------------------------------------------- /.github/workflows/firebase-hosting-live.yml: -------------------------------------------------------------------------------- 1 | name: 'Deploy: Client: Firebase > Live' 2 | on: 3 | push: 4 | branches: 5 | - master 6 | paths: 7 | - 'workspaces/client/**' 8 | - '.github/workflows/firebase-hosting-live.yml' 9 | 10 | jobs: 11 | build_and_deploy: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - uses: actions/setup-node@v3 17 | with: 18 | node-version: 18 19 | cache: 'yarn' 20 | 21 | - name: 'install' 22 | run: yarn install --immutable 23 | 24 | - name: 'build' 25 | run: | 26 | yarn workspace client build 27 | 28 | - uses: FirebaseExtended/action-hosting-deploy@v0 29 | with: 30 | repoToken: '${{ secrets.GITHUB_TOKEN }}' 31 | firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_DRAWSPACE_6C652 }}' 32 | channelId: live 33 | projectId: drawspace-6c652 34 | entryPoint: 'workspaces/client' 35 | -------------------------------------------------------------------------------- /workspaces/client/src/features/ui/CircularProgressWithLabel.tsx: -------------------------------------------------------------------------------- 1 | import CircularProgress, { CircularProgressProps } from '@mui/material/CircularProgress' 2 | import Typography from '@mui/material/Typography' 3 | import Box from '@mui/material/Box' 4 | 5 | export default function CircularProgressWithLabel( 6 | props: CircularProgressProps & { value: number }, 7 | ) { 8 | return ( 9 | 10 | 11 | 23 | {`${Math.round( 24 | props.value, 25 | )}%`} 26 | 27 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /workspaces/server/src/config/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "development": { 3 | "db": { 4 | "url": null, 5 | "username": "postgres", 6 | "password": "postgrespass", 7 | "database": "drawspace", 8 | "schema": "public", 9 | "host": "127.0.0.1", 10 | "ssl": false, 11 | "dialect": "postgres" 12 | }, 13 | "service": { 14 | "port": 3001, 15 | "protocol": "http", 16 | "host": "localhost", 17 | "sslKey": null, 18 | "sslCert": null 19 | } 20 | }, 21 | "production": { 22 | "db": { 23 | "url": "$DB_URL", 24 | "schema": "public", 25 | "ssl": false 26 | }, 27 | "service": { 28 | "port": 8080, 29 | "protocol": "http", 30 | "host": "localhost", 31 | "sslKey": "$SSL_KEY", 32 | "sslCert": "$SSL_CERT" 33 | } 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /workspaces/server/src/shared/types/models/wallet.ts: -------------------------------------------------------------------------------- 1 | import { Wallet, WalletTransaction } from '@lib' 2 | import { DataTypes } from 'sequelize' 3 | import { addModel } from '../../db/util' 4 | 5 | export const WalletModel = addModel({ 6 | name: 'wallet', 7 | attributes: { 8 | walletId: { 9 | type: DataTypes.UUID, 10 | primaryKey: true 11 | }, 12 | balance: { 13 | type: DataTypes.DECIMAL(10, 2) 14 | }, 15 | currency: { 16 | type: DataTypes.STRING 17 | }, 18 | status: { 19 | type: DataTypes.STRING 20 | } 21 | } 22 | }) 23 | 24 | export const WalletTransactionModel = addModel({ 25 | name: 'walletTransaction', 26 | attributes: { 27 | transactionId: { 28 | type: DataTypes.UUID, 29 | primaryKey: true 30 | }, 31 | userId: { 32 | type: DataTypes.UUID 33 | }, 34 | orderId: { 35 | type: DataTypes.UUID 36 | }, 37 | amount: { 38 | type: DataTypes.STRING 39 | } 40 | } 41 | }) 42 | -------------------------------------------------------------------------------- /workspaces/server/src/shared/secrets.ts: -------------------------------------------------------------------------------- 1 | import { v1 } from '@google-cloud/secret-manager' 2 | import logger from './logger' 3 | 4 | export async function getSecrets() { 5 | logger.info(`Getting secrets from Secret Manager...`) 6 | const result = {} as { [key: string]: string } 7 | const client = new v1.SecretManagerServiceClient({ 8 | projectId: 'mstream-368503', 9 | }) 10 | const asyncList = client.listSecretsAsync() 11 | try { 12 | for await (const secret of asyncList) { 13 | const name = secret.name as string 14 | if (!name) { 15 | logger.error(`Secret name is missing`) 16 | continue 17 | } 18 | const [details] = await client.accessSecretVersion({ name }) 19 | logger.info(`Secret: ${name} = ${JSON.stringify(details)}`) 20 | if (!details.payload?.data) { 21 | result[name] = details.payload?.data as string 22 | } 23 | } 24 | } catch (error) { 25 | logger.error(`Error getting secrets: ${error}`) 26 | } 27 | return result 28 | } 29 | -------------------------------------------------------------------------------- /workspaces/client/src/features/home/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import Container from '@mui/material/Container' 3 | import Gallery from '../ui/Gallery' 4 | import HeroSection from './HeroSection' 5 | import Logos from './Logos' 6 | import Box from '@mui/material/Box' 7 | import Typography from '@mui/material/Typography' 8 | import { Divider, Paper } from '@mui/material' 9 | import Footer from './Footer' 10 | import Subscribe from './Subscribe' 11 | export default function HomePage() { 12 | return ( 13 |
14 |
15 | 16 | 17 | 18 |
19 | 20 | Drawings Marketplace 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /workspaces/lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lib", 3 | "version": "1.0.5", 4 | "private": true, 5 | "exports": { 6 | ".": "./dist/src/index.js", 7 | "./package.json": "./package.json" 8 | }, 9 | "scripts": { 10 | "dev": "yarn tsc --watch --verbose", 11 | "build": "yarn tsc --build", 12 | "lint": "eslint .", 13 | "test": "yarn jest", 14 | "clean": "rm -rf node_modules dist" 15 | }, 16 | "dependencies": { 17 | "uuid": "^9.0.0" 18 | }, 19 | "devDependencies": { 20 | "@types/uuid": "^8.3.4", 21 | "@typescript-eslint/eslint-plugin": "^5.57.1", 22 | "@typescript-eslint/parser": "^5.30.6", 23 | "eslint": "^8.42.0", 24 | "eslint-config-standard": "^17.1.0", 25 | "eslint-import-resolver-typescript": "^3.5.5", 26 | "eslint-plugin-import": "^2.27.5", 27 | "eslint-plugin-n": "^16.0.0", 28 | "eslint-plugin-promise": "^6.1.1", 29 | "eslint-plugin-react": "^7.32.2", 30 | "jest": "^29.5.0", 31 | "ts-jest": "^29.1.0", 32 | "typescript": "^5.1.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /workspaces/client/src/features/canvas/LineSize.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles' 2 | import { config } from '../../shared/config' 3 | import { useAppDispatch, useAppSelector } from '../../shared/store' 4 | import { actions } from './slice' 5 | import Container from '@mui/material/Container' 6 | import Slider from '@mui/material/Slider' 7 | 8 | const ContainerStyled = styled(Container)({ 9 | position: 'absolute', 10 | bottom: '3%', 11 | left: '20%', 12 | width: '70%' 13 | }) 14 | 15 | export default function LineSize() { 16 | const activeSize = useAppSelector(state => state.canvas.size) 17 | const dispatch = useAppDispatch() 18 | const onSize = (e: Event, v: number | number[]) => dispatch(actions.patch({ size: v as number })) 19 | return ( 20 | 21 | 29 | 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /workspaces/client/src/features/ui/Card/Skeleton/EarningCard.tsx: -------------------------------------------------------------------------------- 1 | import Card from '@mui/material/Card' 2 | import CardContent from '@mui/material/CardContent' 3 | import Grid from '@mui/material/Grid' 4 | import Skeleton from '@mui/material/Skeleton' 5 | 6 | const EarningCard = () => ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ) 30 | 31 | export default EarningCard 32 | -------------------------------------------------------------------------------- /workspaces/client/src/features/ui/PasswordField.tsx: -------------------------------------------------------------------------------- 1 | import Visibility from '@mui/icons-material/Visibility' 2 | import VisibilityOff from '@mui/icons-material/VisibilityOff' 3 | import InputAdornment from '@mui/material/InputAdornment' 4 | import TextField, { TextFieldProps } from '@mui/material/TextField' 5 | 6 | import React from 'react' 7 | 8 | export default function PasswordField(props: TextFieldProps) { 9 | const [visible, setVisible] = React.useState(false) 10 | return ( 11 | { 22 | setVisible(!visible) 23 | }} 24 | > 25 | {visible ? : } 26 | 27 | ) 28 | }} 29 | {...props} 30 | /> 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /workspaces/client/src/features/admin/Footer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import Box from '@mui/material/Box' 3 | import BottomNavigation from '@mui/material/BottomNavigation' 4 | import BottomNavigationAction from '@mui/material/BottomNavigationAction' 5 | import RestoreIcon from '@mui/icons-material/Restore' 6 | import FavoriteIcon from '@mui/icons-material/Favorite' 7 | import LocationOnIcon from '@mui/icons-material/LocationOn' 8 | 9 | export default function SimpleBottomNavigation() { 10 | const [value, setValue] = React.useState(0) 11 | 12 | return ( 13 | 14 | { 18 | setValue(newValue) 19 | }} 20 | > 21 | } /> 22 | } /> 23 | } /> 24 | 25 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /workspaces/server/src/index.ts: -------------------------------------------------------------------------------- 1 | import config, { canStart } from './shared/config' 2 | import logger from './shared/logger' 3 | import createBackendApp from './app' 4 | import { registerSocket } from './shared/socket' 5 | import { createServerService } from './shared/server' 6 | ;(() => { 7 | if (!canStart()) { 8 | const m = 'No PORT specified: Shutting down - Environment variables undefined' 9 | logger.error(m) 10 | throw new Error(m) 11 | } 12 | 13 | const app = createBackendApp() 14 | const url = config.backendBaseUrl + config.swaggerSetup.basePath 15 | const title = config.swaggerSetup.info?.title 16 | 17 | // Start server 18 | const server = createServerService(app) 19 | registerSocket(server) 20 | 21 | server.listen(config.port, () => 22 | logger.info( 23 | `**************************************************************************\n\ 24 | ⚡️[${title}]: Server is running with SwaggerUI Admin at ${url}\n\ 25 | **************************************************************************`, 26 | ), 27 | ) 28 | return server 29 | })() 30 | -------------------------------------------------------------------------------- /workspaces/client/src/shared/config.ts: -------------------------------------------------------------------------------- 1 | import { ClientSettings } from '@lib' 2 | import packageJson from '../../package.json' 3 | 4 | /** 5 | * move most to redux and settings 6 | */ 7 | export interface Config { 8 | baseName: string 9 | backendUrl: string 10 | defaultTitle: string 11 | defaultLineSize: number 12 | defaultColor: string 13 | thumbnails: { 14 | width: number 15 | height: number 16 | } 17 | settings: ClientSettings 18 | admin: { 19 | path: string 20 | models?: string[] 21 | } 22 | } 23 | const env = process['env'] 24 | const defaultBaseName = process.env.NODE_ENV === 'test' ? '/' : packageJson.homepage || '/' 25 | export const config: Config = { 26 | baseName: env.BASE_NAME || defaultBaseName, 27 | backendUrl: env.BACKEND || 'https://api.drawspace.app', 28 | defaultTitle: 'Drawspace', 29 | defaultColor: 'green', 30 | defaultLineSize: 5, 31 | thumbnails: { 32 | width: 250, 33 | height: 250, 34 | }, 35 | settings: {}, 36 | admin: { 37 | path: '/admin', 38 | models: [], 39 | }, 40 | } 41 | 42 | export default config 43 | -------------------------------------------------------------------------------- /workspaces/client/src/features/pages/Maintenance.tsx: -------------------------------------------------------------------------------- 1 | import Paper from '@mui/material/Paper' 2 | import image from './images/maintenance.svg' 3 | import Grid from '@mui/material/Grid' 4 | import Typography from '@mui/material/Typography' 5 | export default function Maintenance() { 6 | return ( 7 | 8 | 20 | 21 | 22 | Offline for Maintenance 23 | 24 | 25 | Please come back later 26 | 27 | 28 | 29 | 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /workspaces/client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "commonjs": true, 6 | "node": true, 7 | "es6": true, 8 | "jest": true 9 | }, 10 | "extends": ["plugin:@typescript-eslint/recommended", "plugin:react/recommended", "plugin:react/jsx-runtime"], 11 | "parserOptions": { 12 | "ecmaVersion": "latest", 13 | "sourceType": "module", 14 | "project": "workspaces/*/tsconfig.json" 15 | }, 16 | "parser": "@typescript-eslint/parser", 17 | "plugins": ["react", "react-hooks", "@typescript-eslint"], 18 | "rules": { 19 | "@typescript-eslint/no-unused-vars": "error", 20 | "@typescript-eslint/explicit-function-return-type": "off", 21 | "@typescript-eslint/restrict-template-expressions": "off", 22 | "@typescript-eslint/strict-boolean-expressions": "off", 23 | "no-console": "off", 24 | "strict": ["error", "global"], 25 | "curly": "warn" 26 | }, 27 | "overrides": [ 28 | { 29 | "files": ["*.js"], 30 | "rules": { 31 | "@typescript-eslint/no-var-requires": "off" 32 | } 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /workspaces/client/src/features/ui/Card/Skeleton/TotalIncomeCard.tsx: -------------------------------------------------------------------------------- 1 | // material-ui 2 | 3 | import Card from '@mui/material/Card' 4 | import List from '@mui/material/List' 5 | import ListItem from '@mui/material/ListItem' 6 | import ListItemAvatar from '@mui/material/ListItemAvatar' 7 | import ListItemText from '@mui/material/ListItemText' 8 | import Skeleton from '@mui/material/Skeleton' 9 | 10 | // ==============================|| SKELETON - TOTAL INCOME DARK/LIGHT CARD ||============================== // 11 | 12 | const TotalIncomeCard = () => ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | } 22 | secondary={} 23 | /> 24 | 25 | 26 | 27 | ) 28 | 29 | export default TotalIncomeCard 30 | -------------------------------------------------------------------------------- /workspaces/server/src/shared/firebase.ts: -------------------------------------------------------------------------------- 1 | import { initializeApp, cert, App } from 'firebase-admin/app' 2 | import { getSettingsAsync } from './settings' 3 | 4 | let firebaseApp: App | null = null 5 | export const getFirebaseApp = async (): Promise => { 6 | if (firebaseApp) { 7 | return firebaseApp 8 | } 9 | 10 | const settings = await getSettingsAsync(true) 11 | const serviceAccountKeyJson = settings.internal?.secrets?.google.serviceAccountJson 12 | 13 | if (!serviceAccountKeyJson) { 14 | throw new Error('To use firebase authentication you need to input your serviceAccountKey.json') 15 | } 16 | 17 | const serviceAccountObject = JSON.parse(serviceAccountKeyJson || '{}') 18 | const projectId = serviceAccountObject?.project_id 19 | const storageBucket = `${projectId}.appspot.com` 20 | const databaseURL = `https://${projectId}.firebaseio.com` 21 | const credential = serviceAccountObject ? cert(serviceAccountObject) : undefined 22 | firebaseApp = initializeApp({ 23 | credential, 24 | databaseURL, 25 | storageBucket 26 | }) 27 | return firebaseApp as App 28 | } 29 | -------------------------------------------------------------------------------- /workspaces/client/src/features/app/LanguageProvider.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { IntlProvider } from 'react-intl' 3 | import { useAppSelector } from '../../shared/store' 4 | 5 | export type IntlProviderProps = React.ComponentProps 6 | 7 | const cache: Record> = {} 8 | 9 | async function loadLocale(locale: string): Promise> { 10 | if (cache[locale]) { 11 | return cache[locale] 12 | } 13 | cache[locale] = await import(`../languages/${locale}.json`) 14 | return cache[locale] 15 | } 16 | 17 | export default function LanguageProvider(props: { children: React.ReactElement }) { 18 | const { children } = props 19 | const locale = useAppSelector(state => state.app.locale) 20 | const [messages, setMessages] = React.useState>({}) 21 | React.useEffect(() => { 22 | loadLocale(locale).then(res => setMessages(res)) 23 | }, [locale]) 24 | 25 | return ( 26 | 27 | {children} 28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /workspaces/client/src/features/shop/Receipt.tsx: -------------------------------------------------------------------------------- 1 | import Box from '@mui/material/Box' 2 | import Stack from '@mui/material/Stack' 3 | import Typography from '@mui/material/Typography' 4 | import { useAppSelector } from 'src/shared/store' 5 | 6 | export default function Receipt() { 7 | const order = useAppSelector(state => state.shop.receipt) 8 | return ( 9 | 10 | 11 | Thank you 12 | 13 | 14 |
15 |
16 | 17 | 18 |
19 |
20 |
21 |
22 | Your order was placed succesfully! 23 | {order?.orderId} 24 |
25 |
26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /workspaces/client/src/features/home/images/redux.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /workspaces/client/src/features/ui/MainLayout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Header from './Header' 3 | import DrawerRight from './Drawer' 4 | import Routing, { RouteElement } from './Routing' 5 | import Notifications from './Notifications' 6 | import Footer from './Footer' 7 | import LoadingLine from './LoadingLine' 8 | import AuthProviderInjections from '../profile/AuthProviders' 9 | import { currentRoute } from '../../shared/routes' 10 | import Dialogs from './Dialogs' 11 | import CssBaseline from '@mui/material/CssBaseline' 12 | import SocketListener from '../app/SocketListener' 13 | 14 | export function MainLayout() { 15 | const route = currentRoute() 16 | if (route?.cleanLayout) { 17 | return 18 | } 19 | 20 | return ( 21 | 22 | 23 | 24 |
25 |
26 | 27 |
28 | 29 | 30 |