Skip to content

Commit

Permalink
Total restructuring inside the /api dir:
Browse files Browse the repository at this point in the history
- v1 and v2 have their own dirs
- I appended a .v2 to all logger definitions for API V2 controllers/services
- I removed the obsolete endpoints from V2
- Added the new endpoints: PUT and DELETE /mapconfig/{name} instead of /mapconfig/create/{name} and /mapconfig/delete/{name}. This was already changed in the API spec so we will now conform to that
- This is by no means complete (at least I think there's room for improvement). See this as a proposal for future structure inside the Backend dir and please let me know what you think in #1309.
  • Loading branch information
jacobwod committed Mar 30, 2023
1 parent 48a4c8a commit 895629d
Show file tree
Hide file tree
Showing 54 changed files with 2,687 additions and 16 deletions.
File renamed without changes.
File renamed without changes.
43 changes: 43 additions & 0 deletions new-backend/server/apis/v2/controllers/ad/controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import ActiveDirectoryService from "../../services/activedirectory.service";
import handleStandardResponse from "../../utils/handleStandardResponse";
import log4js from "log4js";

// Create a logger for admin events, those will be saved in a separate log file.
const ael = log4js.getLogger("adminEvent.v2");

export class Controller {
availableADGroups(req, res) {
ActiveDirectoryService.getAvailableADGroups().then((data) =>
handleStandardResponse(res, data)
);
}

findCommonADGroupsForUsers(req, res) {
ActiveDirectoryService.findCommonADGroupsForUsers(req.query.users).then(
(data) => handleStandardResponse(res, data)
);
}

getStore(req, res) {
// Extract the store name from request's path
const store = req.route.path.substring(1);
ActiveDirectoryService.getStore(store).then((data) => {
handleStandardResponse(res, data);
// If data doesn't contain the error property, we're good - print event to admin log
!data.error &&
ael.info(
`${res.locals.authUser} viewed contents of AD store "${store}"`
);
});
}

flushStores(req, res) {
ActiveDirectoryService.flushStores().then((data) => {
handleStandardResponse(res, data);
// If data doesn't contain the error property, we're good - print event to admin log
!data.error && ael.info(`${res.locals.authUser} flushed all AD stores`);
});
}
}

export default new Controller();
13 changes: 13 additions & 0 deletions new-backend/server/apis/v2/controllers/ad/router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as express from "express";
import controller from "./controller";
import restrictAdmin from "../../middlewares/restrict.admin";

export default express
.Router()
.use(restrictAdmin) // We will not allow any of the following routes unless user is admin
.get("/availableadgroups", controller.availableADGroups)
.get("/findcommonadgroupsforusers", controller.findCommonADGroupsForUsers)
.get("/users", controller.getStore)
.get("/groups", controller.getStore)
.get("/groupsPerUser", controller.getStore)
.put("/flushStores", controller.flushStores);
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ConfigServiceV2 from "../../services/config.service.v2";
import ConfigService from "../../services/config.service";
import ad from "../../services/activedirectory.service";
import handleStandardResponse from "../../utils/handleStandardResponse";

Expand All @@ -12,7 +12,7 @@ export class Controller {
* @memberof Controller
*/
byMap(req, res) {
ConfigServiceV2.getMapWithLayers(
ConfigService.getMapWithLayers(
req.params.map,
ad.getUserFromRequestHeader(req)
).then((data) => handleStandardResponse(res, data));
Expand Down
62 changes: 62 additions & 0 deletions new-backend/server/apis/v2/controllers/informative/controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import InformativeService from "../../services/informative.service";
import log4js from "log4js";

// Create a logger for admin events, those will be saved in a separate log file.
const ael = log4js.getLogger("adminEvent.v2");

export class Controller {
create(req, res) {
const { documentName, mapName } = JSON.parse(req.body);
InformativeService.create(documentName, mapName).then((r) => {
// FIXME: The buggy admin expects 200 and this string on success,
// but I think that we'd do better with a meaningful JSON response.
if (r && !r.error) {
res.status(200).send("Document created");
ael.info(
`${res.locals.authUser} created a new document, ${documentName}.json, and connected it to map ${mapName}.json`
);
} else res.status(500).send(r.error.message);
});
}

getByName(req, res) {
InformativeService.getByName(req.params.name).then((r) => {
if (r && !r.error) res.json(r);
else {
res
.status(404)
.send(`Document "${req.params.name}" could not be found`);
}
});
}

saveByName(req, res) {
InformativeService.saveByName(req.params.name, req.body).then((r) => {
if (r && !r.error) {
res.status(200).send("File saved");
ael.info(
`${res.locals.authUser} saved document ${req.params.name}.json`
);
} else res.status(500).send(r.error.message);
});
}

deleteByName(req, res) {
InformativeService.deleteByName(req.params.name).then((r) => {
if (r && !r.error) {
res.status(200).send("File saved");
ael.info(
`${res.locals.authUser} deleted document ${req.params.name}.json`
);
} else res.status(500).send(r.error.message);
});
}

list(req, res) {
InformativeService.getAvailableDocuments().then((r) => {
if (r && !r.error) res.json(r);
else res.status(500).send(r.error.message);
});
}
}
export default new Controller();
13 changes: 13 additions & 0 deletions new-backend/server/apis/v2/controllers/informative/router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as express from "express";
import controller from "./controller";
import restrictAdmin from "../../middlewares/restrict.admin";

export default express
.Router()
.get("/load/:name", controller.getByName)
.use(restrictAdmin) // All routes that follow are admin-only!
.put("/create", controller.create) // PUT is correct here, as this operation is idempotent
.delete("/delete/:name", controller.deleteByName)
.get("/list", controller.list)
.get("/list/:name", controller.list) // FIXME: For now, the name paramter is ignored - should list only documents connected to specified map
.put("/save/:name", controller.saveByName); // PUT is correct here, as this operation is idempotent
129 changes: 129 additions & 0 deletions new-backend/server/apis/v2/controllers/mapconfig/controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import ConfigService from "../../services/config.service";
import InformativeService from "../../services/informative.service";
import ad from "../../services/activedirectory.service";
import handleStandardResponse from "../../utils/handleStandardResponse";
import log4js from "log4js";

// Create a logger for admin events, those will be saved in a separate log file.
const ael = log4js.getLogger("adminEvent.v2");

export class Controller {
/**
* @summary Get a specific map config using the supplied
* request parameter "map" as map's name.
*
* @param {*} req
* @param {*} res
* @memberof Controller
*/
byMap(req, res) {
ConfigService.getMapConfig(
req.params.map,
ad.getUserFromRequestHeader(req),
false // 'false' here means that the map config won't be "washed" - this is exactly what we need for this admin request
).then((data) => handleStandardResponse(res, data));
}

/**
* @summary Returns a list of all available layers in specified (often human-readable) format.
*
* @description Sometimes it's useful for admins to get a list of a map's contents and make it
* available for users in some format (be it JSON, XML, Excel). This endpoint can be used as-is
* or by implementing a feature in the client UI, so users themselves can request a description
* of a map's contents from e.g. LayerSwitcher.
* @param {*} req
* @param {*} res
* @memberof Controller
*/
exportMapConfig(req, res) {
ConfigService.exportMapConfig(
req.params.map,
req.params.format,
ad.getUserFromRequestHeader(req)
).then((data) => handleStandardResponse(res, data));
}

/**
* @summary Get the contents of the layers database
*
* @param {*} req
* @param {*} res
* @memberof Controller
*/
layers(req, res) {
ConfigService.getLayersStore(
ad.getUserFromRequestHeader(req),
false // won't "wash" content, which is what we need for admin UI to list the entire layer's store
).then((data) => handleStandardResponse(res, data));
}

layersVerify(req, res) {
ConfigService.verifyLayers(ad.getUserFromRequestHeader(req)).then((data) =>
handleStandardResponse(res, data)
);
}

/**
* @summary List all available map configs - used in admin
*
* @param {*} req
* @param {*} res
* @memberof Controller
*/
list(req, res) {
ConfigService.getAvailableMaps().then((data) =>
handleStandardResponse(res, data)
);
}

listimage(req, res) {
InformativeService.getUploadedFiles("image").then((data) =>
handleStandardResponse(res, data)
);
}

listvideo(req, res) {
InformativeService.getUploadedFiles("video").then((data) =>
handleStandardResponse(res, data)
);
}

listaudio(req, res) {
InformativeService.getUploadedFiles("audio").then((data) =>
handleStandardResponse(res, data)
);
}

createNewMap(req, res) {
ConfigService.createNewMap(req.params.name).then((data) => {
handleStandardResponse(res, data);
!data.error &&
ael.info(
`${res.locals.authUser} created a new map config: ${req.params.name}.json`
);
});
}

duplicateMap(req, res) {
ConfigService.duplicateMap(req.params.nameFrom, req.params.nameTo).then(
(data) => {
handleStandardResponse(res, data);
!data.error &&
ael.info(
`${res.locals.authUser} created a new map config, ${req.params.nameTo}.json, by duplicating ${req.params.nameFrom}.json`
);
}
);
}

deleteMap(req, res) {
ConfigService.deleteMap(req.params.name).then((data) => {
handleStandardResponse(res, data);
!data.error &&
ael.info(
`${res.locals.authUser} deleted map config ${req.params.name}.json`
);
});
}
}
export default new Controller();
22 changes: 22 additions & 0 deletions new-backend/server/apis/v2/controllers/mapconfig/router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as express from "express";
import controller from "./controller";
import restrictAdmin from "../../middlewares/restrict.admin";

export default express
.Router()
.use(restrictAdmin) // We will not allow any of the following routes unless user is admin
// First we handle _specific_ routes, so we can catch them…
.put("/duplicate/:nameFrom/:nameTo", controller.duplicateMap)
.get("/export/:map/:format", controller.exportMapConfig) // Describe all available layers in a human-readable format
.get("/layers", controller.layers) // Get all layers (from layers.json)
.get("/layers/verify", controller.layersVerify) // Check which Hajk layers actually exist in their respective services
.get("/list", controller.list) // List all available maps
.get("/listimage", controller.listimage) // List all available maps
.get("/listvideo", controller.listvideo)
.get("/listaudio", controller.listaudio)

// …but if none of the above matched, let's assume the string
// provided is a param that should be used as map config name.
.get("/:map", controller.byMap) // Get specific map config
.put("/:name", controller.createNewMap)
.delete("/:name", controller.deleteMap);
77 changes: 77 additions & 0 deletions new-backend/server/apis/v2/controllers/settings/controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import SettingsService from "../../services/settings.service";
import handleStandardResponse from "../../utils/handleStandardResponse";
import log4js from "log4js";

// Create a logger for admin events, those will be saved in a separate log file.
const ael = log4js.getLogger("adminEvent.v2");

export class Controller {
putSettingsToMapFile(req, res) {
SettingsService.updateMapFile(req.query.mapFile, req.body, req.url).then(
(data) => {
// Can't use handleStandardResponse here because we need to
// output only data.mapConfig on success – not the entire data.
if (data.error) res.status(500).send(data.error.toString());
else {
// Send response
res.status(200).json(data.mapConfig);
// Log admin action
ael.info(`${res.locals.authUser} saved map ${req.query.mapFile}`);
}
}
);
}

updateMapTool(req, res) {
SettingsService.updateMapTool(
req.params.map,
req.params.tool,
req.body
).then((data) => {
// Can't use handleStandardResponse here because we need to
// output only data.mapConfig on success – not the entire data.
if (data.error) res.status(500).send(data.error.toString());
else {
// Send response
res.sendStatus(data);
// Log admin action
ael.info(`${res.locals.authUser} saved map ${req.query.mapFile}`);
}
});
}

putLayerOfType(req, res) {
SettingsService.createOrUpdateLayer(req.params.type, req.body).then(
(data) => {
// Can't use handleStandardResponse here because we need to
// output only data.newLayer on success – not the entire data.
if (data.error) res.status(500).send(data.error.toString());
else {
// r.status will be either 200 (layer updated) or 201 (layer created)
res.status(data.status).json(data.newLayer);

ael.info(
`${res.locals.authUser} ${
data.status === 201 ? "added" : "updated"
} ${req.params.type} with id ${data.newLayer.id} ("${
data.newLayer.caption
}")`
);
}
}
);
}

deleteLayerFromStore(req, res) {
SettingsService.deleteLayer(req.params.type, req.params.layerId).then(
(data) => {
handleStandardResponse(res, data);
!data.error &&
ael.info(
`${res.locals.authUser} deleted ${req.params.type} with id ${req.params.layerId}`
);
}
);
}
}
export default new Controller();
18 changes: 18 additions & 0 deletions new-backend/server/apis/v2/controllers/settings/router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as express from "express";
import controller from "./controller";
import restrictAdmin from "../../middlewares/restrict.admin";

export default express
.Router()
.use(restrictAdmin) // We will not allow any of the following routes unless user is admin
// We use the same controller method to handle these 3
// PUT requests, as they all write into the same file,
// only different portions of it.
.put("/layermenu", controller.putSettingsToMapFile)
.put("/mapsettings", controller.putSettingsToMapFile)
.put("/toolsettings", controller.putSettingsToMapFile)
.put("/update/:map/:tool", controller.updateMapTool)
.post("/:type", controller.putLayerOfType) // Will add new each time it's called
.put("/:type", controller.putLayerOfType) // Will overwrite existing and the result is idempotent
// Handle layer removal
.delete("/:type/:layerId", controller.deleteLayerFromStore);
Loading

0 comments on commit 895629d

Please sign in to comment.