Skip to content

Commit 115b9ae

Browse files
authored
Merge pull request #266 from UW-Macrostrat/composite-projects
Composite projects
2 parents ec300e3 + 25ed403 commit 115b9ae

File tree

16 files changed

+405
-457
lines changed

16 files changed

+405
-457
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@macrostrat/api-v2",
3-
"version": "2.1.8",
3+
"version": "2.2.0",
44
"description": "An API for stratigraphic and geological information (Version 2).",
55
"main": "server.ts",
66
"repository": {

server.ts

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,48 @@ const dotenv = require("dotenv");
22
// Load environment variables from .env file
33
dotenv.config();
44

5+
import { buildAPI } from "./v2";
6+
57
var express = require("express"),
6-
bodyParser = require("body-parser"),
7-
//v1 = require("./v1"),
8-
v2 = require("./v2"),
9-
defs = require("./v2/defs"),
10-
app = express();
8+
bodyParser = require("body-parser");
9+
//defs = require("./v2/defs"),
1110

12-
// parse application/x-www-form-urlencoded
13-
app.use(bodyParser.urlencoded({ extended: false }));
11+
//TODO: update port to designated env.
12+
const listenPort = process.argv[2] ?? process.env.PORT ?? 5000;
1413

15-
// parse application/json
16-
app.use(bodyParser.json());
14+
async function runServer() {
15+
const app = express();
16+
// parse application/x-www-form-urlencoded
17+
app.use(bodyParser.urlencoded({ extended: false }));
1718

18-
// parse application/vnd.api+json as json
19-
app.use(bodyParser.json({ type: "application/vnd.api+json" }));
19+
// parse application/json
20+
app.use(bodyParser.json());
2021

21-
// Load and prefix all routes with /api and appropriate version
22-
app.use("/v2", v2);
22+
// parse application/vnd.api+json as json
23+
app.use(bodyParser.json({ type: "application/vnd.api+json" }));
2324

24-
app.route("/v1*").get(function (req, res, next) {
25-
res.status(410).send({
26-
error:
27-
"Macrostrat's v1 API has been retired. Please update your usage to newer endpoints.",
28-
});
29-
});
25+
const v2 = await buildAPI();
3026

31-
// If no version specified, fall back to more current
32-
app.use("/", v2);
27+
// Load and prefix all routes with /api and appropriate version
28+
app.use("/v2", v2);
3329

34-
app.set("json spaces", 2);
30+
app.route("/v1*").get(function (req, res, next) {
31+
res.status(410).send({
32+
error:
33+
"Macrostrat's v1 API has been retired. Please update your usage to newer endpoints.",
34+
});
35+
});
3536

36-
//TODO: update port to designated env.
37-
app.port = process.argv[2] ?? process.env.PORT ?? 5000;
37+
// If no version specified, fall back to more current
38+
app.use("/", v2);
3839

39-
app.start = function () {
40-
app.listen(app.port, function () {
41-
console.log("Listening on port " + app.port);
42-
});
43-
};
40+
app.set("json spaces", 2);
4441

45-
if (!module.parent) {
46-
app.start();
42+
app.listen(listenPort, function () {
43+
console.log("Listening on port " + listenPort);
44+
});
4745
}
4846

49-
module.exports = app;
47+
runServer().catch((error) => {
48+
console.error("Failed to start server:", error);
49+
});

v2/column-cache-refresh.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
const larkin = require("./larkin");
2-
31
module.exports = (req, res, next) => {
42
if (
53
req.query &&
64
req.query.cacheRefreshKey &&
75
req.query.cacheRefreshKey === process.env.CACHE_REFRESH_KEY
86
) {
9-
larkin.setupCache();
10-
res.json({ success: "cache refreshed" });
7+
res.status(404);
8+
res.json({ fail: "internal column cache refresh is no longer supported" });
119
} else {
1210
res.status(401);
1311
res.json({ fail: "you do not have permissions to execute this action" });

v2/columns.ts

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { handleUnitsRoute } from "./units";
2+
13
var api = require("./api"),
24
async = require("async"),
35
dbgeo = require("dbgeo"),
@@ -12,24 +14,9 @@ module.exports = function (req, res, next, callback) {
1214

1315
async.waterfall(
1416
[
15-
// First pass the request to the /units route and get the long response
1617
function (callback) {
17-
if ("all" in req.query) {
18-
larkin.cache.fetch("unitSummary", function (data) {
19-
callback(null, data);
20-
});
21-
} else {
22-
callback(null, null);
23-
}
24-
},
25-
26-
function (data, callback) {
27-
if (data) {
28-
return callback(null, data);
29-
}
30-
3118
//call units to group units by col_id
32-
require("./units")(req, null, null, function (error, result) {
19+
handleUnitsRoute(req, null, null, function (error, result) {
3320
if (error) {
3421
callback(error);
3522
}
@@ -122,19 +109,22 @@ module.exports = function (req, res, next, callback) {
122109
return callback(null, null, []);
123110
}
124111

125-
if ("all" in req.query) {
126-
if (req.query.format && api.acceptedFormats.geo[req.query.format]) {
127-
larkin.cache.fetch("columnsGeom", function (data) {
128-
callback(null, new_cols, data);
129-
});
130-
} else {
131-
larkin.cache.fetch("columnsNoGeom", function (data) {
132-
callback(null, new_cols, data);
133-
});
134-
}
135-
} else {
136-
callback(null, new_cols, null);
137-
}
112+
callback(null, new_cols, null);
113+
114+
// TODO: this breaks "all" filtering and brings down the API
115+
// if ("all" in req.query) {
116+
// if (req.query.format && api.acceptedFormats.geo[req.query.format]) {
117+
// larkin.cache.fetch("columnsGeom", function (data) {
118+
// callback(null, new_cols, data);
119+
// });
120+
// } else {
121+
// larkin.cache.fetch("columnsNoGeom", function (data) {
122+
// callback(null, new_cols, data);
123+
// });
124+
// }
125+
// } else {
126+
// callback(null, new_cols, null);
127+
// }
138128
},
139129

140130
// Using the unique column IDs returned from units, query columns

v2/definitions/columns.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ var api = require("../api");
22
var larkin = require("../larkin");
33
var dbgeo = require("dbgeo");
44

5+
import { buildProjectsFilter } from "../utils";
6+
57
module.exports = function (req, res, next, cb) {
68
if (Object.keys(req.query).length < 1) {
79
return larkin.info(req, res, next);
@@ -44,17 +46,23 @@ module.exports = function (req, res, next, cb) {
4446
where.push("cols.col_name = ANY(:col_name)");
4547
params["col_name"] = larkin.parseMultipleStrings(req.query.col_name);
4648
}
47-
if (req.query.project_id) {
48-
where.push("cols.project_id = ANY(:project_id)");
49-
params["project_id"] = larkin.parseMultipleIds(req.query.project_id);
50-
}
49+
50+
const [whereClauses, projectParams] = buildProjectsFilter(
51+
req,
52+
"cols.project_id",
53+
);
54+
where = where.concat(whereClauses);
55+
Object.assign(params, projectParams);
56+
57+
where.push("status_code = ANY(:status_code)");
5158
if (req.query.status_code || req.query.status) {
5259
// `status` parameter still works but has been superseded by `status_code`
5360
// multiple status codes can be provided
54-
where.push("status_code = ANY(:status_code)");
5561
params["status_code"] = larkin.parseMultipleIds(
5662
req.query.status_code ?? req.query.status,
5763
);
64+
} else {
65+
params["status_code"] = ["active"];
5866
}
5967

6068
if (where.length) {

v2/definitions/groups.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
var api = require("../api"),
22
larkin = require("../larkin");
33

4+
import { buildProjectsFilter } from "../utils";
5+
46
module.exports = function (req, res, next, cb) {
57
if (Object.keys(req.query).length < 1) {
68
return larkin.info(req, res, next);
@@ -18,15 +20,16 @@ module.exports = function (req, res, next, cb) {
1820
if (req.query.col_group_id) {
1921
where.push("col_groups.id = ANY(:col_group_id)");
2022
params["col_group_id"] = larkin.parseMultipleIds(req.query.col_group_id);
21-
} else if (req.query.project_id) {
22-
where.push("cols.project_id = ANY(:project_id)");
23-
params["project_id"] = larkin.parseMultipleIds(req.query.project_id);
24-
} else if (req.query.col_id) {
25-
where.push("cols.id = ANY(:col_id)");
26-
params["col_id"] = larkin.parseMultipleIds(req.query.col_id);
2723
}
2824

29-
where = where.length ? "WHERE " + where.join(" AND ") : "";
25+
const [projectWhereClauses, projectParams] = buildProjectsFilter(
26+
req,
27+
"cols.project_id",
28+
);
29+
where = where.concat(projectWhereClauses);
30+
Object.assign(params, projectParams);
31+
32+
const whereClause = where.length ? "WHERE " + where.join(" AND ") : "";
3033

3134
let sql = `SELECT col_groups.id AS col_group_id,
3235
col_group,
@@ -37,7 +40,7 @@ module.exports = function (req, res, next, cb) {
3740
FROM macrostrat.col_groups
3841
LEFT JOIN macrostrat.cols ON cols.col_group_id = col_groups.id
3942
LEFT JOIN macrostrat.units_sections ON units_sections.col_id = cols.id
40-
${where}
43+
${whereClause}
4144
GROUP BY col_groups.id, cols.project_id `;
4245

4346
if ("sample" in req.query) {

v2/definitions/projects.ts

Lines changed: 70 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -7,62 +7,83 @@ module.exports = function (req, res, next, cb) {
77
}
88
//There will be a discrepancy with a key in production. Updated in_proccess_cols to in_process_cols key. Values are
99
//still the same.
10-
var sql = `WITH in_proc AS (
11-
SELECT COUNT(DISTINCT id) AS c, project_id
12-
FROM macrostrat.cols
13-
WHERE status_code = 'in process'
14-
GROUP BY project_id
15-
),
16-
obs AS (
17-
SELECT COUNT(DISTINCT id) AS co, project_id
18-
FROM macrostrat.cols
19-
WHERE status_code = 'obsolete'
20-
GROUP BY project_id
21-
),
22-
col_area_sum AS (
23-
SELECT project_id, SUM(col_area) AS total_area
24-
FROM macrostrat.cols
25-
WHERE status_code = 'active'
26-
GROUP BY project_id
27-
)
28-
29-
SELECT
30-
projects.id AS project_id,
31-
projects.project,
32-
projects.descrip,
33-
projects.timescale_id,
34-
COUNT(DISTINCT units_sections.col_id)::integer AS t_cols,
35-
COALESCE(c, 0)::integer AS in_process_cols,
36-
COALESCE(co, 0)::integer AS obsolete_cols,
37-
COUNT(DISTINCT units_sections.unit_id)::integer AS t_units,
38-
COALESCE(ROUND(total_area), 0)::integer AS area
39-
40-
FROM macrostrat.projects
41-
LEFT JOIN macrostrat.cols ON projects.id = cols.project_id
42-
LEFT JOIN macrostrat.units_sections ON units_sections.col_id = cols.id
43-
LEFT JOIN in_proc USING (project_id)
44-
LEFT JOIN obs USING (project_id)
45-
LEFT JOIN col_area_sum ON projects.id = col_area_sum.project_id
46-
`;
4710

48-
var where = [];
49-
var params = {};
11+
const where = [];
12+
let params = {};
5013

5114
if (req.query.project_id) {
5215
where.push("projects.id = ANY(:project_id)");
5316
params["project_id"] = larkin.parseMultipleIds(req.query.project_id);
5417
}
55-
if (where.length) {
56-
sql += ` WHERE ${where.join(" AND ")}`;
18+
19+
const whereStatement = where.length > 0 ? where.join(" AND ") : "true";
20+
21+
let sql = `
22+
SELECT
23+
p.id AS project_id,
24+
p.project,
25+
p.descrip,
26+
p.timescale_id,
27+
count(DISTINCT units_sections.col_id)::integer AS t_cols,
28+
count(DISTINCT cols.id) FILTER ( WHERE cols.status_code = 'active' )::integer AS active_cols,
29+
count(DISTINCT cols.id) FILTER ( WHERE cols.status_code = 'in process' )::integer AS in_process_cols,
30+
count(DISTINCT cols.id) FILTER ( WHERE cols.status_code = 'obsolete' )::integer AS obsolete_cols,
31+
count(DISTINCT units_sections.unit_id)::integer AS t_units,
32+
coalesce(round(sum(DISTINCT cols.col_area) FILTER ( WHERE cols.status_code = 'active')), 0) AS area
33+
FROM macrostrat.projects p
34+
LEFT JOIN macrostrat.cols ON p.id = cols.project_id
35+
LEFT JOIN macrostrat.units_sections ON units_sections.col_id = cols.id
36+
WHERE ${whereStatement}
37+
GROUP BY
38+
p.id,
39+
p.project,
40+
p.descrip,
41+
p.timescale_id
42+
`;
43+
44+
if (larkin.hasCapability("composite-projects")) {
45+
/** Progressive enhancement for composite projects **/
46+
sql = `
47+
WITH composite_tree AS (
48+
SELECT pt.parent_id, array_agg(pt.child_id) children, jsonb_agg(to_jsonb(p)) AS members
49+
FROM macrostrat.projects_tree pt
50+
JOIN LATERAL (
51+
SELECT p.id, p.slug, p.project name
52+
FROM macrostrat.projects p
53+
WHERE p.id = pt.child_id
54+
) AS p ON true
55+
GROUP BY pt.parent_id
56+
)
57+
SELECT
58+
p.id AS project_id,
59+
p.slug,
60+
p.project,
61+
p.descrip,
62+
p.timescale_id,
63+
ct.members,
64+
count(DISTINCT units_sections.col_id)::integer AS t_cols,
65+
count(DISTINCT cols.id) FILTER ( WHERE cols.status_code = 'active' )::integer AS active_cols,
66+
count(DISTINCT cols.id) FILTER ( WHERE cols.status_code = 'in process' )::integer AS in_process_cols,
67+
count(DISTINCT cols.id) FILTER ( WHERE cols.status_code = 'obsolete' )::integer AS obsolete_cols,
68+
count(DISTINCT units_sections.unit_id)::integer AS t_units,
69+
coalesce(round(sum(DISTINCT cols.col_area) FILTER ( WHERE cols.status_code = 'active')), 0) AS area
70+
FROM macrostrat.projects p
71+
LEFT JOIN composite_tree ct
72+
ON ct.parent_id = p.id
73+
LEFT JOIN macrostrat.cols ON p.id = cols.project_id
74+
OR (p.is_composite AND cols.project_id = ANY(ct.children))
75+
LEFT JOIN macrostrat.units_sections ON units_sections.col_id = cols.id
76+
WHERE ${whereStatement}
77+
GROUP BY
78+
p.id,
79+
p.project,
80+
p.descrip,
81+
p.timescale_id,
82+
p.slug,
83+
ct.children,
84+
ct.members
85+
`;
5786
}
58-
sql += `\nGROUP BY
59-
projects.id,
60-
projects.project,
61-
projects.descrip,
62-
projects.timescale_id,
63-
c,
64-
co,
65-
total_area;`;
6687

6788
larkin.queryPg("burwell", sql, params, function (error, data) {
6889
if (error) {

0 commit comments

Comments
 (0)