Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP - Migrate backend to Deno #1395

Open
wants to merge 56 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
d990dd0
Make backend tests pass
snowteamer May 10, 2022
b702711
Merge master into add-deno-version-without-pogo
snowteamer Aug 27, 2022
ad2c7e8
Use own Pogo fork
snowteamer Aug 27, 2022
3110298
Drop Nodejs backend
snowteamer Aug 28, 2022
35fb008
Ignore backend/ in .flowconfig
snowteamer Aug 28, 2022
bd31982
Install Deno in Travis CI
snowteamer Aug 28, 2022
bd116c5
Restore shared/types.js
snowteamer Aug 28, 2022
e226467
Make grunt deno task non-blocking
snowteamer Aug 28, 2022
5713753
Fix route handler for cached assets
snowteamer Aug 29, 2022
6f5bbae
Update Flow declarations and ignore shared files
snowteamer Aug 29, 2022
4a37434
Fix dangling process issues
snowteamer Aug 29, 2022
c2a6664
Fix spurious empty lines in backend output
snowteamer Aug 30, 2022
7b82ac8
Restore pubsub pingInterval and cleanup output
snowteamer Aug 30, 2022
5a92b17
Restore lost backend logging
snowteamer Aug 31, 2022
a408411
Refactor backend/database.ts using .push()
snowteamer Sep 1, 2022
13382ac
Drop --no-check flag in deno:start
snowteamer Sep 1, 2022
786a4f0
Fix 'npm backend' script
snowteamer Sep 1, 2022
6f6bb96
Make grunt pin run esbuild first (@SebinSong)
snowteamer Sep 10, 2022
71b74d6
Migrate backend tests to Deno
snowteamer Sep 11, 2022
23272a2
Fix test:unit task
snowteamer Sep 11, 2022
b20992c
Exit early when running in CI
snowteamer Sep 11, 2022
80573a7
Make backend and shared pass deno check
snowteamer Sep 14, 2022
747091b
Merge branch 'master' of https://github.com/okTurtles/group-income in…
snowteamer Sep 18, 2022
4800658
Adding typescript linting
snowteamer Sep 20, 2022
1af98c9
Adding typescript linting
snowteamer Sep 20, 2022
67cff98
Add exec:ts task (deno check) in the build
snowteamer Sep 21, 2022
58f7b2c
Merge branch 'add-deno-version-without-pogo' of https://github.com/sn…
snowteamer Oct 8, 2022
7a2c8c7
Fix Flow errors
snowteamer Oct 9, 2022
4f0e68c
Merge branch 'master' of https://github.com/okTurtles/group-income in…
snowteamer Oct 9, 2022
f9228d3
Merge branch master into add-deno-version-without-pogo
snowteamer Oct 12, 2022
6f9a3c9
Migrate i18n validation tests to Deno
snowteamer Oct 17, 2022
bd7a1a1
Migrate disallow-vhtml-directive tests to Deno
snowteamer Oct 17, 2022
3e7ca71
Fix lint errors
snowteamer Oct 18, 2022
c6e2601
Restore Flow typings of a few more files
snowteamer Oct 20, 2022
f1d87f1
Bump contracts version to 0.0.8
snowteamer Oct 20, 2022
b937be3
Merge master into add-deno-version-without-pogo
snowteamer Oct 20, 2022
ea78079
Get rid of 'curl | sh' in travis.yml
snowteamer Oct 20, 2022
207a435
Migrate giLodash tests to Deno
snowteamer Oct 26, 2022
a17ba6a
Migrate time.js tests to Deno
snowteamer Oct 26, 2022
c23f017
Use a single .eslintrc.json
snowteamer Oct 26, 2022
b761ffe
Ignore test/contracts in .gitignore
snowteamer Oct 26, 2022
bec5000
Migrate remaining unit tests to Deno
snowteamer Oct 26, 2022
a8f890f
Drop Mocha
snowteamer Oct 28, 2022
29370e3
Update Deno and import maps
snowteamer Oct 28, 2022
500b0c3
Merge master into add-deno-version-without-pogo
snowteamer Oct 28, 2022
e612402
Merge origin into add-deno-version-without-pogo
snowteamer Oct 29, 2022
ebc3168
Fix app sometimes not loading on port 3000
snowteamer Oct 29, 2022
21f7240
Merge master into add-deno-version-without-pogo
snowteamer Oct 29, 2022
50c6796
Fix exec:ts error in giLodash.js
snowteamer Oct 30, 2022
3f6d704
Cleanup test/contracts
snowteamer Nov 5, 2022
2ab156d
Merge branch master into add-deno-version-without-pogo
snowteamer Jan 9, 2023
cbfeb7d
Merge master into add-deno-version-without-pogo
snowteamer Jan 10, 2023
01c0c52
Rename import-map.json to import_map.json
snowteamer Jan 10, 2023
e0dc0aa
Use some pinned URLs in import map
snowteamer Jan 10, 2023
7ac3edd
Merge master into add-deno-version-without-pogo
snowteamer Jan 24, 2023
55f5047
Merge branch 'master' of https://github.com/okTurtles/group-income in…
snowteamer Jan 30, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions .flowconfig
Expand Up @@ -7,6 +7,10 @@
# - https://flowtype.org/docs/objects.html
# - https://flowtype.org/docs/functions.html
.*/Gruntfile.js
# Backend files use TypeScript instead of Flow since they run using Deno.
.*/backend/.*
# Shared files must be imported by Deno backend files, so they better not contain Flowtype annotations.
.*/shared/.*
.*/dist/.*
.*/contracts/.*
.*/frontend/assets/.*
Expand Down
8 changes: 8 additions & 0 deletions .travis.yml
Expand Up @@ -9,6 +9,14 @@ env:
branches:
only:
- master # https://github.com/okTurtles/group-income/issues/58
before_install:
# https://blog.travis-ci.com/2021-02-01-rundeno
- pwd
- curl -fsSL https://deno.land/x/install/install.sh | sh
snowteamer marked this conversation as resolved.
Show resolved Hide resolved
- ls -l $HOME/.deno
- export DENO_INSTALL="$HOME/.deno"
- export PATH="$DENO_INSTALL/bin:$PATH"
- deno run https://deno.land/std/examples/welcome.ts
cache:
# Caches $HOME/.npm when npm ci is default script command
# Caches node_modules in all other cases
Expand Down
136 changes: 77 additions & 59 deletions Gruntfile.js
Expand Up @@ -5,7 +5,7 @@
//
// Ensures:
//
// - Babel support is available on the backend, in Mocha tests, etc.
// - Babel support is available in Mocha tests, etc.
// - Environment variables are set to different values depending
// on whether we're in a production environment or otherwise.
//
Expand All @@ -14,7 +14,7 @@
const util = require('util')
const chalk = require('chalk')
const crypto = require('crypto')
const { exec, fork } = require('child_process')
const { exec, spawn } = require('child_process')
const execP = util.promisify(exec)
const { copyFile, readFile } = require('fs/promises')
const fs = require('fs')
Expand Down Expand Up @@ -66,7 +66,7 @@ const {
EXPOSE_SBP = ''
} = process.env

