Initial commit

This commit is contained in:
Nathaniel Parke 2020-10-20 17:21:05 +08:00
commit 5f0eed3851
13 changed files with 3352 additions and 0 deletions

3
.eslintignore Normal file
View File

@ -0,0 +1,3 @@
node_modules
lib
bin

23
.eslintrc.json Normal file
View File

@ -0,0 +1,23 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"env": {
"browser": true,
"commonjs": true,
"es2020": true
},
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"parserOptions": {
"ecmaVersion": 11
},
"rules": {
// https://eslint.org/docs/rules/
"semi": ["error", "always"],
"quotes": ["error", "double"],
"object-curly-spacing": ["error", "always"],
"no-unused-vars": ["error", { "args": "none" }],
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-function": "off"
}
}

119
.gitignore vendored Normal file
View File

@ -0,0 +1,119 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
node_modules/.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.idea/
lib/

106
bin/www Executable file
View File

@ -0,0 +1,106 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require("../lib/app").default;
var debug = require("debug")("js:server");
var http = require("http");
var fs = require("fs");
var path = require("path");
const assert = require('assert').strict;
const { PORT } = require("../lib/config");
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(PORT);
app.set("port", port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port, function() {
if (typeof port === "string") {
fs.chmodSync(port, 0o666)
}
});
server.on("error", onError);
server.on("listening", onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort (val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
assert (val.startsWith("/tmp/"));
if (fs.existsSync(PORT)) {
fs.unlinkSync(PORT);
} else if (!fs.existsSync(path.dirname(PORT))) {
fs.mkdirSync(path.dirname(PORT), { mode: 0o755 });
}
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError (error) {
if (error.syscall !== "listen") {
throw error;
}
var bind = typeof port === "string"
? "Pipe " + port
: "Port " + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case "EACCES":
console.error(bind + " requires elevated privileges");
process.exit(1);
// eslint-disable-next-line no-unreachable
break;
case "EADDRINUSE":
console.error(bind + " is already in use");
process.exit(1);
// eslint-disable-next-line no-unreachable
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening () {
var addr = server.address();
var bind = typeof addr === "string"
? "pipe " + addr
: "port " + addr.port;
debug("Listening on " + bind);
}

45
package.json Normal file
View File

@ -0,0 +1,45 @@
{
"name": "serum-rest",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "tsc && node ./bin/www",
"start-prof": "tsc && node --prof ./bin/www",
"build": "tsc",
"clean": "rm -r ./lib",
"lint-dry": "eslint . --ext .js,.jsx,.ts,.tsx",
"fix": "prettier --write . && eslint . --ext .js,.jsx,.ts,.tsx --fix",
"shell": "tsc && node -e \"$(< shell)\" -i --experimental-repl-await"
},
"repository": "git@github.com:project-serum/serum-rest-wip.git",
"author": "Nathaniel Parke <nathaniel.parke@gmail.com>",
"devDependencies": {
"@tsconfig/node12": "^1.0.7",
"@types/express": "^4.17.8",
"@typescript-eslint/eslint-plugin": "^4.5.0",
"@typescript-eslint/parser": "^4.5.0",
"eslint": "^7.11.0",
"eslint-config-prettier": "^6.13.0",
"eslint-config-standard": "^14.1.1",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"prettier": "^2.1.2",
"typescript": "^4.0.3"
},
"dependencies": {
"@project-serum/serum": "^0.13.5",
"@solana/web3.js": "^0.81.0",
"bn.js": "^5.1.3",
"debug": "^4.2.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-async-handler": "^1.1.4",
"http-errors": "^1.8.0",
"morgan": "^1.10.0",
"winston": "^3.3.3",
"winston-daily-rotate-file": "^4.5.0"
}
}

3
shell Normal file
View File

@ -0,0 +1,3 @@
const lib = require('./lib/index');
const solana = require('@solana/web3.js');
const serum = require('@project-serum/serum');

50
src/app.ts Normal file
View File

@ -0,0 +1,50 @@
import createError from "http-errors";
import express from "express";
import { default as morgan } from "morgan";
import indexRouter from "./routes";
import { logger, morganStream } from "./utils";
import * as config from "./config";
const app = express();
app.use(
morgan("combined", {
stream: morganStream,
})
);
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use("/", indexRouter);
// catch 404 and forward to error handler
app.use((req, res, next) => {
next(createError(404));
});
// error handler
app.use((err, req, res, next) => {
logger.log(
"error",
`Express error handler called for error ${err.name}: \n ${err.stack}`
);
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get("env") === "dev" ? err : {};
// render the error page
res.status(err.status || 500);
res.send("error");
});
if (config.RESTART_INTERVAL_SEC) {
const secs = config.RESTART_INTERVAL_SEC + Math.floor(Math.random() * 30);
setTimeout(() => {
logger.error(
`Restarting server on port ${config.PORT} after ${secs} seconds due to timer`
);
process.exit(0);
}, secs * 1000);
}
export default app;

16
src/config.ts Normal file
View File

@ -0,0 +1,16 @@
import dotenv from "dotenv";
// use passed port if sepcified otherwise default to the .env file
const PASSED_PORT = process.env.PORT;
dotenv.config();
export const PORT = PASSED_PORT || process.env.PORT;
export const ENV = process.env.ENVIRONMENT;
export const SECRETS_FILE = process.env.SECRETS_FILE || "";
export const LOGGING_DIR = process.env.LOGGING_DIR || "";
// check truthiness of this to determine if we should restart at interval
export const RESTART_INTERVAL_SEC = parseInt(
process.env.RESTART_INTERVAL_SEC || "0"
);

4
src/index.ts Normal file
View File

@ -0,0 +1,4 @@
import * as utils from "./utils";
import * as configs from "./config";
export { utils, configs };

11
src/routes.ts Normal file
View File

@ -0,0 +1,11 @@
import express from "express";
const router = express.Router();
router.get("/", (req, res, next) => {
res.send(
"Hello from the Serum rest server!"
);
});
export { router as default };

98
src/utils.ts Normal file
View File

@ -0,0 +1,98 @@
import { LOGGING_DIR, SECRETS_FILE } from "./config";
import { readFileSync } from "fs";
import BN from "bn.js";
import winston, { format } from "winston";
import "winston-daily-rotate-file";
const { combine, timestamp, printf } = format;
import fs from "fs";
// Logging
if (
LOGGING_DIR &&
!fs.existsSync(LOGGING_DIR) &&
process.env.ENVIRONMENT === "prod"
) {
fs.mkdirSync(LOGGING_DIR);
}
const logFormat = printf(({ level, message, timestamp }) => {
return `${timestamp} ${level}: ${message}`;
});
export const logger = winston.createLogger({
level: "silly",
format: combine(timestamp(), logFormat),
transports: [
new winston.transports.Console({
format: winston.format.simple(),
level: "info",
}),
],
});
if (process.env.ENVIRONMENT === "prod") {
logger.add(
new winston.transports.DailyRotateFile({
dirname: LOGGING_DIR,
filename: "remote_js_server-ERROR-%DATE%.log",
datePattern: "YYYY-MM-DD-HH",
maxSize: "200m",
maxFiles: "1",
utc: true,
level: "error",
})
);
logger.add(
new winston.transports.DailyRotateFile({
dirname: LOGGING_DIR,
filename: "remote_js_server-INFO-%DATE%.log",
datePattern: "YYYY-MM-DD-HH",
maxSize: "200m",
maxFiles: "3",
utc: true,
level: "info",
})
);
logger.add(
new winston.transports.DailyRotateFile({
dirname: LOGGING_DIR,
filename: "remote_js_server-DEBUG-%DATE%.log",
datePattern: "YYYY-MM-DD-HH",
maxSize: "200m",
maxFiles: "3",
utc: true,
})
);
}
class MorganStream {
write(text: string) {
logger.info(text.replace(/\n$/, ""));
}
}
export const morganStream = new MorganStream();
export const getKeys = (
keys: string[]
): string[] => {
const allSecrets = JSON.parse(readFileSync(SECRETS_FILE, "utf-8"));
const secrets: string[] = [];
for (const key of keys) {
secrets.push(allSecrets[key]);
}
return secrets;
};
export const getUnixTs = (): number => {
return new Date().getTime() / 1000;
};
export function sleep(time: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, time));
}
export function divideBnToNumber(numerator: BN, denominator: BN): number {
const quotient = numerator.div(denominator).toNumber();
const rem = numerator.umod(denominator);
const gcd = rem.gcd(denominator);
return quotient + rem.div(gcd).toNumber() / denominator.div(gcd).toNumber();
}

15
tsconfig.json Normal file
View File

@ -0,0 +1,15 @@
{
"extends": "@tsconfig/node12/tsconfig.json",
"compilerOptions": {
"outDir": "./lib",
"allowJs": true,
"checkJs": true,
"declaration": true,
"declarationMap": true,
"noImplicitAny": false,
"sourceMap": true,
"module": "CommonJS"
},
"include": ["./src/**/*"],
"exclude": ["./src/**/*.test.js", "node_modules", "**/node_modules"]
}

2859
yarn.lock Normal file

File diff suppressed because it is too large Load Diff