Merge pull request #845 from filecoin-project/update/file-privacy

Update: File privacy
This commit is contained in:
martinalong 2021-08-06 18:44:14 -07:00 committed by GitHub
commit d8a51ab42b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 105 additions and 625 deletions

View File

@ -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`, {

View File

@ -518,51 +518,6 @@ class CarouselSidebar extends React.Component {
this.props.onNext();
};
_handleToggleVisibility = async (e) => {
if (this.props.external || !this.props.isOwner || !this.props.viewer) return;
const isPublic = this.props.file.isPublic;
const slateIsPublic = this.props.data?.isPublic;
let selected = cloneDeep(this.state.selected);
const slateIds = Object.entries(this.state.selected)
.filter((entry) => entry[1])
.map((entry) => entry[0]);
const publicSlateIds = [];
const publicSlateNames = [];
for (let slate of this.props.viewer.slates) {
if (slate.isPublic && slateIds.includes(slate.id)) {
publicSlateNames.push(slate.data.name);
publicSlateIds.push(slate.id);
selected[slate.id] = false;
}
}
if (publicSlateNames.length) {
const slateNames = publicSlateNames.join(", ");
const message = `Making this file link-viewing only will remove it from the following public collections: ${slateNames}. Do you wish to continue?`;
if (!window.confirm(message)) {
return;
}
}
if (this.props.carouselType === "SLATE" && slateIsPublic) {
const slateId = this.props.data.id;
let slates = cloneDeep(this.props.viewer.slates);
for (let slate of slates) {
if (slate.id === slateId) {
slate.objects = slate.objects.filter((obj) => obj.id !== this.props.file.id);
break;
}
}
this.props.onAction({ type: "UPDATE_VIEWER", viewer: { slates } });
}
let response = await Actions.toggleFilePrivacy({ ...this.props.file, isPublic: !isPublic });
Events.hasError(response);
if (isPublic) {
this.setState({ selected });
}
};
render() {
const isPublic = this.props.file.isPublic;
const file = this.props.file;
@ -822,74 +777,6 @@ class CarouselSidebar extends React.Component {
);
}
let privacy;
if (editingAllowed) {
privacy = (
<div>
<System.P1 css={STYLES_SECTION_HEADER} style={{ marginBottom: 12 }}>
Visibility
</System.P1>
<System.P1
css={STYLES_TEXT}
style={{
marginTop: 12,
}}
>
{isPublic
? `This ${
isLink ? "link" : "file"
} is currently visible to everyone and searchable within Slate. It may appear in activity feeds and explore.`
: isLink
? "This link is only visible to you"
: "This file is only visible to those with the link."}
</System.P1>
<RadioGroup
name="isPublic"
options={[
{
value: true,
label: (
<div style={{ display: "flex", alignItems: "center" }}>
<SVG.Globe height="16px" style={{ marginRight: 8 }} />
Public
</div>
),
},
{
value: false,
label: (
<div style={{ display: "flex", alignItems: "center" }}>
<SVG.SecurityLock height="16px" style={{ marginRight: 8 }} />
Link-viewing only
</div>
),
},
]}
dark={true}
style={{ marginTop: 12 }}
labelStyle={{ fontFamily: Constants.font.medium }}
selected={isPublic}
onChange={this._handleToggleVisibility}
/>
{!isPublic && !isLink && (
<Input
full
value={isLink ? file.data.link.url : Strings.getURLfromCID(file.cid)}
name="copyLink"
readOnly
copyable
style={{
fontSize: Constants.typescale.lvl1,
...STYLES_INPUT,
marginTop: 12,
}}
textStyle={{ color: Constants.system.white }}
/>
)}
</div>
);
}
return (
<>
{this.state.modalShow && (
@ -913,7 +800,6 @@ class CarouselSidebar extends React.Component {
</div>
{elements}
<div css={STYLES_ACTIONS}>{actions}</div>
{privacy}
{uploadCoverImage}
{!this.props.external && this.props.viewer && (
<>
@ -967,20 +853,3 @@ class CarouselSidebar extends React.Component {
}
export default withTheme(CarouselSidebar);
{
/* <>
<div css={STYLES_SECTION_HEADER} style={{ margin: "48px 0px 8px 0px" }}>
Visibility
</div>
<div css={STYLES_OPTIONS_SECTION}>
<div css={STYLES_TEXT}>{isVisible ? "Everyone" : "Link only"}</div>
<Toggle dark active={isVisible} onChange={this._handleToggleVisibility} />
</div>
<div style={{ color: Constants.system.grayLight2, marginTop: 8 }}>
{isVisible
? "This file is currently visible to everyone and searchable within Slate. It may appear in activity feeds and explore."
: "This file is currently not visible to others unless they have the link."}
</div>
</> */
}

View File

@ -3,25 +3,16 @@ import * as Constants from "~/common/constants";
import * as Strings from "~/common/strings";
import * as SVG from "~/common/svg";
import * as Actions from "~/common/actions";
import * as Utilities from "~/common/utilities";
import * as Events from "~/common/custom-events";
import * as Window from "~/common/window";
import * as Styles from "~/common/styles";
import { useState } from "react";
import { Link } from "~/components/core/Link";
import { GlobalCarousel } from "~/components/system/components/GlobalCarousel";
import { css } from "@emotion/react";
import { ButtonPrimary, ButtonSecondary } from "~/components/system/components/Buttons";
import { TabGroup, SecondaryTabGroup } from "~/components/core/TabGroup";
import { Boundary } from "~/components/system/components/fragments/Boundary";
import { PopoverNavigation } from "~/components/system/components/PopoverNavigation";
import { FileTypeGroup } from "~/components/core/FileTypeIcon";
import { SecondaryTabGroup } from "~/components/core/TabGroup";
import { LoaderSpinner } from "~/components/system/components/Loaders";
import ProcessedText from "~/components/core/ProcessedText";
import SlatePreviewBlocks from "~/components/core/SlatePreviewBlock";
import DataView from "~/components/core/DataView";
import EmptyState from "~/components/core/EmptyState";
import ProfilePhoto from "~/components/core/ProfilePhoto";
import CollectionPreviewBlock from "~/components/core/CollectionPreviewBlock";
@ -139,16 +130,6 @@ const STYLES_STAT = css`
flex-shrink: 0;
`;
const STYLES_EXPLORE = css`
font-size: ${Constants.typescale.lvl1};
font-family: ${Constants.font.text};
font-weight: 400;
margin: 64px auto 64px auto;
width: 120px;
padding-top: 16px;
border-top: 1px solid ${Constants.system.black};
`;
const STYLES_BUTTON = css`
margin-bottom: 32px;
@ -157,164 +138,12 @@ const STYLES_BUTTON = css`
}
`;
const STYLES_ITEM_BOX = css`
position: relative;
justify-self: end;
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
margin-right: 16px;
color: ${Constants.system.grayLight2};
@media (max-width: ${Constants.sizes.mobile}px) {
margin-right: 8px;
}
`;
const STYLES_USER_ENTRY = css`
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
font-size: ${Constants.typescale.lvl1};
cursor: pointer;
border: 1px solid ${Constants.semantic.borderGrayLight};
border-radius: 4px;
margin-bottom: 8px;
background-color: ${Constants.system.white};
`;
const STYLES_USER = css`
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
margin: 16px;
color: ${Constants.system.blue};
font-family: ${Constants.font.medium};
font-size: ${Constants.typescale.lvl1};
@media (max-width: ${Constants.sizes.mobile}px) {
margin: 12px 16px;
}
`;
const STYLES_DIRECTORY_PROFILE_IMAGE = css`
background-color: ${Constants.semantic.bgLight};
background-size: cover;
background-position: 50% 50%;
height: 24px;
width: 24px;
margin-right: 16px;
border-radius: 4px;
position: relative;
`;
const STYLES_DIRECTORY_STATUS_INDICATOR = css`
position: absolute;
bottom: 0;
right: 0;
width: 7px;
height: 7px;
border-radius: 50%;
border: 1.2px solid ${Constants.system.green};
background-color: ${Constants.system.green};
`;
const STYLES_MESSAGE = css`
color: ${Constants.system.black};
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
@media (max-width: 1000px) {
display: none;
}
`;
const STYLES_DIRECTORY_NAME = css`
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
`;
// const STYLES_COPY_INPUT = css`
// pointer-events: none;
// position: absolute;
// opacity: 0;
// `;
function UserEntry({ user, button, onClick, message, checkStatus }) {
const isOnline = checkStatus({ id: user.id });
return (
<div key={user.username} css={STYLES_USER_ENTRY}>
<div css={STYLES_USER} onClick={onClick}>
<div css={STYLES_DIRECTORY_PROFILE_IMAGE}>
<ProfilePhoto user={user} size={24} />
{isOnline && <div css={STYLES_DIRECTORY_STATUS_INDICATOR} />}
</div>
<span css={STYLES_DIRECTORY_NAME}>
{user.data.name || `@${user.username}`}
{message ? <span css={STYLES_MESSAGE}>{message}</span> : null}
</span>
</div>
{button}
</div>
);
}
function FilesPage({
library,
user,
isOwner,
isMobile,
viewer,
onAction,
resources,
page,
tab = "grid",
}) {
return (
<div>
{isMobile ? null : (
<SecondaryTabGroup
tabs={[
{
title: <SVG.GridView height="24px" style={{ display: "block" }} />,
value: { tab: "grid", subtab: "files" },
},
{
title: <SVG.TableView height="24px" style={{ display: "block" }} />,
value: { tab: "table", subtab: "files" },
},
]}
value={tab}
onAction={onAction}
style={{ margin: "0 0 24px 0" }}
/>
)}
{library.length ? (
<DataView
key="scene-profile"
user={user}
onAction={onAction}
viewer={viewer}
isOwner={isOwner}
items={library}
view={tab}
resources={resources}
page={page}
/>
) : (
<EmptyState>
<FileTypeGroup />
<div style={{ marginTop: 24 }}>This user does not have any public files yet</div>
</EmptyState>
)}
</div>
);
}
function CollectionsPage({
user,
viewer,
@ -339,8 +168,8 @@ function CollectionsPage({
<div>
<SecondaryTabGroup
tabs={[
{ title: "Collections", value: { tab: "collections", subtab: "collections" } },
{ title: "Subscribed", value: { tab: "subscribed", subtab: "collections" } },
{ title: "Collections", value: { tab: "collections" } },
{ title: "Subscribed", value: { tab: "subscribed" } },
]}
value={tab}
onAction={onAction}
@ -377,125 +206,12 @@ function CollectionsPage({
);
}
function PeersPage({
checkStatus,
viewer,
following,
followers,
fetched,
tab = "following",
onAction,
onLoginModal,
}) {
const [selectedUser, setSelectedUser] = useState(false);
const selectUser = (e, id) => {
e.stopPropagation();
e.preventDefault();
if (!id || selectedUser === id) {
setSelectedUser(null);
} else {
setSelectedUser(id);
}
};
const followUser = async (e, id) => {
e.stopPropagation();
e.preventDefault();
selectUser(e, null);
if (!viewer) {
onLoginModal();
return;
}
await Actions.createSubscription({
userId: id,
});
};
let peers = tab === "following" ? following : followers;
peers = peers.map((relation) => {
const following = !!(
viewer &&
viewer.following.some((subscription) => {
return subscription.id === relation.id;
}).length
);
let button =
!viewer || relation.id !== viewer?.id ? (
<div css={STYLES_ITEM_BOX} onClick={(e) => selectUser(e, relation.id)}>
<SVG.MoreHorizontal height="24px" />
{selectedUser === relation.id ? (
<Boundary
captureResize={true}
captureScroll={false}
enabled
onOutsideRectEvent={(e) => selectUser(e)}
>
<PopoverNavigation
style={{
top: "40px",
right: "0px",
}}
navigation={[
[
{
text: following ? "Unfollow" : "Follow",
onClick: (e) => followUser(e, relation.id),
},
],
]}
/>
</Boundary>
) : null}
</div>
) : null;
return (
<Link key={relation.id} href={`/$/user/${relation.id}`} onAction={onAction}>
<UserEntry key={relation.id} user={relation} button={button} checkStatus={checkStatus} />
</Link>
);
});
return (
<div>
<SecondaryTabGroup
tabs={[
{ title: "Following", value: { tab: "following", subtab: "peers" } },
{ title: "Followers", value: { tab: "followers", subtab: "peers" } },
]}
value={tab}
onAction={onAction}
style={{ margin: "0 0 24px 0" }}
/>
<div>
{peers?.length ? (
peers
) : (
<EmptyState>
{fetched ? (
<React.Fragment>
<SVG.Users height="24px" style={{ marginBottom: 24 }} />
{tab === "following"
? `This user is not following anyone yet`
: `This user does not have any followers yet`}
</React.Fragment>
) : (
<LoaderSpinner style={{ height: 24, width: 24 }} />
)}
</EmptyState>
)}
</div>
</div>
);
}
export default class Profile extends React.Component {
_ref = null;
state = {
contextMenu: null,
subscriptions: [],
followers: [],
following: [],
isFollowing:
this.props.external || this.props.user.id === this.props.viewer?.id
? false
@ -506,10 +222,6 @@ export default class Profile extends React.Component {
index: -1,
};
componentDidMount = () => {
this.fetchSocial();
};
componentDidUpdate = (prevProps) => {
if (!this.state.fetched && this.props.page.params !== prevProps.page.params) {
this.fetchSocial();
@ -518,12 +230,9 @@ export default class Profile extends React.Component {
fetchSocial = async () => {
if (this.state.fetched) return;
if (this.props.page.params?.subtab !== "peers" && this.props.page.params?.tab !== "subscribed")
return;
let following, followers, subscriptions;
if (this.props.page.params?.tab !== "subscribed") return;
let subscriptions;
if (this.props.user.id === this.props.viewer?.id) {
following = this.props.viewer?.following;
followers = this.props.viewer?.followers;
subscriptions = this.props.viewer?.subscriptions;
} else {
const query = { id: this.props.user.id };
@ -531,19 +240,15 @@ export default class Profile extends React.Component {
if (Events.hasError(response)) {
return;
}
following = response.following;
followers = response.followers;
subscriptions = response.subscriptions;
}
this.setState({
following: following,
followers: followers,
subscriptions: subscriptions,
fetched: true,
});
};
_handleHide = (e) => {
_handleHide = () => {
this.setState({ contextMenu: null });
};
@ -582,33 +287,14 @@ export default class Profile extends React.Component {
};
render() {
let subtab = this.props.page.params?.subtab
? this.props.page.params?.subtab
: this.props.page.params?.cid
? "files"
: "collections";
let tab = this.props.page.params?.tab;
let library = this.props.user.library;
let isOwner = this.props.isOwner;
let user = this.props.user;
let { user, isOwner } = this.props;
let { fileCount } = user;
const showStatusIndicator = this.props.isAuthenticated;
return (
<div>
<GlobalCarousel
carouselType="PROFILE"
resources={this.props.resources}
viewer={this.props.viewer}
objects={library}
isOwner={this.props.isOwner}
onAction={this.props.onAction}
isMobile={this.props.isMobile}
external={this.props.external}
params={this.props.page.params}
index={this.state.index}
onChange={(index) => this.setState({ index })}
/>
<div css={STYLES_PROFILE_BACKGROUND}>
<div css={STYLES_PROFILE_INFO}>
<div css={STYLES_PROFILE_IMAGE}>
@ -650,7 +336,7 @@ export default class Profile extends React.Component {
<div css={STYLES_STATS}>
<div css={STYLES_STAT}>
<div style={{ fontFamily: `${Constants.font.text}` }}>
{library.length}{" "}
{fileCount}{" "}
<span style={{ color: `${Constants.system.grayLight2}` }}>Files</span>
</div>
</div>
@ -665,39 +351,12 @@ export default class Profile extends React.Component {
</div>
</div>
<div css={STYLES_PROFILE}>
<TabGroup
tabs={[
{ title: "Files", value: { subtab: "files" } },
{ title: "Collections", value: { subtab: "collections" } },
{ title: "Peers", value: { subtab: "peers" } },
]}
value={subtab}
onAction={this.props.onAction}
style={{ marginTop: 0, marginBottom: 32 }}
itemStyle={{ margin: "0px 16px" }}
<CollectionsPage
{...this.props}
tab={tab}
fetched={this.state.fetched}
subscriptions={this.state.subscriptions}
/>
{subtab === "files" ? (
<FilesPage {...this.props} user={user} library={library} tab={tab} />
) : null}
{subtab === "collections" ? (
<CollectionsPage
{...this.props}
tab={tab}
fetched={this.state.fetched}
subscriptions={this.state.subscriptions}
/>
) : null}
{subtab === "peers" ? (
<PeersPage
{...this.props}
tab={tab}
onLoginModal={this._handleLoginModal}
checkStatus={this.checkStatus}
following={this.state.following}
followers={this.state.followers}
fetched={this.state.fetched}
/>
) : null}
</div>
</div>
);

View File

@ -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.
</System.P1>
<RadioGroup
name="isPublic"

View File

@ -25,9 +25,8 @@ import getFilesByUserId from "~/node_common/data/methods/get-files-by-user-id";
import deleteFilesByIds from "~/node_common/data/methods/delete-files-by-ids";
import deleteFilesByUserId from "~/node_common/data/methods/delete-files-by-user-id";
import updateFileById from "~/node_common/data/methods/update-file-by-id";
import updateFilePrivacy from "~/node_common/data/methods/update-file-privacy";
import updateFilesPublic from "~/node_common/data/methods/update-files-public";
import incrementFileSavecount from "~/node_common/data/methods/increment-file-savecount";
import recalcFilePrivacy from "~/node_common/data/methods/recalc-file-privacy";
// NOTE(martina):
// Like postgres queries
@ -121,9 +120,8 @@ export {
deleteFilesByIds,
deleteFilesByUserId,
updateFileById,
updateFilePrivacy,
updateFilesPublic,
incrementFileSavecount,
recalcFilePrivacy,
// NOTE(martina): Like postgres queries
createLike,
deleteLikeByFile,

View File

@ -0,0 +1,42 @@
import { runQuery } from "~/node_common/data/utilities";
export default async ({ fileId }) => {
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",
};
},
});
};

View File

@ -11,8 +11,6 @@ export const sanitizeUser = (entity) => {
username: entity.username,
slates: entity.slates, //NOTE(martina): this is not in the database. It is added after
library: entity.library, //NOTE(martina): this is not in the database. It is added after
twitterId: entity.twitterId,
email: entity.email,
data: {
name: entity.data?.name,
photo: entity.data?.photo,

View File

@ -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;
};

View File

@ -108,6 +108,7 @@ export default async (req, res) => {
}
if (slate?.isPublic) {
Utilities.addToPublicCollectionUpdatePrivacy({ files: filesToAddToSlate });
SearchManager.updateFile(createdFiles, "ADD");
}
ViewerManager.hydratePartial(id, { library: true, slates: slate ? true : false });

View File

@ -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 });

View File

@ -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 });

View File

@ -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");
}

View File

@ -1,8 +1,7 @@
import * as Data from "~/node_common/data";
import * as Serializers from "~/node_common/serializers";
export default async (req, res) => {
let id = req.body.data.id;
let { id } = req.body.data;
if (!id) {
return res.status(404).send({ decorator: "SERVER_USER_SOCIAL_NO_USER_ID", error: true });
}
@ -21,38 +20,8 @@ export default async (req, res) => {
.send({ decorator: "SERVER_USER_SOCIAL_SUBSCRIPTIONS_NOT_FOUND", error: true });
}
const following = await Data.getFollowingByUserId({ ownerId: id });
if (!following) {
return res
.status(404)
.send({ decorator: "SERVER_USER_SOCIAL_FOLLOWING_NOT_FOUND", error: true });
}
if (following.error) {
return res
.status(500)
.send({ decorator: "SERVER_USER_SOCIAL_FOLLOWING_NOT_FOUND", error: true });
}
const followers = await Data.getFollowersByUserId({ userId: id });
if (!followers) {
return res
.status(404)
.send({ decorator: "SERVER_USER_SOCIAL_FOLLOWERS_NOT_FOUND", error: true });
}
if (followers.error) {
return res
.status(500)
.send({ decorator: "SERVER_USER_SOCIAL_FOLLOWERS_NOT_FOUND", error: true });
}
return res.status(200).send({
decorator: "SERVER_USER_SOCIAL",
following,
followers,
subscriptions,
});
};

View File

@ -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");
}

View File

@ -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) {

View File

@ -319,7 +319,6 @@ app.prepare().then(async () => {
let user = await Data.getUserByUsername({
username,
includeFiles: true,
sanitize: true,
publicOnly: true,
});