Skip to content

Commit

Permalink
feat: sync vocab state changes across sessions using Socket.IO
Browse files Browse the repository at this point in the history
  • Loading branch information
kyle1an committed May 4, 2024
1 parent fd79076 commit da3a44a
Show file tree
Hide file tree
Showing 31 changed files with 1,435 additions and 826 deletions.
7 changes: 7 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/.git
/node_modules
.dockerignore
.env
Dockerfile
fly.toml
/dist
43 changes: 43 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# syntax = docker/dockerfile:1

# Adjust NODE_VERSION as desired
ARG NODE_VERSION=22.0.0
FROM node:${NODE_VERSION}-slim as base

LABEL fly_launch_runtime="Node.js"

# Node.js app lives here
WORKDIR /app

# Set production environment
# ENV NODE_ENV="production"

# Install pnpm
ARG PNPM_VERSION=9.0.6
RUN npm install -g pnpm@$PNPM_VERSION
ARG TYPESCRIPT_VERSION=5.4.5
RUN npm install -g typescript@$TYPESCRIPT_VERSION

# Throw-away build stage to reduce size of final image
FROM base as build

# Install packages needed to build node modules
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential node-gyp pkg-config python-is-python3

# Install node modules
COPY --link package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

# Copy application code
COPY --link . .

# Final stage for app image
FROM base

# Copy built application
COPY --from=build /app /app

# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD [ "pnpm", "run", "start" ]
99 changes: 86 additions & 13 deletions backend/app.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import path from 'path'
import { createServer } from 'http'
import createError from 'http-errors'
import express from 'express'
import cookieParser from 'cookie-parser'
import logger from 'morgan'
import cors from 'cors'
import type { ErrorRequestHandler, NextFunction, Request, Response } from 'express'
import * as Sentry from '@sentry/node'
import routes from './routes'
import { Server } from 'socket.io'
import Debug from 'debug'
import { parse } from 'cookie'
import routes from './src/routes'
import { isTokenValid } from './src/utils/util'
import type { CookiesObj } from './src/routes/auth'
import type { ClientToServerEvents, InterServerEvents, ServerToClientEvents, SocketData } from './src/types'

const app = express()
Sentry.init({
Expand All @@ -31,21 +38,21 @@ app.use(Sentry.Handlers.requestHandler() as express.RequestHandler)
// TracingHandler creates a trace for every incoming request
app.use(Sentry.Handlers.tracingHandler())

const corsOrigin = [
/.*localhost.*$/,
/.*127.0.0.1.*$/,
/.*10.207.1.106.*$/,
/.*198.18.0.1.*$/,
/.*subvocab.netlify.app/,
/.*subvocab.*.vercel.app/,
/.*sub-vocab.*.vercel.app/,
]
app.use(cors({
origin: [
/.*localhost.*$/,
/.*127.0.0.1.*$/,
/.*10.207.1.106.*$/,
/.*198.18.0.1.*$/,
/.*subvocab.netlify.app/,
/.*subvocab.*.vercel.app/,
/.*sub-vocab.*.vercel.app/,
],
origin: corsOrigin,
credentials: true,
exposedHeaders: ['set-cookie'],
}))

// view engine setup
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'hbs')

Expand All @@ -55,12 +62,11 @@ app.use(express.urlencoded({ extended: false }))
// need cookieParser middleware before we can do anything with cookies
app.use(cookieParser())
// let static middleware do its job
// app.use(express.static(__dirname + '/public'));
app.use(express.static(path.join(__dirname, 'public')))

app.use('/', routes)

app.use(Sentry.Handlers.errorHandler() as express.ErrorRequestHandler)
app.use(Sentry.Handlers.errorHandler())

// catch 404 and forward to error handler
app.use(function (req, res, next) {
Expand All @@ -77,4 +83,71 @@ app.use(function (err: Parameters<ErrorRequestHandler>[0], req: Request, res: Re
res.render('error')
})

const PORT = Number(process.env.PORT || '5001')
app.set('port', PORT)

const server = createServer(app)

export const io = new Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>(server, {
cors: {
origin: [
/.*localhost.*$/,
/.*127.0.0.1.*$/,
/.*10.207.1.106.*$/,
/.*198.18.0.1.*$/,
'https://subvocab.netlify.app',
/.*subvocab.netlify.app/,
'https://subvocab.vercel.app',
/.*subvocab.*.vercel.app/,
/.*sub-vocab.*.vercel.app/,
],
credentials: true,
},
})

io.on('connection', (socket) => {
const { _user: username = '', acct = '' } = parse(socket.handshake.headers.cookie ?? '') as CookiesObj
isTokenValid(username, acct).then(async (isValid) => {
if (isValid) {
await socket.join(username)
}
}).catch(console.error)
})

server.listen(PORT, () => {
console.log(`Socket server running at ${PORT}`)
})

server.on('error', function onError(error: NodeJS.ErrnoException) {
if (error.syscall !== 'listen') {
throw error
}

const bind = 'Port ' + PORT

switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges')
process.exit(1)
break
case 'EADDRINUSE':
console.error(bind + ' is already in use')
process.exit(1)
break
default:
throw error
}
})

const debug = Debug('subvocab-server:server')

server.on('listening', function onListening() {
const addr = server.address()
const bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr?.port
debug('Listening on ' + bind)
console.log(`Listening on port ${PORT}`)
})

export default app
86 changes: 0 additions & 86 deletions backend/bin/www.ts

This file was deleted.

4 changes: 2 additions & 2 deletions backend/env.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
declare let process: {
env: {
namespace NodeJS {
interface ProcessEnv {
DATABASE: string
DB_PASSWORD: string
DB_USER: string
Expand Down
22 changes: 22 additions & 0 deletions backend/fly.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# fly.toml app configuration file generated for subvocab on 2024-05-04T18:15:33+08:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#

app = 'subvocab'
primary_region = 'sin'

[build]

[http_service]
internal_port = 5001
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 1
processes = ['app']

[[vm]]
memory = '1gb'
cpu_kind = 'shared'
cpus = 1
14 changes: 11 additions & 3 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
"version": "0.0.0",
"private": true,
"scripts": {
"start": "nodemon ./bin/www",
"dev": "nodemon app",
"build": "tsc",
"copy-files": "cp -R views dist && cp -R public dist",
"start": "npm run build && npm run copy-files && node dist/app.js",
"lint": "eslint . --ext .js,.ts --report-unused-disable-directives",
"lint:fix": "npm run lint -- --fix"
},
"dependencies": {
"@sentry/node": "^7.112.2",
"@sentry/node": "^7.113.0",
"@types/cookie": "^0.6.0",
"cookie": "^0.6.0",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"date-fns": "^3.6.0",
Expand All @@ -19,9 +24,12 @@
"http-errors": "^2.0.0",
"lru-cache": "^10.2.2",
"morgan": "^1.10.0",
"mysql2": "^3.9.7"
"mysql2": "^3.9.7",
"socket.io": "^4.7.5",
"type-fest": "^4.18.1"
},
"devDependencies": {
"@flydotio/dockerfile": "^0.5.7",
"@stylistic/eslint-plugin": "^1.8.0",
"@total-typescript/ts-reset": "^0.5.1",
"@types/cookie-parser": "^1.4.7",
Expand Down
Loading

0 comments on commit da3a44a

Please sign in to comment.