diff --git a/server/src/database/migrations.js b/server/src/database/migrations.js index 4b718a959..c540736aa 100644 --- a/server/src/database/migrations.js +++ b/server/src/database/migrations.js @@ -1,121 +1,129 @@ // @flow import fs from 'fs'; import type { QueryResults } from 'mysql'; import { getMessageForException } from 'lib/utils/errors'; import { dbQuery, SQL } from './database'; async function makeSureBaseRoutePathExists(filePath: string): Promise { const readFile = await fs.promises.open(filePath, 'r'); const contents = await readFile.readFile('utf8'); const json = JSON.parse(contents); await readFile.close(); if (json.baseRoutePath) { return; } let baseRoutePath; if (json.baseDomain === 'http://localhost') { baseRoutePath = json.basePath; } else if (filePath.endsWith('commapp_url.json')) { baseRoutePath = '/commweb/'; } else { baseRoutePath = '/'; } const newJSON = { ...json, baseRoutePath }; console.warn(`updating ${filePath} to ${JSON.stringify(newJSON)}`); const writeFile = await fs.promises.open(filePath, 'w'); await writeFile.writeFile(JSON.stringify(newJSON, null, ' '), 'utf8'); await writeFile.close(); } const migrations: $ReadOnlyMap Promise> = new Map([ [ 0, async () => { await makeSureBaseRoutePathExists('facts/commapp_url.json'); await makeSureBaseRoutePathExists('facts/squadcal_url.json'); }, ], + [ + 1, + async () => { + try { + await fs.promises.unlink('facts/url.json'); + } catch {} + }, + ], ]); async function migrate(): Promise { let dbVersion = null; try { dbVersion = await getDBVersion(); console.log(`(node:${process.pid}) DB version: ${dbVersion}`); } catch (e) { const dbVersionExceptionMessage = String(getMessageForException(e)); console.error(`(node:${process.pid}) ${dbVersionExceptionMessage}`); return false; } for (const [idx, migration] of migrations.entries()) { if (idx <= dbVersion) { continue; } try { await startTransaction(); await migration(); await updateDBVersion(idx); await commitTransaction(); console.log(`(node:${process.pid}) migration ${idx} succeeded.`); } catch (e) { const transactionExceptionMessage = String(getMessageForException(e)); console.error(`(node:${process.pid}) migration ${idx} failed.`); console.error(transactionExceptionMessage); await rollbackTransaction(); return false; } } return true; } async function getDBVersion(): Promise { const versionQuery = SQL` SELECT data FROM metadata WHERE name = 'db_version'; `; const [[versionResult]] = await dbQuery(versionQuery); if (!versionResult) { return -1; } return versionResult.data; } async function updateDBVersion(dbVersion: number): Promise { const updateQuery = SQL` INSERT INTO metadata (name, data) VALUES ('db_version', ${dbVersion}) ON DUPLICATE KEY UPDATE data = ${dbVersion}; `; return dbQuery(updateQuery); } async function startTransaction(): Promise { const beginTxnQuery = SQL` START TRANSACTION; `; return dbQuery(beginTxnQuery); } async function commitTransaction(): Promise { const endTxnQuery = SQL` COMMIT; `; return dbQuery(endTxnQuery); } async function rollbackTransaction(): Promise { const rollbackTxnQuery = SQL` ROLLBACK; `; return dbQuery(rollbackTxnQuery); } export { migrate }; diff --git a/server/src/server.js b/server/src/server.js index 8a1974bdb..6bcdc3b72 100644 --- a/server/src/server.js +++ b/server/src/server.js @@ -1,133 +1,137 @@ // @flow import cluster from 'cluster'; import cookieParser from 'cookie-parser'; import express from 'express'; import expressWs from 'express-ws'; import os from 'os'; import './cron/cron'; import { migrate } from './database/migrations'; import { jsonEndpoints } from './endpoints'; import { emailSubscriptionResponder } from './responders/comm-landing-responders'; import { jsonHandler, httpGetHandler, downloadHandler, htmlHandler, uploadHandler, } from './responders/handlers'; import landingHandler from './responders/landing-handler'; import { errorReportDownloadResponder } from './responders/report-responders'; import { createNewVersionResponder, markVersionDeployedResponder, } from './responders/version-responders'; import { websiteResponder } from './responders/website-responders'; import { onConnection } from './socket/socket'; import { multerProcessor, multimediaUploadResponder, uploadDownloadResponder, } from './uploads/uploads'; -import { getGlobalURLFacts, getLandingURLFacts } from './utils/urls'; +import { getSquadCalURLFacts, getLandingURLFacts } from './utils/urls'; -const { baseRoutePath } = getGlobalURLFacts(); +const squadCalBaseRoutePath = getSquadCalURLFacts().baseRoutePath; const landingBaseRoutePath = getLandingURLFacts().baseRoutePath; if (cluster.isMaster) { (async () => { const didMigrationsSucceed: boolean = await migrate(); if (!didMigrationsSucceed) { // The following line uses exit code 2 to ensure nodemon exits // in a dev environment, instead of restarting. Context provided // in https://github.com/remy/nodemon/issues/751 process.exit(2); } const cpuCount = os.cpus().length; for (let i = 0; i < cpuCount; i++) { cluster.fork(); } })(); cluster.on('exit', () => cluster.fork()); } else { const server = express(); expressWs(server); server.use(express.json({ limit: '50mb' })); server.use(cookieParser()); - const router = express.Router(); - router.use('/images', express.static('images')); - router.use('/commlanding/images', express.static('images')); - router.use('/fonts', express.static('fonts')); - router.use('/commlanding/fonts', express.static('fonts')); - router.use('/misc', express.static('misc')); - router.use( + const squadCalRouter = express.Router(); + squadCalRouter.use('/images', express.static('images')); + squadCalRouter.use('/fonts', express.static('fonts')); + squadCalRouter.use('/misc', express.static('misc')); + squadCalRouter.use( '/.well-known', express.static( '.well-known', // Necessary for apple-app-site-association file { setHeaders: res => res.setHeader('Content-Type', 'application/json'), }, ), ); const compiledFolderOptions = process.env.NODE_ENV === 'development' ? undefined : { maxAge: '1y', immutable: true }; - router.use( + squadCalRouter.use( '/compiled', express.static('app_compiled', compiledFolderOptions), ); - router.use( - '/commlanding/compiled', - express.static('landing_compiled', compiledFolderOptions), - ); - router.use('/', express.static('icons')); - router.use('/commlanding', express.static('landing_icons')); + squadCalRouter.use('/', express.static('icons')); for (const endpoint in jsonEndpoints) { // $FlowFixMe Flow thinks endpoint is string const responder = jsonEndpoints[endpoint]; const expectCookieInvalidation = endpoint === 'log_out'; - router.post( + squadCalRouter.post( `/${endpoint}`, jsonHandler(responder, expectCookieInvalidation), ); } - router.post('/commlanding/subscribe_email', emailSubscriptionResponder); - - router.get( + squadCalRouter.get( '/create_version/:deviceType/:codeVersion', httpGetHandler(createNewVersionResponder), ); - router.get( + squadCalRouter.get( '/mark_version_deployed/:deviceType/:codeVersion', httpGetHandler(markVersionDeployedResponder), ); - router.get( + squadCalRouter.get( '/download_error_report/:reportID', downloadHandler(errorReportDownloadResponder), ); - router.get( + squadCalRouter.get( '/upload/:uploadID/:secret', downloadHandler(uploadDownloadResponder), ); // $FlowFixMe express-ws has side effects that can't be typed - router.ws('/ws', onConnection); - router.get(`${landingBaseRoutePath}*`, landingHandler); - router.get('*', htmlHandler(websiteResponder)); + squadCalRouter.ws('/ws', onConnection); + squadCalRouter.get('*', htmlHandler(websiteResponder)); - router.post( + squadCalRouter.post( '/upload_multimedia', multerProcessor, uploadHandler(multimediaUploadResponder), ); - server.use(baseRoutePath, router); + server.use(squadCalBaseRoutePath, squadCalRouter); + + const landingRouter = express.Router(); + landingRouter.use('/images', express.static('images')); + landingRouter.use('/fonts', express.static('fonts')); + landingRouter.use( + '/compiled', + express.static('landing_compiled', compiledFolderOptions), + ); + landingRouter.use('/', express.static('landing_icons')); + landingRouter.post('/subscribe_email', emailSubscriptionResponder); + landingRouter.get('*', landingHandler); + + server.use(landingBaseRoutePath, landingRouter); + server.listen(parseInt(process.env.PORT, 10) || 3000, 'localhost'); } diff --git a/server/src/utils/urls.js b/server/src/utils/urls.js index 8511df6a1..dfea9ce3b 100644 --- a/server/src/utils/urls.js +++ b/server/src/utils/urls.js @@ -1,48 +1,38 @@ // @flow import commAppURLFacts from '../../facts/commapp_url'; import landingURLFacts from '../../facts/landing_url'; import squadCalURLFacts from '../../facts/squadcal_url'; -import baseURLFacts from '../../facts/url'; - -type GlobalURLFacts = { - +baseRoutePath: string, -}; - -function getGlobalURLFacts(): GlobalURLFacts { - return baseURLFacts; -} export type AppURLFacts = { +baseDomain: string, +basePath: string, +https: boolean, +baseRoutePath: string, }; function getSquadCalURLFacts(): AppURLFacts { return squadCalURLFacts; } function getCommAppURLFacts(): AppURLFacts { return commAppURLFacts; } function getAppURLFactsFromRequestURL(url: string): AppURLFacts { const commURLFacts = getCommAppURLFacts(); return url.startsWith(commURLFacts.basePath) ? commURLFacts : getSquadCalURLFacts(); } function getLandingURLFacts(): AppURLFacts { return landingURLFacts; } export { - getGlobalURLFacts, getSquadCalURLFacts, getCommAppURLFacts, getLandingURLFacts, getAppURLFactsFromRequestURL, };