diff --git a/keyserver/src/database/migrations.js b/keyserver/src/database/migrations.js index 60f7996ea..ff1e5b081 100644 --- a/keyserver/src/database/migrations.js +++ b/keyserver/src/database/migrations.js @@ -1,152 +1,168 @@ // @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(); + let readFile, json; + try { + readFile = await fs.promises.open(filePath, 'r'); + const contents = await readFile.readFile('utf8'); + json = JSON.parse(contents); + } catch { + return; + } finally { + if (readFile) { + 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(); } async function fixBaseRoutePathForLocalhost(filePath: string): Promise { - const readFile = await fs.promises.open(filePath, 'r'); - const contents = await readFile.readFile('utf8'); - let json = JSON.parse(contents); - await readFile.close(); + let readFile, json; + try { + readFile = await fs.promises.open(filePath, 'r'); + const contents = await readFile.readFile('utf8'); + json = JSON.parse(contents); + } catch { + return; + } finally { + if (readFile) { + await readFile.close(); + } + } if (json.baseDomain !== 'http://localhost') { return; } const baseRoutePath = '/'; json = { ...json, baseRoutePath }; console.warn(`updating ${filePath} to ${JSON.stringify(json)}`); const writeFile = await fs.promises.open(filePath, 'w'); await writeFile.writeFile(JSON.stringify(json, 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 {} }, ], [ 2, async () => { await fixBaseRoutePathForLocalhost('facts/commapp_url.json'); await fixBaseRoutePathForLocalhost('facts/squadcal_url.json'); }, ], ]); 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/keyserver/src/utils/urls.js b/keyserver/src/utils/urls.js index a23b86dbd..2d93d4b72 100644 --- a/keyserver/src/utils/urls.js +++ b/keyserver/src/utils/urls.js @@ -1,81 +1,85 @@ // @flow -import { values } from 'lib/utils/objects'; +import invariant from 'invariant'; -import commAppURLFacts from '../../facts/commapp_url'; -import landingURLFacts from '../../facts/landing_url'; -import squadCalURLFacts from '../../facts/squadcal_url'; +import { values } from 'lib/utils/objects'; export type AppURLFacts = { +baseDomain: string, +basePath: string, +https: boolean, +baseRoutePath: string, }; const sitesObj = Object.freeze({ a: 'landing', b: 'commapp', c: 'squadcal', }); export type Site = $Values; const sites: $ReadOnlyArray = values(sitesObj); const cachedURLFacts = new Map(); async function fetchURLFacts(site: Site): Promise { const cached = cachedURLFacts.get(site); if (cached !== undefined) { return cached; } try { // $FlowFixMe const urlFacts = await import(`../../facts/${site}_url`); if (!cachedURLFacts.has(site)) { cachedURLFacts.set(site, urlFacts.default); } } catch { if (!cachedURLFacts.has(site)) { cachedURLFacts.set(site, null); } } return cachedURLFacts.get(site); } async function prefetchAllURLFacts() { await Promise.all(sites.map(fetchURLFacts)); } function getSquadCalURLFacts(): AppURLFacts { - return squadCalURLFacts; + const urlFacts = cachedURLFacts.get('squadcal'); + invariant(urlFacts, 'keyserver/facts/squadcal_url.json missing'); + return urlFacts; } function getCommAppURLFacts(): AppURLFacts { - return commAppURLFacts; + const urlFacts = cachedURLFacts.get('commapp'); + invariant(urlFacts, 'keyserver/facts/commapp_url.json missing'); + return urlFacts; } function getAppURLFactsFromRequestURL(url: string): AppURLFacts { const commURLFacts = getCommAppURLFacts(); - return url.startsWith(commURLFacts.baseRoutePath) + return commURLFacts && url.startsWith(commURLFacts.baseRoutePath) ? commURLFacts : getSquadCalURLFacts(); } function getLandingURLFacts(): AppURLFacts { - return landingURLFacts; + const urlFacts = cachedURLFacts.get('landing'); + invariant(urlFacts, 'keyserver/facts/landing_url.json missing'); + return urlFacts; } function clientPathFromRouterPath( routerPath: string, urlFacts: AppURLFacts, ): string { const { basePath } = urlFacts; return basePath + routerPath; } export { prefetchAllURLFacts, getSquadCalURLFacts, getCommAppURLFacts, getLandingURLFacts, getAppURLFactsFromRequestURL, clientPathFromRouterPath, };