From c86a738266a7c9dbc979c27b948f99e128a99983 Mon Sep 17 00:00:00 2001 From: Martina Date: Fri, 6 Aug 2021 18:13:45 -0700 Subject: [PATCH] modified backend to recalculate file privacy upon add/remove from public collection --- common/actions.js | 8 ---- components/sidebars/SidebarCreateSlate.js | 4 +- node_common/data/index.js | 6 +-- .../data/methods/recalc-file-privacy.js | 42 +++++++++++++++++++ ...privacy.js => update-file-privacy[dep].js} | 0 ...-public.js => update-files-public[dep].js} | 0 node_common/utilities.js | 34 +++++++++++++++ pages/api/slates/delete.js | 15 +------ pages/api/slates/remove-file.js | 15 +------ pages/api/slates/update.js | 26 ++---------- pages/api/v2/update-collection.js | 24 +---------- pages/api/v2/update-file.js | 19 --------- 12 files changed, 89 insertions(+), 104 deletions(-) create mode 100644 node_common/data/methods/recalc-file-privacy.js rename node_common/data/methods/{update-file-privacy.js => update-file-privacy[dep].js} (100%) rename node_common/data/methods/{update-files-public.js => update-files-public[dep].js} (100%) diff --git a/common/actions.js b/common/actions.js index 77a68314..c57d0832 100644 --- a/common/actions.js +++ b/common/actions.js @@ -404,14 +404,6 @@ export const updateFile = async (data) => { }); }; -export const toggleFilePrivacy = async (data) => { - await Websockets.checkWebsocket(); - return await returnJSON(`/api/data/toggle-privacy`, { - ...DEFAULT_OPTIONS, - body: JSON.stringify({ data }), - }); -}; - export const deleteFiles = async (data) => { await Websockets.checkWebsocket(); return await returnJSON(`/api/data/delete`, { diff --git a/components/sidebars/SidebarCreateSlate.js b/components/sidebars/SidebarCreateSlate.js index a05d7c47..160d14a5 100644 --- a/components/sidebars/SidebarCreateSlate.js +++ b/components/sidebars/SidebarCreateSlate.js @@ -210,8 +210,8 @@ export default class SidebarCreateSlate extends React.Component { marginTop: 12, }} > - All collections are public by default. This means they can be discovered and seen by - anyone on the internet. If you make it private, only you will be able to see it. + Public collections can be discovered and seen by anyone on the internet. If you make it + private, only you will be able to see it. { + return await runQuery({ + label: "RECALC_FILE_PRIVACY", + queryFn: async (DB) => { + const slateIds = `(SELECT ?? FROM ?? WHERE ?? = ?)`; + const slateIdsFields = ["slateId", "slate_files", "fileId", fileId]; + + const filePrivacy = `(SELECT EXISTS (SELECT * FROM ?? JOIN ?? ON ?? = ?? WHERE ?? = ?))`; + const filePrivacyFields = [ + "slates", + "slate_ids", + "slates.id", + "slate_ids.slateId", + "isPublic", + true, + ]; + + const update = `UPDATE ?? SET ?? = ${filePrivacy} WHERE ?? = ?`; + const updateFields = ["files", "isPublic", ...filePrivacyFields, "id", fileId]; + + const updatedFile = await DB.raw(`WITH ?? AS ${slateIds} ${update} RETURNING *`, [ + "slate_ids", + ...slateIdsFields, + ...updateFields, + ]); + let rows = updatedFile.rows; + if (rows?.length) { + return rows.first(); + } + + return; + }, + errorFn: async (e) => { + return { + error: true, + decorator: "RECALC_FILE_PRIVACY", + }; + }, + }); +}; diff --git a/node_common/data/methods/update-file-privacy.js b/node_common/data/methods/update-file-privacy[dep].js similarity index 100% rename from node_common/data/methods/update-file-privacy.js rename to node_common/data/methods/update-file-privacy[dep].js diff --git a/node_common/data/methods/update-files-public.js b/node_common/data/methods/update-files-public[dep].js similarity index 100% rename from node_common/data/methods/update-files-public.js rename to node_common/data/methods/update-files-public[dep].js diff --git a/node_common/utilities.js b/node_common/utilities.js index 50acc889..91ae6164 100644 --- a/node_common/utilities.js +++ b/node_common/utilities.js @@ -6,6 +6,8 @@ import * as Social from "~/node_common/social"; import * as Logging from "~/common/logging"; import * as ArrayUtilities from "~/node_common/array-utilities"; import * as Monitor from "~/node_common/monitor"; +import * as Arrays from "~/common/arrays"; +import * as SearchManager from "~/node_common/managers/search"; import crypto from "crypto"; import JWT from "jsonwebtoken"; @@ -285,3 +287,35 @@ export const addToSlate = async ({ slate, files, user, saveCopy = false }) => { return { added: response.length }; }; + +export const removeFromPublicCollectionUpdatePrivacy = async ({ files }) => { + let targetFiles = Arrays.filterPublic(files); + let madePrivate = []; + for (let file of targetFiles) { + let updatedFile = await Data.recalcFilePrivacy({ fileId: file.id }); + if (!updatedFile) continue; + if (file.isPublic && !updatedFile.isPublic) { + madePrivate.push(updatedFile); + } + } + if (madePrivate.length) { + SearchManager.updateFile(madePrivate, "REMOVE"); + } + return madePrivate; +}; + +export const addToPublicCollectionUpdatePrivacy = async ({ files }) => { + let targetFiles = Arrays.filterPrivate(files); + let madePublic = []; + for (let file of targetFiles) { + let updatedFile = await Data.recalcFilePrivacy({ fileId: file.id }); + if (!updatedFile) continue; + if (!file.isPublic && updatedFile.isPublic) { + madePublic.push(updatedFile); + } + } + if (madePublic.length) { + SearchManager.updateFile(madePublic, "ADD"); + } + return madePublic; +}; diff --git a/pages/api/slates/delete.js b/pages/api/slates/delete.js index ac1e979a..ba3295dd 100644 --- a/pages/api/slates/delete.js +++ b/pages/api/slates/delete.js @@ -35,20 +35,7 @@ export default async (req, res) => { SearchManager.updateSlate(slate, "REMOVE"); if (slate.isPublic) { - //NOTE(martina): if any of the files in it are now private (because they are no longer in any public slates) remove them from search - const files = slate.objects; - - const publicFiles = await Data.getFilesByIds({ - ids: files.map((file) => file.id), - publicOnly: true, - }); - const publicIds = publicFiles.map((file) => file.id); - - let privateFiles = files.filter((file) => !publicIds.includes(file.id)); - - if (privateFiles.length) { - SearchManager.updateFile(privateFiles, "REMOVE"); - } + Utilities.removeFromPublicCollectionUpdatePrivacy({ files: slate.objects }); } return res.status(200).send({ decorator: "SERVER_DELETE_SLATE", error: false }); diff --git a/pages/api/slates/remove-file.js b/pages/api/slates/remove-file.js index ddd04e23..11a7c71c 100644 --- a/pages/api/slates/remove-file.js +++ b/pages/api/slates/remove-file.js @@ -20,7 +20,7 @@ export default async (req, res) => { }); } - const slate = await Data.getSlateById({ id: req.body.data.slateId }); + const slate = await Data.getSlateById({ id: req.body.data.slateId, includeFiles: true }); if (!slate) { return res.status(404).send({ @@ -46,18 +46,7 @@ export default async (req, res) => { } if (slate.isPublic) { - const publicFiles = await Data.getFilesByIds({ ids: fileIds, publicOnly: true }); - const publicIds = publicFiles.map((file) => file.id); - - let privateFiles = fileIds - .filter((id) => !publicIds.includes(id)) - .map((id) => { - return { id }; - }); - - if (privateFiles.length) { - SearchManager.updateFile(privateFiles, "REMOVE"); - } + Utilities.removeFromPublicCollectionUpdatePrivacy({ files: slate.objects }); } ViewerManager.hydratePartial(id, { slates: true }); diff --git a/pages/api/slates/update.js b/pages/api/slates/update.js index 114eb26b..bbe0dcbc 100644 --- a/pages/api/slates/update.js +++ b/pages/api/slates/update.js @@ -48,28 +48,6 @@ export default async (req, res) => { .status(500) .send({ decorator: "SERVER_UPDATE_SLATE_UPDATE_PRIVACY_FAILED", error: true }); } - - if (!updates.isPublic) { - //NOTE(martina): if any of the files in it are now private (because they are no longer in any public slates) remove them from search - const files = slate.objects; - - const publicFiles = await Data.getFilesByIds({ - ids: files.map((file) => file.id), - publicOnly: true, - }); - const publicIds = publicFiles.map((file) => file.id); - - let privateFiles = files.filter((file) => !publicIds.includes(file.id)); - - if (privateFiles.length) { - SearchManager.updateFile(privateFiles, "REMOVE"); - } - } else { - //NOTE(martina): make sure all the now-public files are in search if they weren't already - const files = slate.objects; - - SearchManager.updateFile(files, "ADD"); - } } if (updates.data.name && updates.data.name !== slate.data.name) { @@ -111,8 +89,12 @@ export default async (req, res) => { if (slate.isPublic && !updates.isPublic) { SearchManager.updateSlate(response, "REMOVE"); + + Utilities.removeFromPublicCollectionUpdatePrivacy({ files: slate.objects }); } else if (!slate.isPublic && updates.isPublic) { SearchManager.updateSlate(response, "ADD"); + + Utilities.addToPublicCollectionUpdatePrivacy({ files: slate.objects }); } else { SearchManager.updateSlate(response, "EDIT"); } diff --git a/pages/api/v2/update-collection.js b/pages/api/v2/update-collection.js index eb1ec5ec..3f678856 100644 --- a/pages/api/v2/update-collection.js +++ b/pages/api/v2/update-collection.js @@ -59,28 +59,6 @@ export default async (req, res) => { if (privacyResponse.error) { return res.status(500).send({ decorator: "UPDATE_COLLECTION_PRIVACY_FAILED", error: true }); } - - if (!updates.isPublic) { - //NOTE(martina): if any of the files in it are now private (because they are no longer in any public slates) remove them from search - const files = slate.objects; - - const publicFiles = await Data.getFilesByIds({ - ids: files.map((file) => file.id), - publicOnly: true, - }); - const publicIds = publicFiles.map((file) => file.id); - - let privateFiles = files.filter((file) => !publicIds.includes(file.id)); - - if (privateFiles.length) { - SearchManager.updateFile(privateFiles, "REMOVE"); - } - } else { - //NOTE(martina): make sure all the now-public files are in search if they weren't already - const files = slate.objects; - - SearchManager.updateFile(files, "ADD"); - } } if (updates.data.name && updates.data.name !== slate.data.name) { @@ -118,8 +96,10 @@ export default async (req, res) => { if (slate.isPublic && !updates.isPublic) { SearchManager.updateSlate(updatedSlate, "REMOVE"); + Utilities.removeFromPublicCollectionUpdatePrivacy({ files: slate.objects }); } else if (!slate.isPublic && updates.isPublic) { SearchManager.updateSlate(updatedSlate, "ADD"); + Utilities.addToPublicCollectionUpdatePrivacy({ files: slate.objects }); } else { SearchManager.updateSlate(updatedSlate, "EDIT"); } diff --git a/pages/api/v2/update-file.js b/pages/api/v2/update-file.js index cf91976e..0b915f8c 100644 --- a/pages/api/v2/update-file.js +++ b/pages/api/v2/update-file.js @@ -16,7 +16,6 @@ export default async (req, res) => { //NOTE(martina): cleans the input to remove fields they should not be changing like ownerId, createdAt, filename, size, type etc. let updates = { id: req.body.data.id, - isPublic: req.body.data.isPublic, data: { name: req.body.data.data?.name, body: req.body.data.data?.body, @@ -34,24 +33,6 @@ export default async (req, res) => { }); } - if (typeof updates.isPublic !== "undefined" && updates.isPublic !== file.isPublic) { - let response = await Data.updateFilePrivacy({ - ownerId: file.ownerId, - id: updates.id, - isPublic: updates.isPublic, - }); - - if (!response || response.error) { - return res.status(500).send({ decorator: "UPDATE_FILE_PRIVACY_FAILED", error: true }); - } - - if (response.isPublic) { - SearchManager.updateFile(response, "ADD"); - } else { - SearchManager.updateFile(response, "REMOVE"); - } - } - let response = await Data.updateFileById(updates); if (!response || response.error) {