├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .gitmodules ├── .prettierignore ├── .prettierrc ├── .tools └── insomnia-specs.json ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets-service ├── .gitignore ├── README.md ├── nest-cli.json ├── package.json ├── serverless.yml ├── src │ ├── app.controller.ts │ ├── app.entity.ts │ ├── app.module.ts │ ├── app.service.ts │ ├── lambda.ts │ ├── main.ts │ ├── raw-assets │ │ ├── raw-assets.controller.ts │ │ ├── raw-assets.module.ts │ │ └── raw-assets.service.ts │ └── variant-assets │ │ ├── variant-assets.controller.ts │ │ ├── variant-assets.module.ts │ │ └── variant-assets.service.ts ├── tsconfig.build.json └── tsconfig.json ├── branding ├── README.md ├── cover.png ├── lgoo-type.png └── lgoo.png ├── build-service ├── README.md └── index.ts ├── configs └── .eslintrc.js ├── docs ├── README.md └── security.md ├── forms-service ├── .gitignore ├── CONTRIBUTING.md ├── README.md ├── app.ts ├── docs │ └── references │ │ └── typeform-response-example.json ├── index.ts ├── lib │ ├── assets.ts │ └── index.ts ├── package.json ├── prisma-client │ ├── index.ts │ └── selectors.ts ├── prisma │ └── schema.prisma ├── routes │ ├── assets │ │ └── index.ts │ ├── export │ │ └── index.ts │ ├── forms │ │ └── index.ts │ ├── index.ts │ └── responses │ │ └── index.ts ├── serverless.yml ├── tsconfig.json ├── types │ └── index.ts ├── webpack.config.js └── yarn.lock ├── g11n-service └── README.md ├── hosting-service ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── README.md ├── nest-cli.json ├── package.json ├── serverless.yml ├── src │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ ├── lambda.ts │ ├── main.ts │ ├── resource │ │ ├── resource.controller.ts │ │ ├── resource.module.ts │ │ └── resource.service.ts │ └── site │ │ └── site.module.ts ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock ├── package.json ├── packages ├── api-key-auth-service │ ├── README.md │ ├── index.ts │ ├── issue │ │ ├── README.md │ │ └── index.ts │ ├── package.json │ ├── secret.ts │ └── yarn.lock ├── api-usage-logging-service │ ├── index.ts │ └── package.json └── services-sdk │ ├── development │ ├── index.ts │ └── test-import.ts │ ├── index.ts │ ├── middlewares │ ├── index.ts │ └── project.middleware.ts │ ├── package.json │ └── tsconfig.json ├── page-service ├── .env.defaults ├── .gitignore ├── package.json ├── serverless.yml ├── src │ ├── app.ts │ ├── core │ │ └── index.ts │ ├── index.ts │ ├── routes │ │ ├── figma │ │ │ └── index.ts │ │ └── index.ts │ └── types.ts └── yarn.lock ├── payment-service ├── .gitignore ├── .prettierrc ├── README.md ├── nest-cli.json ├── package.json ├── serverless.yml ├── src │ ├── app.controller.ts │ ├── app.entity.ts │ ├── app.module.ts │ ├── app.service.ts │ ├── lambda.ts │ ├── main.ts │ ├── payment │ │ ├── payment.controller.ts │ │ ├── payment.module.ts │ │ └── payment.service.ts │ └── subcription │ │ ├── subcription.controller.ts │ │ ├── subcription.module.ts │ │ └── subcription.service.ts ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock ├── posts-service ├── .env.defaults ├── .gitignore ├── CONTRIBUTING.md ├── README.md ├── app.ts ├── index.ts ├── lib │ ├── assets.ts │ ├── index.ts │ └── schedule.ts ├── package.json ├── prisma-client │ ├── index.ts │ └── selectors.ts ├── prisma │ └── schema.prisma ├── routes │ ├── assets │ │ ├── README.md │ │ └── index.ts │ ├── categories │ │ └── index.ts │ ├── drafts │ │ └── index.ts │ ├── index.ts │ ├── posts │ │ └── index.ts │ ├── publications │ │ └── index.ts │ ├── tags │ │ └── index.ts │ └── utils │ │ └── index.ts ├── serverless.yml ├── tsconfig.json ├── types │ └── index.ts ├── webpack.config.js └── yarn.lock ├── resources-service ├── .env.defaults ├── .eslintrc.js ├── .gitignore ├── README.md ├── nest-cli.json ├── package.json ├── serverless.yml ├── src │ ├── app.controller.spec.ts │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ ├── graphics │ │ ├── graphics.module.ts │ │ ├── illusts │ │ │ └── index.ts │ │ ├── patterns │ │ │ └── index.ts │ │ ├── photos │ │ │ └── index.ts │ │ └── sources │ │ │ └── unsplash │ │ │ ├── index.d.ts │ │ │ └── index.ts │ ├── icons │ │ ├── icons.controller.ts │ │ ├── icons.module.ts │ │ └── icons.service.ts │ ├── lambda.ts │ └── main.ts ├── static │ ├── README.md │ └── mdi_config.json ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock ├── run-services.sh ├── scene-store-service ├── .env.development ├── .eslintrc.js ├── .gitignore ├── README.md ├── docs │ └── README.md ├── nest-cli.json ├── package.json ├── prisma │ ├── local-dev │ │ ├── database.env │ │ ├── docker-compose.yml │ │ └── start-server.sh │ ├── migrations │ │ ├── 20210823180755_initial │ │ │ └── migration.sql │ │ └── migration_lock.toml │ └── schema.prisma ├── scripts │ └── env_setup.py ├── serverless.yml ├── src │ ├── _auth │ │ ├── jwt-auth.guard.ts │ │ └── verification-api.ts │ ├── _prisma │ │ ├── prisma.module.ts │ │ └── prisma.service.ts │ ├── app.controller.ts │ ├── app.entity.ts.keep │ ├── app.module.ts │ ├── app.service.ts │ ├── authentication │ │ ├── api-jwt.strategy.ts │ │ ├── authentication.module.ts │ │ └── authentication.service.ts │ ├── lambda.ts │ ├── main.ts │ └── scenes │ │ ├── scenes.controller.ts │ │ ├── scenes.module.ts │ │ ├── scenes.object.ts │ │ └── scenes.service.ts ├── tsconfig.build.json ├── tsconfig.json ├── webpack.config.js └── yarn.lock ├── url-service ├── .eslintrc.js ├── .gitignore ├── README.md ├── env.defaults ├── nest-cli.json ├── package.json ├── serverless.yml ├── src │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ ├── lambda.ts │ ├── main.ts │ └── utils │ │ └── index.ts ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.json ├── web │ └── index.html └── yarn.lock ├── web ├── .eslintrc.json ├── .gitignore ├── README.md ├── app │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ └── page.tsx ├── next.config.js ├── package.json ├── postcss.config.js ├── public │ ├── next.svg │ └── vercel.svg ├── tailwind.config.ts ├── tsconfig.json └── yarn.lock └── yarn.lock /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.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 | # envs 10 | .env 11 | .env.production 12 | .env.staging 13 | 14 | # DS_Store 15 | .DS_Store 16 | 17 | # Webpack 18 | .webpack 19 | 20 | # Diagnostic reports (https://nodejs.org/api/report.html) 21 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 22 | 23 | # Runtime data 24 | pids 25 | *.pid 26 | *.seed 27 | *.pid.lock 28 | 29 | # Directory for instrumented libs generated by jscoverage/JSCover 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | coverage 34 | *.lcov 35 | 36 | # nyc test coverage 37 | .nyc_output 38 | 39 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 40 | .grunt 41 | 42 | # Bower dependency directory (https://bower.io/) 43 | bower_components 44 | 45 | # node-waf configuration 46 | .lock-wscript 47 | 48 | # Compiled binary addons (https://nodejs.org/api/addons.html) 49 | build/Release 50 | 51 | # Dependency directories 52 | node_modules/ 53 | jspm_packages/ 54 | 55 | # TypeScript v1 declaration files 56 | typings/ 57 | 58 | # TypeScript cache 59 | *.tsbuildinfo 60 | 61 | # Optional npm cache directory 62 | .npm 63 | 64 | # Optional eslint cache 65 | .eslintcache 66 | 67 | # Microbundle cache 68 | .rpt2_cache/ 69 | .rts2_cache_cjs/ 70 | .rts2_cache_es/ 71 | .rts2_cache_umd/ 72 | 73 | # Optional REPL history 74 | .node_repl_history 75 | 76 | # Output of 'npm pack' 77 | *.tgz 78 | 79 | # Yarn Integrity file 80 | .yarn-integrity 81 | 82 | # dotenv environment variables file 83 | .env 84 | .env.test 85 | 86 | # parcel-bundler cache (https://parceljs.org/) 87 | .cache 88 | 89 | # Next.js build output 90 | .next 91 | 92 | # Nuxt.js build / generate output 93 | .nuxt 94 | dist 95 | 96 | # Gatsby files 97 | .cache/ 98 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 99 | # https://nextjs.org/blog/next-9-1#public-directory-support 100 | # public 101 | 102 | # vuepress build output 103 | .vuepress/dist 104 | 105 | # Serverless directories 106 | .serverless/ 107 | 108 | # FuseBox cache 109 | .fusebox/ 110 | 111 | # DynamoDB Local files 112 | .dynamodb/ 113 | 114 | # TernJS port file 115 | .tern-port 116 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "packages/client-sdk-ts"] 2 | path = packages/client-sdk-ts 3 | url = https://github.com/bridgedxyz/client-sdk-ts.git 4 | [submodule "packages/reflect-core"] 5 | path = packages/reflect-core 6 | url = https://github.com/bridgedxyz/reflect-core-ts.git 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.md 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": false, 6 | "endOfLine": "lf" 7 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.hg": true, 6 | "**/CVS": true, 7 | "**/.DS_Store": true, 8 | "**/Thumbs.db": true, 9 | "**/node_modules": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 4 | ## Docs 5 | 6 | [bridged msa docs on notion](https://www.notion.so/bridgedxyz/services-msa-d00ff606766d4df09a2ea8dcfa1b0de2) 7 | [bridged msa diagram on figma](https://www.figma.com/file/t5EdSlZo7eyWgXLSqTx7ok/hackers?node-id=0%3A1) 8 | 9 | 10 | 11 | ## About port usage on local environment 12 | for running multuple services via sls offline, we set the difference ports by each services. 13 | 14 | - url service : 4000 15 | - asset service : 4001 16 | - build service : 4002 17 | - build-store service : 4003 18 | - g11n service : 4004 19 | - hosting service : 4005 20 | - project service : 4007 21 | - resource service : 4008 22 | - design-store service : 4009 23 | - scene service : 4010 24 | - payment service : 4011 25 | - cors service : 4012 26 | - [proxy analytics service](https://github.com/bridgedxyz/analytics) : 4013 27 | 28 | 29 | 30 | 31 | ## NestJS + Yarn workspace + Typescript + Webpack configuration 32 | 33 | > we are using serverless-bundle , which is an wrapper of webpack + babel + ts loader + node externals for our required stack. but this to work with nestJS seamless, we need extra configurations 34 | 35 | 36 | **serverless.yml** 37 | ``` yml 38 | service: 39 | name: service-name 40 | 41 | plugins: 42 | - serverless-bundle 43 | - serverless-dynamodb-local 44 | - serverless-offline 45 | - serverless-domain-manager 46 | 47 | custom: 48 | bundle: 49 | caching: false 50 | forceExclude: 51 | - '@nestjs/microservices' 52 | - '@nestjs/microservices/microservices-module' 53 | - '@nestjs/websockets/socket-module' 54 | - 'cache-manager' 55 | customDomain: 56 | domainName: service-name.bridged.cc 57 | hostedZoneId: us-west-1 58 | basePath: '' 59 | stage: ${self:provider.stage} 60 | createRoute53Record: true 61 | serverless-offline: 62 | httpPort: 4004 63 | ``` 64 | 65 | ## Usage of dynamoose 66 | > using dynamoose requires `dynamodb:DescribeTable` permission 67 | ``` yml 68 | 69 | provider: 70 | name: aws 71 | runtime: nodejs12.x 72 | region: us-west-1 73 | apiGateway: 74 | minimumCompressionSize: 1024 75 | environment: 76 | AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1' 77 | DYNAMODB_KEY_TABLE: "${self:service}-key-${opt:stage, self:provider.stage}" 78 | iamRoleStatements: 79 | - Effect: Allow 80 | Action: 81 | - dynamodb:Query 82 | - dynamodb:Scan 83 | - dynamodb:GetItem 84 | - dynamodb:PutItem 85 | - dynamodb:UpdateItem 86 | - dynamodb:DeleteItem 87 | - dynamodb:DescribeTable 88 | Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_KEY_TABLE}" 89 | ``` 90 | 91 | 92 | 93 | 94 | 95 | ## Service Layer Function Naming Conventions 96 | 97 | **verb forms** 98 | - **register**: when registering record with a characteristic of key. (when creatting a key, we call it as `register key`) 99 | - **create**: when generally creating a data record 100 | - **add**: when adding a component, nested form of data to a object with a characteristic of key. (i.e. `register key`, `add value`) 101 | - **put**: when creating or updating a data record (when performing http put operation) 102 | - ~~patch~~ *do not use this as a function name* 103 | - **update**: when updating single / multiple property of data record. (also when performing http patch) 104 | - **get**: *avoid using this when performing remote data request* 105 | - **fetch**: when get-ing the data from remote 106 | - **link**: when linking two different record as a joint 107 | - **unlink**: when disconnecting two different record from a joint 108 | - **remove**: opposite of `add`. when unlinking, and deleting the record 109 | - **delete**: when actually, permanatly deeting a data record 110 | - **archive**: when removing, but not deleting a data, making it not accessable. 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](./branding/cover.png) 2 | 3 | # BASE: Plug & Play Open Cloud Functions (Bridged App Services) - [bridged.cc](https://bridged.cc) 4 | 5 | > public services (all services except auth and billing) msa repository 6 | 7 | 8 | ## Are you looking for ready-to-use SDK of this service? 9 | 10 | In that case, you are looking for [base-sdk](https://github.com/bridgedxyz/base-sdk) 11 | 12 | # development 13 | 14 | ## micros 15 | - [asset-service](./assets-service) 16 | - [g11n-service](https://github.com/bridgedxyz/g11n) 17 | - resource-hosting 18 | - application hosting 19 | - [cors-service](./cors-service) 20 | - auth (read the development token generation docs) 21 | 22 | ## packages 23 | - [dartservices](./packages/dartservices) 24 | 25 | ## fogs 26 | Fogs are managed in separate repository. Visit [functions](https://github.com/bridgedxyz/functions) 27 | 28 | 29 | ```shell 30 | # cloning with git submodules 31 | git clone https://github.com/bridgedxyz/services.git --recurse-submodules 32 | 33 | # pulling with git submodules (second entry) 34 | git submodule update --init --recursive 35 | ``` 36 | 37 | ## Ready to use services 38 | - [CORS.SH](https://cors.sh) 39 | 40 | 41 | 42 | ## running services 43 | 44 | > you need serverless framework to be installed on your local machine 45 | 46 | ```sh 47 | yarn 48 | 49 | # replace target-service with actual directory name that you are targetting 50 | cd ./target-service 51 | sls offline 52 | ``` 53 | 54 | 55 | 56 | 57 | ## Resources 58 | - [base-sdk](https://github.com/bridgedxyz/base-sdk) 59 | - [base-cdk](https://github.com/bridgedxyz/base-cdk) 60 | - [base-cli](https://github.com/bridgedxyz/base-cli) 61 | 62 | 63 | 64 | # usage 65 | 66 | > if you are simply lookin for bridged cloud access, this can be done with [client-sdk](https://github.com/bridgedxyz/client-sdk-ts) 67 | ## Client usage - Installation 68 | 69 | goto https://github.com/bridgedxyz/client-sdk-ts 70 | 71 | 72 | ## Who is using BASE? 73 | - [reddit downloader](https://github.com/RedditDownloader/redditdownloader.github.io) 74 | - [GALLAUDET University](https://www.gallaudet.edu/) 75 | - [F1-Web-Viewer](https://github.com/bootsie123/F1-Web-Viewer) 76 | 77 | 78 | ## Service Providers 79 | 80 | - [Base](https://base.grida.co) 81 | - [Vercel](https://vercel.com) 82 | - [Supabase](https://firebase.google.com) 83 | - [AWS](https://aws.amazon.com) 84 | - [Azure](https://azure.microsoft.com) -------------------------------------------------------------------------------- /assets-service/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | .build 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json -------------------------------------------------------------------------------- /assets-service/README.md: -------------------------------------------------------------------------------- 1 | # Asset & G11n Service 2 | 3 | 4 | 5 | ## Asset 6 | 7 | 8 | 9 | ## G11n -------------------------------------------------------------------------------- /assets-service/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /assets-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "assets-service", 3 | "version": "0.0.0", 4 | "description": "", 5 | "author": "bridged.xyz authors", 6 | "private": false, 7 | "license": "MIT", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "sls offline start", 13 | "start:dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/main", 16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "test:e2e": "jest --config ./test/jest-e2e.json" 22 | }, 23 | "dependencies": { 24 | "@bridged.xyz/client-sdk": "0.0.1-14", 25 | "@nestjs/common": "^7.0.0", 26 | "@nestjs/core": "^7.0.0", 27 | "@nestjs/platform-express": "^7.0.0", 28 | "aws-lambda": "^1.0.6", 29 | "aws-sdk": "^2.783.0", 30 | "aws-serverless-express": "^3.3.8", 31 | "dynamoose": "^2.4.1", 32 | "nanoid": "^3.1.16", 33 | "reflect-metadata": "^0.1.13", 34 | "rimraf": "^3.0.2", 35 | "rxjs": "^6.5.4" 36 | }, 37 | "devDependencies": { 38 | "@hewmen/serverless-plugin-typescript": "^1.1.17", 39 | "@nestjs/cli": "^7.0.0", 40 | "@nestjs/schematics": "^7.0.0", 41 | "@nestjs/testing": "^7.0.0", 42 | "@types/aws-lambda": "^8.10.64", 43 | "@types/express": "^4.17.3", 44 | "@types/jest": "26.0.10", 45 | "@types/node": "^13.9.1", 46 | "@types/supertest": "^2.0.8", 47 | "@typescript-eslint/eslint-plugin": "3.9.1", 48 | "@typescript-eslint/parser": "3.9.1", 49 | "eslint": "7.7.0", 50 | "eslint-config-prettier": "^6.10.0", 51 | "eslint-plugin-import": "^2.20.1", 52 | "jest": "26.4.2", 53 | "plugin": "^0.3.3", 54 | "prettier": "^1.19.1", 55 | "serverless-bundle": "^4.1.0", 56 | "serverless-domain-manager": "^5.1.0", 57 | "serverless-dynamodb-local": "^0.2.39", 58 | "serverless-offline": "^6.8.0", 59 | "serverless-plugin-optimize": "^4.1.4-rc.1", 60 | "supertest": "^4.0.2", 61 | "ts-jest": "26.2.0", 62 | "ts-loader": "^6.2.1", 63 | "ts-node": "9.0.0", 64 | "tsconfig-paths": "^3.9.0", 65 | "typescript": "^4.1.2" 66 | }, 67 | "jest": { 68 | "moduleFileExtensions": [ 69 | "js", 70 | "json", 71 | "ts" 72 | ], 73 | "rootDir": "src", 74 | "testRegex": ".spec.ts$", 75 | "transform": { 76 | "^.+\\.(t|j)s$": "ts-jest" 77 | }, 78 | "coverageDirectory": "../coverage", 79 | "testEnvironment": "node" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /assets-service/serverless.yml: -------------------------------------------------------------------------------- 1 | service: 2 | name: assets 3 | 4 | plugins: 5 | - '@hewmen/serverless-plugin-typescript' 6 | - serverless-plugin-optimize 7 | - serverless-offline 8 | - serverless-dynamodb-local 9 | - serverless-domain-manager 10 | 11 | custom: 12 | customDomain: 13 | domainName: assets.bridged.cc 14 | hostedZoneId: us-west-1 15 | basePath: '' 16 | stage: ${self:provider.stage} 17 | createRoute53Record: true 18 | serverless-offline: 19 | httpPort: 4001 20 | 21 | resources: 22 | Resources: 23 | rawAssetsTable: 24 | Type: AWS::DynamoDB::Table 25 | Properties: 26 | TableName: '${self:provider.environment.DYNAMODB_TABLE_RAW_ASSETS}' 27 | KeySchema: 28 | - AttributeName: id 29 | KeyType: HASH 30 | AttributeDefinitions: 31 | - AttributeName: id 32 | AttributeType: S 33 | ProvisionedThroughput: 34 | ReadCapacityUnits: 1 35 | WriteCapacityUnits: 1 36 | variantAssetsTable: 37 | Type: AWS::DynamoDB::Table 38 | Properties: 39 | TableName: '${self:provider.environment.DYNAMODB_TABLE_VARIANT_ASSETS}' 40 | KeySchema: 41 | - AttributeName: id 42 | KeyType: HASH 43 | AttributeDefinitions: 44 | - AttributeName: id 45 | AttributeType: S 46 | - AttributeName: projectId 47 | AttributeType: S 48 | GlobalSecondaryIndexes: 49 | - IndexName: projectIndex 50 | KeySchema: 51 | - AttributeName: projectId 52 | KeyType: HASH 53 | Projection: 54 | ProjectionType: 'ALL' 55 | ProvisionedThroughput: 56 | ReadCapacityUnits: 1 57 | WriteCapacityUnits: 1 58 | ProvisionedThroughput: 59 | ReadCapacityUnits: 1 60 | WriteCapacityUnits: 1 61 | 62 | provider: 63 | name: aws 64 | runtime: nodejs12.x 65 | region: us-west-1 66 | apiGateway: 67 | minimumCompressionSize: 1024 68 | environment: 69 | AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1' 70 | DYNAMODB_TABLE_RAW_ASSETS: '${self:service}-raw-assets-${opt:stage, self:provider.stage}' 71 | DYNAMODB_TABLE_VARIANT_ASSETS: '${self:service}-variant-assets-${opt:stage, self:provider.stage}' 72 | iamRoleStatements: 73 | - Effect: Allow 74 | Action: 75 | - dynamodb:Query 76 | - dynamodb:Scan 77 | - dynamodb:GetItem 78 | - dynamodb:PutItem 79 | - dynamodb:UpdateItem 80 | - dynamodb:DeleteItem 81 | - dynamodb:DescribeTable 82 | Resource: 'arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE_RAW_ASSETS}' 83 | - Effect: Allow 84 | Action: 85 | - dynamodb:Query 86 | - dynamodb:Scan 87 | - dynamodb:GetItem 88 | - dynamodb:PutItem 89 | - dynamodb:UpdateItem 90 | - dynamodb:DeleteItem 91 | - dynamodb:DescribeTable 92 | Resource: 'arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE_VARIANT_ASSETS}' 93 | - Effect: Allow 94 | Action: 95 | - dynamodb:Query 96 | - dynamodb:Scan 97 | Resource: 'arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE_VARIANT_ASSETS}/index/*' 98 | 99 | package: 100 | individually: true 101 | 102 | functions: 103 | main: 104 | handler: src/lambda.handler 105 | events: 106 | - http: 107 | method: any 108 | path: /{proxy+} 109 | -------------------------------------------------------------------------------- /assets-service/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get('/') 9 | async getHello() { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /assets-service/src/app.entity.ts: -------------------------------------------------------------------------------- 1 | import * as dynamoose from 'dynamoose'; 2 | import { nanoid } from 'nanoid'; 3 | 4 | import { AssetType } from '@bridged.xyz/client-sdk/lib/assets'; 5 | 6 | /** 7 | // * the table definition of asset. the asset can be temporary, and it does not contains any information of which holds this asset. 8 | */ 9 | export interface RawAssetTable { 10 | id: string; 11 | name: string; 12 | type: AssetType; 13 | value: string; 14 | tags?: string[]; 15 | } 16 | 17 | /** 18 | * record of project's managed variant asset. 19 | * unique with (project-id & key & type) 20 | */ 21 | export interface VariantAssetTable { 22 | /** 23 | * id generated by the server 24 | */ 25 | id: string; 26 | 27 | /** 28 | * the id of the project. (this is not a file id!) 29 | * i.e. figma project is not a bridged project. multiple projects on the design tools can be a single project on bridged system. 30 | */ 31 | projectId: string; 32 | 33 | /** 34 | * *REQUIRED* 35 | * key of this variant asset. 36 | */ 37 | key: string; 38 | 39 | /** 40 | * *REQUIRED* 41 | * explicit name set by editor 42 | */ 43 | name?: string; 44 | 45 | /** 46 | * description of this localized, variantized asset set by editor 47 | */ 48 | description?: string; 49 | 50 | /** 51 | * *REQUIRED* 52 | * type of asset alias 53 | */ 54 | type: AssetType; 55 | 56 | /** 57 | * *REQUIRED* 58 | * variant : asset id 59 | * e.g. "en_US": "G2ggs93", where 'G2ggs93' is an id of asset 60 | * */ 61 | assets: Map; 62 | 63 | tags?: string[]; 64 | } 65 | 66 | const RawAssetSchema = new dynamoose.Schema({ 67 | id: { 68 | type: String, 69 | default: () => nanoid(), 70 | }, 71 | name: { 72 | type: String, 73 | required: true, 74 | }, 75 | type: { 76 | type: String, 77 | enum: [ 78 | 'URI', 79 | 'TEXT', 80 | 'IMAGE', 81 | 'ICON', 82 | 'ILLUST', 83 | 'COLOR', 84 | 'FILE', 85 | 'UNKNOWN', 86 | ], 87 | required: true, 88 | }, 89 | value: { 90 | type: String, 91 | required: true, 92 | }, 93 | tags: { 94 | type: Set, 95 | schema: [String], 96 | required: false, 97 | }, 98 | }); 99 | 100 | const TBL_RAW_ASSETS = process.env.DYNAMODB_TABLE_RAW_ASSETS; 101 | export const RawAssetModel = dynamoose.model(TBL_RAW_ASSETS, RawAssetSchema, { 102 | create: false, 103 | }); 104 | 105 | const VariantAssetSchema = new dynamoose.Schema( 106 | { 107 | id: { 108 | type: String, 109 | default: () => nanoid(), 110 | }, 111 | projectId: { 112 | type: String, 113 | required: true, 114 | index: { 115 | name: 'projectIndex', 116 | }, 117 | }, 118 | key: { 119 | type: String, 120 | required: false, 121 | }, 122 | name: { 123 | type: String, 124 | required: false, 125 | }, 126 | description: { 127 | type: String, 128 | required: false, 129 | }, 130 | type: { 131 | type: String, 132 | enum: [ 133 | 'URI', 134 | 'TEXT', 135 | 'IMAGE', 136 | 'ICON', 137 | 'ILLUST', 138 | 'COLOR', 139 | 'FILE', 140 | 'UNKNOWN', 141 | ], 142 | required: true, 143 | }, 144 | // TODO change type to string: string map -> {variant: raw-asset-id} 145 | assets: { 146 | type: Object, 147 | default: { default: '__empty__' }, 148 | required: true, 149 | }, 150 | // tags: { 151 | // type: Set, 152 | // default: [], 153 | // required: false 154 | // } 155 | }, 156 | { 157 | saveUnknown: true, 158 | } 159 | ); 160 | 161 | const TBL_VARIANT_ASSETS = process.env.DYNAMODB_TABLE_VARIANT_ASSETS; 162 | export const VariantAssetModel = dynamoose.model( 163 | TBL_VARIANT_ASSETS, 164 | VariantAssetSchema, 165 | { 166 | create: false, 167 | } 168 | ); 169 | -------------------------------------------------------------------------------- /assets-service/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { AppController } from './app.controller'; 4 | import { AppService } from './app.service'; 5 | import { RawAssetsModule } from './raw-assets/raw-assets.module'; 6 | import { VariantAssetsModule } from './variant-assets/variant-assets.module'; 7 | 8 | @Module({ 9 | imports: [VariantAssetsModule, RawAssetsModule], 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }) 13 | export class AppModule {} 14 | -------------------------------------------------------------------------------- /assets-service/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Welcome to bridged hosting service. Learn more at https://github.com/bridgedxyz/services/'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /assets-service/src/lambda.ts: -------------------------------------------------------------------------------- 1 | // lambda.ts 2 | import { NestFactory } from '@nestjs/core'; 3 | import { ExpressAdapter } from '@nestjs/platform-express'; 4 | import { Handler, Context } from 'aws-lambda'; 5 | import { createServer, proxy } from 'aws-serverless-express'; 6 | import { eventContext } from 'aws-serverless-express/middleware'; 7 | import { Server } from 'http'; 8 | import express from 'express'; 9 | 10 | import { AppModule } from './app.module'; 11 | 12 | // NOTE: If you get ERR_CONTENT_DECODING_FAILED in your browser, this 13 | // is likely due to a compressed response (e.g. gzip) which has not 14 | // been handled correctly by aws-serverless-express and/or API 15 | // Gateway. Add the necessary MIME types to binaryMimeTypes below 16 | const binaryMimeTypes: string[] = []; 17 | 18 | let cachedServer: Server; 19 | 20 | // Create the Nest.js server and convert it into an Express.js server 21 | async function bootstrapServer(): Promise { 22 | if (!cachedServer) { 23 | const expressApp = express(); 24 | const nestApp = await NestFactory.create( 25 | AppModule, 26 | new ExpressAdapter(expressApp) 27 | ); 28 | nestApp.use(eventContext()); 29 | nestApp.enableCors({ 30 | origin: true, 31 | }); 32 | await nestApp.init(); 33 | cachedServer = createServer(expressApp, undefined, binaryMimeTypes); 34 | } 35 | return cachedServer; 36 | } 37 | 38 | // Export the handler : the entry point of the Lambda function 39 | export const handler: Handler = async (event: any, context: Context) => { 40 | cachedServer = await bootstrapServer(); 41 | return proxy(cachedServer, event, context, 'PROMISE').promise; 42 | }; 43 | -------------------------------------------------------------------------------- /assets-service/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | 3 | import { AppModule } from './app.module'; 4 | 5 | async function bootstrap() { 6 | const app = await NestFactory.create(AppModule); 7 | app.listen(3000, () => console.log('Microservice is listening')); 8 | } 9 | 10 | bootstrap(); 11 | -------------------------------------------------------------------------------- /assets-service/src/raw-assets/raw-assets.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, Param, Post } from '@nestjs/common'; 2 | 3 | import { 4 | RawAsset, 5 | RawAssetRegisterRequest, 6 | RawAssetRegisterResponse, 7 | } from '@bridged.xyz/client-sdk/lib/assets'; 8 | import { RawAssetsService } from './raw-assets.service'; 9 | 10 | @Controller('raw-assets') 11 | export class RawAssetsController { 12 | constructor(private readonly rawAssetsService: RawAssetsService) {} 13 | 14 | @Post('/') 15 | async postRawAsset( 16 | @Body() req: RawAssetRegisterRequest 17 | ): Promise { 18 | const asset = await this.rawAssetsService.createRawAsset(req); 19 | 20 | return { 21 | success: true, 22 | data: asset, 23 | message: 'asset registered successfully', 24 | }; 25 | } 26 | 27 | @Get(':id') 28 | async getRawAsset(@Param() p: { id: string }): Promise { 29 | const id = p.id; 30 | const asset = await this.rawAssetsService.fetchRawAsset(id); 31 | return asset; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /assets-service/src/raw-assets/raw-assets.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { RawAssetsController } from './raw-assets.controller'; 3 | import { RawAssetsService } from './raw-assets.service'; 4 | 5 | @Module({ 6 | imports: [], 7 | controllers: [RawAssetsController], 8 | providers: [RawAssetsService], 9 | exports: [RawAssetsService], 10 | }) 11 | export class RawAssetsModule {} 12 | -------------------------------------------------------------------------------- /assets-service/src/raw-assets/raw-assets.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import * as AWS from 'aws-sdk'; 3 | import { DocumentClient } from 'aws-sdk/lib/dynamodb/document_client'; 4 | import { nanoid } from 'nanoid'; 5 | 6 | import { 7 | RawAssetRegisterRequest, 8 | RawAsset, 9 | RawAssetUpdateRequest, 10 | } from '@bridged.xyz/client-sdk/lib/assets'; 11 | import { RawAssetModel, RawAssetTable } from '../app.entity'; 12 | 13 | const dynamoDb = new AWS.DynamoDB.DocumentClient(); 14 | 15 | const TBL_RAW_ASSETS = process.env.DYNAMODB_TABLE_RAW_ASSETS; 16 | @Injectable() 17 | export class RawAssetsService { 18 | getHello(): string { 19 | return 'Welcome to bridged hosting service. Learn more at https://github.com/bridgedxyz/services/'; 20 | } 21 | 22 | async createRawAsset(request: RawAssetRegisterRequest): Promise { 23 | // TODO handle by types. -> if input contains image, then host image and make a asset record. 24 | // only text asset is supported at this point 25 | 26 | const id = nanoid(); 27 | const body: DocumentClient.PutItemInput = { 28 | TableName: TBL_RAW_ASSETS, 29 | Item: { 30 | id: id, 31 | ...request, 32 | }, 33 | }; 34 | dynamoDb.put(body).promise(); 35 | 36 | return { 37 | id: id, 38 | ...request, 39 | }; 40 | } 41 | 42 | async fetchRawAsset(id: string): Promise { 43 | const query: DocumentClient.GetItemInput = { 44 | TableName: TBL_RAW_ASSETS, 45 | Key: { id: id }, 46 | }; 47 | 48 | const itemRes = await dynamoDb.get(query).promise(); 49 | const record: RawAsset = itemRes.Item as RawAsset; 50 | return record; 51 | } 52 | 53 | async fetchRawAssets(ids: string[]): Promise> { 54 | // TODO -> implement this via batch query for better performance 55 | const requests: Array> = []; 56 | for (const id of ids) { 57 | requests.push(this.fetchRawAsset(id)); 58 | } 59 | 60 | const results = await Promise.all(requests); 61 | return results; 62 | } 63 | 64 | async updateRawAsset(id: string, request: RawAssetUpdateRequest) { 65 | const update = { 66 | // name: request.name, 67 | // type: request.type, 68 | value: request.value, 69 | // tags: request.tags 70 | }; 71 | console.log( 72 | 'updating raw asset with request', 73 | request, 74 | 'the update value is..', 75 | update 76 | ); 77 | const updated = await RawAssetModel.update({ id: id }, { $SET: update }); 78 | console.log('updated raw asset', id, updated); 79 | return updated; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /assets-service/src/variant-assets/variant-assets.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | forwardRef, 5 | Get, 6 | Inject, 7 | Param, 8 | Patch, 9 | Post, 10 | Put, 11 | } from '@nestjs/common'; 12 | 13 | import { 14 | NestedAssetPutRequest, 15 | VariantAssetRegisterRequest, 16 | } from '@bridged.xyz/client-sdk/lib/assets'; 17 | import { VariantAssetsService } from './variant-assets.service'; 18 | 19 | @Controller('variant-assets') 20 | export class VariantAssetsController { 21 | constructor( 22 | @Inject(forwardRef(() => VariantAssetsService)) 23 | private readonly variantAssetsService: VariantAssetsService 24 | ) {} 25 | 26 | @Post('/') 27 | async postCreateVariantAsset( 28 | @Body() req: VariantAssetRegisterRequest 29 | ): Promise { 30 | const projectId = 'temp'; 31 | return await this.variantAssetsService.registerVariantAsset(projectId, req); 32 | } 33 | 34 | @Get('/:id') 35 | async getVariantAsset(@Param() p: { id: string }): Promise { 36 | const id = p.id; 37 | return await this.variantAssetsService.fetchVariantAsset(id); 38 | } 39 | 40 | /** 41 | * get all variant assets in the project. project id provided in header 42 | */ 43 | @Get('/') 44 | async getVariantAssets() { 45 | console.log('getting all variant assets in project'); 46 | const projectId = 'demo'; 47 | return this.variantAssetsService.fetchVariantAssetsInProject(projectId); 48 | } 49 | 50 | @Patch('/:id/variants/:variant') 51 | async updateVariant( 52 | @Param() p: VariantAccessorParam, 53 | @Body() body: NestedAssetPutRequest 54 | ): Promise { 55 | const updatedVariantAsset = await this.variantAssetsService.updateVariant({ 56 | variantAssetId: p.id, 57 | variant: p.variant, 58 | asset: body, 59 | }); 60 | 61 | return updatedVariantAsset; 62 | } 63 | 64 | @Put('/:id/variants/:variant') 65 | async putVariant( 66 | @Param() p: VariantAccessorParam, 67 | @Body() body: NestedAssetPutRequest 68 | ) { 69 | return await this.variantAssetsService.putVariant({ 70 | variantAssetId: p.id, 71 | variant: p.variant, 72 | asset: body, 73 | }); 74 | } 75 | 76 | @Post('/:id/variants/:variant') 77 | async addVariant( 78 | @Param() p: VariantAccessorParam, 79 | @Body() body: NestedAssetPutRequest 80 | ) { 81 | return await this.variantAssetsService.addVariant({ 82 | variantAssetId: p.id, 83 | variant: p.variant, 84 | asset: body, 85 | }); 86 | } 87 | } 88 | 89 | /** 90 | * an indicator to locate variant (raw asset) from variant asset. via id and variant key 91 | */ 92 | interface VariantAccessorParam { 93 | id: string; 94 | variant: string; 95 | } 96 | -------------------------------------------------------------------------------- /assets-service/src/variant-assets/variant-assets.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { RawAssetsService } from '../raw-assets/raw-assets.service'; 4 | import { VariantAssetsController } from './variant-assets.controller'; 5 | import { VariantAssetsService } from './variant-assets.service'; 6 | 7 | @Module({ 8 | imports: [RawAssetsService], 9 | controllers: [VariantAssetsController], 10 | providers: [VariantAssetsService, RawAssetsService], 11 | }) 12 | export class VariantAssetsModule {} 13 | -------------------------------------------------------------------------------- /assets-service/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /assets-service/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "moduleResolution": "node", 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "target": "es6", 14 | "sourceMap": true, 15 | "outDir": "./dist", 16 | "baseUrl": "./", 17 | "typeRoots": ["./node_modules/@types"] 18 | }, 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /branding/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | for full bridged branding resources, visit [bridgedxyz/branding](https://github/com/bridgedxyz/branding) -------------------------------------------------------------------------------- /branding/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridaco/base/a1e5aa7a851d84873fc548648dc703badafe930d/branding/cover.png -------------------------------------------------------------------------------- /branding/lgoo-type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridaco/base/a1e5aa7a851d84873fc548648dc703badafe930d/branding/lgoo-type.png -------------------------------------------------------------------------------- /branding/lgoo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridaco/base/a1e5aa7a851d84873fc548648dc703badafe930d/branding/lgoo.png -------------------------------------------------------------------------------- /build-service/README.md: -------------------------------------------------------------------------------- 1 | # build service for node and flutter -------------------------------------------------------------------------------- /build-service/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridaco/base/a1e5aa7a851d84873fc548648dc703badafe930d/build-service/index.ts -------------------------------------------------------------------------------- /configs/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'prettier/@typescript-eslint', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # docs for bridged services -------------------------------------------------------------------------------- /docs/security.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | ## So, is base safe to use? 4 | > Bridged internally uses base for all of the service operation, including non opensourced services such as accounts-services (private). Base runs on any platform ensuring super secure connection. 5 | 6 | All request are validated by TOTP for CLI, no-origin web apps, and embedded app. and for authenticated apps such as user interaction and OAuth app, It's secured by accounts services. 7 | 8 | So you can 9 | - build your app with base 10 | - build your own cloud service with base 11 | In a most secure way. 12 | -------------------------------------------------------------------------------- /forms-service/.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | .env 3 | .env.* 4 | !.env.defaults -------------------------------------------------------------------------------- /forms-service/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributors docs 2 | 3 | 4 | ## Architecture 5 | 6 | 7 | ## Database 8 | 9 | MongoDB 10 | 11 | ## Search 12 | 13 | - MongoDB query (for form service) 14 | - MongoDB full form search (for post service) 15 | - Elasticsearch (for external service, optional) 16 | 17 | 18 | ## API Routes 19 | 20 | > base url: `https://forms.grida.cc` 21 | 22 | ## Models 23 | 24 | ```ts 25 | 26 | ``` 27 | 28 | 29 | 30 | ## Trouble shooting 31 | 32 | - hit payload size for assets uploading? - https://theburningmonk.com/2020/04/hit-the-6mb-lambda-payload-limit-heres-what-you-can-do/ 33 | 34 | 35 | 36 | ## References 37 | - https://developers.google.com/forms/api 38 | - https://developers.notion.com/reference/page 39 | - https://developers.notion.com/reference/block -------------------------------------------------------------------------------- /forms-service/README.md: -------------------------------------------------------------------------------- 1 | # Forms service 2 | 3 | > forms.grida.cc 4 | 5 | ## Assets 6 | 7 | the assets linked to a forms is hosted on s3 bucket - forms-cms 8 | 9 | - the assets are public by default. 10 | - the assets from responses are private by default. 11 | 12 | ### Object pattern 13 | 14 | ```txt 15 | - https://forms-cms.s3.amazonaws.com/forms/{form-id}/{asset-name} 16 | - https://forms-cms.s3.amazonaws.com/responses/{form-id}/{asset-name} 17 | ``` 18 | 19 | 20 | ## DOM (Document Object Model) 21 | 22 | - boring standard 23 | - json 24 | - html 25 | - md 26 | - reflect-ui standard 27 | - json 28 | 29 | ## Events 30 | 31 | *internal infra event* 32 | ```json 33 | { 34 | "id": "form-id", 35 | "action": "publish", 36 | "schedule": "2022-12-25T00:00:00Z" 37 | } 38 | ``` 39 | 40 | ```json 41 | { 42 | "id": "response-id", 43 | "action": "submit", 44 | "schedule": "2022-12-25T00:00:00Z" 45 | } 46 | ``` 47 | 48 | ## Integrations (Under development) 49 | 50 | - Slack - on publish 51 | - Slack - on submit 52 | - Github - on publish 53 | - Webhooks 54 | 55 | ## API / SDK 56 | 57 | - `@base-sdk/forms` (node/ts) 58 | - rest api 59 | 60 | 61 | -------------------------------------------------------------------------------- /forms-service/app.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import useragent from "express-useragent"; 3 | import cors from "cors"; 4 | import bodyParser from "body-parser"; 5 | import router from "./routes"; 6 | 7 | const app = express(); 8 | 9 | app.use(cors()); 10 | 11 | app.use(useragent.express()); 12 | 13 | app.use(bodyParser.json()); 14 | 15 | app.use(router); 16 | 17 | export { app }; 18 | -------------------------------------------------------------------------------- /forms-service/index.ts: -------------------------------------------------------------------------------- 1 | import { configure as serverlessExpress } from "@vendia/serverless-express"; 2 | import { APIGatewayProxyHandler } from "aws-lambda"; 3 | 4 | import { app } from "./app"; 5 | 6 | export const handler: APIGatewayProxyHandler = serverlessExpress({ app }); 7 | -------------------------------------------------------------------------------- /forms-service/lib/assets.ts: -------------------------------------------------------------------------------- 1 | export async function uploadAssets({}: { path: string; file; key: string }) {} 2 | export async function removeAssets() {} 3 | export function buildPath( 4 | key, 5 | { 6 | publication, 7 | post, 8 | }: { 9 | publication?: string; 10 | post: string; 11 | } 12 | ): string { 13 | return ""; 14 | } 15 | -------------------------------------------------------------------------------- /forms-service/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./assets"; 2 | -------------------------------------------------------------------------------- /forms-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forms-services", 3 | "version": "0.0.0", 4 | "homepage": "https://forms.grida.cc", 5 | "scripts": { 6 | "prisma:studio": "npx prisma studio", 7 | "prisma:generate": "npx prisma generate", 8 | "prisma:generate:watch": "npx prisma generate --watch", 9 | "postinstall": "npm run prisma:generate", 10 | "db:push": "npx prisma db push", 11 | "setup:prod": "echo 'passing'", 12 | "clean": "rimraf .build", 13 | "dev": "sls offline start", 14 | "deploy:staging": "yarn clean && yarn setup:staging && yarn db:push && sls deploy --stage staging", 15 | "deploy:prod": "yarn clean && yarn setup:prod && yarn db:push && sls deploy --stage production" 16 | }, 17 | "dependencies": { 18 | "@aws-sdk/client-s3": "^3.79.0", 19 | "@prisma/client": "3.14.0", 20 | "@vendia/serverless-express": "^4.8.0", 21 | "body-parser": "^1.20.0", 22 | "cors": "^2.8.5", 23 | "csv": "^6.0.5", 24 | "express": "^4.18.1", 25 | "express-useragent": "^1.0.15", 26 | "multer": "^1.4.4", 27 | "nanoid": "^3.3.3", 28 | "serverless-http": "^3.0.1" 29 | }, 30 | "devDependencies": { 31 | "@types/aws-lambda": "^8.10.95", 32 | "@types/body-parser": "^1.19.2", 33 | "@types/cors": "^2.8.12", 34 | "@types/express": "^4.17.13", 35 | "@types/multer": "^1.4.7", 36 | "@types/node": "^17.0.29", 37 | "prisma": "^3.14.0", 38 | "serverless": "^3.17.0", 39 | "serverless-domain-manager": "^6.0.3", 40 | "serverless-offline": "^8.7.0", 41 | "serverless-plugin-optimize": "^4.2.1-rc.1", 42 | "serverless-plugin-typescript": "^2.1.2", 43 | "serverless-webpack": "^5.7.1", 44 | "serverless-webpack-prisma": "^1.1.0", 45 | "ts-loader": "^9.3.0", 46 | "typescript": "^4.6.4", 47 | "webpack": "^5.72.1", 48 | "webpack-node-externals": "^3.0.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /forms-service/prisma-client/index.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | const prisma = new PrismaClient(); 4 | 5 | export { prisma }; 6 | 7 | export * as selectors from "./selectors"; 8 | -------------------------------------------------------------------------------- /forms-service/prisma-client/selectors.ts: -------------------------------------------------------------------------------- 1 | import { Prisma } from "@prisma/client"; 2 | 3 | export const form_summary_select: Prisma.FormSelect = { 4 | id: true, 5 | title: true, 6 | thumbnail: true, 7 | createdAt: true, 8 | }; 9 | -------------------------------------------------------------------------------- /forms-service/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | datasource db { 5 | provider = "mongodb" 6 | url = env("DATABASE_URL") 7 | } 8 | 9 | generator client { 10 | provider = "prisma-client-js" 11 | binaryTargets = ["native", "rhel-openssl-1.0.x"] 12 | } 13 | 14 | model Form { 15 | id String @id @default(auto()) @map("_id") @db.ObjectId 16 | title String 17 | description String? 18 | cover String? 19 | thumbnail String? 20 | body Json? 21 | slug String @unique @default(cuid()) 22 | scheduledAt DateTime? 23 | createdAt DateTime @default(now()) 24 | postedAt DateTime? 25 | language String? 26 | archived Boolean @default(false) 27 | author String? @db.ObjectId 28 | config FormConfig 29 | responses Response[] 30 | } 31 | 32 | type FormConfig{ 33 | allow_multiple_responses Boolean @default(true) 34 | } 35 | 36 | model Field{ 37 | id String @id @default(auto()) @map("_id") @db.ObjectId 38 | } 39 | 40 | model Response{ 41 | id String @id @default(auto()) @map("_id") @db.ObjectId 42 | landedAt DateTime? @default(now()) 43 | submittedAt DateTime? 44 | metadata ResponseMeta 45 | form Form @relation(references: [id], fields: [formId]) 46 | formId String 47 | answers Answer[] 48 | } 49 | 50 | type Answer { 51 | field String // can be both name or id 52 | data Json 53 | } 54 | 55 | type ResponseMeta{ 56 | ip String? 57 | browser String? 58 | platform Platform @default(web) 59 | referer String? 60 | ua String? 61 | } 62 | 63 | enum Platform{ 64 | web 65 | api 66 | } -------------------------------------------------------------------------------- /forms-service/routes/assets/index.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import multer from "multer"; 3 | 4 | const m = multer({ 5 | storage: multer.memoryStorage(), 6 | }); 7 | 8 | const router = express.Router(); 9 | 10 | export default router; 11 | -------------------------------------------------------------------------------- /forms-service/routes/export/index.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | const router = express.Router(); 3 | import { prisma } from "../../prisma-client"; 4 | import { stringify } from "csv-stringify"; 5 | 6 | // exports the responses of the form 7 | router.get("/:id", async (req, res) => { 8 | const { id } = req.params; 9 | const { f } = req.query; 10 | 11 | const form = await prisma.form.findUnique({ 12 | where: { id }, 13 | select: { 14 | title: true, 15 | }, 16 | }); 17 | 18 | const responses = await prisma.response.findMany({ 19 | where: { 20 | formId: id, 21 | }, 22 | }); 23 | 24 | const filename = `grida-forms-export-${ 25 | form.title 26 | }-${new Date().toISOString()}.${f}`; 27 | 28 | switch (f) { 29 | case "csv": { 30 | const stringifier = stringify({ 31 | header: true, 32 | columns: { 33 | time: "time", 34 | }, 35 | }); 36 | stringifier.write([]); 37 | stringifier.end(); 38 | } 39 | case "json": { 40 | const payload = JSON.stringify(responses); 41 | break; 42 | } 43 | case "xls": 44 | case "xlsx": { 45 | } 46 | default: { 47 | } 48 | } 49 | 50 | res.json(responses); 51 | }); 52 | 53 | export default router; 54 | -------------------------------------------------------------------------------- /forms-service/routes/index.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import router_forms from "./forms"; 3 | import router_assets from "./assets"; 4 | import router_responses from "./responses"; 5 | import router_export from "./export"; 6 | 7 | const router = express.Router(); 8 | 9 | router.use("/", router_forms); 10 | router.use("/responses", router_responses); 11 | router.use("/assets", router_assets); 12 | router.use("/export", router_export); 13 | 14 | export default router; 15 | -------------------------------------------------------------------------------- /forms-service/routes/responses/index.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import { prisma } from "../../prisma-client"; 3 | 4 | const router = express.Router(); 5 | 6 | router.post("/start", (req, res) => { 7 | // create new response session for assets uploading & saving etc.. 8 | }); 9 | 10 | router.post("/:id/submit", (req, res) => { 11 | // submit (close) the response with session 12 | }); 13 | 14 | router.post("/submit", async (req, res) => { 15 | // submit the response (w/o session) 16 | const { form, landedAt, answers, platform } = req.body; 17 | 18 | try { 19 | const response = await prisma.response.create({ 20 | data: { 21 | form: { 22 | connect: { 23 | id: form, 24 | }, 25 | }, 26 | answers, 27 | metadata: { 28 | ip: req.ip, 29 | ua: req.headers["user-agent"], 30 | // referer: req.headers.referer, 31 | platform: platform ?? "web", 32 | }, 33 | landedAt, 34 | submittedAt: new Date(), 35 | }, 36 | }); 37 | 38 | res.status(202).json(response); 39 | } catch (e) { 40 | console.error(e) 41 | res.status(400).json({ 42 | error: "cannot create new response (metadata may be invalid)", 43 | }); 44 | } 45 | }); 46 | 47 | export default router; 48 | -------------------------------------------------------------------------------- /forms-service/serverless.yml: -------------------------------------------------------------------------------- 1 | service: cms-forms 2 | useDotenv: true 3 | 4 | plugins: 5 | - serverless-webpack 6 | - serverless-webpack-prisma 7 | - serverless-offline 8 | - serverless-domain-manager 9 | 10 | custom: 11 | customDomain: 12 | domainName: forms.grida.cc 13 | certificateName: "*.grida.cc" 14 | basePath: "" 15 | createRoute53Record: true 16 | stage: production 17 | serverless-offline: 18 | httpPort: 4016 19 | noPrependStageInUrl: true 20 | webpack: 21 | includeModules: true 22 | 23 | provider: 24 | name: aws 25 | environment: 26 | DATABASE_URL: ${env:DATABASE_URL} 27 | runtime: nodejs12.x 28 | region: us-west-1 29 | apiGateway: 30 | # https://stackoverflow.com/questions/61003311/serverless-i-image-upload-to-s3-broken-after-deploy-local-worked-only/61003498#61003498 31 | binaryMediaTypes: 32 | - "*/*" 33 | iamRoleStatements: 34 | - Effect: Allow 35 | Action: 36 | - s3:PutObject 37 | - s3:GetObject 38 | - s3:PutObjectAcl 39 | Resource: "arn:aws:s3:::cms-forms/*" 40 | 41 | resource: 42 | s3bucket: 43 | Type: S3::Bucket 44 | Properties: 45 | BucketName: cms-forms 46 | 47 | functions: 48 | main: 49 | handler: index.handler 50 | events: 51 | - http: 52 | method: any 53 | path: /{proxy+} 54 | cors: 55 | origin: "*" 56 | 57 | package: 58 | individually: true 59 | -------------------------------------------------------------------------------- /forms-service/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "es6", 13 | "sourceMap": true, 14 | "outDir": "./dist", 15 | "baseUrl": "./", 16 | "typeRoots": ["./node_modules/@types"] 17 | }, 18 | "exclude": ["node_modules"] 19 | } 20 | -------------------------------------------------------------------------------- /forms-service/types/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridaco/base/a1e5aa7a851d84873fc548648dc703badafe930d/forms-service/types/index.ts -------------------------------------------------------------------------------- /forms-service/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require("path"); 3 | const nodeExternals = require("webpack-node-externals"); 4 | const slsw = require("serverless-webpack"); 5 | const { isLocal } = slsw.lib.webpack; 6 | 7 | module.exports = { 8 | target: "node", 9 | stats: "normal", 10 | entry: slsw.lib.entries, 11 | externals: [nodeExternals()], 12 | mode: isLocal ? "development" : "production", 13 | optimization: { concatenateModules: false }, 14 | resolve: { extensions: [".js", ".ts"] }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.tsx?$/, 19 | loader: "ts-loader", 20 | exclude: /node_modules/, 21 | }, 22 | ], 23 | }, 24 | output: { 25 | libraryTarget: "commonjs", 26 | filename: "[name].js", 27 | path: path.resolve(__dirname, ".webpack"), 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /g11n-service/README.md: -------------------------------------------------------------------------------- 1 | # Globalization service 2 | 3 | > This project's service is being maintained under [bridgedxyz/g11n](https://github.com/bridgedxyz/g11n) due to it's project size. -------------------------------------------------------------------------------- /hosting-service/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'prettier/@typescript-eslint', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /hosting-service/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | .build 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json -------------------------------------------------------------------------------- /hosting-service/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /hosting-service/README.md: -------------------------------------------------------------------------------- 1 | # web hosting service 2 | - host temporary file 3 | - host built website 4 | 5 | 6 | ## Description 7 | 8 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 9 | 10 | ## Installation 11 | 12 | ```bash 13 | $ npm install 14 | ``` 15 | 16 | ## Running the app 17 | 18 | ```bash 19 | # development 20 | $ npm run start 21 | 22 | # watch mode 23 | $ npm run start:dev 24 | 25 | # production mode 26 | $ npm run start:prod 27 | ``` 28 | 29 | ## Test 30 | 31 | ```bash 32 | # unit tests 33 | $ npm run test 34 | 35 | # e2e tests 36 | $ npm run test:e2e 37 | 38 | # test coverage 39 | $ npm run test:cov 40 | ``` 41 | 42 | ## Support 43 | 44 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 45 | 46 | ## Stay in touch 47 | 48 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 49 | - Website - [https://nestjs.com](https://nestjs.com/) 50 | - Twitter - [@nestframework](https://twitter.com/nestframework) 51 | 52 | ## License 53 | 54 | Nest is [MIT licensed](LICENSE). 55 | -------------------------------------------------------------------------------- /hosting-service/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /hosting-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hosting-service", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "sls offline start", 13 | "start:dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/main", 16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "test:e2e": "jest --config ./test/jest-e2e.json" 22 | }, 23 | "dependencies": { 24 | "@bridged.xyz/client-sdk": "0.0.1-14", 25 | "@nestjs/common": "^7.0.0", 26 | "@nestjs/core": "^7.0.0", 27 | "@nestjs/platform-express": "^7.0.0", 28 | "aws-lambda": "^1.0.6", 29 | "aws-sdk": "^2.783.0", 30 | "aws-serverless-express": "^3.3.8", 31 | "nanoid": "^3.1.16", 32 | "reflect-metadata": "^0.1.13", 33 | "rimraf": "^3.0.2", 34 | "rxjs": "^6.5.4", 35 | "uguu-api": "^0.1.8" 36 | }, 37 | "devDependencies": { 38 | "@hewmen/serverless-plugin-typescript": "^1.1.17", 39 | "@nestjs/cli": "^7.0.0", 40 | "@nestjs/schematics": "^7.0.0", 41 | "@nestjs/testing": "^7.0.0", 42 | "@types/aws-lambda": "^8.10.64", 43 | "@types/express": "^4.17.3", 44 | "@types/jest": "26.0.10", 45 | "@types/node": "^13.9.1", 46 | "@types/supertest": "^2.0.8", 47 | "@typescript-eslint/eslint-plugin": "3.9.1", 48 | "@typescript-eslint/parser": "3.9.1", 49 | "eslint": "7.7.0", 50 | "eslint-config-prettier": "^6.10.0", 51 | "eslint-plugin-import": "^2.20.1", 52 | "jest": "26.4.2", 53 | "plugin": "^0.3.3", 54 | "prettier": "^1.19.1", 55 | "serverless-domain-manager": "^5.1.0", 56 | "serverless-offline": "^6.8.0", 57 | "serverless-plugin-optimize": "^4.1.4-rc.1", 58 | "supertest": "^4.0.2", 59 | "ts-jest": "26.2.0", 60 | "ts-loader": "^6.2.1", 61 | "ts-node": "9.0.0", 62 | "tsconfig-paths": "^3.9.0", 63 | "typescript": "^4.1.2" 64 | }, 65 | "jest": { 66 | "moduleFileExtensions": [ 67 | "js", 68 | "json", 69 | "ts" 70 | ], 71 | "rootDir": "src", 72 | "testRegex": ".spec.ts$", 73 | "transform": { 74 | "^.+\\.(t|j)s$": "ts-jest" 75 | }, 76 | "coverageDirectory": "../coverage", 77 | "testEnvironment": "node" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /hosting-service/serverless.yml: -------------------------------------------------------------------------------- 1 | service: 2 | name: resource-hosting 3 | 4 | plugins: 5 | - '@hewmen/serverless-plugin-typescript' 6 | - serverless-plugin-optimize 7 | - serverless-offline 8 | - serverless-domain-manager 9 | 10 | custom: 11 | customDomain: 12 | domainName: hosting.bridged.cc 13 | basePath: '' 14 | stage: ${self:provider.stage} 15 | createRoute53Record: true 16 | serverless-offline: 17 | httpPort: 4005 18 | 19 | provider: 20 | name: aws 21 | runtime: nodejs12.x 22 | region: us-west-1 23 | apiGateway: 24 | binaryMediaTypes: 25 | - 'multipart/form-data' 26 | iamRoleStatements: 27 | - Effect: Allow 28 | Action: 29 | - s3:PutObject 30 | - s3:GetObject 31 | - s3:PutObjectAcl 32 | Resource: 'arn:aws:s3:::resource-hosting/*' 33 | 34 | resource: 35 | s3bucket: 36 | Type: S3::Bucket 37 | Properties: 38 | BucketName: resource-hosting 39 | 40 | package: 41 | individually: true 42 | 43 | functions: 44 | main: 45 | handler: src/lambda.handler 46 | events: 47 | - http: 48 | method: any 49 | path: /{proxy+} 50 | cors: true 51 | -------------------------------------------------------------------------------- /hosting-service/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | 3 | import { AppService } from './app.service'; 4 | 5 | @Controller() 6 | export class AppController { 7 | constructor(private readonly appService: AppService) {} 8 | 9 | @Get() 10 | getHello(): string { 11 | return this.appService.getHello(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /hosting-service/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { AppController } from './app.controller'; 4 | import { AppService } from './app.service'; 5 | import { ResourceModule } from './resource/resource.module'; 6 | import { SiteModule } from './site/site.module'; 7 | 8 | @Module({ 9 | imports: [ResourceModule, SiteModule], 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }) 13 | export class AppModule {} 14 | -------------------------------------------------------------------------------- /hosting-service/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Welcome to bridged hosting service. Learn more at https://github.com/bridgedxyz/services/'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /hosting-service/src/lambda.ts: -------------------------------------------------------------------------------- 1 | // lambda.ts 2 | import { Handler, Context } from 'aws-lambda'; 3 | import { Server } from 'http'; 4 | import { createServer, proxy } from 'aws-serverless-express'; 5 | import { eventContext } from 'aws-serverless-express/middleware'; 6 | import express from 'express'; 7 | import { NestFactory } from '@nestjs/core'; 8 | import { ExpressAdapter } from '@nestjs/platform-express'; 9 | 10 | import { AppModule } from './app.module'; 11 | 12 | // NOTE: If you get ERR_CONTENT_DECODING_FAILED in your browser, this 13 | // is likely due to a compressed response (e.g. gzip) which has not 14 | // been handled correctly by aws-serverless-express and/or API 15 | // Gateway. Add the necessary MIME types to binaryMimeTypes below 16 | const binaryMimeTypes: string[] = []; 17 | 18 | let cachedServer: Server; 19 | 20 | // Create the Nest.js server and convert it into an Express.js server 21 | async function bootstrapServer(): Promise { 22 | if (!cachedServer) { 23 | const expressApp = express(); 24 | const nestApp = await NestFactory.create( 25 | AppModule, 26 | new ExpressAdapter(expressApp), 27 | ); 28 | nestApp.use(eventContext()); 29 | nestApp.enableCors({ 30 | origin: true, 31 | }); 32 | await nestApp.init(); 33 | cachedServer = createServer(expressApp, undefined, binaryMimeTypes); 34 | } 35 | return cachedServer; 36 | } 37 | 38 | // Export the handler : the entry point of the Lambda function 39 | export const handler: Handler = async (event: any, context: Context) => { 40 | cachedServer = await bootstrapServer(); 41 | return proxy(cachedServer, event, context, 'PROMISE').promise; 42 | }; 43 | -------------------------------------------------------------------------------- /hosting-service/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | 3 | import { AppModule } from './app.module'; 4 | 5 | async function bootstrap() { 6 | const app = await NestFactory.create(AppModule); 7 | app.listen(3000, () => console.log('Microservice is listening')); 8 | } 9 | bootstrap(); 10 | -------------------------------------------------------------------------------- /hosting-service/src/resource/resource.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Post, 4 | UseInterceptors, 5 | UploadedFile, 6 | } from '@nestjs/common'; 7 | import { FileInterceptor } from '@nestjs/platform-express'; 8 | 9 | import { ResourceService } from './resource.service'; 10 | 11 | @Controller('resources') 12 | export class ResourceController { 13 | constructor(private readonly resourceService: ResourceService) {} 14 | 15 | @Post('/') 16 | @UseInterceptors(FileInterceptor('file')) 17 | async upload(@UploadedFile() file) { 18 | console.log( 19 | `uploading file..`, 20 | file.originalname, 21 | file.mimetype, 22 | file.encoding, 23 | ); 24 | return await this.resourceService.upload({ 25 | file: file.buffer, 26 | encoding: file.encoding, 27 | name: file.originalname ? file.originalname : '', 28 | mimeType: file.mimetype ? file.mimetype : '*/*', 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /hosting-service/src/resource/resource.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { ResourceService } from './resource.service'; 4 | import { ResourceController } from './resource.controller'; 5 | 6 | @Module({ 7 | controllers: [ResourceController], 8 | providers: [ResourceService], 9 | }) 10 | export class ResourceModule {} 11 | -------------------------------------------------------------------------------- /hosting-service/src/resource/resource.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { nanoid } from 'nanoid'; 3 | import S3 from 'aws-sdk/clients/s3'; 4 | 5 | import { FileHostingResult } from '@bridged.xyz/client-sdk/dist/hosting/types'; 6 | 7 | const REGION = 'us-west-1'; 8 | const s3 = new S3({ 9 | region: REGION, 10 | }); 11 | 12 | const FILE_HOSTING_BUKET = 'resource-hosting'; 13 | const SITE_HOSTING_BUKET = 'site-hosting'; 14 | 15 | function buildS3ResourceUrl(props: { 16 | region: string; 17 | buket: string; 18 | key: string; 19 | }) { 20 | // e.g. - https://seamus.s3.eu-west-1.amazonaws.com/dogs/setter.png 21 | return `https://${props.buket}.s3.${props.region}.amazonaws.com/${props.key}`; 22 | } 23 | 24 | @Injectable() 25 | export class ResourceService { 26 | async upload(args: { 27 | file: Buffer; 28 | mimeType: string; 29 | encoding: string; 30 | name: string; 31 | }): Promise { 32 | const { file, mimeType, encoding, name } = args; 33 | // File name you want to save as in S3 34 | const key = `${nanoid(8)}-${name}`; 35 | // Setting up S3 upload parameters 36 | 37 | const params: S3.Types.PutObjectRequest = { 38 | Bucket: FILE_HOSTING_BUKET, 39 | Key: key, 40 | ACL: 'public-read', 41 | Body: file, 42 | }; 43 | 44 | // Uploading files to the bucket 45 | await s3.upload(params).promise(); 46 | const url = buildS3ResourceUrl({ 47 | buket: FILE_HOSTING_BUKET, 48 | region: REGION, 49 | key: key, 50 | }); 51 | 52 | // this is a signed url, which is valid for short time. 53 | // var url = s3.getSignedUrl('getObject', { 54 | // Bucket: FILE_HOSTING_BUKET, 55 | // Key: key, 56 | // }); 57 | 58 | return { 59 | url: url, 60 | key: key, 61 | }; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /hosting-service/src/site/site.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({}) 4 | export class SiteModule {} 5 | -------------------------------------------------------------------------------- /hosting-service/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import request from 'supertest'; 4 | import { AppModule } from '../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /hosting-service/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /hosting-service/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /hosting-service/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "es6", 13 | "sourceMap": true, 14 | "outDir": "./dist", 15 | "baseUrl": "./", 16 | "typeRoots": ["./node_modules/@types"] 17 | }, 18 | "exclude": ["node_modules"] 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bridged.xyz/services", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "setup": "git submodule update --init --recursive", 7 | "editor": "yarn workspace editor run dev", 8 | "_editor": "yarn editor" 9 | }, 10 | "workspaces": [ 11 | "_editor", 12 | "packages/*" 13 | ] 14 | } -------------------------------------------------------------------------------- /packages/api-key-auth-service/README.md: -------------------------------------------------------------------------------- 1 | # Api authentication with static app api key. 2 | -------------------------------------------------------------------------------- /packages/api-key-auth-service/index.ts: -------------------------------------------------------------------------------- 1 | import * as jwt from "jsonwebtoken"; 2 | import { secret } from "./secret"; 3 | 4 | interface AppApiKeyPayload { 5 | appid: string; 6 | } 7 | 8 | /** EG 9 | * const firebaseConfig = { 10 | projectId: "bridged-xyz", 11 | appId: "1:950487613542:web:75d26831172c4c326beda7", 12 | apiKey: "AIzaS00-9f25zcSD9bkw", 13 | storageBucket: "bridged-xyz.appspot.com", 14 | messagingSenderId: "950487613542", 15 | databaseURL: "https://bridged-xyz.firebaseio.com", 16 | measurementId: "G-M2EY2WB66R" 17 | authDomain: "bridged-xyz.firebaseapp.com", 18 | }; 19 | */ 20 | 21 | /** 22 | * issue new app's access token 23 | */ 24 | 25 | function verify(token: string): boolean { 26 | try { 27 | const decoded = jwt.verify(token, secret); 28 | } catch (_) { 29 | // invalid token 30 | return false; 31 | } 32 | return true; 33 | } 34 | -------------------------------------------------------------------------------- /packages/api-key-auth-service/issue/README.md: -------------------------------------------------------------------------------- 1 | # Issue new api key for authorized app -------------------------------------------------------------------------------- /packages/api-key-auth-service/issue/index.ts: -------------------------------------------------------------------------------- 1 | import * as jwt from "jsonwebtoken"; 2 | import { secret } from "../secret"; 3 | 4 | function issue() { 5 | const payload = {}; 6 | 7 | jwt.sign(payload, secret); 8 | } 9 | -------------------------------------------------------------------------------- /packages/api-key-auth-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-key-auth-service", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "jsonwebtoken": "^8.5.1" 6 | }, 7 | "devDependencies": { 8 | "@types/jsonwebtoken": "^8.5.2" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/api-key-auth-service/secret.ts: -------------------------------------------------------------------------------- 1 | export const secret = "--add-me--"; 2 | -------------------------------------------------------------------------------- /packages/api-usage-logging-service/index.ts: -------------------------------------------------------------------------------- 1 | interface ProxyApiRequestLog { 2 | /** 3 | * ip address of request client (could be server / app / web) 4 | */ 5 | ip: string; 6 | /** 7 | * compressed / raw user agent data of the request 8 | */ 9 | ua: string; 10 | 11 | /** 12 | * unique id of the request 13 | */ 14 | id: string; 15 | } 16 | 17 | interface ProxyApiUsageLog { 18 | /** 19 | * billed duration in ms 20 | */ 21 | dur: number; 22 | 23 | /** 24 | * data payload 25 | */ 26 | load: number; 27 | } 28 | 29 | function log() {} 30 | -------------------------------------------------------------------------------- /packages/api-usage-logging-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-usage-logging-service", 3 | "version": "0.0.0" 4 | } -------------------------------------------------------------------------------- /packages/services-sdk/development/index.ts: -------------------------------------------------------------------------------- 1 | export * from './test-import'; 2 | -------------------------------------------------------------------------------- /packages/services-sdk/development/test-import.ts: -------------------------------------------------------------------------------- 1 | export function addAB(a: number, b: number): number { 2 | return a + b; 3 | } 4 | -------------------------------------------------------------------------------- /packages/services-sdk/index.ts: -------------------------------------------------------------------------------- 1 | export * from './middlewares'; 2 | -------------------------------------------------------------------------------- /packages/services-sdk/middlewares/index.ts: -------------------------------------------------------------------------------- 1 | export * from './project.middleware'; 2 | -------------------------------------------------------------------------------- /packages/services-sdk/middlewares/project.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NestMiddleware } from '@nestjs/common'; 2 | import { Request, Response } from 'express'; 3 | 4 | @Injectable() 5 | export class ProjectMiddleware implements NestMiddleware { 6 | use(req: Request, res: Response, next: Function) { 7 | const projectId = req.headers['x-project-id']; 8 | next(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/services-sdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "services-sdk", 3 | "description": "internal server-shared typings definitions for bridged msa infrastructure", 4 | "version": "0.0.0", 5 | "main": "dist/index.js" 6 | } 7 | -------------------------------------------------------------------------------- /packages/services-sdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "es6", 13 | "sourceMap": true, 14 | "outDir": "./dist", 15 | "baseUrl": "./" 16 | }, 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /page-service/.env.defaults: -------------------------------------------------------------------------------- 1 | PAGE_HOST=https://pages.grida.co -------------------------------------------------------------------------------- /page-service/.gitignore: -------------------------------------------------------------------------------- 1 | .build -------------------------------------------------------------------------------- /page-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "page-service", 3 | "description": "simple page hosting service", 4 | "version": "0.0.0", 5 | "scripts": { 6 | "clean": "rimraf .build", 7 | "deploy:prod": "sls deploy --stage production", 8 | "dev": "sls offline start" 9 | }, 10 | "dependencies": { 11 | "@aws-sdk/client-s3": "^3.79.0", 12 | "@vendia/serverless-express": "^4.8.0", 13 | "cors": "^2.8.5", 14 | "express": "^4.18.0", 15 | "express-useragent": "^1.0.15", 16 | "moment": "^2.29.3", 17 | "multer": "^1.4.4", 18 | "nanoid": "^3.3.3", 19 | "serverless-http": "^3.0.1" 20 | }, 21 | "devDependencies": { 22 | "@types/aws-lambda": "^8.10.95", 23 | "@types/cors": "^2.8.12", 24 | "@types/express": "^4.17.13", 25 | "@types/multer": "^1.4.7", 26 | "@types/node": "^17.0.29", 27 | "serverless": "^3.15.2", 28 | "serverless-domain-manager": "^6.0.3", 29 | "serverless-offline": "8.5.0", 30 | "serverless-plugin-monorepo": "^0.11.0", 31 | "serverless-plugin-optimize": "^4.2.1-rc.1", 32 | "serverless-plugin-typescript": "^2.1.2", 33 | "typescript": "^4.6.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /page-service/serverless.yml: -------------------------------------------------------------------------------- 1 | service: page-hosting 2 | 3 | plugins: 4 | - serverless-plugin-monorepo 5 | - serverless-plugin-typescript 6 | - serverless-plugin-optimize 7 | - serverless-offline 8 | - serverless-domain-manager 9 | 10 | custom: 11 | customDomain: 12 | domainName: pages.grida.cc 13 | certificateName: pages.grida.cc 14 | basePath: "" 15 | createRoute53Record: true 16 | stage: production 17 | serverless-offline: 18 | httpPort: 4014 19 | noPrependStageInUrl: true 20 | 21 | provider: 22 | name: aws 23 | runtime: nodejs12.x 24 | region: us-west-1 25 | apiGateway: 26 | # https://stackoverflow.com/questions/61003311/serverless-i-image-upload-to-s3-broken-after-deploy-local-worked-only/61003498#61003498 27 | binaryMediaTypes: 28 | - "*/*" 29 | iamRoleStatements: 30 | - Effect: Allow 31 | Action: 32 | - s3:PutObject 33 | - s3:GetObject 34 | - s3:PutObjectAcl 35 | Resource: "arn:aws:s3:::page-hosting/*" 36 | 37 | resource: 38 | s3bucket: 39 | Type: S3::Bucket 40 | Properties: 41 | BucketName: page-hosting 42 | 43 | package: 44 | individually: true 45 | 46 | functions: 47 | main: 48 | handler: src/index.handler 49 | events: 50 | - http: 51 | method: any 52 | path: /{proxy+} 53 | cors: 54 | origin: "*" 55 | -------------------------------------------------------------------------------- /page-service/src/app.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import * as useragent from "express-useragent"; 3 | import * as cors from "cors"; 4 | import router from "./routes"; 5 | 6 | const app = express(); 7 | 8 | app.use(cors()); 9 | 10 | app.use(useragent.express()); 11 | 12 | app.get("/", (req, res) => { 13 | res.redirect("https://grida.co"); 14 | }); 15 | 16 | app.use(router); 17 | 18 | export { app }; 19 | -------------------------------------------------------------------------------- /page-service/src/core/index.ts: -------------------------------------------------------------------------------- 1 | import * as AWS from "@aws-sdk/client-s3"; 2 | import { PutObjectCommand } from "@aws-sdk/client-s3"; 3 | 4 | const PAGE_HOSTING_BUKET = "page-hosting"; 5 | const client = new AWS.S3({}); 6 | 7 | export async function upload( 8 | path: string, 9 | file: { body: string | Buffer; mimetype?: string; encoding?: string }, 10 | key: string 11 | ) { 12 | const command = new PutObjectCommand({ 13 | Bucket: PAGE_HOSTING_BUKET, 14 | Key: path + "/" + key, 15 | Body: file.body, 16 | ContentEncoding: file.encoding, 17 | ContentType: file.mimetype, 18 | ACL: "public-read", 19 | }); 20 | return await client.send(command); 21 | } 22 | 23 | export async function uploadAssets({ 24 | path, 25 | file, 26 | key, 27 | }: { 28 | path: string; 29 | file: { body: Buffer; mimetype?: string }; 30 | key: string; 31 | }) { 32 | // this won't work locally. with sls offline. it's a serverless' issue 33 | return await upload(path, { body: file.body, mimetype: file.mimetype }, key); 34 | } 35 | 36 | export async function uploadDocument({ 37 | path, 38 | id, 39 | document, 40 | }: { 41 | path: string; 42 | id: string; 43 | document: string; 44 | }) { 45 | return await upload(path, { body: document, mimetype: "text/html" }, id); 46 | } 47 | 48 | export function buildPath(key: string, service: string) { 49 | switch (service) { 50 | case "figma": 51 | return `f-${key}`; 52 | case "sketch": 53 | return `s-${key}`; 54 | case "invision": 55 | return `i-${key}`; 56 | case "xd": 57 | return `xd-${key}`; 58 | default: 59 | throw "Unknown service"; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /page-service/src/index.ts: -------------------------------------------------------------------------------- 1 | import { configure as serverlessExpress } from "@vendia/serverless-express"; 2 | import { APIGatewayProxyHandler } from "aws-lambda"; 3 | 4 | import { app } from "./app"; 5 | 6 | export const handler: APIGatewayProxyHandler = serverlessExpress({ app }); 7 | -------------------------------------------------------------------------------- /page-service/src/routes/figma/index.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import * as multer from "multer"; 3 | import { uploadAssets, uploadDocument, buildPath } from "../../core"; 4 | import type { PagePutRequest } from "../../types"; 5 | const router = express.Router(); 6 | 7 | const PAGE_HOST = process.env.PAGE_HOST ?? "https://pages.grida.co"; 8 | 9 | const m = multer({ 10 | storage: multer.memoryStorage(), 11 | }); 12 | 13 | router.put(`/:filekey`, m.array("assets"), async (req, res) => { 14 | const { filekey } = req.params; 15 | const { id, document } = req.body as PagePutRequest; 16 | const assets = req.files; 17 | 18 | const path = buildPath(filekey, "figma"); 19 | 20 | const uploads: Array> = []; 21 | 22 | // html document file (without .html extension) 23 | uploads.push(uploadDocument({ path, document, id })); 24 | 25 | if (assets && Array.isArray(assets)) { 26 | assets.forEach((asset) => { 27 | const { buffer, originalname, mimetype } = asset; 28 | uploads.push( 29 | uploadAssets({ 30 | path, 31 | file: { body: buffer, mimetype: mimetype ?? "image/png" }, 32 | key: originalname, 33 | }) 34 | ); 35 | }); 36 | } 37 | 38 | try { 39 | await Promise.all(uploads); 40 | res.json({ 41 | status: "ok", 42 | page_path: path + "/" + id, 43 | page_id: id, 44 | page_url: `${PAGE_HOST}/${path}/${id}`, 45 | }); 46 | } catch (e) { 47 | res.status(500).json({ 48 | status: "error", 49 | message: e.message, 50 | }); 51 | } 52 | }); 53 | 54 | export default router; 55 | -------------------------------------------------------------------------------- /page-service/src/routes/index.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import router_figma from "./figma"; 3 | 4 | const router = express.Router(); 5 | 6 | router.use("/figma", router_figma); 7 | 8 | export default router; 9 | -------------------------------------------------------------------------------- /page-service/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface PagePutRequest { 2 | id: string; 3 | title: string; 4 | document: string; 5 | /** 6 | * this part will be from multer 7 | */ 8 | assets?: { [key: string]: any }; 9 | } 10 | -------------------------------------------------------------------------------- /payment-service/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | .build 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json -------------------------------------------------------------------------------- /payment-service/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /payment-service/README.md: -------------------------------------------------------------------------------- 1 | # Payment 2 | 3 | 4 | 5 | > Base on Stripe -------------------------------------------------------------------------------- /payment-service/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /payment-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "payment-service", 3 | "version": "0.0.0", 4 | "description": "", 5 | "author": "bridged.xyz authors", 6 | "private": false, 7 | "license": "MIT", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "sls offline start", 13 | "start:dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/main", 16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "test:e2e": "jest --config ./test/jest-e2e.json" 22 | }, 23 | "dependencies": { 24 | "@bridged.xyz/client-sdk": "0.0.1-14", 25 | "@nestjs/common": "^7.0.0", 26 | "@nestjs/core": "^7.0.0", 27 | "@nestjs/platform-express": "^7.0.0", 28 | "aws-lambda": "^1.0.6", 29 | "aws-sdk": "^2.783.0", 30 | "aws-serverless-express": "^3.3.8", 31 | "dynamoose": "^2.4.1", 32 | "nanoid": "^3.1.16", 33 | "reflect-metadata": "^0.1.13", 34 | "rimraf": "^3.0.2", 35 | "rxjs": "^6.5.4" 36 | }, 37 | "devDependencies": { 38 | "@hewmen/serverless-plugin-typescript": "^1.1.17", 39 | "@nestjs/cli": "^7.0.0", 40 | "@nestjs/schematics": "^7.0.0", 41 | "@nestjs/testing": "^7.0.0", 42 | "@types/aws-lambda": "^8.10.64", 43 | "@types/express": "^4.17.3", 44 | "@types/jest": "26.0.10", 45 | "@types/node": "^13.9.1", 46 | "@types/supertest": "^2.0.8", 47 | "@typescript-eslint/eslint-plugin": "3.9.1", 48 | "@typescript-eslint/parser": "3.9.1", 49 | "eslint": "7.7.0", 50 | "eslint-config-prettier": "^6.10.0", 51 | "eslint-plugin-import": "^2.20.1", 52 | "jest": "26.4.2", 53 | "plugin": "^0.3.3", 54 | "prettier": "^1.19.1", 55 | "serverless-bundle": "^4.1.0", 56 | "serverless-domain-manager": "^5.1.0", 57 | "serverless-dynamodb-local": "^0.2.39", 58 | "serverless-offline": "^6.8.0", 59 | "serverless-plugin-optimize": "^4.1.4-rc.1", 60 | "supertest": "^4.0.2", 61 | "ts-jest": "26.2.0", 62 | "ts-loader": "^6.2.1", 63 | "ts-node": "9.0.0", 64 | "tsconfig-paths": "^3.9.0", 65 | "typescript": "^4.1.2" 66 | }, 67 | "jest": { 68 | "moduleFileExtensions": [ 69 | "js", 70 | "json", 71 | "ts" 72 | ], 73 | "rootDir": "src", 74 | "testRegex": ".spec.ts$", 75 | "transform": { 76 | "^.+\\.(t|j)s$": "ts-jest" 77 | }, 78 | "coverageDirectory": "../coverage", 79 | "testEnvironment": "node" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /payment-service/serverless.yml: -------------------------------------------------------------------------------- 1 | service: 2 | name: payment 3 | 4 | plugins: 5 | - '@hewmen/serverless-plugin-typescript' 6 | - serverless-plugin-optimize 7 | - serverless-offline 8 | - serverless-dynamodb-local 9 | - serverless-domain-manager 10 | 11 | custom: 12 | customDomain: 13 | domainName: payment.bridged.cc 14 | hostedZoneId: us-west-1 15 | basePath: '' 16 | stage: ${self:provider.stage} 17 | createRoute53Record: true 18 | serverless-offline: 19 | httpPort: 4001 20 | 21 | resources: 22 | Resources: 23 | rawAssetsTable: 24 | Type: AWS::DynamoDB::Table 25 | Properties: 26 | TableName: "${self:provider.environment.DYNAMODB_TABLE_SUBSCRIPTION}" 27 | KeySchema: 28 | - AttributeName: id 29 | KeyType: HASH 30 | AttributeDefinitions: 31 | - AttributeName: id 32 | AttributeType: S 33 | ProvisionedThroughput: 34 | ReadCapacityUnits: 1 35 | WriteCapacityUnits: 1 36 | variantAssetsTable: 37 | Type: AWS::DynamoDB::Table 38 | Properties: 39 | TableName: "${self:provider.environment.DYNAMODB_TABLE_PAYMENT}" 40 | KeySchema: 41 | - AttributeName: id 42 | KeyType: HASH 43 | AttributeDefinitions: 44 | - AttributeName: id 45 | AttributeType: S 46 | ProvisionedThroughput: 47 | ReadCapacityUnits: 1 48 | WriteCapacityUnits: 1 49 | 50 | 51 | provider: 52 | name: aws 53 | runtime: nodejs12.x 54 | region: us-west-1 55 | apiGateway: 56 | minimumCompressionSize: 1024 57 | environment: 58 | AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1' 59 | DYNAMODB_TABLE_RAW_ASSETS: "${self:service}-raw-assets-${opt:stage, self:provider.stage}" 60 | DYNAMODB_TABLE_VARIANT_ASSETS: "${self:service}-variant-assets-${opt:stage, self:provider.stage}" 61 | iamRoleStatements: 62 | - Effect: Allow 63 | Action: 64 | - dynamodb:Query 65 | - dynamodb:Scan 66 | - dynamodb:GetItem 67 | - dynamodb:PutItem 68 | - dynamodb:UpdateItem 69 | - dynamodb:DeleteItem 70 | - dynamodb:DescribeTable 71 | Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE_SUBSCRIPTION}" 72 | - Effect: Allow 73 | Action: 74 | - dynamodb:Query 75 | - dynamodb:Scan 76 | - dynamodb:GetItem 77 | - dynamodb:PutItem 78 | - dynamodb:UpdateItem 79 | - dynamodb:DeleteItem 80 | - dynamodb:DescribeTable 81 | Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE_PAYMENT}" 82 | 83 | 84 | package: 85 | individually: true 86 | 87 | functions: 88 | main: 89 | handler: src/lambda.handler 90 | events: 91 | - http: 92 | method: any 93 | path: /{proxy+} -------------------------------------------------------------------------------- /payment-service/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, HttpCode, Param, Patch, Post, Put, Query, Redirect, Req } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) { } 7 | 8 | @Get('/') 9 | async getHello() { 10 | return this.appService.getHello() 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /payment-service/src/app.entity.ts: -------------------------------------------------------------------------------- 1 | import * as dynamoose from 'dynamoose'; 2 | import { nanoid } from 'nanoid'; 3 | 4 | export interface SubcriptionTable { 5 | /** 6 | * id generated by the server 7 | */ 8 | id: string; 9 | 10 | /** 11 | * *REQUIRED* 12 | * sell whether the product is available for purchase 13 | */ 14 | activation: boolean; 15 | 16 | /** 17 | * *REQUIRED* 18 | * an amount payable once a year. 19 | */ 20 | yearlyPrice: number; 21 | 22 | /** 23 | * *REQUIRED* 24 | * an amount payable once a month. 25 | */ 26 | monthlyPrice: number; 27 | 28 | /** 29 | * *REQUIRED* 30 | * The name of the product you purchased. 31 | */ 32 | planName: number; 33 | 34 | /** 35 | * *REQUIRED* 36 | * Unique value(id) generated by stripe when a new product is released. 37 | */ 38 | stripeId: string; 39 | } 40 | 41 | export interface PaymentTable { 42 | /** 43 | * id generated by the server 44 | */ 45 | id: string; 46 | 47 | /** 48 | * *REQUIRED* 49 | * Unique value of the product. 50 | * The id value that is automatically generated when new. 51 | */ 52 | subscriptionId: boolean; 53 | 54 | /** 55 | * *REQUIRED* 56 | * Account's unique ID value 57 | * The id value that is automatically generated when new. ( Account Service ) 58 | */ 59 | accountId: number; 60 | 61 | /** 62 | * *REQUIRED* 63 | * Invoice ID generated automatically by Stripe 64 | * ( The value that occurs when a payment is attempted, regardless of whether the payment was successful or not. ) 65 | */ 66 | invoiceId: number; 67 | } 68 | 69 | const SubcriptionSchema = new dynamoose.Schema({ 70 | id: { 71 | type: String, 72 | default: () => nanoid(), 73 | }, 74 | activation: { 75 | type: Boolean, 76 | required: true, 77 | }, 78 | yearlyPrice: { 79 | type: Number, 80 | required: true, 81 | }, 82 | monthlyPrice: { 83 | type: Number, 84 | required: true, 85 | }, 86 | planName: { 87 | type: String, 88 | required: true, 89 | }, 90 | stripeId: { 91 | type: String, 92 | required: true, 93 | }, 94 | }); 95 | 96 | const TBL_SUBSCRIPTION = process.env.DYNAMODB_TABLE_SUBSCRIPTION; 97 | export const SuscriptionModel = dynamoose.model( 98 | TBL_SUBSCRIPTION, 99 | SubcriptionSchema, 100 | ); 101 | 102 | const PaymentSchema = new dynamoose.Schema({ 103 | id: { 104 | type: String, 105 | default: () => nanoid(), 106 | }, 107 | subscriptionId: { 108 | type: String, 109 | required: true, 110 | }, 111 | accountId: { 112 | type: String, 113 | required: true, 114 | }, 115 | invoiceId: { 116 | type: String, 117 | required: true, 118 | }, 119 | }); 120 | 121 | const TBL_PAYMENT = process.env.DYNAMODB_TABLE_PAYMENT; 122 | export const PaymentModel = dynamoose.model(TBL_PAYMENT, PaymentSchema); 123 | -------------------------------------------------------------------------------- /payment-service/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | import { SubcriptionModule } from './subcription/subcription.module'; 6 | import { PaymentModule } from './payment/payment.module'; 7 | @Module({ 8 | imports: [SubcriptionModule, PaymentModule], 9 | controllers: [AppController], 10 | providers: [AppService], 11 | }) 12 | export class AppModule { } 13 | -------------------------------------------------------------------------------- /payment-service/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Welcome to bridged payment service. Learn more at https://github.com/bridgedxyz/services/'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /payment-service/src/lambda.ts: -------------------------------------------------------------------------------- 1 | // lambda.ts 2 | import { Handler, Context } from 'aws-lambda'; 3 | import { Server } from 'http'; 4 | import { createServer, proxy } from 'aws-serverless-express'; 5 | import { eventContext } from 'aws-serverless-express/middleware'; 6 | 7 | import { NestFactory } from '@nestjs/core'; 8 | import { ExpressAdapter } from '@nestjs/platform-express'; 9 | import { AppModule } from './app.module'; 10 | 11 | /* eslint-disable */ 12 | const express = require('express'); 13 | 14 | // NOTE: If you get ERR_CONTENT_DECODING_FAILED in your browser, this 15 | // is likely due to a compressed response (e.g. gzip) which has not 16 | // been handled correctly by aws-serverless-express and/or API 17 | // Gateway. Add the necessary MIME types to binaryMimeTypes below 18 | const binaryMimeTypes: string[] = []; 19 | 20 | let cachedServer: Server; 21 | 22 | // Create the Nest.js server and convert it into an Express.js server 23 | async function bootstrapServer(): Promise { 24 | if (!cachedServer) { 25 | const expressApp = express(); 26 | const nestApp = await NestFactory.create( 27 | AppModule, 28 | new ExpressAdapter(expressApp), 29 | ); 30 | nestApp.use(eventContext()); 31 | nestApp.enableCors({ 32 | origin: true, 33 | }); 34 | await nestApp.init(); 35 | cachedServer = createServer(expressApp, undefined, binaryMimeTypes); 36 | } 37 | return cachedServer; 38 | } 39 | 40 | // Export the handler : the entry point of the Lambda function 41 | export const handler: Handler = async (event: any, context: Context) => { 42 | cachedServer = await bootstrapServer(); 43 | return proxy(cachedServer, event, context, 'PROMISE').promise; 44 | }; 45 | -------------------------------------------------------------------------------- /payment-service/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | 4 | async function bootstrap() { 5 | const app = await NestFactory.create(AppModule); 6 | app.listen(3000, () => console.log('Microservice is listening')); 7 | } 8 | bootstrap(); 9 | -------------------------------------------------------------------------------- /payment-service/src/payment/payment.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | forwardRef, 5 | Get, 6 | Inject, 7 | Param, 8 | Patch, 9 | Post, 10 | Put, 11 | } from '@nestjs/common'; 12 | 13 | import { PaymentService } from './payment.service'; 14 | 15 | @Controller("payment") 16 | export class PaymentController { 17 | constructor( 18 | @Inject(forwardRef(() => PaymentService)) 19 | private readonly paymentService: PaymentService 20 | ) { } 21 | 22 | 23 | } -------------------------------------------------------------------------------- /payment-service/src/payment/payment.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { PaymentController } from './payment.controller'; 4 | import { PaymentService } from './payment.service'; 5 | 6 | @Module({ 7 | imports: [PaymentService], 8 | controllers: [PaymentController], 9 | providers: [PaymentService] 10 | }) 11 | export class PaymentModule { } -------------------------------------------------------------------------------- /payment-service/src/payment/payment.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { nanoid } from 'nanoid'; 3 | 4 | @Injectable() 5 | export class PaymentService { } -------------------------------------------------------------------------------- /payment-service/src/subcription/subcription.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | forwardRef, 5 | Get, 6 | Inject, 7 | Param, 8 | Patch, 9 | Post, 10 | Put, 11 | } from '@nestjs/common'; 12 | 13 | import { SubcriptionService } from './subcription.service'; 14 | 15 | @Controller("subcription") 16 | export class SubcriptionController { 17 | constructor( 18 | @Inject(forwardRef(() => SubcriptionService)) 19 | private readonly subcriptionService: SubcriptionService 20 | ) { } 21 | 22 | 23 | } -------------------------------------------------------------------------------- /payment-service/src/subcription/subcription.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { SubcriptionController } from './subcription.controller'; 4 | import { SubcriptionService } from './subcription.service'; 5 | 6 | @Module({ 7 | imports: [SubcriptionService], 8 | controllers: [SubcriptionController], 9 | providers: [SubcriptionService] 10 | }) 11 | export class SubcriptionModule {} -------------------------------------------------------------------------------- /payment-service/src/subcription/subcription.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { nanoid } from 'nanoid'; 3 | 4 | @Injectable() 5 | export class SubcriptionService {} -------------------------------------------------------------------------------- /payment-service/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /payment-service/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "moduleResolution": "node", 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "target": "es6", 14 | "sourceMap": true, 15 | "outDir": "./dist", 16 | "baseUrl": "./", 17 | "typeRoots": [ 18 | "./node_modeuls/@types" 19 | ] 20 | }, 21 | "exclude": [ 22 | "node_modules" 23 | ] 24 | } -------------------------------------------------------------------------------- /posts-service/.env.defaults: -------------------------------------------------------------------------------- 1 | # replace this with your own mongodb connection str 2 | # See the documentation for all the connection string options: https://pris.ly/d/connection-strings 3 | DATABASE_URL="" 4 | 5 | # replace this with your own bucket. 6 | S3_URL="https://cms-posts.s3.us-west-1.amazonaws.com" -------------------------------------------------------------------------------- /posts-service/.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | 3 | # we use yarn. 4 | package-lock.json -------------------------------------------------------------------------------- /posts-service/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributors docs 2 | 3 | 4 | ## Architecture 5 | 6 | 7 | ## Database 8 | 9 | MongoDB 10 | 11 | ## Search 12 | 13 | - MongoDB query (for post service) 14 | - MongoDB full text search (for post service) 15 | - Elasticsearch (for external service, optional) 16 | 17 | 18 | ## API Routes 19 | 20 | > base url: `https://posts.grida.cc/:publication/` 21 | 22 | - `GET` / 23 | - get all published post (as summary) 24 | - `GET` /:id 25 | - get post details 26 | - `GET` /:id/summary 27 | - get post summary 28 | - `POST` /drafts 29 | - create new draft 30 | - `DELETE` /drafts/:id 31 | - remove draft 32 | - `GET` /drafts 33 | - list drafts 34 | - `POST` /:id/publish 35 | - publish draft 36 | - `POST` /:id/schedule 37 | - set schedule 38 | - update schedule 39 | - remove schedule 40 | - `POST` /:id/unlist 41 | - unlist (remove post) 42 | - `GET` /unlisted 43 | - `GET` /scheduled 44 | - `PUT` /:id 45 | - `PUT` /:id/body 46 | - save the edits 47 | - `PUT` /:id/tags 48 | - `GET` /:id/tags 49 | - `PUT` /:id/category 50 | - tags 51 | - `POST` /tags 52 | - `GET` /tags/:tag 53 | - assets 54 | - `POST` /:id/assets 55 | - upload assets 56 | - `POST` /:id/assets/link 57 | - register linked assets 58 | - `DELETE` /:id/assets/link/:link 59 | - `DELETE` /:id/assets/:asset 60 | - `GET` /:id/assets 61 | - list assets under post 62 | - publications 63 | - `GET` /publications 64 | - `GET` /publications/:publication 65 | - `POST` /publications 66 | - `DELETE` /publications/:publication 67 | - archive publication 68 | - search 69 | - wip 70 | 71 | ## Models 72 | 73 | ```ts 74 | interface Post { 75 | id: string; 76 | title: string; 77 | summary: string; 78 | thumbnail?: string; 79 | body: PostBody; 80 | cover?: string 81 | tags: TagLike[] 82 | isDraft: boolean 83 | category: CategoryLike 84 | slug: string 85 | visibility?: PostVisibility 86 | scheduledAt?: Date 87 | createdAt: Date 88 | postedAt?: Date 89 | locale?: String 90 | author?: AuthorLike 91 | } 92 | 93 | type Tag = { 94 | id: string 95 | name: string 96 | } 97 | 98 | type Category = { 99 | id: string 100 | name: string 101 | } 102 | 103 | type TagLike = Tag | string 104 | type CategoryLike = '\\default\\' | Category | string 105 | type AuthorLike = { 106 | id: string 107 | } 108 | 109 | interface PostBody{ 110 | 111 | } 112 | 113 | enum PostVisibility { 114 | public = 'public', 115 | private = 'private', 116 | password = 'password_protected' 117 | } 118 | ``` 119 | 120 | 121 | ## DTOs 122 | 123 | ```ts 124 | interface PostSummary { 125 | id: string 126 | title: string 127 | summary: string 128 | thumbnail?: string 129 | tags?: TagLike[] 130 | isDraft: boolean 131 | category: CategoryLike 132 | createdAt: Date 133 | postedAt?: Date 134 | locale?: String 135 | author?: AuthorLike 136 | } 137 | ``` 138 | 139 | 140 | 141 | ## Trouble shooting 142 | 143 | - hit payload size for assets uploading? - https://theburningmonk.com/2020/04/hit-the-6mb-lambda-payload-limit-heres-what-you-can-do/ -------------------------------------------------------------------------------- /posts-service/README.md: -------------------------------------------------------------------------------- 1 | # Posts service 2 | 3 | > posts.grida.cc 4 | 5 | ## Assets 6 | 7 | the assets linked to a post is hosted on s3 bucket - posts-assets 8 | 9 | - the assets are public by default. 10 | - the assets can be private for private posts. 11 | - the assets are public for public draft or scheduled posts. 12 | 13 | ### Object pattern 14 | 15 | ```txt 16 | - https://posts-assets.s3.amazonaws.com/{workspace-id}/{post-id}/{asset-name} 17 | - https://posts-assets.s3.amazonaws.com/{workspace-id}/assets/{asset-name} 18 | - https://posts-assets.s3.amazonaws.com/default/{post-id}/{asset-name} 19 | - https://posts-assets.s3.amazonaws.com/tmp/{asset-name} 20 | ``` 21 | 22 | ## Scheduled posts 23 | 24 | 25 | ## DOM (Document Object Model) 26 | 27 | - boring standard 28 | - json 29 | - html 30 | - md 31 | - custom (for custom editors & custom renderers) 32 | 33 | 34 | ## Events 35 | 36 | *internal infra event* 37 | ```json 38 | { 39 | "id": "post-id", 40 | "action": "publish", 41 | "schedule": "2022-12-25T00:00:00Z" 42 | } 43 | ``` 44 | - wip 45 | 46 | ## Integrations (Under development) 47 | 48 | - Slack - on publish 49 | - Slack - on comment 50 | - Slack - on remove 51 | - Github - on publish 52 | - Webhooks 53 | 54 | ## API / SDK 55 | 56 | - `@base-sdk/posts` (node/ts) 57 | - rest api 58 | 59 | 60 | ## Contributors references 61 | - https://developers.notion.com/reference/page 62 | - https://developers.notion.com/reference/block -------------------------------------------------------------------------------- /posts-service/app.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import useragent from "express-useragent"; 3 | import cors from "cors"; 4 | import bodyParser from "body-parser"; 5 | import router from "./routes"; 6 | 7 | function logErrors(err, req, res, next) { 8 | console.error(err.stack); 9 | next(err); 10 | } 11 | 12 | const app = express(); 13 | 14 | app.use(cors()); 15 | 16 | app.use( 17 | bodyParser.urlencoded({ 18 | extended: true, 19 | }) 20 | ); 21 | 22 | app.use(useragent.express()); 23 | 24 | app.use(bodyParser.json()); 25 | 26 | app.use(logErrors); 27 | 28 | app.use(router); 29 | 30 | export { app }; 31 | -------------------------------------------------------------------------------- /posts-service/index.ts: -------------------------------------------------------------------------------- 1 | import { configure as serverlessExpress } from "@vendia/serverless-express"; 2 | import { APIGatewayProxyHandler } from "aws-lambda"; 3 | 4 | import { app } from "./app"; 5 | 6 | export const handler: APIGatewayProxyHandler = serverlessExpress({ app }); 7 | -------------------------------------------------------------------------------- /posts-service/lib/assets.ts: -------------------------------------------------------------------------------- 1 | import * as AWS from "@aws-sdk/client-s3"; 2 | import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; 3 | import { PutObjectCommand } from "@aws-sdk/client-s3"; 4 | const POSTS_CMS_BUKET = "cms-posts"; 5 | const client = new AWS.S3({}); 6 | 7 | export async function upload( 8 | path: string, 9 | file: { body: string | Buffer; mimetype?: string; encoding?: string }, 10 | key?: string 11 | ) { 12 | const command = new PutObjectCommand({ 13 | Bucket: POSTS_CMS_BUKET, 14 | Key: key ? path + "/" + key : path, 15 | Body: file.body, 16 | ContentEncoding: file.encoding, 17 | ContentType: file.mimetype, 18 | ACL: "public-read", 19 | }); 20 | return await client.send(command); 21 | } 22 | 23 | /** 24 | * use this for exporting the post as html file for faster access and downloading. 25 | * @returns 26 | */ 27 | export async function uploadDocument({ 28 | path, 29 | id, 30 | document, 31 | }: { 32 | path: string; 33 | id: string; 34 | document: string; 35 | }) { 36 | return await upload(path, { body: document, mimetype: "text/html" }, id); 37 | } 38 | 39 | export async function makeClient( 40 | abskey: string, 41 | { encoding, mimetype }: { encoding; mimetype } 42 | ) { 43 | const command = new PutObjectCommand({ 44 | Bucket: POSTS_CMS_BUKET, 45 | Key: abskey, 46 | Body: "BODY", 47 | ContentEncoding: encoding, 48 | ContentType: mimetype, 49 | ACL: "public-read", // TODO: follow the post visibility -> but for to be visible on editor, it needs to be public...hmm 50 | }); 51 | 52 | // Create the presigned URL. 53 | const expiresIn = 3600; 54 | const signedUrl = await getSignedUrl(client, command, { 55 | expiresIn: expiresIn, 56 | }); 57 | 58 | return signedUrl; 59 | } 60 | 61 | export async function removeAssets() {} 62 | 63 | export function buildPath(postid: string) { 64 | return `posts/${postid}`; 65 | } 66 | -------------------------------------------------------------------------------- /posts-service/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./assets"; 2 | -------------------------------------------------------------------------------- /posts-service/lib/schedule.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridaco/base/a1e5aa7a851d84873fc548648dc703badafe930d/posts-service/lib/schedule.ts -------------------------------------------------------------------------------- /posts-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "posts-services", 3 | "version": "0.0.0", 4 | "homepage": "https://pages.grida.cc", 5 | "scripts": { 6 | "prisma:studio": "npx prisma studio", 7 | "prisma:generate": "npx prisma generate", 8 | "prisma:generate:watch": "npx prisma generate --watch", 9 | "postinstall": "npm run prisma:generate", 10 | "db:push": "npx prisma db push", 11 | "setup:prod": "echo 'passing'", 12 | "clean": "rimraf .build", 13 | "dev": "sls offline start", 14 | "deploy:staging": "yarn clean && yarn setup:staging && yarn db:push && sls deploy --stage staging", 15 | "deploy:prod": "yarn clean && yarn setup:prod && yarn db:push && sls deploy --stage production" 16 | }, 17 | "dependencies": { 18 | "@aws-sdk/client-s3": "^3.79.0", 19 | "@aws-sdk/s3-request-presigner": "^3.88.0", 20 | "@prisma/client": "3.14.0", 21 | "@vendia/serverless-express": "^4.8.0", 22 | "body-parser": "^1.20.0", 23 | "cors": "^2.8.5", 24 | "express": "^4.18.1", 25 | "express-useragent": "^1.0.15", 26 | "multer": "^1.4.4", 27 | "nanoid": "^3.3.3", 28 | "reading-time": "^1.5.0", 29 | "serverless-http": "^3.0.1" 30 | }, 31 | "devDependencies": { 32 | "@types/aws-lambda": "^8.10.95", 33 | "@types/body-parser": "^1.19.2", 34 | "@types/cors": "^2.8.12", 35 | "@types/express": "^4.17.13", 36 | "@types/multer": "^1.4.7", 37 | "@types/node": "^17.0.29", 38 | "prisma": "^3.14.0", 39 | "serverless": "^3.17.0", 40 | "serverless-domain-manager": "^6.0.3", 41 | "serverless-offline": "^8.7.0", 42 | "serverless-webpack": "^5.7.1", 43 | "serverless-webpack-prisma": "^1.1.0", 44 | "ts-loader": "^9.3.0", 45 | "typescript": "^4.6.4", 46 | "webpack": "^5.72.1", 47 | "webpack-node-externals": "^3.0.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /posts-service/prisma-client/index.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | const prisma = new PrismaClient(); 4 | 5 | export { prisma }; 6 | 7 | export * as selectors from "./selectors"; 8 | -------------------------------------------------------------------------------- /posts-service/prisma-client/selectors.ts: -------------------------------------------------------------------------------- 1 | import { Prisma } from "@prisma/client"; 2 | 3 | export const post_summary_select: Prisma.PostSelect = { 4 | id: true, 5 | title: true, 6 | displayTitle: true, 7 | summary: true, 8 | slug: true, 9 | tags: true, 10 | thumbnail: true, 11 | category: true, 12 | author: true, 13 | readingTime: true, 14 | createdAt: true, 15 | postedAt: true, 16 | isListed: true, 17 | publicationId: true, 18 | }; 19 | -------------------------------------------------------------------------------- /posts-service/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | datasource db { 5 | provider = "mongodb" 6 | url = env("DATABASE_URL") 7 | } 8 | 9 | generator client { 10 | provider = "prisma-client-js" 11 | binaryTargets = ["native", "rhel-openssl-1.0.x"] 12 | } 13 | 14 | model Publication{ 15 | id String @id @default(auto()) @map("_id") @db.ObjectId 16 | workspace String 17 | name String 18 | hosts PublicationHost[] 19 | // public image 20 | logo String? 21 | // public image 22 | cover String? 23 | slug String @unique @default(cuid()) 24 | createdAt DateTime @default(now()) 25 | posts Post[] 26 | // featuredPosts Post[] @relation(fields: [featuredPostIds], references: [id], name: "a") 27 | 28 | // publication slug must be unique per workspace 29 | @@unique([slug, workspace]) 30 | } 31 | 32 | type PublicationHost{ 33 | // optional display name (e.g. blog) 34 | name String? 35 | // e.g https://blog.grida.co/posts 36 | homepage String 37 | // e.g blog.grida.co 38 | // host String 39 | // e.g /posts/:id - https://blog.grida.co/posts/:id 40 | pattern String @default("/:id") 41 | } 42 | 43 | model Post { 44 | id String @id @default(auto()) @map("_id") @db.ObjectId 45 | title String 46 | displayTitle String? 47 | summary String? 48 | thumbnail String? 49 | body PostBody 50 | cover String? 51 | tags String[] 52 | category String? 53 | slug String? @default(cuid()) 54 | visibility Visibility @default(private) 55 | scheduledAt DateTime? 56 | language String? 57 | author String? @db.ObjectId 58 | isDraft Boolean 59 | isListed Boolean @default(false) 60 | draft EditingPost? 61 | assets PostAsset[] 62 | 63 | // reading time estimated on serverside based on https://github.com/ngryman/reading-time 64 | readingTime Int? 65 | createdAt DateTime @default(now()) 66 | lastEditAt DateTime? @default(now()) 67 | postedAt DateTime? 68 | publication Publication @relation(fields: [publicationId], references: [id]) 69 | publicationId String @db.ObjectId 70 | 71 | // slugs are unique in publication 72 | @@unique([slug, publicationId]) 73 | } 74 | 75 | // Editing post is used when post is being updated after it has been published 76 | // once posted / updated, this object will be removed from parent. 77 | type EditingPost{ 78 | title String 79 | displayTitle String 80 | summary String 81 | body PostBody 82 | cover String? 83 | // thumbnail - editing thumbnail will be affected to production immediately 84 | // tags - editing tags will be affected to production immediately 85 | } 86 | 87 | type PostBody{ 88 | html String? 89 | } 90 | 91 | // private comments for editors 92 | type PrivateNote{ 93 | body String 94 | replies String 95 | } 96 | 97 | model PostAsset{ 98 | id String @id @default(auto()) @map("_id") @db.ObjectId 99 | post Post @relation(fields: [postId], references: [id]) 100 | postId String @db.ObjectId 101 | // rather if the asset is removable permanently. - true if the asset is on drafting post. 102 | removalble Boolean @default(true) 103 | createdAt DateTime @default(now()) 104 | // absolute path of the asset 105 | path String @unique 106 | // full url to the asset (s3) 107 | url String @unique 108 | // name of the asset e.g. image-001.png 109 | key String 110 | } 111 | 112 | enum Visibility{ 113 | public 114 | private 115 | password_protected 116 | } -------------------------------------------------------------------------------- /posts-service/routes/assets/README.md: -------------------------------------------------------------------------------- 1 | # Assets for posts 2 | 3 | 4 | **Allowed file types:** 5 | - images 6 | - `image/jpeg` 7 | - `image/png` 8 | - `image/gif` 9 | - `image/svg+xml` 10 | - `image/webp` 11 | - `image/tiff` 12 | - `image/bmp` 13 | - `image/x-icon` 14 | - videos 15 | - `video/mp4` 16 | - `video/webm` 17 | - `video/ogg` 18 | - `video/quicktime` (.mov) 19 | - `video/x-msvideo` 20 | - `video/x-ms-wmv` 21 | - `video/x-flv` 22 | - embedable documents 23 | - `application/pdf` 24 | - files (attatchments) 25 | - `*/*` 26 | 27 | 28 | ## Size limit 29 | 30 | ### 6mb api 31 | 32 | By the payload limit of lambda, we can only send up to 6mb of data. to insert image bigger than 6mb, we need to use a below api. 33 | 34 | ### 6mb+ api (any) 35 | 36 | 1. request presigned url for direct upload from client 37 | 2. upload file to presigned url 38 | 3. notify server to update post (optional) -------------------------------------------------------------------------------- /posts-service/routes/assets/index.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import multer from "multer"; 3 | import assert from "assert"; 4 | import { makeClient, upload, buildPath } from "../../lib"; 5 | import { nanoid } from "nanoid"; 6 | 7 | const S3_URL = process.env.S3_URL; 8 | const router = express.Router(); 9 | 10 | const m = multer({ 11 | storage: multer.memoryStorage(), 12 | }); 13 | 14 | router.post("/:id/upload", m.array("files", 25), async (req, res) => { 15 | const { id: postid } = req.params as { id: string }; 16 | const assets = req.files ?? (req.file && [req.file]); 17 | 18 | const uploads: Array> = []; 19 | const results = {}; 20 | 21 | if (assets && Array.isArray(assets)) { 22 | assets.forEach((asset) => { 23 | const { buffer, originalname, mimetype } = asset; 24 | const ext = originalname.split(".").pop(); 25 | const cuid = nanoid(); 26 | const name = ext ? cuid + "." + ext : cuid; 27 | const path = "posts/" + postid + "/" + name; 28 | const s3path = S3_URL + "/" + path; 29 | uploads.push(upload(path, { body: buffer, mimetype: mimetype })); 30 | results[originalname] = s3path; 31 | }); 32 | } 33 | 34 | try { 35 | await Promise.all(uploads); 36 | // TODO: db injection is not yet supported. 37 | res.json({ 38 | post_id: postid, 39 | assets: results, 40 | }); 41 | } catch (e) { 42 | res.status(500).json({ 43 | status: "error", 44 | message: e.message, 45 | }); 46 | } 47 | }); 48 | 49 | // request the asset uploader client 50 | router.get("/client", async (req, res) => { 51 | const { post } = req.query; // post id to link the asset to. 52 | const { post: _post, mimetype, key, encoding } = req.body; 53 | 54 | const postid = post ?? _post; 55 | assert(postid, '"post" is required'); 56 | 57 | const path = buildPath(postid); 58 | const abskey = path + "/" + key; 59 | 60 | const expiresIn = 3600; 61 | const signedUrl = await makeClient(abskey, { 62 | encoding, 63 | mimetype, 64 | }); 65 | 66 | res.json({ 67 | url: signedUrl, 68 | expires_in: expiresIn, 69 | expires_at: new Date(Date.now() + expiresIn * 1000), 70 | }); 71 | }); 72 | 73 | export default router; 74 | -------------------------------------------------------------------------------- /posts-service/routes/categories/index.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | 3 | const router = express.Router(); 4 | 5 | export default router; 6 | -------------------------------------------------------------------------------- /posts-service/routes/drafts/index.ts: -------------------------------------------------------------------------------- 1 | import { prisma, selectors } from "../../prisma-client"; 2 | import * as express from "express"; 3 | 4 | const router = express.Router(); 5 | 6 | // list all drafts (no pagination, max limit) 7 | router.get("/", async (req, res) => { 8 | // 0. auth guard - publication check 9 | // 1. list all drafts (as summary) 10 | 11 | const { publication } = req.query; 12 | 13 | const posts = await prisma.post.findMany({ 14 | where: { 15 | publication: publication 16 | ? { 17 | id: publication as string, 18 | } 19 | : undefined, 20 | isDraft: true, 21 | visibility: publication ? undefined : "public", 22 | }, 23 | select: selectors.post_summary_select, 24 | }); 25 | 26 | res.json(posts); 27 | }); 28 | 29 | // create new draft 30 | router.post("/", async (req, res) => { 31 | const { title, publication, visibility } = req.body; // as ?? 32 | // 1. create new draft 33 | // 2. return draft id 34 | 35 | const post = await prisma.post.create({ 36 | data: { 37 | publication: { 38 | connect: { 39 | id: publication, 40 | }, 41 | }, 42 | // we can create new draft document optionally with title. 43 | title: title ?? "", 44 | displayTitle: title, 45 | body: {}, 46 | isDraft: true, 47 | isListed: false, 48 | visibility: visibility ?? "private", 49 | author: undefined, // TODO: 50 | }, 51 | }); 52 | 53 | res.status(201).json(post); 54 | }); 55 | 56 | // create new draft 57 | router.delete("/:id", async (req, res) => { 58 | const { id } = req.params; 59 | // 0. auth guard - post permission 60 | // 1. check if draft 61 | // 2. delete draft 62 | // 3. delete associated assets 63 | 64 | const removed = await prisma.post.deleteMany({ 65 | where: { 66 | id: id, 67 | isDraft: true, 68 | }, 69 | }); 70 | 71 | if (removed.count) { 72 | res.status(202).json({}); 73 | } else { 74 | res.status(400).json({ 75 | error: "this post cannot be removed (already published or non existent)", 76 | }); 77 | } 78 | }); 79 | 80 | router.post("/discard", (req, res) => { 81 | // discard draft 82 | }); 83 | export default router; 84 | -------------------------------------------------------------------------------- /posts-service/routes/index.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import router_posts from "./posts"; 3 | import router_drafts from "./drafts"; 4 | import router_assets from "./assets"; 5 | import router_tags from "./tags"; 6 | import router_categories from "./categories"; 7 | import router_publications from "./publications"; 8 | import router_utils from "./utils"; 9 | 10 | const router = express.Router(); 11 | 12 | router.use("/", router_posts); 13 | router.use("/drafts", router_drafts); 14 | router.use("/assets", router_assets); 15 | router.use("/tags", router_tags); 16 | router.use("/categories", router_categories); 17 | router.use("/publications", router_publications); 18 | 19 | // utils 20 | router.use("/utils", router_utils); 21 | 22 | export default router; 23 | -------------------------------------------------------------------------------- /posts-service/routes/publications/index.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import { prisma, selectors } from "../../prisma-client"; 3 | const router = express.Router(); 4 | 5 | router.get("/", async (req, res) => { 6 | // get publications from my workspace 7 | const workspace = ""; // TODO: get my workspace 8 | const publications = await prisma.publication.findMany({ 9 | where: { 10 | // 11 | }, 12 | }); 13 | res.json(publications); 14 | }); 15 | 16 | router.get("/:id", async (req, res) => { 17 | // get publication 18 | const { id } = req.params; 19 | 20 | const publication = await prisma.publication.findUnique({ 21 | where: { 22 | id: id, 23 | }, 24 | }); 25 | 26 | res.json(publication); 27 | }); 28 | 29 | router.get("/:id/posts", async (req, res) => { 30 | // get publication 31 | const { id } = req.params; 32 | 33 | const posts = await prisma.post.findMany({ 34 | where: { 35 | publicationId: id, 36 | }, 37 | select: selectors.post_summary_select, 38 | }); 39 | 40 | res.json(posts); 41 | }); 42 | 43 | router.post("/", async (req, res) => { 44 | const { name, workspace, slug } = req.body; 45 | 46 | // 0. auth user 47 | // 1. validate workspace ownership or authorization 48 | // slug 49 | // - 1. try to use user provided slug 50 | // - 2. try to use workspace slug 51 | // - 3. if both won't work, generate new slug (provide undefined for authgen cuid) 52 | // create new publication 53 | 54 | try { 55 | const publication = await prisma.publication.create({ 56 | data: { 57 | name: name, 58 | workspace: workspace, 59 | slug: slug, 60 | }, 61 | }); 62 | 63 | res.status(201).json(publication); 64 | } catch (e) { 65 | console.error(e, { ...req.body }); 66 | res.status(400).json({ 67 | error: "cannot create new publication", 68 | }); 69 | } 70 | }); 71 | 72 | export default router; 73 | -------------------------------------------------------------------------------- /posts-service/routes/tags/index.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | 3 | const router = express.Router(); 4 | 5 | export default router; 6 | -------------------------------------------------------------------------------- /posts-service/routes/utils/index.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import readingTime from "reading-time"; 3 | 4 | const router = express.Router(); 5 | 6 | router.post("/reading-time", (req, res) => { 7 | const { text, html, md, markdown } = req.body; 8 | const payload = text ?? html ?? md ?? markdown; 9 | const rt = readingTime(payload); 10 | res.json(rt); 11 | }); 12 | 13 | export default router; 14 | -------------------------------------------------------------------------------- /posts-service/serverless.yml: -------------------------------------------------------------------------------- 1 | service: cms-posts 2 | useDotenv: true 3 | plugins: 4 | - serverless-webpack 5 | - serverless-webpack-prisma 6 | - serverless-offline 7 | - serverless-domain-manager 8 | 9 | custom: 10 | customDomain: 11 | domainName: posts.grida.cc 12 | certificateName: "*.grida.cc" 13 | basePath: "" 14 | createRoute53Record: true 15 | stage: production 16 | serverless-offline: 17 | httpPort: 4015 18 | noPrependStageInUrl: true 19 | webpack: 20 | includeModules: true 21 | 22 | provider: 23 | name: aws 24 | environment: 25 | DATABASE_URL: ${env:DATABASE_URL} 26 | S3_URL: ${env:S3_URL} 27 | runtime: nodejs12.x 28 | region: us-west-1 29 | apiGateway: 30 | # https://stackoverflow.com/questions/61003311/serverless-i-image-upload-to-s3-broken-after-deploy-local-worked-only/61003498#61003498 31 | binaryMediaTypes: 32 | - "*/*" 33 | iamRoleStatements: 34 | - Effect: Allow 35 | Action: 36 | - s3:PutObject 37 | - s3:GetObject 38 | - s3:PutObjectAcl 39 | Resource: "arn:aws:s3:::cms-posts/*" 40 | 41 | resource: 42 | s3bucket: 43 | Type: S3::Bucket 44 | Properties: 45 | BucketName: cms-posts 46 | 47 | functions: 48 | main: 49 | handler: index.handler 50 | events: 51 | - http: 52 | method: any 53 | path: /{proxy+} 54 | cors: 55 | origin: "*" 56 | - http: 57 | method: get 58 | path: / 59 | cors: 60 | origin: "*" 61 | 62 | package: 63 | individually: true 64 | -------------------------------------------------------------------------------- /posts-service/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "es6", 13 | "sourceMap": true, 14 | "outDir": "./dist", 15 | "baseUrl": "./", 16 | "typeRoots": ["./node_modules/@types"] 17 | }, 18 | "exclude": ["node_modules"] 19 | } 20 | -------------------------------------------------------------------------------- /posts-service/types/index.ts: -------------------------------------------------------------------------------- 1 | // ================================================================== 2 | // id 3 | // ================================================================== 4 | 5 | type PostId = string; 6 | type PublicationId = string; 7 | type TagId = string; 8 | type CategoryId = string; 9 | type AuthorId = string; 10 | 11 | // ================================================================== 12 | // models 13 | // ================================================================== 14 | 15 | export interface Post { 16 | id: string; 17 | title: string; 18 | summary: string; 19 | thumbnail?: string; 20 | body: PostBody; 21 | cover?: string; 22 | tags: TagLike[]; 23 | isDraft: boolean; 24 | category: CategoryLike; 25 | slug: string; 26 | visibility?: PostVisibility; 27 | scheduledAt?: Date; 28 | createdAt: Date; 29 | postedAt?: Date; 30 | /** 31 | * locale code 32 | * e.g. en-us 33 | */ 34 | locale?: String; 35 | author?: AuthorLike; 36 | publicationId: PublicationId; 37 | } 38 | 39 | export enum PostVisibility { 40 | public = "public", 41 | private = "private", 42 | password = "password_protected", 43 | } 44 | 45 | export interface Publication {} 46 | export interface PostBody {} 47 | export interface PostAsset {} 48 | 49 | // ================================================================== 50 | // dto 51 | // ================================================================== 52 | 53 | export interface PostSummary { 54 | id: string; 55 | title: string; 56 | summary: string; 57 | thumbnail?: string; 58 | tags?: TagLike[]; 59 | isDraft: boolean; 60 | category: CategoryLike; 61 | createdAt: Date; 62 | postedAt?: Date; 63 | locale?: String; 64 | author?: AuthorLike; 65 | } 66 | 67 | export type Tag = { 68 | id: TagId | "\\none\\"; 69 | name: string; 70 | }; 71 | 72 | export type Category = { 73 | id: CategoryId; 74 | name: string; 75 | }; 76 | 77 | export type TagLike = Tag | string; 78 | export type TagReferenceLike = TagId | string; 79 | export type CategoryLike = "\\default\\" | Category | string; 80 | export type CategoryReferenceLike = "\\default\\" | CategoryId | string; 81 | export type AuthorLike = { 82 | id: AuthorId; 83 | name: string; 84 | avatar?: string; 85 | }; 86 | 87 | // ================================================================== 88 | // responses 89 | // ================================================================== 90 | 91 | export type Actions = 92 | | CreateDraftPostRequest 93 | | UpdatePostBodyRequest 94 | | CreateAssetRequest 95 | | UpdatePostTagsRequest 96 | | UpdatePostTitleRequest 97 | | UpdatePostThumbnailRequest 98 | | UpdatePostCategoryRequest 99 | | UpdatePostScheduleRequest; 100 | 101 | export type CreateDraftPostRequest = { 102 | type: "create-draft-post"; 103 | }; 104 | 105 | export type UpdatePostRequest = { 106 | type: "update-post"; 107 | } & Post; 108 | 109 | export type UpdatePostBodyRequest = { 110 | type: "update-post-body"; 111 | id: PostId; 112 | body: PostBody; 113 | }; 114 | 115 | export type CreateAssetRequest = { 116 | type: "create-asset"; 117 | post: PostId; 118 | asset: PostAsset; 119 | }; 120 | 121 | export type UpdatePostTagsRequest = { 122 | type: "update-post-tags"; 123 | id: PostId; 124 | tags: ReadonlyArray; 125 | }; 126 | 127 | export type UpdatePostTitleRequest = { 128 | type: "update-post-title"; 129 | id: PostId; 130 | title: string; 131 | }; 132 | 133 | export type UpdatePostThumbnailRequest = { 134 | type: "update-post-thumbnail"; 135 | id: PostId; 136 | asset: PostAsset; 137 | }; 138 | 139 | export type UpdatePostCategoryRequest = { 140 | type: "update-post-category"; 141 | id: PostId; 142 | category: CategoryReferenceLike; 143 | }; 144 | 145 | export type UpdatePostScheduleRequest = 146 | | { 147 | type: "update-post-schedule"; 148 | id: PostId; 149 | date: Date; 150 | } 151 | | { 152 | type: "remove-post-schedule"; 153 | id: PostId; 154 | and: "publish-now" | "mark-as-draft"; 155 | }; 156 | 157 | // ================================================================== 158 | // responses 159 | // ================================================================== 160 | 161 | export type UpdatePostScheduleResponse = 162 | | { 163 | error: string; 164 | } 165 | | { 166 | id: PostId; 167 | date: Date; 168 | success: true; 169 | }; 170 | 171 | export type GetPostResponse = Post; 172 | export type GetPostTagsResponse = ReadonlyArray; 173 | export type GetPostCategoryResponse = Category; 174 | export type GetPostsResponse = ReadonlyArray; 175 | export type GetUnlistedPostsResponse = ReadonlyArray; 176 | export type GetDraftPostsResponse = ReadonlyArray; 177 | export type GetPostAssetsResponse = ReadonlyArray; 178 | -------------------------------------------------------------------------------- /posts-service/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require("path"); 3 | const nodeExternals = require("webpack-node-externals"); 4 | const slsw = require("serverless-webpack"); 5 | const { isLocal } = slsw.lib.webpack; 6 | 7 | module.exports = { 8 | target: "node", 9 | stats: "normal", 10 | entry: slsw.lib.entries, 11 | externals: [nodeExternals()], 12 | mode: isLocal ? "development" : "production", 13 | optimization: { concatenateModules: false }, 14 | resolve: { extensions: [".js", ".ts"] }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.tsx?$/, 19 | loader: "ts-loader", 20 | exclude: /node_modules/, 21 | }, 22 | ], 23 | }, 24 | output: { 25 | libraryTarget: "commonjs", 26 | filename: "[name].js", 27 | path: path.resolve(__dirname, ".webpack"), 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /resources-service/.env.defaults: -------------------------------------------------------------------------------- 1 | UNSPLASH_ACCESS_KEY="get your api key" -------------------------------------------------------------------------------- /resources-service/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'prettier/@typescript-eslint', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /resources-service/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # secrets 6 | .env 7 | 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | lerna-debug.log* 15 | 16 | # OS 17 | .DS_Store 18 | 19 | # Tests 20 | /coverage 21 | /.nyc_output 22 | 23 | # IDEs and editors 24 | /.idea 25 | .project 26 | .classpath 27 | .c9/ 28 | *.launch 29 | .settings/ 30 | *.sublime-workspace 31 | 32 | # IDE - VSCode 33 | .vscode/* 34 | !.vscode/settings.json 35 | !.vscode/tasks.json 36 | !.vscode/launch.json 37 | !.vscode/extensions.json -------------------------------------------------------------------------------- /resources-service/README.md: -------------------------------------------------------------------------------- 1 | # service:resources 2 | 3 | service id : resource.api.bridged.xyz 4 | 5 | ## provide your own credentials for 3rd party api 6 | 7 | - unsplash api key in `.env` 8 | 9 | 10 | 11 | 12 | 13 | # NESTJS with microservices 14 | 15 | ## Installation 16 | 17 | ```bash 18 | $ yarn 19 | ``` 20 | 21 | ## Running the app 22 | 23 | ```bash 24 | # development 25 | $ yarn run start 26 | 27 | # watch mode 28 | $ yarn run start:dev 29 | 30 | # production mode 31 | $ yarn run start:prod 32 | ``` 33 | 34 | ## Test 35 | 36 | ```bash 37 | # unit tests 38 | $ yarn run test 39 | 40 | # e2e tests 41 | $ yarn run test:e2e 42 | 43 | # test coverage 44 | $ yarn run test:cov 45 | ``` 46 | 47 | ## Support 48 | 49 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 50 | 51 | ## Stay in touch 52 | 53 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 54 | - Website - [https://nestjs.com](https://nestjs.com/) 55 | - Twitter - [@nestframework](https://twitter.com/nestframework) 56 | 57 | ## License 58 | 59 | Nest is [MIT licensed](LICENSE). 60 | -------------------------------------------------------------------------------- /resources-service/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /resources-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "resources-service", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "nest start", 13 | "start:dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/main", 16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "test:e2e": "jest --config ./test/jest-e2e.json" 22 | }, 23 | "dependencies": { 24 | "@bridged.xyz/client-sdk": "0.0.1-14", 25 | "@nestjs/common": "^7.0.0", 26 | "@nestjs/core": "^7.0.0", 27 | "@nestjs/platform-express": "^7.0.0", 28 | "@nestjs/serve-static": "^2.1.3", 29 | "aws-lambda": "^1.0.6", 30 | "aws-sdk": "^2.783.0", 31 | "aws-serverless-express": "^3.3.8", 32 | "nanoid": "^3.1.16", 33 | "node-fetch": "^2.6.1", 34 | "reflect-metadata": "^0.1.13", 35 | "rimraf": "^3.0.2", 36 | "rxjs": "^6.5.4", 37 | "unsplash-js": "^6.3.0" 38 | }, 39 | "devDependencies": { 40 | "@hewmen/serverless-plugin-typescript": "^1.1.17", 41 | "@nestjs/cli": "^7.0.0", 42 | "@nestjs/schematics": "^7.0.0", 43 | "@nestjs/testing": "^7.0.0", 44 | "@types/aws-lambda": "^8.10.64", 45 | "@types/express": "^4.17.3", 46 | "@types/jest": "26.0.10", 47 | "@types/node": "^13.9.1", 48 | "@types/node-fetch": "^2.5.7", 49 | "@types/supertest": "^2.0.8", 50 | "@types/unsplash-js": "^6.3.1", 51 | "@typescript-eslint/eslint-plugin": "3.9.1", 52 | "@typescript-eslint/parser": "3.9.1", 53 | "eslint": "7.7.0", 54 | "eslint-config-prettier": "^6.10.0", 55 | "eslint-plugin-import": "^2.20.1", 56 | "jest": "26.4.2", 57 | "plugin": "^0.3.3", 58 | "prettier": "^1.19.1", 59 | "serverless-domain-manager": "^5.1.0", 60 | "serverless-offline": "^6.8.0", 61 | "serverless-plugin-optimize": "^4.1.4-rc.1", 62 | "supertest": "^4.0.2", 63 | "ts-jest": "26.2.0", 64 | "ts-loader": "^6.2.1", 65 | "ts-node": "9.0.0", 66 | "tsconfig-paths": "^3.9.0", 67 | "typescript": "^4.1.2" 68 | }, 69 | "jest": { 70 | "moduleFileExtensions": [ 71 | "js", 72 | "json", 73 | "ts" 74 | ], 75 | "rootDir": "src", 76 | "testRegex": ".spec.ts$", 77 | "transform": { 78 | "^.+\\.(t|j)s$": "ts-jest" 79 | }, 80 | "coverageDirectory": "../coverage", 81 | "testEnvironment": "node" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /resources-service/serverless.yml: -------------------------------------------------------------------------------- 1 | service: 2 | name: resource-hosting 3 | 4 | plugins: 5 | - '@hewmen/serverless-plugin-typescript' 6 | - serverless-plugin-optimize 7 | - serverless-offline 8 | - serverless-domain-manager 9 | 10 | custom: 11 | customDomain: 12 | domainName: resources.bridged.cc 13 | basePath: '' 14 | stage: ${self:provider.stage} 15 | createRoute53Record: true 16 | serverless-offline: 17 | httpPort: 4008 18 | 19 | provider: 20 | name: aws 21 | runtime: nodejs12.x 22 | region: us-west-1 23 | apiGateway: 24 | binaryMediaTypes: 25 | - 'multipart/form-data' 26 | iamRoleStatements: 27 | - Effect: Allow 28 | Action: 29 | - s3:GetObject 30 | Resource: 'arn:aws:s3:::reflect-icons/*' 31 | 32 | package: 33 | individually: true 34 | 35 | functions: 36 | main: 37 | handler: src/lambda.handler 38 | events: 39 | - http: 40 | method: any 41 | path: /{proxy+} 42 | cors: true 43 | -------------------------------------------------------------------------------- /resources-service/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { AppController } from './app.controller'; 4 | import { AppService } from './app.service'; 5 | 6 | describe('AppController', () => { 7 | let appController: AppController; 8 | 9 | beforeEach(async () => { 10 | const app: TestingModule = await Test.createTestingModule({ 11 | controllers: [AppController], 12 | providers: [AppService], 13 | }).compile(); 14 | 15 | appController = app.get(AppController); 16 | }); 17 | 18 | describe('root', () => { 19 | it('should return "Hello World!"', () => { 20 | expect(appController.getHello()).toBe('Hello World!'); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /resources-service/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /resources-service/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ServeStaticModule } from '@nestjs/serve-static'; 3 | import { join } from 'path'; 4 | 5 | import { AppController } from './app.controller'; 6 | import { AppService } from './app.service'; 7 | import { GraphicsModule } from './graphics/graphics.module'; 8 | import { IconsModule } from './icons/icons.module'; 9 | 10 | @Module({ 11 | imports: [ 12 | ServeStaticModule.forRoot({ 13 | rootPath: join(__dirname, '..', 'static'), 14 | }), 15 | GraphicsModule, 16 | IconsModule, 17 | ], 18 | controllers: [AppController], 19 | providers: [AppService], 20 | }) 21 | export class AppModule {} 22 | -------------------------------------------------------------------------------- /resources-service/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /resources-service/src/graphics/graphics.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({ 4 | imports: [], 5 | controllers: [], 6 | providers: [], 7 | }) 8 | export class GraphicsModule {} 9 | -------------------------------------------------------------------------------- /resources-service/src/graphics/illusts/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridaco/base/a1e5aa7a851d84873fc548648dc703badafe930d/resources-service/src/graphics/illusts/index.ts -------------------------------------------------------------------------------- /resources-service/src/graphics/patterns/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridaco/base/a1e5aa7a851d84873fc548648dc703badafe930d/resources-service/src/graphics/patterns/index.ts -------------------------------------------------------------------------------- /resources-service/src/graphics/photos/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridaco/base/a1e5aa7a851d84873fc548648dc703badafe930d/resources-service/src/graphics/photos/index.ts -------------------------------------------------------------------------------- /resources-service/src/graphics/sources/unsplash/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * example 3 | * 4 | ``` 5 | { 6 | id: 'U6nlG0Y5sfs', 7 | created_at: '2018-06-20T01:22:40-04:00', 8 | updated_at: '2020-10-21T01:04:19-04:00', 9 | promoted_at: '2018-06-21T11:39:13-04:00', 10 | width: 2303, 11 | height: 3594, 12 | color: '#060205', 13 | blur_hash: 'LvOd3noLoLju|7f7WVjuO,a{WUj[', 14 | description: 'Pink Wall Full of Dogs', 15 | alt_description: 'litter of dogs fall in line beside wall', 16 | urls: [Object], 17 | links: [Object], 18 | categories: [], 19 | likes: 1203, 20 | liked_by_user: false, 21 | current_user_collections: [], 22 | sponsorship: null, 23 | user: [Object], 24 | tags: [Array] 25 | }, 26 | ``` 27 | */ 28 | interface UnsplashImage { 29 | id: string; 30 | created_at: Date; 31 | updated_at: Date; 32 | width: number; 33 | height: number; 34 | color: string; 35 | description: string; 36 | alt_description: string; 37 | urls: UnsplashImageUrlMap; 38 | links: UnsplashLinkMap; 39 | tags: Array; 40 | } 41 | 42 | interface UnsplashImageUrlMap { 43 | raw: string; 44 | regular: string; 45 | small: string; 46 | thumb: string; 47 | } 48 | 49 | interface UnsplashResponse { 50 | total: number; 51 | total_pages: number; 52 | results: Array; 53 | } 54 | 55 | /** 56 | * example 57 | ``` 58 | { 59 | self: 'https://api.unsplash.com/photos/KIqJfzbII9w', 60 | html: 'https://unsplash.com/photos/KIqJfzbII9w', 61 | download: 'https://unsplash.com/photos/KIqJfzbII9w/download', 62 | download_location: 'https://api.unsplash.com/photos/KIqJfzbII9w/download' 63 | } 64 | ``` 65 | */ 66 | interface UnsplashLinkMap { 67 | self: string; 68 | html: string; 69 | download: string; 70 | download_location: string; 71 | } 72 | 73 | /** 74 | * example 75 | ``` 76 | { 77 | type: 'landing_page', 78 | title: 'dog', 79 | source: { 80 | ancestry: [Object], 81 | title: 'Dog Images & Pictures', 82 | subtitle: 'Download free dog images', 83 | description: "Man's best friend is something to behold in all forms: gorgeous Golden Retrievers, tiny yapping chihuahuas, fearsome pitbulls. Unsplash's community of incredible photographers has helped us curate an amazing selection of dog images that you can access and use free of charge.", 84 | meta_title: 'Dog Pictures | Download Free Images on Unsplash', 85 | meta_description: 'Choose from hundreds of free dog pictures. Download HD dog photos for free on Unsplash.', 86 | cover_photo: [Object] 87 | } 88 | } 89 | ``` 90 | */ 91 | interface UnsplashTag { 92 | type: string; 93 | title: string; 94 | source: { 95 | ancestry: any; 96 | title: string; 97 | subtitle: string; 98 | description: string; 99 | cover_photo: any; 100 | }; 101 | } 102 | -------------------------------------------------------------------------------- /resources-service/src/graphics/sources/unsplash/index.ts: -------------------------------------------------------------------------------- 1 | import Unsplash from 'unsplash-js'; 2 | 3 | interface UnsplashResponse { 4 | results: { 5 | tags: { 6 | source: string; 7 | }[]; 8 | }[]; 9 | } 10 | 11 | const unsplash = new Unsplash({ accessKey: process.env.UNSPLASH_ACCESS_KEY }); 12 | 13 | async function getRandomFromTopic(topic: string) { 14 | const res: UnsplashResponse = await ( 15 | await unsplash.search.photos(topic, 1, 1) 16 | ).json(); 17 | console.log(res.results[0].tags[0].source); 18 | return res; 19 | } 20 | 21 | getRandomFromTopic('dogs'); 22 | -------------------------------------------------------------------------------- /resources-service/src/icons/icons.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | 3 | import { IconsService } from './icons.service'; 4 | 5 | @Controller('icons') 6 | export class IconsController { 7 | constructor(private readonly appService: IconsService) {} 8 | 9 | @Get() 10 | getHello(): string { 11 | return this.appService.getHello(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /resources-service/src/icons/icons.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({ 4 | imports: [], 5 | controllers: [], 6 | providers: [], 7 | }) 8 | export class IconsModule {} 9 | -------------------------------------------------------------------------------- /resources-service/src/icons/icons.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class IconsService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /resources-service/src/lambda.ts: -------------------------------------------------------------------------------- 1 | // lambda.ts 2 | import { Handler, Context } from 'aws-lambda'; 3 | import { Server } from 'http'; 4 | import { createServer, proxy } from 'aws-serverless-express'; 5 | import { eventContext } from 'aws-serverless-express/middleware'; 6 | import express from 'express'; 7 | import { NestFactory } from '@nestjs/core'; 8 | import { ExpressAdapter } from '@nestjs/platform-express'; 9 | 10 | import { AppModule } from './app.module'; 11 | 12 | // NOTE: If you get ERR_CONTENT_DECODING_FAILED in your browser, this 13 | // is likely due to a compressed response (e.g. gzip) which has not 14 | // been handled correctly by aws-serverless-express and/or API 15 | // Gateway. Add the necessary MIME types to binaryMimeTypes below 16 | const binaryMimeTypes: string[] = []; 17 | 18 | let cachedServer: Server; 19 | 20 | // Create the Nest.js server and convert it into an Express.js server 21 | async function bootstrapServer(): Promise { 22 | if (!cachedServer) { 23 | const expressApp = express(); 24 | const nestApp = await NestFactory.create( 25 | AppModule, 26 | new ExpressAdapter(expressApp) 27 | ); 28 | nestApp.use(eventContext()); 29 | nestApp.enableCors({ 30 | origin: true, 31 | }); 32 | await nestApp.init(); 33 | cachedServer = createServer(expressApp, undefined, binaryMimeTypes); 34 | } 35 | return cachedServer; 36 | } 37 | 38 | // Export the handler : the entry point of the Lambda function 39 | export const handler: Handler = async (event: any, context: Context) => { 40 | cachedServer = await bootstrapServer(); 41 | return proxy(cachedServer, event, context, 'PROMISE').promise; 42 | }; 43 | -------------------------------------------------------------------------------- /resources-service/src/main.ts: -------------------------------------------------------------------------------- 1 | // import { NestFactory } from '@nestjs/core'; 2 | // import { AppModule } from './app.module'; 3 | 4 | // async function bootstrap() { 5 | // const app = await NestFactory.createMicroservice( 6 | // AppModule, 7 | // { 8 | // transport: Transport.TCP, 9 | // }, 10 | // ); 11 | // app.listen(() => console.log('Microservice is listening')); 12 | // } 13 | // bootstrap(); 14 | -------------------------------------------------------------------------------- /resources-service/static/README.md: -------------------------------------------------------------------------------- 1 | ## serve static for bridged's resource service. 2 | 3 | > static serving is mainly for serving static contents with small sizes, config.json, for instance. -------------------------------------------------------------------------------- /resources-service/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import request from 'supertest'; 4 | 5 | import { AppModule } from '../src/app.module'; 6 | 7 | describe('AppController (e2e)', () => { 8 | let app: INestApplication; 9 | 10 | beforeEach(async () => { 11 | const moduleFixture: TestingModule = await Test.createTestingModule({ 12 | imports: [AppModule], 13 | }).compile(); 14 | 15 | app = moduleFixture.createNestApplication(); 16 | await app.init(); 17 | }); 18 | 19 | it('/ (GET)', () => { 20 | return request(app.getHttpServer()) 21 | .get('/') 22 | .expect(200) 23 | .expect('Hello World!'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /resources-service/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /resources-service/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /resources-service/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /run-services.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PORT=3000 3 | DIRECTORYPORTPAIRS="" 4 | for i in * 5 | do 6 | if [[ $i =~ "-service" ]]; then 7 | DIRECTORYPORTPAIRS="${DIRECTORYPORTPAIRS} --directory $i --port $PORT" 8 | ((++PORT)) 9 | fi 10 | done || exit 1 11 | 12 | printf "\n%s\n" "Using command: ${DIRECTORYPORTPAIRS}" 13 | 14 | serverless-offline-multi $DIRECTORYPORTPAIRS -------------------------------------------------------------------------------- /scene-store-service/.env.development: -------------------------------------------------------------------------------- 1 | # See the documentation for all the connection string options: https://pris.ly/d/connection-strings 2 | DATABASE_URL="postgresql://public-dev.scene-store.bridged.cc:public-public-dev-password@localhost:5432/scene?schema=public" 3 | 4 | # Jwt secret key for development 5 | JWT_SECRET_KEY=00000000 6 | S2S_OTP_SECRET=00000000 7 | NODE_ENV=development -------------------------------------------------------------------------------- /scene-store-service/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@typescript-eslint/parser", 3 | parserOptions: { 4 | project: "tsconfig.json", 5 | sourceType: "module", 6 | }, 7 | plugins: ["@typescript-eslint/eslint-plugin"], 8 | extends: [ 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "prettier", 12 | "prettier/@typescript-eslint", 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | "@typescript-eslint/interface-name-prefix": "off", 21 | "@typescript-eslint/explicit-function-return-type": "off", 22 | "@typescript-eslint/explicit-module-boundary-types": "off", 23 | "@typescript-eslint/no-explicit-any": "off", 24 | "@typescript-eslint/no-empty-function": "off", 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /scene-store-service/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | # Keep environment variables out of version control 3 | .env 4 | -------------------------------------------------------------------------------- /scene-store-service/README.md: -------------------------------------------------------------------------------- 1 | # web hosting service 2 | - host temporary file 3 | - host built website 4 | 5 | 6 | ## Description 7 | 8 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 9 | 10 | ## Installation 11 | 12 | ```bash 13 | $ npm install 14 | ``` 15 | 16 | ## Running the app 17 | 18 | ```bash 19 | # development 20 | $ npm run start 21 | 22 | # watch mode 23 | $ npm run start:dev 24 | 25 | # production mode 26 | $ npm run start:prod 27 | ``` 28 | 29 | ## Test 30 | 31 | ```bash 32 | # unit tests 33 | $ npm run test 34 | 35 | # e2e tests 36 | $ npm run test:e2e 37 | 38 | # test coverage 39 | $ npm run test:cov 40 | ``` 41 | 42 | ## Support 43 | 44 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 45 | 46 | ## Stay in touch 47 | 48 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 49 | - Website - [https://nestjs.com](https://nestjs.com/) 50 | - Twitter - [@nestframework](https://twitter.com/nestframework) 51 | 52 | ## License 53 | 54 | Nest is [MIT licensed](LICENSE). 55 | -------------------------------------------------------------------------------- /scene-store-service/docs/README.md: -------------------------------------------------------------------------------- 1 | # Scene store service (Design store service) -------------------------------------------------------------------------------- /scene-store-service/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /scene-store-service/prisma/local-dev/database.env: -------------------------------------------------------------------------------- 1 | POSTGRES_PASSWORD=public-public-dev-password 2 | POSTGRES_USER=public-dev.scene-store.bridged.cc 3 | POSTGRES_DB=scene -------------------------------------------------------------------------------- /scene-store-service/prisma/local-dev/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | scene-database: 4 | image: "postgres" # use latest official postgres version 5 | command: postgres -c 'max_connections=50' # increase max connections 6 | env_file: 7 | - database.env # configure postgres 8 | ports: 9 | - "5432:5432" 10 | volumes: 11 | - scene-database-data:/var/lib/postgresql/data/ # persist data even if container shuts down 12 | volumes: 13 | scene-database-data: # named volumes can be managed easier using docker-compose 14 | -------------------------------------------------------------------------------- /scene-store-service/prisma/local-dev/start-server.sh: -------------------------------------------------------------------------------- 1 | EXECDIR=$PWD 2 | 3 | # this file's directory 4 | SOURCEDIR="$(dirname "$BASH_SOURCE")" 5 | 6 | # cd to this file's directory to look up docker-compose.yml 7 | cd "$SOURCEDIR" 8 | docker compose up -d 9 | 10 | # since docker compose is complete, cd back to origin execution dir 11 | cd "$EXECDIR" -------------------------------------------------------------------------------- /scene-store-service/prisma/migrations/20210823180755_initial/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "DesignOrigin" AS ENUM ('FIGMA_DESKTOP', 'FIGMA_WEB', 'SKETCH_DESKTOP', 'SKETCH_FILE', 'XD_DESKTOP', 'IMAGE_UPLOAD', 'UNKNOWN'); 3 | 4 | -- CreateEnum 5 | CREATE TYPE "StorableSceneType" AS ENUM ('ANYNODE', 'SCREEN', 'COMPONENT', 'DOCS'); 6 | 7 | -- CreateTable 8 | CREATE TABLE "SceneRecord" ( 9 | "owner" TEXT NOT NULL, 10 | "sharing" TEXT NOT NULL DEFAULT E'none', 11 | "id" TEXT NOT NULL, 12 | "fileId" TEXT NOT NULL, 13 | "nodeId" TEXT NOT NULL, 14 | "sdkVersion" TEXT NOT NULL, 15 | "raw" JSONB NOT NULL, 16 | "rawname" TEXT NOT NULL, 17 | "preview" TEXT, 18 | "newname" TEXT, 19 | "description" TEXT, 20 | "from" "DesignOrigin" NOT NULL, 21 | "sceneType" "StorableSceneType" NOT NULL DEFAULT E'ANYNODE', 22 | "route" TEXT, 23 | "tags" TEXT[], 24 | "customdata_1p" JSONB NOT NULL DEFAULT E'{}', 25 | "customdata_3p" JSONB NOT NULL DEFAULT E'{}', 26 | "background" TEXT DEFAULT E'#FFFFFF', 27 | "width" DOUBLE PRECISION NOT NULL, 28 | "height" DOUBLE PRECISION NOT NULL, 29 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 30 | "updatedAt" TIMESTAMP(3) NOT NULL, 31 | "archived" BOOLEAN NOT NULL DEFAULT false, 32 | "archivedAt" TIMESTAMP(3), 33 | 34 | PRIMARY KEY ("id") 35 | ); 36 | 37 | -- CreateIndex 38 | CREATE UNIQUE INDEX "SceneRecord.fileId_nodeId_unique" ON "SceneRecord"("fileId", "nodeId"); 39 | -------------------------------------------------------------------------------- /scene-store-service/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /scene-store-service/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | datasource db { 5 | provider = "postgresql" 6 | url = env("DATABASE_URL") 7 | } 8 | 9 | generator client { 10 | provider = "prisma-client-js" 11 | binaryTargets = ["native", "rhel-openssl-1.0.x"] 12 | } 13 | 14 | model SceneRecord { 15 | // owner id (currently user id - will be altered later) 16 | // also used for public demo display. - if owner is examples@grida.co, then it's a public demo. 17 | owner String 18 | 19 | // sharing policy (currently supports ["none", "*"]) 20 | sharing String @default("none") 21 | 22 | // the unique id on the database 23 | id String @id @default(uuid()) 24 | 25 | // the id of project this scene is assosiated with. 26 | // TODO: projectId String 27 | 28 | // the id of this scene's origin design file 29 | fileId String 30 | 31 | // the id of this scene's origin design file's node of this content 32 | nodeId String 33 | 34 | @@unique([fileId, nodeId]) 35 | 36 | // TODO: hash String 37 | 38 | // the sdk version of this scene used for conversion and uploading 39 | sdkVersion String 40 | 41 | // raw big json tree data (snapshot) 42 | raw Json 43 | 44 | // raw (original) name at the point of registration. 45 | rawname String 46 | 47 | // preview of this scene as png hosted url 48 | preview String? 49 | 50 | // name of this layer described by designer, defaults to the node's name, can be overriden through the console. 51 | // which means, the name can be different with the design node's name. 52 | newname String? 53 | 54 | // the explicit description of this scene set by editor. for human communication purpose. 55 | description String? 56 | 57 | // the design platform used for design of this origin design file. 58 | from DesignOrigin 59 | 60 | // the type of this scene. rather it can be screen, component, or docs. 61 | sceneType StorableSceneType @default(ANYNODE) 62 | 63 | // the route of this scene, used for screen. 64 | route String? 65 | 66 | // the explicit tags set by editor. for human communication. 67 | tags String[] 68 | 69 | customdata_1p Json @default("{}") 70 | customdata_3p Json @default("{}") 71 | 72 | 73 | // region layer property 74 | background String? @default("#FFFFFF") 75 | width Float 76 | height Float 77 | // endregion layer property 78 | 79 | // alias for containing many variants 80 | // ``` 81 | // e.g. "main-page" is the alias for screens below 82 | // - "main-page/(lg)" 83 | // - "main-page/(md)" 84 | // - "main-page/(xs)" 85 | // ``` 86 | // TODO: alias String? 87 | 88 | 89 | createdAt DateTime @default(now()) 90 | updatedAt DateTime @updatedAt 91 | 92 | // archived = removed 93 | archived Boolean @default(false) 94 | archivedAt DateTime? 95 | } 96 | 97 | enum DesignOrigin { 98 | FIGMA_DESKTOP // figma desktop - figma plugin 99 | FIGMA_WEB // figma web // via api / url 100 | SKETCH_DESKTOP // sketch desktop - sketch file or sketch plugin 101 | SKETCH_FILE // via sketch file upload 102 | // SKETCH_WEB // sketch web is currently not available since sketch does not have a web api. 103 | XD_DESKTOP // xd desktop - xd plugin 104 | IMAGE_UPLOAD // design from uploaded image (unknown source) 105 | UNKNOWN // totally unknown source 106 | } 107 | 108 | enum StorableSceneType { 109 | ANYNODE 110 | SCREEN 111 | COMPONENT 112 | DOCS 113 | } -------------------------------------------------------------------------------- /scene-store-service/scripts/env_setup.py: -------------------------------------------------------------------------------- 1 | import sys, getopt 2 | import os 3 | import re 4 | script_dir = os.path.dirname(__file__) #<-- absolute dir the script is in 5 | 6 | env_file = "../.env" 7 | path = os.path.join(script_dir, env_file) 8 | 9 | 10 | def main(argv): 11 | envname = '' 12 | try: 13 | opts, args = getopt.getopt(argv,"e:",["env="]) 14 | except getopt.GetoptError as err: 15 | print(str(err)) 16 | sys.exit(2) 17 | for current_argument, current_value in opts: 18 | if current_argument in ("-e", "--env"): 19 | envname = current_value 20 | 21 | with open(path, 'r') as f: 22 | content = f.read() 23 | f.close() 24 | 25 | with open(path, 'w') as f: 26 | line = f"\nNODE_ENV={envname}\n" 27 | final_content = re.sub(r"\nNODE_ENV=.*\n", line, content) 28 | f.write(final_content) 29 | f.close() 30 | # print(f'env_setup.py: setting up .env as "{envname}"') 31 | 32 | 33 | if __name__ == "__main__": 34 | main(sys.argv[1:]) -------------------------------------------------------------------------------- /scene-store-service/serverless.yml: -------------------------------------------------------------------------------- 1 | service: 2 | name: scene-store 3 | 4 | useDotenv: true 5 | 6 | plugins: 7 | - serverless-webpack 8 | - serverless-plugin-warmup 9 | - serverless-offline 10 | 11 | custom: 12 | webpack: 13 | webpackConfig: webpack.config.js 14 | packager: "yarn" 15 | includeModules: true # This is required 16 | packagerOptions: 17 | scripts: # this is the magic 18 | - prisma generate 19 | 20 | customDomain: 21 | domainName: scene-store.bridged.cc 22 | hostedZoneId: us-west-1 23 | basePath: "" 24 | stage: "production" 25 | createRoute53Record: true 26 | 27 | serverless-offline: 28 | httpPort: 9002 29 | warmup: 30 | - production 31 | - staging 32 | 33 | provider: 34 | name: aws 35 | runtime: nodejs12.x 36 | region: us-west-1 37 | environment: 38 | AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1" 39 | apiGateway: 40 | minimumCompressionSize: 1024 41 | 42 | functions: 43 | main: 44 | handler: src/lambda.handler 45 | events: 46 | - http: 47 | method: any 48 | path: /{proxy+} 49 | cors: 50 | origins: 51 | - "*" 52 | - https://www.accounts.grida.co 53 | - https://app.grida.co 54 | - https://console.grida.co 55 | - https://webdev.grida.co 56 | - https://grida.co 57 | - https://www.grida.co 58 | - https://staging-branch.accounts.grida.co 59 | - https://staging-branch-accounts.grida.co 60 | - https://grida-accounts-web.ngrok.io 61 | allowCredentials: true 62 | - http: 63 | method: GET 64 | path: / 65 | 66 | # only include the Prisma binary required on AWS Lambda while packaging 67 | package: 68 | patterns: 69 | - "!node_modules/.prisma/client/query-engine-*" 70 | - "node_modules/.prisma/client/query-engine-rhel-*" 71 | 72 | # TODO: add s3 bucket for preview image hosting 73 | resources: 74 | # Resources: 75 | # sceneStoreTable: 76 | # Type: AWS::DynamoDB::Table 77 | # Properties: 78 | # TableName: "${self:provider.environment.DYNAMODB_TABLE}" 79 | # KeySchema: 80 | # - AttributeName: id 81 | # KeyType: HASH 82 | # AttributeDefinitions: 83 | # - AttributeName: id 84 | # AttributeType: S 85 | # ProvisionedThroughput: 86 | # ReadCapacityUnits: 1 87 | # WriteCapacityUnits: 1 88 | -------------------------------------------------------------------------------- /scene-store-service/src/_auth/jwt-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | 4 | @Injectable() 5 | export class JwtAuthGuard extends AuthGuard('jwt') {} 6 | -------------------------------------------------------------------------------- /scene-store-service/src/_auth/verification-api.ts: -------------------------------------------------------------------------------- 1 | import Axios from "axios"; 2 | 3 | export async function signinWithUserCredential(payload: { 4 | email: string; 5 | password: string; 6 | }) { 7 | const _r = await Axios.post( 8 | "https://accounts.services.grida.co/signin/with-email", 9 | { 10 | ...payload, 11 | } 12 | ); 13 | 14 | return _r.data; 15 | } 16 | 17 | export async function verify(token): Promise { 18 | try { 19 | const _r = await Axios.get<{ valid: boolean }>( 20 | "https://accounts.services.grida.co/verify/", 21 | { 22 | headers: { 23 | Authorization: `Bearer ${token}`, 24 | }, 25 | } 26 | ); 27 | 28 | return _r.data.valid; 29 | } catch (_) { 30 | return false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /scene-store-service/src/_prisma/prisma.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { PrismaService } from "./prisma.service"; 3 | 4 | @Module({ 5 | imports: [PrismaService], 6 | exports: [PrismaService], 7 | }) 8 | export class PrismaModule { 9 | constructor() {} 10 | } 11 | -------------------------------------------------------------------------------- /scene-store-service/src/_prisma/prisma.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnModuleInit, OnModuleDestroy } from "@nestjs/common"; 2 | /// 3 | import { PrismaClient } from "@prisma/client"; 4 | 5 | @Injectable() 6 | export class PrismaService extends PrismaClient 7 | implements OnModuleInit, OnModuleDestroy { 8 | async onModuleInit() { 9 | await this.$connect(); 10 | } 11 | 12 | async onModuleDestroy() { 13 | await this.$disconnect(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /scene-store-service/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | 3 | import { AppService } from './app.service'; 4 | 5 | @Controller() 6 | export class AppController { 7 | constructor(private readonly appService: AppService) {} 8 | 9 | @Get() 10 | async getHello() { 11 | return await this.appService.getHello(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /scene-store-service/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { AppController } from './app.controller'; 4 | import { AppService } from './app.service'; 5 | import { ScenesModule } from './scenes/scenes.module'; 6 | 7 | @Module({ 8 | imports: [ScenesModule], 9 | controllers: [AppController], 10 | providers: [AppService], 11 | }) 12 | export class AppModule {} 13 | -------------------------------------------------------------------------------- /scene-store-service/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@nestjs/common"; 2 | 3 | @Injectable() 4 | export class AppService { 5 | async getHello(): Promise { 6 | return "Welcome to Grida scene-store service. Learn more at https://grida.co"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /scene-store-service/src/authentication/api-jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, UnauthorizedException } from "@nestjs/common"; 2 | import { PassportStrategy } from "@nestjs/passport"; 3 | import { env } from "process"; 4 | import { ExtractJwt, Strategy } from "passport-jwt"; 5 | import { AuthenticationService } from "./authentication.service"; 6 | 7 | @Injectable() 8 | export class ApiJwtStrategy extends PassportStrategy(Strategy) { 9 | constructor(private readonly authservice: AuthenticationService) { 10 | super({ 11 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 12 | secretOrKey: env.JWT_SECRET_KEY, 13 | }); 14 | } 15 | async validate(payload: any) { 16 | const user = this.authservice.verify(payload); 17 | if (!user) { 18 | throw new UnauthorizedException("Invalid token"); 19 | } 20 | return user; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /scene-store-service/src/authentication/authentication.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { JwtModule } from "@nestjs/jwt"; 3 | import { env } from "process"; 4 | import { PrismaModule } from "../_prisma/prisma.module"; 5 | import { PrismaService } from "../_prisma/prisma.service"; 6 | import { AuthenticationService } from "./authentication.service"; 7 | import { ApiJwtStrategy } from "./api-jwt.strategy"; 8 | 9 | @Module({ 10 | imports: [ 11 | PrismaModule, 12 | JwtModule.register({ 13 | secret: env.JWT_SECRET_KEY, 14 | }), 15 | ], 16 | providers: [PrismaService, AuthenticationService, ApiJwtStrategy], 17 | exports: [AuthenticationService], 18 | }) 19 | export class AuthenticationModule { 20 | constructor() {} 21 | } 22 | -------------------------------------------------------------------------------- /scene-store-service/src/authentication/authentication.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, UnauthorizedException } from "@nestjs/common"; 2 | import { JwtService } from "@nestjs/jwt"; 3 | import { verify } from "../_auth/verification-api"; 4 | import { totp } from "otplib"; 5 | 6 | @Injectable() 7 | export class AuthenticationService { 8 | constructor(private readonly jwtService: JwtService) {} 9 | 10 | async verify(payload) { 11 | switch (payload?.type) { 12 | case "human-user": 13 | return this.verifyHumanUser(payload); 14 | case "human-user/s2s-otp": 15 | return this.verifyHumanUserS2SOtp(payload); 16 | } 17 | throw new UnauthorizedException(); 18 | } 19 | 20 | private async verifyHumanUser(payload) { 21 | try { 22 | if (payload && Date.now() / 1000 < payload.exp) { 23 | const token = this.jwtService.sign(payload); 24 | const verified = await verify(token); 25 | if (verified) { 26 | // user object 27 | return { 28 | id: payload.id, 29 | email: payload.email, 30 | username: payload.username, 31 | }; 32 | } 33 | } 34 | console.log("pload", payload, Date.now() / 1000); 35 | } catch (_) { 36 | console.log("failed auth", _); 37 | throw new UnauthorizedException(); 38 | } 39 | } 40 | 41 | private async verifyHumanUserS2SOtp(payload) { 42 | const verified = totp.verify({ 43 | token: payload.otp, 44 | secret: process.env.S2S_OTP_SECRET, 45 | }); 46 | 47 | if (verified) { 48 | // user object 49 | return { 50 | id: payload.id, 51 | email: payload.email, 52 | username: payload.username, 53 | }; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /scene-store-service/src/lambda.ts: -------------------------------------------------------------------------------- 1 | // lambda.ts 2 | import { Handler, Context } from "aws-lambda"; 3 | import { Server } from "http"; 4 | import { createServer, proxy } from "aws-serverless-express"; 5 | import { eventContext } from "aws-serverless-express/middleware"; 6 | import express from "express"; 7 | import { NestFactory } from "@nestjs/core"; 8 | import { ExpressAdapter } from "@nestjs/platform-express"; 9 | 10 | import { AppModule } from "./app.module"; 11 | 12 | // NOTE: If you get ERR_CONTENT_DECODING_FAILED in your browser, this 13 | // is likely due to a compressed response (e.g. gzip) which has not 14 | // been handled correctly by aws-serverless-express and/or API 15 | // Gateway. Add the necessary MIME types to binaryMimeTypes below 16 | const binaryMimeTypes: string[] = []; 17 | 18 | let cachedServer: Server; 19 | 20 | // Create the Nest.js server and convert it into an Express.js server 21 | async function bootstrapServer(): Promise { 22 | if (!cachedServer) { 23 | const expressApp = express(); 24 | const nestApp = await NestFactory.create( 25 | AppModule, 26 | new ExpressAdapter(expressApp) 27 | ); 28 | nestApp.use(eventContext()); 29 | nestApp.use(express.json({ limit: "3mb" })); 30 | nestApp.enableCors({ 31 | origin: true, 32 | }); 33 | await nestApp.init(); 34 | cachedServer = createServer(expressApp, undefined, binaryMimeTypes); 35 | } 36 | return cachedServer; 37 | } 38 | 39 | // Export the handler : the entry point of the Lambda function 40 | export const handler: Handler = async (event: any, context: Context) => { 41 | cachedServer = await bootstrapServer(); 42 | return proxy(cachedServer, event, context, "PROMISE").promise; 43 | }; 44 | -------------------------------------------------------------------------------- /scene-store-service/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | 3 | import { AppModule } from './app.module'; 4 | 5 | async function bootstrap() { 6 | const app = await NestFactory.create(AppModule); 7 | app.listen(3000, () => console.log('Microservice is listening')); 8 | } 9 | bootstrap(); 10 | -------------------------------------------------------------------------------- /scene-store-service/src/scenes/scenes.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Get, 5 | Param, 6 | Post, 7 | Query, 8 | Req, 9 | UseGuards, 10 | } from "@nestjs/common"; 11 | 12 | import { SceneRegisterRequest } from "@base-sdk/scene-store/dist/__api/requests"; 13 | import { ScenesService } from "./scenes.service"; 14 | import { JwtAuthGuard } from "../_auth/jwt-auth.guard"; 15 | 16 | @Controller("/scenes") 17 | export class ScenesController { 18 | constructor(private readonly scenesService: ScenesService) {} 19 | 20 | @UseGuards(JwtAuthGuard) // TODO: add s2s auth guard 21 | @Post("/new") 22 | async postRegisterScene(@Req() req, @Body() body: SceneRegisterRequest) { 23 | return await this.scenesService.registerScreen(req.user, body); 24 | } 25 | 26 | @UseGuards(JwtAuthGuard) // TODO: add s2s auth guard 27 | @Get("/:id") 28 | async getScene( 29 | @Req() req, 30 | @Param() 31 | params: { 32 | id: string; 33 | } 34 | // @Query() query: IGetSceneQuery 35 | ) { 36 | const id = params.id; 37 | return await this.scenesService.fetchScene(req.user, id); 38 | } 39 | 40 | @Get("/demos") 41 | async getPublicDemoScenes() { 42 | return await this.scenesService.fetchPublicDemoScenes(); 43 | } 44 | 45 | @Get("/demos/:id") 46 | async getPublicDemoScene( 47 | @Param() 48 | params: { 49 | id: string; 50 | } 51 | ) { 52 | const id = params.id; 53 | return await this.scenesService.fetchPublicDemoScene(id); 54 | } 55 | 56 | @UseGuards(JwtAuthGuard) 57 | @Post("/:id/sharing") 58 | async postUpdateSharingPolicy( 59 | @Req() req, 60 | @Param() 61 | params: { 62 | id: string; 63 | }, 64 | @Body() 65 | body: { 66 | policy: string; 67 | } 68 | ) { 69 | return await this.scenesService.updateSharingPolicy( 70 | req.user, 71 | params.id, 72 | body 73 | ); 74 | } 75 | 76 | /** public api (no guard) */ 77 | @Get("/shared/:id") 78 | async getSharedScene( 79 | @Req() req, 80 | @Param() 81 | params: { 82 | id: string; 83 | } 84 | // @Query() query: IGetSceneQuery 85 | ) { 86 | const id = params.id; 87 | return this.scenesService.fetchSharedScene(id); 88 | } 89 | 90 | /** 91 | * by default, if no queries are provided, this will return all scenes in the project (included in request header). 92 | */ 93 | @UseGuards(JwtAuthGuard) // TODO: add s2s auth guard 94 | @Get("/") 95 | async getScenes(@Req() req, @Query() query? /*notused*/) { 96 | return this.scenesService.fetchMyScenes(req.user); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /scene-store-service/src/scenes/scenes.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { JwtModule } from "@nestjs/jwt"; 3 | import { PrismaService } from "../_prisma/prisma.service"; 4 | import { PrismaModule } from "../_prisma/prisma.module"; 5 | import { ScenesController } from "./scenes.controller"; 6 | import { ScenesService } from "./scenes.service"; 7 | import { env } from "process"; 8 | import { ApiJwtStrategy } from "../authentication/api-jwt.strategy"; 9 | import { AuthenticationModule } from "../authentication/authentication.module"; 10 | 11 | @Module({ 12 | imports: [ 13 | PrismaModule, 14 | AuthenticationModule, 15 | JwtModule.register({ 16 | secret: env.JWT_SECRET_KEY, 17 | }), 18 | ], 19 | controllers: [ScenesController], 20 | providers: [ScenesService, PrismaService, ApiJwtStrategy], 21 | }) 22 | export class ScenesModule {} 23 | -------------------------------------------------------------------------------- /scene-store-service/src/scenes/scenes.object.ts: -------------------------------------------------------------------------------- 1 | // import { StorableSceneType } from "@bridged.xyz/client-sdk"; 2 | 3 | // TODO -> indexing for complex query might be useful 4 | interface IGetSceneQuery { 5 | /** 6 | * the components' scene id. finds scene that contains the components givven. 7 | * example usecase : "find usage of this component" - when user clicks component, and wants to see where it's being used more. 8 | */ 9 | components?: string[]; 10 | 11 | /** 12 | * filters type of the quering scene. 13 | * when no types or '*' provided, it wont run extra query for filtering the scene type 14 | * example usecase : find more screen with this component used. - originally, if types not set, it will both return components and screens as result, but that won't be the desired result. 15 | */ 16 | // types?: "*" | StorableSceneType; 17 | 18 | /** 19 | * retrieves scene with givven alias name. 20 | */ 21 | alias?: string; 22 | 23 | /** 24 | * the givven references are ids of the scene, wich returns the scenes referenced by givven scene ids. 25 | * inclluded options are, 26 | * - routing (if the routing points to the search targets, it will be included on this filter) 27 | * - component usage (if the component points to the target, it will be included on this filter) 28 | */ 29 | references: string[]; 30 | 31 | /** 32 | * if this field is provided, the result will explicitly be screens, since only the scene with type = "SCREEN" can contain routes. 33 | * TODO -> logic gate - should it iterate the routes as target scene? or should we find scenes referencing this routes? 34 | */ 35 | routesFrom?: string[]; 36 | } 37 | -------------------------------------------------------------------------------- /scene-store-service/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /scene-store-service/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "es6", 13 | "sourceMap": true, 14 | "outDir": "./dist", 15 | "baseUrl": "./", 16 | "typeRoots": ["./node_modules/@types"] 17 | }, 18 | "exclude": ["node_modules"] 19 | } 20 | -------------------------------------------------------------------------------- /scene-store-service/webpack.config.js: -------------------------------------------------------------------------------- 1 | const slsw = require("serverless-webpack"); 2 | 3 | const path = require("path"); 4 | const webpack = require("webpack"); 5 | const nodeExternals = require("webpack-node-externals"); 6 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 7 | 8 | module.exports = { 9 | mode: "production", // TODO: read from the serverless.yml 10 | entry: slsw.lib.entries, 11 | externals: [ 12 | nodeExternals(), // this is required 13 | ], 14 | plugins: [ 15 | new CopyWebpackPlugin({ 16 | patterns: [ 17 | // without this the prisma generate above will not work 18 | { from: "./prisma/schema.prisma" }, 19 | // copy .env file since nest js app referes it on runtime. -> not a good pattern, needs to be fixed. 20 | { from: ".env" }, 21 | ], 22 | }), 23 | 24 | new webpack.IgnorePlugin({ 25 | checkResource(resource) { 26 | const lazyImports = [ 27 | "@nestjs/microservices", 28 | "@nestjs/platform-express", 29 | "@nestjs/websockets", 30 | "@nestjs/websockets/socket-module", 31 | "@nestjs/microservices/microservices-module", 32 | "@prisma/client", 33 | "cache-manager", 34 | "class-validator", 35 | "class-transformer", 36 | ]; 37 | if (!lazyImports.includes(resource)) { 38 | return false; 39 | } 40 | try { 41 | require.resolve(resource); 42 | } catch (err) { 43 | return true; 44 | } 45 | return false; 46 | }, 47 | }), 48 | ], 49 | module: { 50 | rules: [ 51 | { 52 | test: /\.ts$/, 53 | loader: "ts-loader", 54 | include: [__dirname], 55 | exclude: /node_modules/, 56 | }, 57 | ], 58 | }, 59 | resolve: { 60 | extensions: [".ts", ".js"], 61 | }, 62 | optimization: { 63 | minimize: false, 64 | }, 65 | }; 66 | -------------------------------------------------------------------------------- /url-service/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'prettier/@typescript-eslint', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /url-service/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | .build 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json -------------------------------------------------------------------------------- /url-service/README.md: -------------------------------------------------------------------------------- 1 | # web hosting service 2 | - host temporary file 3 | - host built website 4 | 5 | 6 | ## Description 7 | 8 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 9 | 10 | ## Installation 11 | 12 | ```bash 13 | $ npm install 14 | ``` 15 | 16 | ## Running the app 17 | 18 | ```bash 19 | # development 20 | $ npm run start 21 | 22 | # watch mode 23 | $ npm run start:dev 24 | 25 | # production mode 26 | $ npm run start:prod 27 | ``` 28 | 29 | ## Test 30 | 31 | ```bash 32 | # unit tests 33 | $ npm run test 34 | 35 | # e2e tests 36 | $ npm run test:e2e 37 | 38 | # test coverage 39 | $ npm run test:cov 40 | ``` 41 | 42 | ## Support 43 | 44 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 45 | 46 | ## Stay in touch 47 | 48 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 49 | - Website - [https://nestjs.com](https://nestjs.com/) 50 | - Twitter - [@nestframework](https://twitter.com/nestframework) 51 | 52 | ## License 53 | 54 | Nest is [MIT licensed](LICENSE). 55 | -------------------------------------------------------------------------------- /url-service/env.defaults: -------------------------------------------------------------------------------- 1 | HOST=bridged.cc -------------------------------------------------------------------------------- /url-service/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /url-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "url-service", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "sls offline start", 13 | "start:dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/main", 16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "test:e2e": "jest --config ./test/jest-e2e.json" 22 | }, 23 | "dependencies": { 24 | "@bridged.xyz/client-sdk": "0.0.1-14", 25 | "@nestjs/common": "^7.0.0", 26 | "@nestjs/core": "^7.0.0", 27 | "@nestjs/platform-express": "^7.0.0", 28 | "aws-lambda": "^1.0.6", 29 | "aws-sdk": "^2.783.0", 30 | "aws-serverless-express": "^3.3.8", 31 | "nanoid": "^3.1.16", 32 | "reflect-metadata": "^0.1.13", 33 | "rimraf": "^3.0.2", 34 | "rxjs": "^6.5.4" 35 | }, 36 | "devDependencies": { 37 | "@hewmen/serverless-plugin-typescript": "^1.1.17", 38 | "@nestjs/cli": "^7.0.0", 39 | "@nestjs/schematics": "^7.0.0", 40 | "@nestjs/testing": "^7.0.0", 41 | "@types/aws-lambda": "^8.10.64", 42 | "@types/express": "^4.17.3", 43 | "@types/jest": "26.0.10", 44 | "@types/node": "^13.9.1", 45 | "@types/supertest": "^2.0.8", 46 | "@typescript-eslint/eslint-plugin": "3.9.1", 47 | "@typescript-eslint/parser": "3.9.1", 48 | "eslint": "7.7.0", 49 | "eslint-config-prettier": "^6.10.0", 50 | "eslint-plugin-import": "^2.20.1", 51 | "jest": "26.4.2", 52 | "plugin": "^0.3.3", 53 | "prettier": "^1.19.1", 54 | "serverless-dotenv-plugin": "^3.1.0", 55 | "serverless-dynamodb-local": "^0.2.39", 56 | "serverless-domain-manager": "^5.1.0", 57 | "serverless-offline": "^6.8.0", 58 | "serverless-plugin-optimize": "^4.1.4-rc.1", 59 | "supertest": "^4.0.2", 60 | "ts-jest": "26.2.0", 61 | "ts-loader": "^6.2.1", 62 | "ts-node": "9.0.0", 63 | "tsconfig-paths": "^3.9.0", 64 | "typescript": "^4.1.2" 65 | }, 66 | "jest": { 67 | "moduleFileExtensions": [ 68 | "js", 69 | "json", 70 | "ts" 71 | ], 72 | "rootDir": "src", 73 | "testRegex": ".spec.ts$", 74 | "transform": { 75 | "^.+\\.(t|j)s$": "ts-jest" 76 | }, 77 | "coverageDirectory": "../coverage", 78 | "testEnvironment": "node" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /url-service/serverless.yml: -------------------------------------------------------------------------------- 1 | service: 2 | name: url-service 3 | 4 | plugins: 5 | - serverless-dotenv-plugin 6 | - "@hewmen/serverless-plugin-typescript" 7 | - serverless-plugin-optimize 8 | - serverless-offline 9 | - serverless-dynamodb-local 10 | - serverless-domain-manager 11 | 12 | custom: 13 | customDomain: 14 | - http: 15 | domainName: bridged.cc 16 | hostedZoneId: us-west-1 17 | basePath: "" 18 | stage: ${self:provider.stage} 19 | createRoute53Record: true 20 | - http: 21 | domainName: grida.cc 22 | hostedZoneId: us-west-1 23 | basePath: "" 24 | stage: "production" 25 | createRoute53Record: true 26 | serverless-offline: 27 | httpPort: 4000 28 | optimize: 29 | includePaths: ["web"] 30 | 31 | provider: 32 | name: aws 33 | runtime: nodejs12.x 34 | region: us-west-1 35 | apiGateway: 36 | minimumCompressionSize: 1024 37 | environment: 38 | AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1" 39 | DYNAMODB_TABLE: "${self:service}-${opt:stage, self:provider.stage}" 40 | iamRoleStatements: 41 | - Effect: Allow 42 | Action: 43 | - dynamodb:Query 44 | - dynamodb:Scan 45 | - dynamodb:GetItem 46 | - dynamodb:PutItem 47 | - dynamodb:UpdateItem 48 | - dynamodb:DeleteItem 49 | Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" 50 | 51 | resources: 52 | Resources: 53 | shortUrlTable: 54 | Type: AWS::DynamoDB::Table 55 | Properties: 56 | TableName: "${self:provider.environment.DYNAMODB_TABLE}" 57 | KeySchema: 58 | - AttributeName: id 59 | KeyType: HASH 60 | AttributeDefinitions: 61 | - AttributeName: id 62 | AttributeType: S 63 | ProvisionedThroughput: 64 | ReadCapacityUnits: 1 65 | WriteCapacityUnits: 1 66 | 67 | package: 68 | individually: true 69 | include: 70 | - web/** 71 | 72 | functions: 73 | main: 74 | handler: src/lambda.handler 75 | events: 76 | - http: 77 | method: any 78 | path: /{proxy+} 79 | - http: 80 | method: GET 81 | path: / 82 | -------------------------------------------------------------------------------- /url-service/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Get, 5 | Param, 6 | Post, 7 | Redirect, 8 | Res, 9 | } from "@nestjs/common"; 10 | import * as path from "path"; 11 | 12 | import { 13 | UrlShortenRequest, 14 | UrlShortenResult, 15 | } from "@bridged.xyz/client-sdk/dist/url/types"; 16 | import { AppService } from "./app.service"; 17 | import { checkIfValidUrl } from "./utils"; 18 | 19 | @Controller() 20 | export class AppController { 21 | constructor(private readonly appService: AppService) {} 22 | 23 | @Get() 24 | getHello(@Res() res) { 25 | const file = path.resolve( 26 | process.env.LAMBDA_TASK_ROOT as string, 27 | "_optimize", 28 | process.env.AWS_LAMBDA_FUNCTION_NAME as string, 29 | "web", 30 | "index.html" 31 | ); 32 | res.sendFile(file); 33 | } 34 | 35 | @Get(":id") 36 | @Redirect("https://grida.co/", 302) // the default redirection 37 | async getRedirect(@Param() params) { 38 | const id = params.id; 39 | const redirect = await this.appService.getRedirect(id); 40 | return { 41 | url: redirect, 42 | }; 43 | } 44 | 45 | @Post("/short") 46 | async postShort(@Body() req: UrlShortenRequest): Promise { 47 | const url = req.url; 48 | if (checkIfValidUrl(url)) { 49 | const result = await this.appService.createRecord(url); 50 | return result; 51 | } 52 | throw `the url: ${url} is not a valid url.`; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /url-service/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | @Module({ 6 | imports: [], 7 | controllers: [AppController], 8 | providers: [AppService], 9 | }) 10 | export class AppModule {} 11 | -------------------------------------------------------------------------------- /url-service/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@nestjs/common"; 2 | import * as AWS from "aws-sdk"; 3 | import { DocumentClient } from "aws-sdk/lib/dynamodb/document_client"; 4 | 5 | import { UrlShortenResult } from "@bridged.xyz/client-sdk/dist/url/types"; 6 | import { buildShortUrl, generateHash } from "./utils"; 7 | 8 | const dynamoDb = new AWS.DynamoDB.DocumentClient(); 9 | 10 | interface TBL_URL_SPEC { 11 | id: string; 12 | host: string; 13 | target: string; 14 | } 15 | 16 | @Injectable() 17 | export class AppService { 18 | getHello(): string { 19 | return "Welcome to Grida hosting service. Learn more at https://github.com/gridaco/base/"; 20 | } 21 | 22 | async getRedirect(id: string): Promise { 23 | const param: DocumentClient.GetItemInput = { 24 | TableName: process.env.DYNAMODB_TABLE, 25 | Key: { 26 | id: id, 27 | }, 28 | }; 29 | const record = await dynamoDb.get(param).promise(); 30 | const data = record.Item as TBL_URL_SPEC; 31 | const origin = data.target; 32 | return origin; 33 | } 34 | 35 | async createRecord(url: string): Promise { 36 | const hash = generateHash(); 37 | const data: TBL_URL_SPEC = { 38 | id: hash, 39 | host: process.env.HOST, 40 | target: url, 41 | }; 42 | const params: DocumentClient.PutItemInput = { 43 | TableName: process.env.DYNAMODB_TABLE, 44 | Item: data, 45 | }; 46 | await dynamoDb.put(params).promise(); 47 | const newUrl = buildShortUrl(data.id, data.host); 48 | const result: UrlShortenResult = { 49 | id: data.id, 50 | url: newUrl, 51 | origin: url, 52 | host: data.host, 53 | }; 54 | return result; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /url-service/src/lambda.ts: -------------------------------------------------------------------------------- 1 | // lambda.ts 2 | import { Handler, Context } from 'aws-lambda'; 3 | import { Server } from 'http'; 4 | import { createServer, proxy } from 'aws-serverless-express'; 5 | import { eventContext } from 'aws-serverless-express/middleware'; 6 | import express from 'express'; 7 | import { NestFactory } from '@nestjs/core'; 8 | import { ExpressAdapter } from '@nestjs/platform-express'; 9 | 10 | import { AppModule } from './app.module'; 11 | 12 | // NOTE: If you get ERR_CONTENT_DECODING_FAILED in your browser, this 13 | // is likely due to a compressed response (e.g. gzip) which has not 14 | // been handled correctly by aws-serverless-express and/or API 15 | // Gateway. Add the necessary MIME types to binaryMimeTypes below 16 | const binaryMimeTypes: string[] = []; 17 | 18 | let cachedServer: Server; 19 | 20 | // Create the Nest.js server and convert it into an Express.js server 21 | async function bootstrapServer(): Promise { 22 | if (!cachedServer) { 23 | const expressApp = express(); 24 | const nestApp = await NestFactory.create( 25 | AppModule, 26 | new ExpressAdapter(expressApp) 27 | ); 28 | nestApp.use(eventContext()); 29 | nestApp.enableCors({ 30 | origin: true, 31 | }); 32 | await nestApp.init(); 33 | cachedServer = createServer(expressApp, undefined, binaryMimeTypes); 34 | } 35 | return cachedServer; 36 | } 37 | 38 | // Export the handler : the entry point of the Lambda function 39 | export const handler: Handler = async (event: any, context: Context) => { 40 | cachedServer = await bootstrapServer(); 41 | return proxy(cachedServer, event, context, 'PROMISE').promise; 42 | }; 43 | -------------------------------------------------------------------------------- /url-service/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | 3 | import { AppModule } from './app.module'; 4 | 5 | async function bootstrap() { 6 | const app = await NestFactory.create(AppModule); 7 | app.listen(3000, () => console.log('Microservice is listening')); 8 | } 9 | bootstrap(); 10 | -------------------------------------------------------------------------------- /url-service/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { URL } from 'url'; 2 | import { nanoid } from 'nanoid'; 3 | 4 | /** 5 | * check if givven url is a valid, and the host does not conficts. 6 | * @param s 7 | */ 8 | export function checkIfValidUrl(s): boolean { 9 | try { 10 | const url = new URL(s); 11 | console.log(url.host); 12 | return !url.host.includes(process.env.HOST); 13 | } catch (err) { 14 | return false; 15 | } 16 | } 17 | 18 | // calculate it here. https://zelark.github.io/nano-id-cc/ 19 | const RAND_RANGE = 16; 20 | 21 | export function generateHash() { 22 | return nanoid(RAND_RANGE); 23 | } 24 | 25 | export function buildShortUrl(id: string, host: string) { 26 | return `https://${host}/${id}`; 27 | } 28 | -------------------------------------------------------------------------------- /url-service/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import request from 'supertest'; 4 | 5 | import { AppModule } from '../src/app.module'; 6 | 7 | describe('AppController (e2e)', () => { 8 | let app: INestApplication; 9 | 10 | beforeEach(async () => { 11 | const moduleFixture: TestingModule = await Test.createTestingModule({ 12 | imports: [AppModule], 13 | }).compile(); 14 | 15 | app = moduleFixture.createNestApplication(); 16 | await app.init(); 17 | }); 18 | 19 | it('/ (GET)', () => { 20 | return request(app.getHttpServer()) 21 | .get('/') 22 | .expect(200) 23 | .expect('Hello World!'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /url-service/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /url-service/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /url-service/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "es6", 13 | "sourceMap": true, 14 | "outDir": "./dist", 15 | "baseUrl": "./", 16 | "typeRoots": ["./node_modules/@types"] 17 | }, 18 | "exclude": ["node_modules"] 19 | } 20 | -------------------------------------------------------------------------------- /url-service/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | About - bridged.cc 8 | 9 | 13 | 84 | 85 | 86 | 87 |
88 | 125 |
126 |
127 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /web/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /web/.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 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /web/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 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /web/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gridaco/base/a1e5aa7a851d84873fc548648dc703badafe930d/web/app/favicon.ico -------------------------------------------------------------------------------- /web/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | -------------------------------------------------------------------------------- /web/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import { Inter } from 'next/font/google' 3 | import './globals.css' 4 | 5 | const inter = Inter({ subsets: ['latin'] }) 6 | 7 | export const metadata: Metadata = { 8 | title: 'Create Next App', 9 | description: 'Generated by create next app', 10 | } 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: { 15 | children: React.ReactNode 16 | }) { 17 | return ( 18 | 19 | {children} 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /web/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "react": "^18", 13 | "react-dom": "^18", 14 | "next": "14.0.3" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5", 18 | "@types/node": "^20", 19 | "@types/react": "^18", 20 | "@types/react-dom": "^18", 21 | "autoprefixer": "^10.0.1", 22 | "postcss": "^8", 23 | "tailwindcss": "^3.3.0", 24 | "eslint": "^8", 25 | "eslint-config-next": "14.0.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /web/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /web/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss' 2 | 3 | const config: Config = { 4 | content: [ 5 | './pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './app/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 13 | 'gradient-conic': 14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | } 20 | export default config 21 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | --------------------------------------------------------------------------------