Skip to content

Commit

Permalink
Merge branch 'minor' into layer-tables-query
Browse files Browse the repository at this point in the history
dbauszus-glx authored Jan 16, 2025

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents 937ecc8 + 8ba1934 commit 1039a6c
Showing 6 changed files with 143 additions and 74 deletions.
2 changes: 1 addition & 1 deletion api/api.js
Original file line number Diff line number Diff line change
@@ -110,7 +110,7 @@ const routes = {

process.env.COOKIE_TTL ??= '36000';

process.env.TITLE ??= 'XYZ | MAPP'
process.env.TITLE ??= 'XYZ | MAPP';

process.env.DIR ??= '';

8 changes: 7 additions & 1 deletion lib/location/get.mjs
Original file line number Diff line number Diff line change
@@ -52,6 +52,12 @@ export async function get(location, list = location.layer.mapview.locations) {
// Location has no infoj to decorate, return.
if (!location.layer.infoj) return;

if (location.layer.Key) {

// Assign Key from location layer entry select.
location.layer.key = location.layer.Key
}

// Build location hook from layer key and location id.
location.hook ??= `${location.layer.key}!${location.id}`;

@@ -210,7 +216,7 @@ async function getFeatureResponse(location) {
const queryParams = {
template: location.getTemplate,
locale: location.locale,
layer: location.layer.Key || location.layer.key,
layer: location.layer.key,
table: location.table,
id: location.id,
};
40 changes: 19 additions & 21 deletions lib/mapp.mjs
Original file line number Diff line number Diff line change
@@ -15,42 +15,40 @@ The `mapp.mjs` module is used as entry point for the esbuild process to bundle t
@module mapp
*/

import utils from './utils/_utils.mjs'
import utils from './utils/_utils.mjs';

import hooks from './hooks.mjs'
import hooks from './hooks.mjs';

import dictionary from './dictionaries/_dictionary.mjs'
import dictionary from './dictionaries/_dictionary.mjs';

import dictionaries from './dictionaries/_dictionaries.mjs'
import dictionaries from './dictionaries/_dictionaries.mjs';

import layer from './layer/_layer.mjs'
import layer from './layer/_layer.mjs';

import location from './location/_location.mjs'
import location from './location/_location.mjs';

import Mapview from './mapview/_mapview.mjs'
import Mapview from './mapview/_mapview.mjs';

import plugins from './plugins/_plugins.mjs'
import plugins from './plugins/_plugins.mjs';

hooks.parse();

const _ol = {
current: '10.3.1'
}
current: '10.3.1',
};

if (window.ol === undefined) {

console.warn(`Openlayers has not been loaded.`)

console.warn(`Openlayers has not been loaded.`);
} else {

const olVersion = parseFloat(ol?.util.VERSION)
const olVersion = parseFloat(ol?.util.VERSION);
const olCurrent = parseFloat(_ol.current);

console.log(`OpenLayers version ${olVersion}`)
console.log(`OpenLayers version ${olVersion}`);

if (olVersion < olCurrent) {

console.warn(`Update the current OpenLayers version:${ol?.util.VERSION} to ${_ol.current}.`)
console.warn(
`Update the current OpenLayers version:${ol?.util.VERSION} to ${_ol.current}.`,
);
}
}

@@ -59,7 +57,7 @@ globalThis.mapp = {

version: '4.13.0-alpha',

hash: 'e19adad70af7047ccebda80df4825e8e408c0d91',
hash: 'f573772a1d230559bd4f041d9a0d57dcea44de1f',

host: document.head?.dataset?.dir || '',

@@ -79,5 +77,5 @@ globalThis.mapp = {

utils,

plugins
}
plugins,
};
8 changes: 4 additions & 4 deletions lib/mapview/interactions/highlight.mjs
Original file line number Diff line number Diff line change
@@ -97,9 +97,9 @@ export default function highlight(params) {
@param {Object} L Openlayers layer.
*/
function layerFilter(L) {

return Object.values(mapview.layers)
.some(layer => layer.qID && layer.L === L)
return Object.values(mapview.layers).some(
(layer) => layer.qID && layer.L === L
);
}

/**
@@ -261,7 +261,7 @@ export default function highlight(params) {
// Assign feature.id from id property or getId method.
feature.id = feature.F.get('id') || feature.F.getId()

// Required for featureStyle() styling of highlight feature.
// Required for featureStyle() styling of highlight feature.
feature.layer.highlight = feature.id

// Assign feature as current.
89 changes: 74 additions & 15 deletions lib/ui/locations/entries/layer.mjs
Original file line number Diff line number Diff line change
@@ -18,23 +18,31 @@ The layer.key is a concatenated from the entry.layer [key] and the entry.locatio
A layer.view will be created for the decorated layer and appended to the entry.node for the location view.
featureLookup is an array of properties objects which filter and style requested features.
When using query-based featureLookup the query should return unique indentifiers within their original layer and any relevant fields used for thematic styling.
A featureSet of featureLookup can be defined for the layer feature styling. The values for the featureLookup or featureSet can be defined as entry.data. The entry.data may be populated from the get feature field or queried from a parameterised entry.query [template]. A featureSet consists of an array of feature IDs. A featureLookup consists of an array of feature objects with the feature properties being assigned for styling in the [featureProperties]{@link module:/layer/featureStyle~featureProperties} style method.
@param {infoj-entry} entry type:layer entry.
@property {string} [entry.layer] lookup layer key for locale.layers[].
@property {object} [entry.data] An array of feature id or objects for the featureSet or featureLookup.
@property {string} [entry.query] A query to execute to populate the entry.data.
@property {object} [entry.featureSet] A set of feature ID to filter the layer features.
@property {object} [entry.featureLookup] Feature objects as lookup to filter the layer features.
@return {HTMLElement} Node element to hold the layer view drawer.
*/
export default function layer(entry) {

entry.mapview ??= entry.location.layer.mapview
checkData(entry)

//An entry needs to have a featureLookup array as it's used in the show method,
entry.featureLookup ??= []
entry.mapview ??= entry.location.layer.mapview

// The layer lookup is optional. An entry maybe defined as a layer.
if (entry.layer) {

// The layer.key must be unique.
entry.key = `${entry.layer}|${entry.location.hook}`
entry.key = entry.layer

// The locale layer key.
entry.Key = entry.layer
@@ -68,6 +76,48 @@ export default function layer(entry) {
return entry.panel
}

/**
@function checkData
@description
The value from a [dependent] entry field will override the entry.data when the layer location entry method is called from the infoj entries iteration.
Empty array data of no features will be assigned as null.
The entry and the display_toggle checkbox will be disabled if the layer entry should use a featureSet or featureLookup array with no query and no [feature] data.
@param {infoj-entry} entry type:layer entry.
@property {object} [entry.value] Value assigned from a field in the get feature response.
@property {object} [entry.data] An array of feature id or objects for the featureSet or featureLookup.
@property {string} [entry.query] A query to execute to populate the entry.data.
@property {object} [entry.featureSet] A set of feature ID to filter the layer features.
@property {object} [entry.featureLookup] Feature objects as lookup to filter the layer features.
@return {HTMLElement} Node element to hold the layer view drawer.
*/
function checkData(entry) {

if (entry.value !== undefined) {
entry.data = entry.value

if (entry.data?.length === 0) {

entry.data = null
}
}

if (entry.featureSet || entry.featureLookup) {
if (!entry.query && !entry.data) {
entry.disabled = true;
entry.display_toggle?.classList.add('disabled')

} else {
entry.disabled = false;
entry.display_toggle?.classList.remove('disabled')
}
}
}

/**
@function decorateLayer
@async
@@ -81,6 +131,10 @@ async function decorateLayer(entry) {

await mapp.layer.decorate(entry)

entry.key += `-${ol.util.getUid(entry.L)}`

entry.L.set('key', entry.key)

entry.show = showLayer;

entry.hide = hideLayer;
@@ -91,6 +145,7 @@ async function decorateLayer(entry) {
data_id: `${entry.key}-display`,
label: entry.name,
checked: entry.display,
disabled: entry.disabled,
onchange: (checked) => {
checked ? entry.show() : entry.hide()
}
@@ -165,28 +220,32 @@ async function showLayer() {

const entry = this

entry.data = entry.value

if (!entry.data && entry.query) {
if (entry.query) {

const paramString = mapp.utils.paramString(mapp.utils.queryParams(entry))

entry.data = await mapp.utils.xhr(`${entry.mapview.host}/api/query?${paramString}`)
entry.data = await mapp.utils.xhr(`${entry.mapview.host}/api/query?${paramString}`)

if (!entry.data) {

entry.view.classList.add('disabled')
entry.disabled = true;
entry.display_toggle?.classList.add('disabled')
return;
}
}

if (entry.featureSet && Array.isArray(entry.data)) {
if (entry.featureSet) {

// layer entry may have featureLookup or featureSet but not both.
delete entry.featureLookup
entry.featureSet = new Set(entry.data)
if (!entry.data) return;

const featureID = Array.isArray(entry.data) ? entry.data : [entry.data];

entry.featureSet = new Set(featureID)
}

if (entry.featureLookup) {

} else {
if (!entry.data) return;

// featureLookup on layer must be arrays
entry.featureLookup = Array.isArray(entry.data) ? entry.data : [entry.data]
70 changes: 38 additions & 32 deletions mod/user/saml.js
Original file line number Diff line number Diff line change
@@ -182,16 +182,15 @@ Authentication Flow:
7. JWT token created and set as cookie
@param {Object} req - HTTP request object
@param {req} req.url - Request URL path
@param {req} req.body - POST request body
@param {req} req.query - URL query parameters
@param {req} req.cookies - Request cookies
@param {req} req.params - Route parameters
@property {string} req.url - Request URL path
@property {Object} req.body - POST request body
@property {Object} req.query - URL query parameters
@property {Object} req.cookies - Request cookies
@property {Object} req.params - Route parameters
@param {Object} res - HTTP response object
@param {res} res.redirect - Redirect function
@param {res} res.send - Send response function
@param {res} res.setHeader - Set response header
@property {function} res.send - Send response function
@property {function} res.setHeader - Set response header
@throws {Error} If SAML is not configured
@throws {Error} If authentication fails
@@ -236,9 +235,8 @@ function saml(req, res) {
@description Handles the metadata response
@param {Object} res - HTTP response object
@param {res} res.redirect - Redirect function
@param {res} res.send - Send response function
@param {res} res.setHeader - Set response header
@property {function} res.send - Send response function
@property {function} res.setHeader - Set response header
**/
function metadata(res) {
res.setHeader('Content-Type', 'application/xml');
@@ -254,8 +252,7 @@ function metadata(res) {
@description Handles the logoutCallback POST from the idp
@param {Object} res - HTTP response object
@param {res} res.redirect - Redirect function
@param {res} res.setHeader - Set response header
@property {function} res.setHeader - Set response header
**/
function logoutCallback(res) {
try {
@@ -266,10 +263,13 @@ function logoutCallback(res) {
`${process.env.TITLE}=; HttpOnly; Path=${process.env.DIR || '/'}; Expires=Thu, 01 Jan 1970 00:00:00 GMT`, // But these cookies go to zero. That's one less.
);

res.redirect(`${process.env.DIR || '/'}`);
res.setHeader('location', `${process.env.DIR || '/'}`);
return res.status(302).send();
} catch (error) {
console.error('Logout validation failed:', error);
return res.redirect('/');

res.setHeader('location', `${process.env.DIR || '/'}`);
return res.status(302).send();
}
}

@@ -278,31 +278,35 @@ function logoutCallback(res) {
@description Handles the logout request from the api.js
@param {Object} req - HTTP request object
@param {req} req.cookies - Request Cookies
@property {Object} req.cookies - Request Cookies
@param {Object} res - HTTP response object
@param {res} res.redirect - Redirect function
@param {res} res.setHeader - Set response header
@property {function} res.setHeader - Set response header
**/
async function logout(req, res) {
try {
const user = await jwt.decode(req.cookies[`${process.env.TITLE}`]);

// If no user/cookie, redirect to home
if (!user) {
return res.redirect(process.env.DIR || '/');
res.setHeader('location', `${process.env.DIR || '/'}`);
return res.status(302).send();
}

if (user.sessionIndex) {
// Get logout URL from IdP if session exists
const url = await samlStrat.getLogoutUrlAsync(user);
return res.redirect(url);

res.setHeader('location', url);
return res.status(302).send();
} else {
return logoutCallback(res);
logoutCallback(res);
}
} catch (error) {
console.error('Logout process failed:', error);
return res.redirect('/');

res.setHeader('location', `${process.env.DIR || '/'}`);
return res.status(302).send();
}
}

@@ -311,10 +315,9 @@ async function logout(req, res) {
@description Handles the login request from the api.js and redirects to login url.
@param {Object} req - HTTP request object
@param {req} req.get - Request get function
@property {function} req.get - Request get function
@param {Object} res - HTTP response object
@param {res} res.redirect - Redirect function
**/
async function login(req, res) {
try {
@@ -328,7 +331,8 @@ async function login(req, res) {
{ additionalParams: {} },
);

res.redirect(url);
res.setHeader('location', url);
return res.status(302).send();
} catch (error) {
console.error('SAML authorization error:', error);
res.status(500).send('Authentication failed');
@@ -340,13 +344,12 @@ async function login(req, res) {
@description Handles the acs POST request from the idp
@param {Object} req - HTTP request object
@param {req} req.body - Request Body
@property {Object} req.body - Request Body
@param {Object} res - HTTP response object
@param {res} res.redirect - Redirect function
@param {res} res.status - request status
@param {res} res.send - Send response function
@param {res} res.setHeader - Set response header
@property {string} res.status - request status
@property {function} res.send - Send response function
@property {function} res.setHeader - Set response header
**/
async function acs(req, res) {
try {
@@ -371,7 +374,9 @@ async function acs(req, res) {
const url = await samlStrat.getLogoutUrlAsync(user);

// Login with non exist SAML user will destroy session and return login.
return res.redirect(url);
//
res.setHeader('location', url);
return res.status(302).send();
}

if (aclResponse instanceof Error) {
@@ -393,7 +398,8 @@ async function acs(req, res) {

res.setHeader('Set-Cookie', cookie);

res.redirect(`${process.env.DIR || '/'}`);
res.setHeader('location', `${process.env.DIR || '/'}`);
return res.status(302).send();
} catch (error) {
console.log(error);
}

0 comments on commit 1039a6c

Please sign in to comment.