Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions API/Backend/Config/setup.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const router = require("./routes/configs");
const triggerWebhooks = require("../Webhooks/processes/triggerwebhooks.js");
const configurePackageJson = require("../../../configure/package.json");
const { MODE, isLean } = require("../Utils/deploymentMode");
const { MODE } = require("../Utils/deploymentMode");
const { enabled } = require("../Utils/capabilities");

let setup = {
//Once the app initializes
Expand Down Expand Up @@ -36,12 +37,18 @@ let setup = {
? ""
: process.env.WEBSOCKET_ROOT_PATH || "",
IS_DOCKER: process.env.IS_DOCKER,
WITH_STAC: isLean() ? "false" : process.env.WITH_STAC,
WITH_TIPG: isLean() ? "false" : process.env.WITH_TIPG,
WITH_TITILER: isLean() ? "false" : process.env.WITH_TITILER,
WITH_TITILER_PGSTAC: isLean()
? "false"
: process.env.WITH_TITILER_PGSTAC,
WITH_STAC: enabled("localSidecars")
? process.env.WITH_STAC
: "false",
WITH_TIPG: enabled("localSidecars")
? process.env.WITH_TIPG
: "false",
WITH_TITILER: enabled("localSidecars")
? process.env.WITH_TITILER
: "false",
WITH_TITILER_PGSTAC: enabled("localSidecars")
? process.env.WITH_TITILER_PGSTAC
: "false",
DEPLOYMENT_MODE: MODE,
});
}
Expand Down
20 changes: 10 additions & 10 deletions API/Backend/Datasets/setup.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
const router = require("./routes/datasets");
const { isFull } = require("../Utils/deploymentMode");
let setup = {
// Gated off in lean. The discovery seam (API/setups.js) skips the
// feature-presence hooks when this capability is disabled.
capability: "datasets",
//Once the app initializes
onceInit: (s) => {
if (isFull()) {
s.app.use(
s.ROOT_PATH + "/api/datasets",
s.ensureAdmin(),
s.checkHeadersCodeInjection,
s.setContentType,
router
);
}
s.app.use(
s.ROOT_PATH + "/api/datasets",
s.ensureAdmin(),
s.checkHeadersCodeInjection,
s.setContentType,
router
);
},
//Once the server starts
onceStarted: (s) => {},
Expand Down
24 changes: 12 additions & 12 deletions API/Backend/Deployments/setup.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
const { isLean } = require("../Utils/deploymentMode");

// Requiring the model registers it with Sequelize so the global
// sequelize.sync() on boot creates the `deployments` table in BOTH modes —
// a later mode flip needs no migration. In full mode the table stays
// passive: the routes below are never mounted, so nothing writes to it.
// passive: the routes below are never mounted (capability gated off via the
// seam), so nothing writes to it.
require("./models/deployment");

let setup = {
// The publish flow exists ONLY in lean. The discovery seam mounts the routes
// below only when this capability is enabled.
capability: "deployments",
//Once the app initializes
onceInit: (s) => {
if (isLean()) {
const routeDeployments = require("./routes/deployments");
s.app.use(
s.ROOT_PATH + "/api/deployments",
s.ensureAdmin(),
s.checkHeadersCodeInjection,
routeDeployments.router
);
}
const routeDeployments = require("./routes/deployments");
s.app.use(
s.ROOT_PATH + "/api/deployments",
s.ensureAdmin(),
s.checkHeadersCodeInjection,
routeDeployments.router
);
},
//Once the server starts
onceStarted: (s) => {},
Expand Down
54 changes: 27 additions & 27 deletions API/Backend/Draw/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,39 @@ const routerDraw = require("./routes/draw").router;
const routerAggregations = require("./routes/aggregations");
const ufiles = require("./models/userfiles");
const file_histories = require("./models/filehistories");
const { isFull } = require("../Utils/deploymentMode");

let setup = {
// Gated off in lean (route mounts). onceSynced still runs unconditionally at
// the seam, so Draw's tables are created in both modes.
capability: "draw",
//Once the app initializes
onceInit: (s) => {
if (isFull()) {
s.app.use(
s.ROOT_PATH + "/api/files",
s.ensureUser(),
s.checkHeadersCodeInjection,
s.setContentType,
s.stopGuests,
routerFiles
);
s.app.use(
s.ROOT_PATH + "/api/files",
s.ensureUser(),
s.checkHeadersCodeInjection,
s.setContentType,
s.stopGuests,
routerFiles
);

s.app.use(
s.ROOT_PATH + "/api/draw",
s.ensureUser(),
s.checkHeadersCodeInjection,
s.setContentType,
s.stopGuests,
routerDraw
);
s.app.use(
s.ROOT_PATH + "/api/draw",
s.ensureUser(),
s.checkHeadersCodeInjection,
s.setContentType,
s.stopGuests,
routerDraw
);

s.app.use(
s.ROOT_PATH + "/api/draw",
s.ensureUser(),
s.checkHeadersCodeInjection,
s.setContentType,
s.stopGuests,
routerAggregations
);
}
s.app.use(
s.ROOT_PATH + "/api/draw",
s.ensureUser(),
s.checkHeadersCodeInjection,
s.setContentType,
s.stopGuests,
routerAggregations
);
},
//Once the server starts
onceStarted: (s) => {},
Expand Down
21 changes: 10 additions & 11 deletions API/Backend/Geodatasets/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@ const router = require("./routes/geodatasets");

const geodatasets = require("./models/geodatasets");

const { isFull } = require("../Utils/deploymentMode");

let setup = {
// Gated off in lean (route mounts). onceSynced still runs unconditionally at
// the seam, so the geodatasets table is created in both modes.
capability: "geodatasets",
//Once the app initializes
onceInit: (s) => {
if (isFull()) {
s.app.use(
s.ROOT_PATH + "/api/geodatasets",
s.ensureAdmin(),
s.checkHeadersCodeInjection,
s.setContentType,
router
);
}
s.app.use(
s.ROOT_PATH + "/api/geodatasets",
s.ensureAdmin(),
s.checkHeadersCodeInjection,
s.setContentType,
router
);
},
//Once the server starts
onceStarted: (s) => {},
Expand Down
20 changes: 10 additions & 10 deletions API/Backend/Shortener/setup.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
const router = require("./routes/shortener");
const { isFull } = require("../Utils/deploymentMode");

let setup = {
// Gated off in lean. The discovery seam (API/setups.js) skips the
// feature-presence hooks when this capability is disabled.
capability: "shortener",
//Once the app initializes
onceInit: (s) => {
if (isFull()) {
s.app.use(
s.ROOT_PATH + "/api/shortener",
s.ensureUser(),
s.checkHeadersCodeInjection,
s.setContentType,
router
);
}
s.app.use(
s.ROOT_PATH + "/api/shortener",
s.ensureUser(),
s.checkHeadersCodeInjection,
s.setContentType,
router
);
},
//Once the server starts
onceStarted: (s) => {},
Expand Down
77 changes: 77 additions & 0 deletions API/Backend/Utils/capabilities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* capabilities.js
* The single authoritative definition of what each deployment mode includes.
*
* `deploymentMode.js` stays the one env read (MMGIS_DEPLOYMENT_MODE -> MODE);
* this file is the one *meaning* of each mode. Reading the FEATURES map below
* answers "what does lean turn off (and on)" without grepping call sites.
*
* A capability's rule is a predicate over a small context ({ mode }), so a
* future composite condition (two axes) does not force a rewrite from a flat
* boolean table. Booleans and mode-lists are accepted as sugar for the common
* single-axis case; full predicates are used only where they earn their keep.
*
* Contract (backend): an unknown capability THROWS — a typo is a deploy error,
* not a silently-disabled feature. (The frontend twin in
* configure/src/core/capabilities.js deliberately warns + hides instead, so a
* render-time gate fails safe rather than white-screening the SPA.)
*
* Transitional artifact: enumerating every gated feature by name in the core is
* the kind of core-knows-every-feature coupling the plugin direction aims to
* dissolve. The durable half is that a module/tool *declares* the capability it
* needs and a seam decides; expect a future plugin manifest to subsume this map.
*/

const { MODE } = require("./deploymentMode");

// Sugar coercions:
// - boolean -> enabled in every mode (or none)
// - string[] -> enabled only in the listed modes
// - function -> predicate over { mode }
const coerce = (rule) => {
if (typeof rule === "function") return rule;
if (Array.isArray(rule)) return ({ mode }) => rule.includes(mode);
if (typeof rule === "boolean") return () => rule;
throw new Error(`Invalid capability rule: ${JSON.stringify(rule)}`);
};

// enabling mode(s)
const FEATURES = {
// Geodata management — gated off in lean.
datasets: ["full"],
geodatasets: ["full"],
// Collaborative drawing — gated off in lean.
draw: ["full"],
// Link shortener — gated off in lean.
shortener: ["full"],
// Dashboard publish flow — exists ONLY in lean.
deployments: ["lean"],
// On-disk Missions/ filesystem (static mount) plus the server-side raster /
// SPICE utils endpoints (GDAL + Python shellouts) that read it. Not a
// sidecar — its own capability.
localMissions: ["full"],
// The bundled sidecar cluster: the adjacent-servers proxy mounts, the
// adjacent-servers spawner, the mmgis-stac database creation, and the
// derived WITH_* Configure flags. They always move together, so one row.
localSidecars: ["full"],
};

const RULES = Object.fromEntries(
Object.entries(FEATURES).map(([name, rule]) => [name, coerce(rule)])
);

/**
* enabled(capability) -> boolean
* Throws on an unknown capability so typos fail at boot.
*/
const enabled = (capability) => {
const rule = RULES[capability];
if (rule == null) {
throw new Error(
`Unknown capability: '${capability}'. Add it to API/Backend/Utils/capabilities.js or fix the typo.`
);
}
return rule({ mode: MODE });
};

module.exports = { enabled };
6 changes: 3 additions & 3 deletions API/Backend/Utils/routes/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const execFile = require("child_process").execFile;
const Sequelize = require("sequelize");
const { sequelizeSTAC } = require("../../../connection");
const logger = require("../../../logger");
const { isFull } = require("../deploymentMode");
const { enabled } = require("../capabilities");

const rootDir = `${__dirname}/../../../..`;

Expand Down Expand Up @@ -240,7 +240,7 @@ router.get("/healthcheck", function (req, res) {
// Missions/ tree, local SPICE data, or shells out to Python — none of which
// exist in a lean container. healthcheck (above) is the only utils route
// lean serves.
if (isFull()) {
if (enabled("localMissions")) {
// Reads the on-disk Missions/<...>/_time_/ tree
router.get("/queryTilesetTimes", function (req, res) {
if (req.query.stacCollection != null) queryTilesetTimesStac(req, res);
Expand Down Expand Up @@ -400,6 +400,6 @@ if (isFull()) {
}
);
});
} // if (isFull()) — full-only utils routes
} // if (enabled("localMissions")) — full-only utils routes

module.exports = router;
24 changes: 23 additions & 1 deletion API/setups.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@ const fs = require("fs");
const path = require("path");

const logger = require("./logger");
const { enabled } = require("./Backend/Utils/capabilities");

// A setup with no `capability` is always wired (Users, Accounts, Config, …).
// A setup declaring `capability: "<name>"` is wired only when that capability
// is enabled in the current deployment mode. This is the single seam where the
// per-module on/off decision lives — module bodies stay mode-agnostic.
// INVARIANT: this gates the feature-presence hooks (onceInit, onceStarted)
// ONLY. onceSynced (model sync) runs unconditionally, so gated-off features
// still get their tables in every mode and a later mode flip needs no
// migration. Because the gate lives here, a gated setup's onceInit assumes it
// is enabled — only this discovery seam may call it.
const isSetupEnabled = (setup) =>
setup == null || setup.capability == null || enabled(setup.capability);

let getBackendSetups = (cb) => {
let setups = {};
Expand Down Expand Up @@ -170,15 +183,24 @@ let getBackendSetups = (cb) => {
cb({
init: (s) => {
for (let f in setups) {
// A module declaring `capability` is wired only when that
// capability is enabled in the current mode. onceInit is
// feature-presence (route mounts) — gate it.
if (!isSetupEnabled(setups[f])) continue;
if (typeof setups[f].onceInit === "function") setups[f].onceInit(s);
}
},
started: (s) => {
for (let f in setups)
for (let f in setups) {
// onceStarted is feature-presence too — same gate as onceInit.
if (!isSetupEnabled(setups[f])) continue;
if (typeof setups[f].onceStarted === "function")
setups[f].onceStarted(s);
}
},
synced: (s) => {
// UNCONDITIONAL by design: model sync must run in every mode so
// gated-off tables still exist (no migration on a later mode flip).
for (let f in setups)
if (typeof setups[f].onceSynced === "function")
setups[f].onceSynced(s);
Expand Down
Loading