diff --git a/src/client/chat--leftRooms.jsx b/src/client/chat--leftRooms.jsx
index 44586f8..0276e63 100644
--- a/src/client/chat--leftRooms.jsx
+++ b/src/client/chat--leftRooms.jsx
@@ -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 (
Favorites
- {favorites.map((channel) => (
- -
-
-
- ))}
+ {favorites.map((channel) => {
+ const favoritesActions = [
+ {
+ icon: '💔',
+ label: 'Unfavorite',
+ onClick: () => dispatch(submitRemoveFavorite(`/r/${channel}`)),
+ },
+ ];
+ return (
+ -
+
+
+ );
+ })}
>
)}
@@ -59,7 +67,7 @@ export default function ChatLeftRooms() {
{
icon: '❤️',
label: 'Favorite',
- onClick: () => {},
+ onClick: () => dispatch(submitAddFavorite(`/r/${channel}`)),
},
{
icon: '×',
diff --git a/src/client/store/effects.js b/src/client/store/effects.js
index 8ec3340..5e6a556 100644
--- a/src/client/store/effects.js
+++ b/src/client/store/effects.js
@@ -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) => {
diff --git a/src/common/packets/blur.packet.js b/src/common/packets/add-favorite.packet.js
similarity index 61%
rename from src/common/packets/blur.packet.js
rename to src/common/packets/add-favorite.packet.js
index 5e16dca..98d923a 100644
--- a/src/common/packets/blur.packet.js
+++ b/src/common/packets/add-favorite.packet.js
@@ -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',
};
}
diff --git a/src/common/packets/chatstatus.packet.js b/src/common/packets/chat-status.packet.js
similarity index 100%
rename from src/common/packets/chatstatus.packet.js
rename to src/common/packets/chat-status.packet.js
diff --git a/src/common/packets/focus.packet.js b/src/common/packets/remove-favorite.packet.js
similarity index 60%
rename from src/common/packets/focus.packet.js
rename to src/common/packets/remove-favorite.packet.js
index 3509698..5432876 100644
--- a/src/common/packets/focus.packet.js
+++ b/src/common/packets/remove-favorite.packet.js
@@ -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',
};
}
diff --git a/src/common/state/user.js b/src/common/state/user.js
index 1ec92fe..bf131d9 100644
--- a/src/common/state/user.js
+++ b/src/common/state/user.js
@@ -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;
diff --git a/src/server/entry.js b/src/server/entry.js
index 68ef3f5..4778b69 100644
--- a/src/server/entry.js
+++ b/src/server/entry.js
@@ -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,
diff --git a/src/server/models/favorite.model.js b/src/server/models/favorite.model.js
new file mode 100644
index 0000000..c86b1c3
--- /dev/null
+++ b/src/server/models/favorite.model.js
@@ -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;
diff --git a/src/server/models/user.model.js b/src/server/models/user.model.js
index 7e499dd..0f14f9c 100644
--- a/src/server/models/user.model.js
+++ b/src/server/models/user.model.js
@@ -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() {
diff --git a/src/server/sockets.js b/src/server/sockets.js
index 9c78234..bdb8a43 100644
--- a/src/server/sockets.js
+++ b/src/server/sockets.js
@@ -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);