diff --git a/.env.production b/.env.production index 8cabc5a..e729e7c 100644 --- a/.env.production +++ b/.env.production @@ -1,2 +1,6 @@ CREDENTIALS_ENABLED = 1 +CREDENTIALS_PATH = /home/ubuntu/.certbot/config/live/algorithm-visualizer.org +CREDENTIALS_CA = fullchain.pem +CREDENTIALS_KEY = privkey.pem +CREDENTIALS_CERT = cert.pem WEBHOOK_ENABLED = 1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..dbd56dc --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,54 @@ +# Contributing + +> #### Table of Contents +> - [Running Locally](#running-locally) +> - [Directory Structure](#directory-structure) + +Are you a first-timer in contributing to open source? [These guidelines](https://opensource.guide/how-to-contribute/#how-to-submit-a-contribution) from GitHub might help! + +## Running Locally + +1. Fork this repository. + +2. Clone your forked repo to your machine. + + ```bash + git clone https://github.com//server.git + ``` + +3. Install [Docker](https://docs.docker.com/install/), if not done already. + +4. Create `.env.local` in the project root: + ```bash + # By putting dummy values, GitHub sign in will not work locally + GITHUB_CLIENT_ID = dummy + GITHUB_CLIENT_SECRET = dummy + + # By putting dummy values, extracting visualizing commands will not work locally (except for JavaScript). + AWS_ACCESS_KEY_ID = dummy + AWS_SECRET_ACCESS_KEY = dummy + ``` + +5. Install dependencies, and run the server. + + ```bash + cd server + + npm install + + npm run watch + ``` + +6. Open [`http://localhost:8080/`](http://localhost:8080/) in a web browser. + +## Directory Structure + +- [**src/**](src) contains source code. + - [**config/**](src/config) contains configuration files. + - [**controllers/**](src/controllers) routes and processes incoming requests. + - [**middlewares/**](src/middlewares) contains Express middlewares. + - [**models/**](src/models) manages algorithm visualizations and their hierarchy. + - [**tracers/**](src/tracers) build visualization libraries and compiles/runs code. + - [**utils/**](src/utils) contains utility files. + +**NOTE** that for JavaScript, it builds a web worker rather than a docker image. Once a browser fetches the web worker, it will submit users' code to the web worker locally, instead of submitting to the remote server, to extract visualizing commands. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2a52e6b --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Server + +> This repository is part of the project [Algorithm Visualizer](https://github.com/algorithm-visualizer). + +`server` serves [`algorithm-visualizer`](https://github.com/algorithm-visualizer/algorithm-visualizer) and provides APIs that the web app needs on the fly. (e.g., GitHub sign in, compiling/running code, etc.) + +## Contributing + +Check out the [contributing guidelines](https://github.com/algorithm-visualizer/server/blob/master/CONTRIBUTING.md). diff --git a/certbot.ini b/certbot.ini new file mode 100644 index 0000000..dab450e --- /dev/null +++ b/certbot.ini @@ -0,0 +1,7 @@ +config-dir = /home/ubuntu/.certbot/config +work-dir = /home/ubuntu/.certbot/work +logs-dir = /home/ubuntu/.certbot/logs +email = parkjs814@gmail.com +authenticator = webroot +webroot-path = /home/ubuntu/server/public/frontend-built +domains = algorithm-visualizer.org diff --git a/package-lock.json b/package-lock.json index ea58289..80b8362 100644 --- a/package-lock.json +++ b/package-lock.json @@ -112,6 +112,12 @@ "integrity": "sha512-1YKeT4JitGgE4SOzyB9eMwO0nGVNkNEsm9qlIt1Lqm/tG2QEiSMTD4kS3aO6L+w5SClLVxALmIBESK6Mk5wX0A==", "dev": true }, + "@types/node-cron": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.1.tgz", + "integrity": "sha512-BkMHHonDT8NJUE/pQ3kr5v2GLDKm5or9btLBoBx4F2MB2cuqYC748LYMDC55VlrLI5qZZv+Qgc3m4P3dBPcmeg==", + "dev": true + }, "@types/range-parser": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", @@ -198,13 +204,28 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "aws-sdk": { + "version": "2.814.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.814.0.tgz", + "integrity": "sha512-empd1m/J/MAkL6d9OeRpmg9thobULu0wk4v8W3JToaxGi2TD7PIdvE6yliZKyOVAdJINhBWEBhxR4OUIHhcGbQ==", + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + } + }, "axios": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", - "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.2.tgz", + "integrity": "sha512-87otirqUw3e8CzHTMO+/9kh/FSgXt/eVDvipijwDtEuwbkySWZ9SBm6VEubmJ/kLKEoLQV/POhxXFb66bfekfg==", "requires": { - "follow-redirects": "1.5.10", - "is-buffer": "^2.0.2" + "follow-redirects": "^1.14.0" } }, "balanced-match": { @@ -213,6 +234,11 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, "basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -258,6 +284,16 @@ "concat-map": "0.0.1" } }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -418,14 +454,6 @@ "integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==", "dev": true }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -516,6 +544,11 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, "express": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", @@ -615,12 +648,9 @@ } }, "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "requires": { - "debug": "=3.1.0" - } + "version": "1.14.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz", + "integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==" }, "forwarded": { "version": "0.1.2", @@ -686,9 +716,9 @@ "dev": true }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "http-errors": { @@ -711,6 +741,11 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, "indent-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", @@ -746,11 +781,6 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, - "is-buffer": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", - "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" - }, "is-finite": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", @@ -772,12 +802,22 @@ "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", "dev": true }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -919,6 +959,19 @@ } } }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, + "moment-timezone": { + "version": "0.5.34", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz", + "integrity": "sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==", + "requires": { + "moment": ">= 2.9.0" + } + }, "morgan": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", @@ -951,6 +1004,14 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "node-cron": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.0.tgz", + "integrity": "sha512-DDwIvvuCwrNiaU7HEivFDULcaQualDv7KoNlB/UU1wPW0n1tDEmBJKhEIE6DlF2FuoOHcNbLJ8ITL2Iv/3AWmA==", + "requires": { + "moment-timezone": "^0.5.31" + } + }, "node-notifier": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", @@ -1040,9 +1101,9 @@ "dev": true }, "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "path-to-regexp": { @@ -1091,11 +1152,21 @@ "ipaddr.js": "1.9.0" } }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -1185,6 +1256,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, "semver": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", @@ -1357,9 +1433,9 @@ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, "tree-kill": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", - "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true }, "trim-newlines": { @@ -1494,6 +1570,15 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1534,6 +1619,20 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index 22904c9..d33045a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "name": "@algorithm-visualizer/server", "version": "2.0.0", - "description": "Server for Algorithm Visualizer", + "title": "Algorithm Visualizer", + "description": "Algorithm Visualizer is an interactive online platform that visualizes algorithms from code.", "scripts": { "watch": "NODE_ENV=development NODE_PATH=src ts-node-dev --respawn --ignore-watch node_modules --no-notify src", "start": "NODE_ENV=production NODE_PATH=src ts-node --transpile-only src", @@ -19,13 +20,15 @@ "@types/fs-extra": "^7.0.0", "@types/morgan": "^1.7.35", "@types/node": "^12.0.0", + "@types/node-cron": "^3.0.1", "@types/remove-markdown": "^0.1.1", "@types/uuid": "^3.4.4", "ts-node-dev": "^1.0.0-pre.39", "tslint": "^5.16.0" }, "dependencies": { - "axios": "^0.19.0", + "aws-sdk": "^2.814.0", + "axios": "^0.21.2", "body-parser": "^1.18.2", "compression": "^1.7.3", "dotenv": "^8.0.0", @@ -34,6 +37,7 @@ "express-github-webhook": "^1.0.6", "fs-extra": "^6.0.1", "morgan": "^1.9.1", + "node-cron": "^3.0.0", "remove-markdown": "^0.3.0", "ts-httpexceptions": "^4.1.0", "ts-node": "^8.1.0", diff --git a/pm2.json b/pm2.json new file mode 100644 index 0000000..9450d37 --- /dev/null +++ b/pm2.json @@ -0,0 +1,8 @@ +{ + "apps": [ + { + "name": "algorithm-visualizer", + "script": "npm start" + } + ] +} diff --git a/src/Server.ts b/src/Server.ts index ad9d1d0..637b2df 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -7,18 +7,19 @@ import compression from 'compression'; import { __PROD__, credentials, httpPort, httpsPort, webhookOptions } from 'config/environments'; import http from 'http'; import https from 'https'; +import cron from 'node-cron'; import { Hierarchy } from 'models'; import * as Tracers from 'tracers'; import { errorHandlerMiddleware, frontendMiddleware, redirectMiddleware } from 'middlewares'; -import { execute, pull } from 'utils/misc'; +import { execute, issueHttpsCertificate, pull } from 'utils/misc'; import { frontendBuildDir, frontendBuiltDir, frontendDir, rootDir } from 'config/paths'; const Webhook = require('express-github-webhook'); export default class Server { - private readonly app = express(); readonly hierarchy = new Hierarchy(); readonly tracers = Object.values(Tracers).map(Tracer => new Tracer()); + private readonly app = express(); private readonly webhook = webhookOptions && Webhook(webhookOptions); constructor() { @@ -27,17 +28,17 @@ export default class Server { .use(morgan(__PROD__ ? 'tiny' : 'dev')) .use(redirectMiddleware()) .use(bodyParser.json()) - .use(bodyParser.urlencoded({extended: true})) + .use(bodyParser.urlencoded({ extended: true })) .use('/api', this.getApiRouter()) .use(frontendMiddleware(this)); if (this.webhook) { - this.app.use('/webhook', this.webhook); + this.app.use(this.webhook); } this.app.use(errorHandlerMiddleware()); if (this.webhook) { this.webhook.on('push', async (repo: string, data: any) => { - const {ref, head_commit} = data; + const { ref, head_commit } = data; if (ref !== 'refs/heads/master') return; if (!head_commit) throw new Error('The `head_commit` is empty.'); @@ -62,6 +63,12 @@ export default class Server { await tracer.update(data.release); }); } + + if (credentials) { + cron.schedule('0 0 1 * *', () => { + issueHttpsCertificate(); + }); + } } getApiRouter() { @@ -75,7 +82,11 @@ export default class Server { async update(commit?: string) { await pull(rootDir, 'server', commit); - await execute('npm install', {cwd: rootDir}); + await execute('npm install', { + cwd: rootDir, + stdout: process.stdout, + stderr: process.stderr, + }); process.exit(0); }; @@ -86,7 +97,11 @@ export default class Server { 'npm run build', `rm -rf ${frontendBuiltDir}`, `mv ${frontendBuildDir} ${frontendBuiltDir}`, - ].join(' && '), {cwd: frontendDir}); + ].join(' && '), { + cwd: frontendDir, + stdout: process.stdout, + stderr: process.stderr, + }); } start() { diff --git a/src/config/environments.ts b/src/config/environments.ts index 0a8ae31..912d9c8 100644 --- a/src/config/environments.ts +++ b/src/config/environments.ts @@ -1,15 +1,10 @@ import fs from 'fs'; import { ServerOptions } from 'https'; import path from 'path'; +import { issueHttpsCertificate } from '../utils/misc'; require('dotenv-flow').config(); -declare var process: { - env: { - [key: string]: string, - } -}; - const { NODE_ENV, @@ -27,7 +22,12 @@ const { GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, -} = process.env; + + AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY, +} = process.env as { + [key: string]: string, +}; const isEnabled = (v: string) => v === '1'; @@ -48,6 +48,8 @@ const missingVars = [ ] : []), 'GITHUB_CLIENT_ID', 'GITHUB_CLIENT_SECRET', + 'AWS_ACCESS_KEY_ID', + 'AWS_SECRET_ACCESS_KEY', ].filter(variable => process.env[variable] === undefined); if (missingVars.length) throw new Error(`The following environment variables are missing: ${missingVars.join(', ')}`); @@ -62,12 +64,22 @@ export const webhookOptions = isEnabled(WEBHOOK_ENABLED) ? { secret: WEBHOOK_SECRET, } : undefined; -const readCredentials = (file: string) => fs.readFileSync(path.resolve(CREDENTIALS_PATH, file)); -export const credentials: ServerOptions | undefined = isEnabled(CREDENTIALS_ENABLED) ? { - ca: readCredentials(CREDENTIALS_CA), - key: readCredentials(CREDENTIALS_KEY), - cert: readCredentials(CREDENTIALS_CERT), -} : undefined; +export let credentials: ServerOptions | undefined; +if (isEnabled(CREDENTIALS_ENABLED)) { + if (fs.existsSync(CREDENTIALS_PATH)) { + const readCredentials = (file: string) => fs.readFileSync(path.resolve(CREDENTIALS_PATH, file)); + credentials = { + ca: readCredentials(CREDENTIALS_CA), + key: readCredentials(CREDENTIALS_KEY), + cert: readCredentials(CREDENTIALS_CERT), + }; + } else { + issueHttpsCertificate(); + } +} export const githubClientId = GITHUB_CLIENT_ID; export const githubClientSecret = GITHUB_CLIENT_SECRET; + +export const awsAccessKeyId = AWS_ACCESS_KEY_ID; +export const awsSecretAccessKey = AWS_SECRET_ACCESS_KEY; diff --git a/src/middlewares/errorHandlerMiddleware.ts b/src/middlewares/errorHandlerMiddleware.ts index 8a1936a..8eeb830 100644 --- a/src/middlewares/errorHandlerMiddleware.ts +++ b/src/middlewares/errorHandlerMiddleware.ts @@ -8,7 +8,7 @@ export function errorHandlerMiddleware() { err = new InternalServerError(err.message, err); } - const {name, message, status} = err; - res.status(status).json({name, message, status}); + const {message, status} = err; + res.status(status).send(message); }; } diff --git a/src/tracers/DockerTracer.ts b/src/tracers/DockerTracer.ts index b3d5e27..3672e2f 100644 --- a/src/tracers/DockerTracer.ts +++ b/src/tracers/DockerTracer.ts @@ -15,13 +15,15 @@ export class DockerTracer extends Tracer { super(lang); this.directory = path.resolve(__dirname, lang); this.imageName = `tracer-${this.lang}`; - - this.build = this.build.bind(this); } build(release: Release) { const {tag_name} = release; - return execute(`docker build -t ${this.imageName} . --build-arg tag_name=${tag_name}`, {cwd: this.directory}); + return execute(`docker build -t ${this.imageName} . --build-arg tag_name=${tag_name}`, { + cwd: this.directory, + stdout: process.stdout, + stderr: process.stderr, + }); } route(router: express.Router) { diff --git a/src/tracers/LambdaTracer.ts b/src/tracers/LambdaTracer.ts new file mode 100644 index 0000000..b608a45 --- /dev/null +++ b/src/tracers/LambdaTracer.ts @@ -0,0 +1,33 @@ +import AWS from 'aws-sdk'; +import express from 'express'; +import { Release, Tracer } from 'tracers/Tracer'; +import { awsAccessKeyId, awsSecretAccessKey } from 'config/environments'; +import { BadRequest } from 'ts-httpexceptions'; + +export class LambdaTracer extends Tracer { + static lambda = new AWS.Lambda({ + region: 'us-east-2', + accessKeyId: awsAccessKeyId, + secretAccessKey: awsSecretAccessKey, + }); + + async build(release: Release) { + } + + route(router: express.Router) { + router.post(`/${this.lang}`, (req, res, next) => { + const {code} = req.body; + LambdaTracer.lambda.invoke({ + FunctionName: `extractor-${this.lang}`, + InvocationType: 'RequestResponse', + Payload: JSON.stringify(code), + }, function (err, data) { + if (err) return next(err); + if (typeof data.Payload !== 'string') return next(new Error('Unexpected Payload Type')); + const payload = JSON.parse(data.Payload); + if (!payload.success) return next(new BadRequest(payload.errorMessage)); + res.send(payload.commands); + }); + }); + } +} diff --git a/src/tracers/java/Dockerfile b/src/tracers/java/Dockerfile deleted file mode 100644 index 8aabc07..0000000 --- a/src/tracers/java/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM openjdk:8 - -ARG tag_name - -RUN curl --create-dirs -o /usr/local/lib/algorithm-visualizer.jar -L "https://github.com/algorithm-visualizer/tracers.java/releases/download/${tag_name}/algorithm-visualizer.jar" - -CMD javac -cp /usr/local/lib/algorithm-visualizer.jar Main.java \ - && java -cp /usr/local/lib/algorithm-visualizer.jar:. Main diff --git a/src/tracers/java/JavaTracer.ts b/src/tracers/java/JavaTracer.ts index 043c3c6..e4e5f2b 100644 --- a/src/tracers/java/JavaTracer.ts +++ b/src/tracers/java/JavaTracer.ts @@ -1,6 +1,6 @@ -import { DockerTracer } from 'tracers/DockerTracer'; +import { LambdaTracer } from 'tracers/LambdaTracer'; -export class JavaTracer extends DockerTracer { +export class JavaTracer extends LambdaTracer { constructor() { super('java'); } diff --git a/src/tracers/js/JsTracer.ts b/src/tracers/js/JsTracer.ts index b253a92..a798816 100644 --- a/src/tracers/js/JsTracer.ts +++ b/src/tracers/js/JsTracer.ts @@ -1,26 +1,27 @@ import path from 'path'; -import { download } from 'utils/misc'; import { Release, Tracer } from 'tracers/Tracer'; import express from 'express'; -import { publicDir } from 'config/paths'; export class JsTracer extends Tracer { - readonly tracerPath: string; readonly workerPath: string; + tagName?: string; constructor() { super('js'); - this.tracerPath = path.resolve(publicDir, 'algorithm-visualizer.js'); this.workerPath = path.resolve(__dirname, 'worker.js'); } - build(release: Release) { + async build(release: Release) { const {tag_name} = release; - return download(`https://github.com/algorithm-visualizer/tracers.js/releases/download/${tag_name}/algorithm-visualizer.js`, this.tracerPath); + this.tagName = tag_name; } route(router: express.Router) { - router.get(`/${this.lang}`, (req, res) => res.sendFile(this.tracerPath)); + router.get(`/${this.lang}`, (req, res) => { + if (!this.tagName) throw new Error('JsTracer has not been built yet.'); + const version = this.tagName.slice(1); + res.redirect(`https://unpkg.com/algorithm-visualizer@${version}/dist/algorithm-visualizer.umd.js`); + }); router.get(`/${this.lang}/worker`, (req, res) => res.sendFile(this.workerPath)); } } diff --git a/src/utils/misc.ts b/src/utils/misc.ts index 6074ad2..1b5fe67 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -3,10 +3,12 @@ import fs from 'fs-extra'; import { File } from 'models'; import removeMarkdown from 'remove-markdown'; import * as child_process from 'child_process'; -import { ExecOptions } from 'child_process'; +import { ExecOptions, spawn } from 'child_process'; +import { rootDir } from '../config/paths'; +import path from 'path'; export function download(url: string, localPath: string) { - return axios({url, method: 'GET', responseType: 'stream'}) + return axios({ url, method: 'GET', responseType: 'stream' }) .then(response => new Promise((resolve, reject) => { const writer = fs.createWriteStream(localPath); writer.on('finish', resolve); @@ -17,11 +19,22 @@ export function download(url: string, localPath: string) { export async function pull(dir: string, repo: string, commit = 'origin/master') { if (fs.pathExistsSync(dir)) { - await execute(`git fetch`, {cwd: dir}); + await execute(`git fetch`, { + cwd: dir, + stdout: process.stdout, + stderr: process.stderr, + }); } else { - await execute(`git clone https://github.com/algorithm-visualizer/${repo}.git ${dir}`); + await execute(`git clone https://github.com/algorithm-visualizer/${repo}.git ${dir}`, { + stdout: process.stdout, + stderr: process.stderr, + }); } - await execute(`git reset --hard ${commit}`, {cwd: dir}); + await execute(`git reset --hard ${commit}`, { + cwd: dir, + stdout: process.stdout, + stderr: process.stderr, + }); } export function getDescription(files: File[]) { @@ -30,7 +43,7 @@ export function getDescription(files: File[]) { const lines = readmeFile.content.split('\n'); lines.shift(); while (lines.length && !lines[0].trim()) lines.shift(); - let descriptionLines = []; + const descriptionLines = []; while (lines.length && lines[0].trim()) descriptionLines.push(lines.shift()); return removeMarkdown(descriptionLines.join(' ')); } @@ -38,9 +51,9 @@ export function getDescription(files: File[]) { type ExecuteOptions = ExecOptions & { stdout?: NodeJS.WriteStream; stderr?: NodeJS.WriteStream; -} +}; -export function execute(command: string, {stdout, stderr, ...options}: ExecuteOptions = {}): Promise { +export function execute(command: string, { stdout, stderr, ...options }: ExecuteOptions = {}): Promise { return new Promise((resolve, reject) => { const child = child_process.exec(command, options, (error, stdout, stderr) => { if (error) return reject(error.code ? new Error(stderr) : error); @@ -50,3 +63,18 @@ export function execute(command: string, {stdout, stderr, ...options}: ExecuteOp if (child.stderr && stderr) child.stderr.pipe(stderr); }); } + +export function issueHttpsCertificate() { + const certbotIniPath = path.resolve(rootDir, 'certbot.ini'); + const childProcess = spawn('certbot', ['certonly', '--non-interactive', '--agree-tos', '--config', certbotIniPath]); + childProcess.stdout.pipe(process.stdout); + childProcess.stderr.pipe(process.stderr); + childProcess.on('error', console.error); + childProcess.on('exit', code => { + if (code === 0) { + process.exit(0); + } else { + console.error(new Error(`certbot failed with exit code ${code}.`)); + } + }); +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy