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

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