feat: favorites

This commit is contained in:
cha0s 2020-07-18 22:48:11 -05:00
parent d29a98bef5
commit c369846fa8
10 changed files with 114 additions and 40 deletions

View File

@ -3,7 +3,13 @@ import './chat--leftRooms.scss';
import React from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {favoriteChannelsSelector, recentSelector, removeRecent} from '~/common/state/user';
import {
favoriteChannelsSelector,
recentSelector,
removeRecent,
submitAddFavorite,
submitRemoveFavorite,
} from '~/common/state/user';
import Channel from './channel';
@ -12,13 +18,6 @@ export default function ChatLeftRooms() {
const favorites = useSelector(favoriteChannelsSelector);
const recent = useSelector(recentSelector)
.filter((channel) => -1 === favorites.indexOf(channel));
const favoritesActions = [
{
icon: '💔',
label: 'Unfavorite',
onClick: () => {},
},
];
return (
<div
className="chat--leftRooms"
@ -35,18 +34,27 @@ export default function ChatLeftRooms() {
<>
<h2 className="channels__chatsTitle">Favorites</h2>
<ul className="channels__chatsList">
{favorites.map((channel) => (
<li
className="channels__item"
>
<Channel
actions={favoritesActions}
href={`/chat/r/${channel}`}
name={channel}
prefix="/r/"
/>
</li>
))}
{favorites.map((channel) => {
const favoritesActions = [
{
icon: '💔',
label: 'Unfavorite',
onClick: () => dispatch(submitRemoveFavorite(`/r/${channel}`)),
},
];
return (
<li
className="channels__item"
>
<Channel
actions={favoritesActions}
href={`/chat/r/${channel}`}
name={channel}
prefix="/r/"
/>
</li>
);
})}
</ul>
</>
)}
@ -59,7 +67,7 @@ export default function ChatLeftRooms() {
{
icon: '❤️',
label: 'Favorite',
onClick: () => {},
onClick: () => dispatch(submitAddFavorite(`/r/${channel}`)),
},
{
icon: '×',

View File

@ -12,6 +12,14 @@ import {
submitMessage,
} from '~/common/state/chat';
import AddFavorite from '../../common/packets/add-favorite.packet';
import RemoveFavorite from '../../common/packets/remove-favorite.packet';
import {
addToFavorites,
removeFromFavorites,
submitAddFavorite,
submitRemoveFavorite,
} from '../../common/state/user';
import {
fetchUsernames,
} from '~/common/state/usernames';
@ -44,6 +52,14 @@ const effects = {
dispatch(confirmMessage({current, previous: payload.uuid, timestamp}));
});
},
[submitAddFavorite]: ({dispatch}, {payload}) => {
dispatch(addToFavorites(payload));
socket.send(new AddFavorite(payload));
},
[submitRemoveFavorite]: ({dispatch}, {payload}) => {
dispatch(removeFromFavorites(payload));
socket.send(new RemoveFavorite(payload));
},
};
export const middleware = (store) => (next) => (action) => {

View File

@ -1,11 +1,11 @@
import {Packet} from '@avocado/net';
export default class Blur extends Packet {
export default class AddFavorite extends Packet {
static get schema() {
return {
...super.schema,
data: {},
data: 'string',
};
}

View File

@ -1,11 +1,11 @@
import {Packet} from '@avocado/net';
export default class Focus extends Packet {
export default class RemoveFavorite extends Packet {
static get schema() {
return {
...super.schema,
data: {},
data: 'string',
};
}

View File

@ -89,7 +89,7 @@ const slice = createSlice({
addFriendship: (state, {payload: {friendship}}) => {
state.friendship.push(friendship);
},
addToFavorites: (state, {payload: {favorite}}) => {
addToFavorites: (state, {payload: favorite}) => {
state.favorites.push(favorite);
},
blockUser: (state, {payload: {blocked}}) => {
@ -101,14 +101,14 @@ const slice = createSlice({
removeFriendship: (state, {payload: {id}}) => {
state.friendship.splice(state.friendship.findIndex((friendship) => friendship.id === id), 1);
},
removeFromFavorites: (state, {payload: {type, name}}) => {
state.favorites.splice(state.favorites.findIndex(
(favorite) => favorite.name === name && favorite.type === type,
), 1);
removeFromFavorites: (state, {payload: favorite}) => {
state.favorites.splice(state.favorites.indexOf(favorite), 1);
},
removeRecent: ({recent}, {payload: channel}) => {
recent.splice(recent.indexOf(channel), 1);
},
submitAddFavorite: () => {},
submitRemoveFavorite: () => {},
updateFriendshipStatus: (state, {payload: {id, status}}) => {
state.friendship.find((friendship) => friendship.id === id).status = status;
},
@ -135,10 +135,14 @@ const slice = createSlice({
export const {
addFriend,
addToFavorites,
blockUser,
focus,
removeFriend,
removeFromFavorites,
removeRecent,
submitAddFavorite,
submitRemoveFavorite,
} = slice.actions;
export default slice.reducer;

View File

@ -1,7 +1,7 @@
/* eslint-disable import/no-extraneous-dependencies */
import {promisify} from 'util';
import {joinChannel} from '~/common/channel';
import {joinChannel, parseChannel} from '~/common/channel';
import {allModels} from './models/registrar';
import createRedisClient, {keys} from './redis';
@ -49,16 +49,22 @@ export const channelState = async (req, channel) => {
};
};
export const channelsToHydrate = async (req) => (
(req.channel ? [req.channel] : []).concat(req.user ? await req.user.favorites() : [])
);
export const channelsToHydrate = async (req) => {
const channels = await Promise.all(
(req.channel ? [req.channel] : [])
.concat(req.user ? await req.user.favorites() : []),
);
const channelStrings = channels.map(joinChannel);
const uniqueChannels = (new Set(channelStrings)).values();
return Array.from(uniqueChannels).map((channel) => parseChannel(`/chat${channel}`));
};
const userState = async (req) => {
const {channel, user} = req;
const toHydrate = await channelsToHydrate(req);
return user
? {
favorites: [],
favorites: (await user.favorites()).map(joinChannel),
focus: channel ? joinChannel(channel) : '',
friends: await user.friends(),
id: user.id,

View File

@ -0,0 +1,23 @@
import {DataTypes as Types} from 'sequelize';
import BaseModel from './base';
class Favorite extends BaseModel {
static get attributes() {
return {
channel: Types.STRING,
};
}
static associate({User}) {
User.hasMany(this);
this.belongsTo(User);
}
static get name() {
return 'Favorite';
}
}
export default Favorite;

View File

@ -5,6 +5,8 @@ import bcrypt from 'bcrypt';
import {registerHooks} from 'scwp';
import {DataTypes as Types, Op} from 'sequelize';
import {parseChannel} from '~/common/channel';
import BaseModel from './base';
import {allModels} from './registrar';
@ -35,7 +37,9 @@ class User extends BaseModel {
}
async favorites() {
return [];
return (await this.getFavorites())
.map(({channel}) => channel)
.map((channel) => parseChannel(`/chat${channel}`));
}
async friends() {

View File

@ -1,16 +1,17 @@
/* eslint-disable import/no-extraneous-dependencies */
import {promisify} from 'util';
import {ServerSocket, SocketServer} from '@avocado/net/server/socket';
import socketSession from 'express-socket.io-session';
import redisAdapter from 'socket.io-redis';
import {v4 as uuidv4} from 'uuid';
import {ServerSocket, SocketServer} from '@avocado/net/server/socket';
import socketSession from 'express-socket.io-session';
import {joinChannel, parseChannel} from '~/common/channel';
import AddFavorite from '~/common/packets/add-favorite.packet';
import Join from '~/common/packets/join.packet';
import Leave from '~/common/packets/leave.packet';
import Message from '~/common/packets/message.packet';
import RemoveFavorite from '~/common/packets/remove-favorite.packet';
import Usernames from '~/common/packets/usernames.packet';
import {
@ -30,7 +31,7 @@ const subClient = createRedisClient();
const adapter = redisAdapter({pubClient, subClient});
export function createSocketServer(httpServer) {
const {User} = allModels();
const {Favorite, User} = allModels();
const socketServer = new SocketServer(httpServer, {
adapter,
});
@ -76,6 +77,18 @@ export function createSocketServer(httpServer) {
socketServer.on('connect', (socket) => {
const {req} = socket;
socket.on('packet', async (packet, fn) => {
if (packet instanceof AddFavorite) {
await req.user.createFavorite({channel: packet.data});
}
if (packet instanceof RemoveFavorite) {
const favorites = await req.user.getFavorites();
const toRemove = favorites.find(({channel}) => channel === packet.data);
await Favorite.destroy({
where: {
id: toRemove.id,
},
});
}
if (packet instanceof Join) {
const {channel} = packet.data;
await userJoin(channel, socket.socket);