const backendIndex = './backend/index.js'
const backendIndex = './backend/index.ts'
const distAssets = 'dist/assets'
const distCSS = 'dist/assets/css'
const distDir = 'dist'
Expand Down Expand Up @@ -378,53 +378,6 @@ module.exports = (grunt) => {

let child = null

// Useful helper task for `grunt test`.
grunt.registerTask('backend:launch', '[internal]', function () {
const done = this.async()
grunt.log.writeln('backend: launching...')
// Provides Babel support for the backend files.
require('@babel/register')
require(backendIndex).then(done).catch(done)
})

// Used with `grunt dev` only, makes it possible to restart just the server when
// backend or shared files are modified.
grunt.registerTask('backend:relaunch', '[internal]', function () {
const done = this.async() // Tell Grunt we're async.
const fork2 = function () {
grunt.log.writeln('backend: forking...')
child = fork(backendIndex, process.argv, {
env: process.env,
execArgv: ['--require', '@babel/register']
})
child.on('error', (err) => {
if (err) {
console.error('error starting or sending message to child:', err)
process.exit(1)
}
})
child.on('exit', (c) => {
if (c !== 0) {
grunt.log.error(`child exited with error code: ${c}`.bold)
// ^C can cause c to be null, which is an OK error.
process.exit(c || 0)
}
})
done()
}
if (child) {
grunt.log.writeln('Killing child!')
// Wait for successful shutdown to avoid EADDRINUSE errors.
child.on('message', () => {
child = null
fork2()
})
child.send({ shutdown: 1 })
} else {
fork2()
}
})

grunt.registerTask('build', function () {
const esbuild = this.flags.watch ? 'esbuild:watch' : 'esbuild'

Expand Down Expand Up @@ -482,8 +435,57 @@ module.exports = (grunt) => {
})

grunt.registerTask('default', ['dev'])

grunt.registerTask('deno:start', function () {
const done = this.async() // Tell Grunt we're async.
child = spawn(
'deno',
['run', '--allow-env', '--allow-net', '--allow-read', '--allow-write', '--import-map=import-map.json', backendIndex],
{
stdio: 'inherit'
}
)
child.on('error', (err) => {
if (err) {
console.error('Error starting or sending message to child:', err)
process.exit(1)
}
})
child.on('exit', (c) => {
// ^C can cause c to be null, which is an OK error.
if (c === null) {
grunt.log.writeln('Backend process exited with null code.')
// process.exit(0)
} else if (c !== 0) {
grunt.log.error(`Backend process exited with error code: ${c}`.bold)
process.exit(c)
} else {
grunt.log.writeln('Backend process exited normally.')
}
})
child.on('close', (code) => {
console.log(`Backend process closed with code ${code}`)
})
child.on('spawn', () => {
grunt.log.writeln('Backend process spawned.')
done()
})
})

grunt.registerTask('deno:stop', function () {
if (child) {
const killed = child.kill()
if (killed) {
grunt.log.writeln('Deno backend stopped.')
child = null
} else {
grunt.log.error('Failed to quit dangling child!')
}
}
})

// TODO: add 'deploy' as per https://github.com/okTurtles/group-income/issues/10
grunt.registerTask('dev', ['checkDependencies', 'exec:chelDeployAll', 'build:watch', 'backend:relaunch', 'keepalive'])
grunt.registerTask('dev', ['checkDependencies', 'exec:chelDeployAll', 'build:watch', 'deno:start', 'keepalive'])
grunt.registerTask('dist', ['build'])

// --------------------
Expand Down Expand Up @@ -547,7 +549,7 @@ module.exports = (grunt) => {

;[
[['Gruntfile.js'], [eslint]],
[['backend/**/*.js', 'shared/**/*.js'], [eslint, 'backend:relaunch']],
[['backend/**/*.ts', 'shared/**/*.js'], [eslint, 'deno:stop', 'deno:start']],
[['frontend/**/*.html'], ['copy']],
[['frontend/**/*.js'], [eslint]],
[['frontend/assets/{fonts,images}/**/*'], ['copy']],
Expand Down Expand Up @@ -620,6 +622,19 @@ module.exports = (grunt) => {
done()
})

// Stops the Flowtype server.
grunt.registerTask('flow:stop', function () {
const done = this.async()
exec('./node_modules/.bin/flow stop', (err, stdout, stderr) => {
if (!err) {
grunt.log.writeln('Flowtype server stopped')
} else {
grunt.log.error('Could not stop Flowtype server:', err.message)
}
done(err)
})
})

// eslint-disable-next-line no-unused-vars
let killKeepAlive = null
grunt.registerTask('keepalive', function () {
Expand All @@ -628,27 +643,30 @@ module.exports = (grunt) => {
killKeepAlive = this.async()
})

grunt.registerTask('test', ['build', 'exec:chelDeployAll', 'backend:launch', 'exec:test', 'cypress'])
grunt.registerTask('test:unit', ['backend:launch', 'exec:test'])
grunt.registerTask('test', ['build', 'exec:chelDeployAll', 'deno:start', 'exec:test', 'cypress', 'deno:stop', 'flow:stop'])
grunt.registerTask('test:unit', ['deno:start', 'exec:test', 'deno:stop'])

// -------------------------------------------------------------------------
// Process event handlers
// -------------------------------------------------------------------------

process.on('exit', () => {
process.on('exit', (code, signal) => {
console.log('[node] Exiting with code:', code, 'signal:', signal)
// Note: 'beforeExit' doesn't work.
// In cases where 'watch' fails while child (server) is still running
// we will exit and child will continue running in the background.
// This can happen, for example, when running two GIS instances via
// the PORT_SHIFT envar. If grunt-contrib-watch livereload process
// cannot bind to the port for some reason, then the parent process
// will exit leaving a dangling child server process.
if (child) {
if (child && !child.killed) {
grunt.log.writeln('Quitting dangling child!')
child.send({ shutdown: 2 })
child.kill()
}
// Stops the Flowtype server.
exec('./node_modules/.bin/flow stop')
// Make sure to stop the Flowtype server in case `flow:stop` wasn't called.
exec('./node_modules/.bin/flow stop', () => {
grunt.log.writeln('Flowtype server stopped in process exit handler')
})
snowteamer marked this conversation as resolved.
Show resolved Hide resolved
})

process.on('uncaughtException', (err) => {
Expand Down
19 changes: 10 additions & 9 deletions backend/auth.js → backend/auth.ts
@@ -1,36 +1,37 @@
// create our auth plugin. see:
/* globals Deno */
// Create our auth plugin. See:
// https://hapijs.com/tutorials/auth
// https://hapijs.com/tutorials/plugins

import { verify, b64ToStr } from '~/shared/functions.js'

const Boom = require('@hapi/boom')
const { BadRequest, PermissionDenied } = Deno.errors

exports.plugin = {
export default {
name: 'gi-auth',
register: function (server: Object, opts: Object) {
register (server: Object, opts: Object) {
server.auth.scheme('gi-auth', function (server, options) {
return {
authenticate: function (request, h) {
authenticate (request, h) {
const { authorization } = request.headers
if (!authorization) h.unauthenticated(Boom.unauthorized('Missing authorization'))
if (!authorization) h.unauthenticated(new PermissionDenied('Missing authorization'))

let [scheme, json] = authorization.split(/\s+/)
// NOTE: if you want to add any signature verification, do it here
// eslint-disable-next-line no-constant-condition
if (false) {
if (!scheme.includes('gi')) h.unauthenticated(Boom.badRequest('Bad authentication'))
if (!scheme.includes('gi')) h.unauthenticated(new BadRequest('Bad authentication'))

try {
json = JSON.parse(b64ToStr(json))
} catch (e) {
return h.unauthenticated(Boom.badRequest('Invalid token format'))
return h.unauthenticated(new BadRequest('Invalid token format'))
}
// http://hapijs.com/api/#serverauthschemename-scheme
const isValid = verify(json.msg, json.key, json.sig)
json.userId = json.key
const credentials = { credentials: json }
if (!isValid) return h.unauthenticated(Boom.unauthorized('Bad credentials'), credentials)
if (!isValid) return h.unauthenticated(new PermissionDenied('Bad credentials'), credentials)
return h.authenticated(credentials)
} else {
// remove this if you decide to implement it
Expand Down