From 5c68d11b805453fc9e1bb64596ad586647976fd1 Mon Sep 17 00:00:00 2001 From: tecc Date: Fri, 2 Jun 2023 00:21:52 +0200 Subject: [PATCH] fix(api-profile-filter): Fix several bugs filtering data in profile requests profile-routes-options: Profile-related routes were not properly instantiating/passing a ProfileOptions object - this has now been fixed. profile-options: Some of the logic for the ProfileOptions class was rewritten/reorganised for clarity and stability. profile-v2: Because some fields were nullish, the conversion between v2 and v1 wasn't working properly. Now, everything checks for nullability AFAIK. --- server/profileV2.js | 8 ++- server/routes/profile.js | 116 ++++++++++++++++++++------------------- 2 files changed, 65 insertions(+), 59 deletions(-) diff --git a/server/profileV2.js b/server/profileV2.js index 8083c1811..1c499c900 100644 --- a/server/profileV2.js +++ b/server/profileV2.js @@ -7,12 +7,14 @@ const opinions = { } const upgradeOpinionsListToV2 = (opinionsListV1) => { + if (opinionsListV1 == null) return undefined; return Object.entries(opinionsListV1).map(([value, opinion]) => { return { value, opinion: Object.keys(opinions).find(key => opinions[key] === opinion) }; }); } const downgradeOpinionsListToV1 = (opinionsListV2) => { + if (opinionsListV2 == null) return undefined; const opinionsListV1 = {}; for (let {value, opinion} of opinionsListV2) { opinionsListV1[value] = opinions[opinion]; @@ -21,12 +23,14 @@ const downgradeOpinionsListToV1 = (opinionsListV2) => { } const upgradeCustomFlagsListToV2 = (customFlagListV1) => { + if (customFlagListV1 == null) return undefined; return Object.entries(customFlagListV1).map(([value, name]) => { return { value, name }; }); } const downgradeCustomFlagsToV1 = (customFlagListV2) => { + if (customFlagListV2 == null) return undefined; const customFlagListV1 = {}; for (let {value, name} of customFlagListV2) { customFlagListV1[value] = name; @@ -39,7 +43,7 @@ module.exports.upgradeToV2 = (profileV1) => { ...profileV1, names: upgradeOpinionsListToV2(profileV1.names), pronouns: upgradeOpinionsListToV2(profileV1.pronouns), - words: profileV1.words.map(column => { + words: profileV1.words && profileV1.words.map(column => { return {'header': null, 'values': upgradeOpinionsListToV2(column)}; }), customFlags: upgradeCustomFlagsListToV2(profileV1.customFlags), @@ -51,7 +55,7 @@ module.exports.downgradeToV1 = (profileV2) => { ...profileV2, names: downgradeOpinionsListToV1(profileV2.names), pronouns: downgradeOpinionsListToV1(profileV2.pronouns), - words: profileV2.words.map(({values}) => { + words: profileV2.words && profileV2.words.map(({values}) => { return downgradeOpinionsListToV1(values); }), customFlags: downgradeCustomFlagsToV1(profileV2.customFlags), diff --git a/server/routes/profile.js b/server/routes/profile.js index 9d3729927..b3afb0d39 100644 --- a/server/routes/profile.js +++ b/server/routes/profile.js @@ -72,10 +72,7 @@ class ProfileOptions { enabled: true, default_value: true }; - locale_specific_props = { - enabled: true, - default_value: true - }; + locale_specific_props = {}; constructor(query_obj, locales) { if (query_obj == null) return; @@ -84,13 +81,14 @@ class ProfileOptions { this.default_props = ProfileOptions._parse_props(default_props) if (locales != null) { - for (const key of Object.keys(locales)) { + for (const locale of Object.values(locales)) { + const key = locale.code.toLowerCase(); // i'm purely paranoid here const lprops_obj = query_obj["lprops"]; let props; if (typeof lprops_obj === "object") { props = lprops_obj[key]; } else { - props = query_obj[`lprops.${key.toLowerCase()}`]; + props = query_obj[`lprops.${key}`]; } if (props != null) { this.locale_specific_props[key] = ProfileOptions._parse_props(props); @@ -102,62 +100,65 @@ class ProfileOptions { } static _parse_props(raw_param) { - if (raw_param) { - /** @type {Array} */ - let properties; - let default_value = false; - const obj = { - default_value - }; - if (Array.isArray(raw_param)) { - properties = raw_param - } else if (typeof raw_param == "object") { - return { - ...raw_param, - enabled: true, - default_value: false, - } - } else if (typeof raw_param === 'string') { - if (raw_param === "none") { - // Don't show the locale at all - return { - enabled: false, - default_value: false - } - } else if (raw_param === "empty") { - // Include it as an object, but don't include any data - return { - enabled: true, - default_value: false - } - } else if (raw_param === "all") { - // All properties should be shown - return { - enabled: true, - default_value: true - } - } - properties = raw_param.split(','); - } - - for (const property of properties) { - obj[property] = !default_value; - } - - return { - ...obj, - enabled: true, - }; - } else { + if (raw_param == null) { return { enabled: true, default_value: true } } + /** @type {Array} */ + let properties; + let default_value = false; + const obj = {}; + if (Array.isArray(raw_param)) { + properties = raw_param + } else if (typeof raw_param == "object") { + return { + ...raw_param, + enabled: true, + default_value: false, + } + } else if (typeof raw_param === 'string') { + switch (raw_param) { + case "none": + // Don't show the locale at all + return { + enabled: false, + default_value: false + } + case "empty": + // Include it as an object, but don't include any data + return { + enabled: true, + default_value: false + } + case "all": + // All properties should be shown + return { + enabled: true, + default_value: true + } + } + // console.log("unrecognised: ", raw_param) + properties = raw_param.split(','); + } + + for (const property of properties) { + obj[property] = !default_value; + } + + return { + ...obj, + enabled: true, + default_value + }; } _data_for(locale) { + if (typeof locale != "string") { + throw new Error("Locale must be string") + } return this.locale_specific_props[locale] ?? this.default_props; } @@ -174,8 +175,10 @@ class ProfileOptions { * @return {boolean} */ prop(locale, property) { + if (!this.show_at_all(locale)) return false; + // it's duplicate data getting (show_at_all also calls _data_for), + // but it's not that important to optimise const data = this._data_for(locale); - if (!(data.enabled ?? true)) return false; return data[property] ?? data.default_value ?? true; } @@ -225,7 +228,7 @@ const fetchProfiles = async (db, username, self, opts) => { const p = {} for (let profile of profiles) { - if (!opts.show_at_all(profile)) continue; + if (!opts.show_at_all(profile.locale)) continue; const links = JSON.parse(profile.links).map(l => normaliseUrl(l)).filter(l => !!l); const linksMetadata = {}; for (let link of await db.all(SQL`SELECT * FROM links WHERE url IN (`.append(links.map(k => `'${k.replace(/'/g, "''")}'`).join(',')).append(SQL`)`))) { @@ -236,8 +239,7 @@ const fetchProfiles = async (db, username, self, opts) => { }; } - - const propv = (key, value) => opts.propv(profile, key, value); + const propv = (property, value) => opts.propv(profile.locale, property, value); const profile_obj = { opinions: propv("opinions", () => JSON.parse(profile.opinions)),