Merge pull request #496 from filecoin-project/@tarafanlin/explore

add api endpoints for social and explore & tab routing
This commit is contained in:
Tara Lin 2021-01-25 17:00:31 -08:00 committed by GitHub
commit 48bc72d2e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 316 additions and 227 deletions

View File

@ -97,13 +97,19 @@ export const getSlateById = async (data) => {
};
export const getSlatesByIds = async (data) => {
await Websockets.checkWebsocket();
return await returnJSON(`/api/slates/get`, {
...DEFAULT_OPTIONS,
body: JSON.stringify({ data }),
});
};
export const getSocial = async (data) => {
return await returnJSON(`/api/users/get-social`, {
...DEFAULT_OPTIONS,
body: JSON.stringify({ data }),
});
};
export const deleteTrustRelationship = async (data) => {
await Websockets.checkWebsocket();
return await returnJSON(`/api/users/trust-delete`, {

View File

@ -188,6 +188,10 @@ export const error = {
GET_USER_BY_USERNAME:
"We weren't able to fetch information on that user. Please check your input",
USER_NOT_FOUND: "We aren't able to locate that user at the moment. Please try again",
SERVER_USER_SUBSCRIPTIONS_NOT_FOUND:
"We weren't able to fetch information on that user. Please try again later",
SERVER_USER_SUBSCRIBERS_NOT_FOUND:
"We weren't able to fetch information on that user. Please try again later",
//Hydrate
HYDRATE_FAILURE: "Please make sure you're logged in",

View File

@ -128,6 +128,13 @@ export const navigation = [
pageTitle: "Your Profile & Account Settings",
ignore: true,
},
{
id: "NAV_PROFILE_FILES",
decorator: "PROFILE_FILES",
name: "Profile",
pageTitle: "Profile",
ignore: true,
},
{
id: "NAV_PROFILE",
decorator: "PROFILE",
@ -135,6 +142,13 @@ export const navigation = [
pageTitle: "Profile",
ignore: true,
},
{
id: "NAV_PROFILE_PEERS",
decorator: "PROFILE_PEERS",
name: "Profile",
pageTitle: "Profile",
ignore: true,
},
{
id: "NAV_FILE",
decorator: "FILE",

View File

@ -75,7 +75,9 @@ const SCENES = {
ACTIVITY: <SceneActivity tab={0} />,
EXPLORE: <SceneActivity tab={1} />,
DIRECTORY: <SceneDirectory />,
PROFILE: <SceneProfile />,
PROFILE_FILES: <SceneProfile tab={0} />,
PROFILE: <SceneProfile tab={1} />,
PROFILE_PEERS: <SceneProfile tab={2} />,
DATA: <SceneFilesFolder />,
FILE: <SceneFile />,
SLATE: <SceneSlate />,

View File

@ -241,22 +241,43 @@ export default class Profile extends React.Component {
_ref = null;
state = {
tab: 1,
view: 0,
slateTab: 0,
peerTab: 0,
copyValue: "",
contextMenu: null,
publicFiles: [],
// isFollowing: this.props.external
// ? false
// : !!this.props.viewer.subscriptions.filter((entry) => {
// return entry.target_user_id === this.props.creator.id;
// }).length,
slates: this.props.creator.slates,
subscriptions: [],
subscribers: [],
isFollowing: null,
fetched: false,
};
componentDidMount = () => {
this.filterByVisibility();
this.setState({
isFollowing: this.props.external
? false
: !!this.state.subscriptions.filter((entry) => {
return entry.target_user_id === this.props.creator.id;
}).length,
});
};
componentDidUpdate = (prevProps, prevState) => {
if (prevProps.tab != this.props.tab || prevState.slateTab != this.props.slateTab) {
if (!this.state.fetched) {
if (this.state.slateTab === 1 || this.props.tab === 2) this.fetchSocial();
}
}
};
fetchSocial = async () => {
let query = { userId: this.props.creator.id };
const { subscribers } = await Actions.getSocial(query);
const { subscriptions } = await Actions.getSocial(query);
this.setState({ subscribers: subscribers, subscriptions: subscriptions, fetched: true });
};
filterByVisibility = () => {
@ -311,137 +332,135 @@ export default class Profile extends React.Component {
let isOwner = this.props.isOwner;
let creator = this.props.creator;
let username = this.state.slateTab === 0 ? creator.username : null;
// let subscriptions = this.props.creator.subscriptions || [];
// let subscribers = this.props.creator.subscribers || [];
// let exploreSlates = this.props.exploreSlates;
let subscriptions = this.state.subscriptions;
let subscribers = this.state.subscribers;
let slates = [];
// if (this.state.tab === 1) {
// if (this.state.slateTab === 0) {
if (this.props.tab === 1) {
if (this.state.slateTab === 0) {
slates = isOwner
? creator.slates.filter((slate) => slate.data.public === true)
: creator.slates;
// } else {
// slates = subscriptions
// .filter((relation) => {
// return !!relation.target_slate_id;
// })
// .map((relation) => relation.slate);
// }
// }
// let peers = [];
// if (this.state.tab === 2) {
// if (this.state.peerTab === 0) {
// peers = subscriptions
// .filter((relation) => {
// return !!relation.target_user_id;
// })
// .map((relation) => {
// let button = (
// <div css={STYLES_ITEM_BOX} onClick={(e) => this._handleClick(e, relation.id)}>
// <SVG.MoreHorizontal height="24px" />
// {this.state.contextMenu === relation.id ? (
// <Boundary
// captureResize={true}
// captureScroll={false}
// enabled
// onOutsideRectEvent={(e) => this._handleClick(e, relation.id)}
// >
// <PopoverNavigation
// style={{
// top: "40px",
// right: "0px",
// }}
// navigation={[
// {
// text: this.props.viewer?.subscriptions.filter((subscription) => {
// return subscription.target_user_id === relation.target_user_id;
// }).length
// ? "Unfollow"
// : "Follow",
// onClick: this.props.viewer
// ? (e) => this._handleFollow(e, relation.target_user_id)
// : () => this.setState({ visible: true }),
// },
// ]}
// />
// </Boundary>
// ) : null}
// </div>
// );
// return (
// <UserEntry
// key={relation.id}
// user={relation.user}
// button={button}
// onClick={() => {
// this.props.onAction({
// type: "NAVIGATE",
// value: this.props.sceneId,
// scene: "PROFILE",
// data: relation.user,
// });
// }}
// external={this.props.external}
// url={`/${relation.user.username}`}
// />
// );
// });
// } else {
// peers = subscribers.map((relation) => {
// let button = (
// <div css={STYLES_ITEM_BOX} onClick={(e) => this._handleClick(e, relation.id)}>
// <SVG.MoreHorizontal height="24px" />
// {this.state.contextMenu === relation.id ? (
// <Boundary
// captureResize={true}
// captureScroll={false}
// enabled
// onOutsideRectEvent={(e) => this._handleClick(e, relation.id)}
// >
// <PopoverNavigation
// style={{
// top: "40px",
// right: "0px",
// }}
// navigation={[
// {
// text: this.props.viewer?.subscriptions.filter((subscription) => {
// return subscription.target_user_id === relation.owner_user_id;
// }).length
// ? "Unfollow"
// : "Follow",
// onClick: this.props.viewer
// ? (e) => this._handleFollow(e, relation.owner_user_id)
// : () => this.setState({ visible: true }),
// },
// ]}
// />
// </Boundary>
// ) : null}
// </div>
// );
// return (
// <UserEntry
// key={relation.id}
// user={relation.owner}
// button={button}
// onClick={() => {
// this.props.onAction({
// type: "NAVIGATE",
// value: this.props.sceneId,
// scene: "PROFILE",
// data: relation.owner,
// });
// }}
// external={this.props.external}
// url={`https://slate.host/${relation.owner.username}`}
// />
// );
// });
// }
// }
} else {
slates = subscriptions
.filter((relation) => {
return !!relation.target_slate_id;
})
.map((relation) => relation.slate);
}
}
let exploreSlates = this.props.exploreSlates;
let peers = [];
if (this.props.tab === 2) {
if (this.state.peerTab === 0) {
peers = subscriptions
.filter((relation) => {
return !!relation.target_user_id;
})
.map((relation) => {
let button = (
<div css={STYLES_ITEM_BOX} onClick={(e) => this._handleClick(e, relation.id)}>
<SVG.MoreHorizontal height="24px" />
{this.state.contextMenu === relation.id ? (
<Boundary
captureResize={true}
captureScroll={false}
enabled
onOutsideRectEvent={(e) => this._handleClick(e, relation.id)}
>
<PopoverNavigation
style={{
top: "40px",
right: "0px",
}}
navigation={[
{
text: this.props.viewer?.subscriptions.filter((subscription) => {
return subscription.target_user_id === relation.target_user_id;
}).length
? "Unfollow"
: "Follow",
onClick: this.props.viewer
? (e) => this._handleFollow(e, relation.target_user_id)
: () => this.setState({ visible: true }),
},
]}
/>
</Boundary>
) : null}
</div>
);
return (
<UserEntry
key={relation.id}
user={relation.user}
button={button}
onClick={() => {
this.props.onAction({
type: "NAVIGATE",
value: this.props.sceneId,
scene: "PROFILE",
data: relation.user,
});
}}
external={this.props.external}
url={`/${relation.user.username}`}
/>
);
});
} else {
peers = subscribers.map((relation) => {
let button = (
<div css={STYLES_ITEM_BOX} onClick={(e) => this._handleClick(e, relation.id)}>
<SVG.MoreHorizontal height="24px" />
{this.state.contextMenu === relation.id ? (
<Boundary
captureResize={true}
captureScroll={false}
enabled
onOutsideRectEvent={(e) => this._handleClick(e, relation.id)}
>
<PopoverNavigation
style={{
top: "40px",
right: "0px",
}}
navigation={[
{
text: this.props.viewer?.subscriptions.filter((subscription) => {
return subscription.target_user_id === relation.owner_user_id;
}).length
? "Unfollow"
: "Follow",
onClick: this.props.viewer
? (e) => this._handleFollow(e, relation.owner_user_id)
: () => this.setState({ visible: true }),
},
]}
/>
</Boundary>
) : null}
</div>
);
return (
<UserEntry
key={relation.id}
user={relation.owner}
button={button}
onClick={() => {
this.props.onAction({
type: "NAVIGATE",
value: this.props.sceneId,
scene: "PROFILE",
data: relation.owner,
});
}}
external={this.props.external}
url={`/${relation.owner.username}`}
/>
);
});
}
}
let total = creator.slates.reduce((total, slate) => {
return total + slate.data?.objects?.length || 0;
@ -527,13 +546,22 @@ export default class Profile extends React.Component {
)}
<div css={STYLES_PROFILE}>
<TabGroup
tabs={["Files", "Slates" /*"Peers"*/]}
value={this.state.tab}
onChange={(value) => this.setState({ tab: value })}
tabs={
this.props.external
? ["Files", "Slates", "Peers"]
: [
{ title: "Files", value: "NAV_PROFILE_FILES" },
{ title: "Slates", value: "NAV_PROFILE" },
{ title: "Peers", value: "NAV_PROFILE_PEERS" },
]
}
value={this.props.tab}
onChange={this.props.external ? this.props.changeTab : null}
onAction={this.props.external ? null : this.props.onAction}
style={{ marginTop: 0, marginBottom: 32 }}
itemStyle={{ margin: "0px 16px" }}
/>
{this.state.tab === 0 ? (
{this.props.tab === 0 ? (
<div>
{this.props.mobile ? null : (
<div style={{ display: `flex` }}>
@ -565,17 +593,17 @@ export default class Profile extends React.Component {
)}
</div>
) : null}
{this.state.tab === 1 ? (
{this.props.tab === 1 ? (
<div>
{/* <SecondaryTabGroup
<SecondaryTabGroup
tabs={["Slates", "Following"]}
value={this.state.slateTab}
onChange={(value) => this.setState({ slateTab: value })}
style={{ margin: "0 0 24px 0" }}
/> */}
/>
{slates?.length ? (
<SlatePreviewBlocks
isOwner={!!isOwner ? isOwner : false}
isOwner={this.state.slateTab === 0 ? isOwner : false}
external={this.props.external}
slates={slates}
username={username}
@ -583,13 +611,7 @@ export default class Profile extends React.Component {
/>
) : (
<React.Fragment>
<EmptyState>
<SVG.Slate height="24px" style={{ marginBottom: 24 }} />
{this.state.slateTab === 0
? `This user does not have any public slates yet`
: `This user is not following any slates yet`}
</EmptyState>
{/* {this.props.external ? (
{this.props.external && exploreSlates.length != 0 ? (
<React.Fragment>
<EmptyState style={{ border: `none`, height: `120px` }}>
<SVG.Slate height="24px" style={{ marginBottom: 24 }} />
@ -606,12 +628,19 @@ export default class Profile extends React.Component {
onAction={this.props.onAction}
/>
</React.Fragment>
) : null} */}
) : (
<EmptyState>
<SVG.Slate height="24px" style={{ marginBottom: 24 }} />
{this.state.slateTab === 0
? `This user does not have any public slates yet`
: `This user is not following any slates yet`}
</EmptyState>
)}
</React.Fragment>
)}
</div>
) : null}
{/* {this.state.tab === 2 ? (
{this.props.tab === 2 ? (
<div>
<SecondaryTabGroup
tabs={["Following", "Followers"]}
@ -641,7 +670,7 @@ export default class Profile extends React.Component {
css={STYLES_COPY_INPUT}
/>
</div>
) : null} */}
) : null}
</div>
</div>
);

View File

@ -32,6 +32,7 @@ const STYLES_ROOT = css`
export default class ProfilePage extends React.Component {
state = {
visible: false,
tab: 1,
};
render() {
@ -51,7 +52,14 @@ export default class ProfilePage extends React.Component {
<WebsitePrototypeWrapper title={title} description={description} url={url} image={image}>
<WebsitePrototypeHeader />
<div css={STYLES_ROOT}>
<Profile {...this.props} buttons={buttons} isOwner={false} external />
<Profile
{...this.props}
buttons={buttons}
isOwner={false}
external
tab={this.state.tab}
changeTab={(value) => this.setState({ tab: value })}
/>
</div>
{this.state.visible && (
<div>

View File

@ -25,7 +25,6 @@ export default async (req, res) => {
error: true,
});
}
if (Array.isArray(req.body.data.id)) {
const responseMultiple = await Data.getSlatesByIds({ ids: req.body.data.id });
if (!responseMultiple) {

View File

@ -0,0 +1,49 @@
import * as Data from "~/node_common/data";
import * as Serializers from "~/node_common/serializers";
export default async (req, res) => {
const subscriptions = await Data.getSubscriptionsByUserId({ userId: req.body.data.userId });
if (!subscriptions) {
return res.status(404).send({ decorator: "SERVER_USER_SUBSCRIPTIONS_NOT_FOUND", error: true });
}
if (subscriptions.error) {
return res.status(500).send({ decorator: "SERVER_USER_SUBSCRIPTIONS_NOT_FOUND", error: true });
}
const subscribers = await Data.getSubscribersByUserId({ userId: req.body.data.userId });
if (!subscribers) {
return res.status(404).send({ decorator: "SERVER_USER_SUBSCRIBERS_NOT_FOUND", error: true });
}
if (subscribers.error) {
return res.status(500).send({ decorator: "SERVER_USER_SUBSCRIBERS_NOT_FOUND", error: true });
}
let serializedUsersMap = { [req.body.data.userId]: req.body.data };
let serializedSlatesMap = {};
const serializedSubscriptions = await Serializers.doSubscriptions({
users: [],
slates: [],
subscriptions,
serializedUsersMap,
serializedSlatesMap,
});
const serializedSubscribers = await Serializers.doSubscribers({
users: [],
slates: [],
subscribers,
serializedUsersMap: serializedSubscriptions.serializedUsersMap,
serializedSlatesMap: serializedSubscriptions.serializedSlatesMap,
});
return res.status(200).send({
decorator: "SERVER_USER_SOCIAL",
subscriptions: serializedSubscriptions.serializedSubscriptions,
subscribers: serializedSubscribers.serializedSubscribers,
});
};

116
server.js
View File

@ -61,6 +61,49 @@ const EXTERNAL_RESOURCES = {
search: Strings.isEmpty(Environment.RESOURCE_URI_SEARCH) ? null : Environment.RESOURCE_URI_SEARCH,
};
let exploreSlates = [];
const fetchExploreSlates = async () => {
let exploreSlates = [];
if (Environment.IS_PRODUCTION) {
exploreSlates = await Data.getSlatesByIds({
ids: [
//NOTE(tara): slates in prod
"d2861ac4-fc41-4c07-8f21-d0bf06be364c",
"9c2c458c-d92a-4e81-a4b6-bf6ab4607470",
"7f461144-0647-43d7-8294-788b37ae5979",
"f72c2594-b8ac-41f6-91e0-b2da6788ae23",
"a0d6e2f2-564d-47ed-bf56-13c42634703d",
"0ba92c73-92e7-4b00-900e-afae4856c9ea",
],
});
for (let exploreSlate of exploreSlates) {
let user = await Data.getUserById({ id: exploreSlate.data.ownerId });
exploreSlate.username = user.username;
}
}
// else {
// exploreSlates = await Data.getSlatesByIds({
// ids: [
// //NOTE(tara): slates in localhost for testing
// "857ad84d-7eff-4861-a988-65c84b62fc23",
// "81fa0b39-0e96-4c7f-8587-38468bb67cb3",
// "c4e8dad7-4ba0-4f25-a92a-c73ef5522d29",
// "df05cb1f-2ecf-4872-b111-c4b8493d08f8",
// "435035e6-dee4-4bbf-9521-64c219a527e7",
// "ac907aa3-2fb2-46fd-8eba-ec8ceb87b5eb",
// ],
// });
// for (let exploreSlate of exploreSlates) {
// let user = await Data.getUserById({ id: exploreSlate.data.ownerId });
// exploreSlate.username = user.username;
// }
// }
return exploreSlates;
};
app.prepare().then(async () => {
const server = express();
@ -304,79 +347,12 @@ app.prepare().then(async () => {
creator.library = library;
/*
const subscriptions = await Data.getSubscriptionsByUserId({ userId: creator.id });
const subscribers = await Data.getSubscribersByUserId({ userId: creator.id });
let serializedUsersMap = { [creator.id]: creator };
let serializedSlatesMap = {};
const r1 = await Serializers.doSubscriptions({
users: [],
slates: [],
subscriptions,
serializedUsersMap,
serializedSlatesMap,
});
creator.subscriptions = r1.serializedSubscriptions;
const r2 = await Serializers.doSubscribers({
users: [],
slates: [],
subscribers,
serializedUsersMap: r1.serializedUsersMap,
serializedSlatesMap: r1.serializedSlatesMap,
});
creator.subscribers = r2.serializedSubscribers;
*/
// NOTE(tara+martina)
// Remove this at some point.
/*
if (Environment.IS_PRODUCTION) {
exploreSlates = await Data.getSlatesByIds({
ids: [
//NOTE(tara): slates in prod
"d2861ac4-fc41-4c07-8f21-d0bf06be364c",
"9c2c458c-d92a-4e81-a4b6-bf6ab4607470",
"7f461144-0647-43d7-8294-788b37ae5979",
"f72c2594-b8ac-41f6-91e0-b2da6788ae23",
"a0d6e2f2-564d-47ed-bf56-13c42634703d",
"0ba92c73-92e7-4b00-900e-afae4856c9ea",
],
});
for (let exploreSlate of exploreSlates) {
let user = await Data.getUserById({ id: exploreSlate.data.ownerId });
exploreSlate.username = user.username;
}
} else {
exploreSlates = await Data.getSlatesByIds({
ids: [
//NOTE(tara): slates in localhost for testing
"857ad84d-7eff-4861-a988-65c84b62fc23",
"81fa0b39-0e96-4c7f-8587-38468bb67cb3",
"c4e8dad7-4ba0-4f25-a92a-c73ef5522d29",
"df05cb1f-2ecf-4872-b111-c4b8493d08f8",
"435035e6-dee4-4bbf-9521-64c219a527e7",
"ac907aa3-2fb2-46fd-8eba-ec8ceb87b5eb",
],
});
for (let exploreSlate of exploreSlates) {
let user = await Data.getUserById({ id: exploreSlate.data.ownerId });
exploreSlate.username = user.username;
}
}
*/
return app.render(req, res, "/_/profile", {
viewer,
creator,
mobile,
resources: EXTERNAL_RESOURCES,
exploreSlates,
});
});
@ -518,10 +494,12 @@ app.prepare().then(async () => {
server.all("*", async (r, s) => handler(r, s, r.url));
const listenServer = server.listen(Environment.PORT, (e) => {
const listenServer = server.listen(Environment.PORT, async (e) => {
if (e) throw e;
Websocket.create();
NodeLogging.log(`started on http://localhost:${Environment.PORT}`);
exploreSlates = await fetchExploreSlates();
});
});