From 26ed411129a43f675fd59e45c0dfc2a8d763d638 Mon Sep 17 00:00:00 2001 From: HF Date: Tue, 24 Nov 2020 16:44:33 +0100 Subject: [PATCH] add blocking of Users --- src/actions/fetch.js | 120 +++++++++++++++++++++++ src/actions/index.js | 132 ++++++++++++++++++++------ src/actions/types.js | 7 +- src/components/Chat.jsx | 147 ++++++++++++++++------------- src/components/ChatModal.jsx | 9 +- src/components/MdToggleButton.jsx | 2 +- src/components/SocialSettings.jsx | 116 +++++++++++++++++++++++ src/components/UserArea.jsx | 26 +++++ src/components/UserContextMenu.jsx | 10 +- src/data/models/RegUser.js | 6 ++ src/data/models/User.js | 1 + src/reducers/canvas.js | 31 ------ src/reducers/chat.js | 39 +++++--- src/reducers/fetching.js | 74 +++++++++++++++ src/reducers/index.js | 5 + src/reducers/user.js | 13 +++ src/routes/api/block.js | 61 ++++++++++-- src/routes/api/blockdm.js | 46 +++++++++ src/routes/api/index.js | 3 + src/routes/api/startdm.js | 7 ++ src/styles/default.css | 23 +++++ 21 files changed, 721 insertions(+), 157 deletions(-) create mode 100644 src/actions/fetch.js create mode 100644 src/components/SocialSettings.jsx create mode 100644 src/reducers/fetching.js create mode 100644 src/routes/api/blockdm.js diff --git a/src/actions/fetch.js b/src/actions/fetch.js new file mode 100644 index 0000000..7afdcc7 --- /dev/null +++ b/src/actions/fetch.js @@ -0,0 +1,120 @@ +/* + * Collect api fetch commands for actions here + * (chunk and tiles requests in ui/ChunkLoader*.js) + * (user settings requests in their components) + * + * @flow + */ + + +/* + * Adds customizeable timeout to fetch + * defaults to 8s + */ +async function fetchWithTimeout(resource, options) { + const { timeout = 8000 } = options; + + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), timeout); + + const response = await fetch(resource, { + ...options, + signal: controller.signal, + }); + clearTimeout(id); + + return response; +} + +/* + * block / unblock user + * userId id of user to block + * block true if block, false if unblock + * return error string or null if successful + */ +export async function requestBlock(userId: number, block: boolean) { + const response = await fetchWithTimeout('api/block', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + userId, + block, + }), + }); + + try { + const res = await response.json(); + if (res.errors) { + return res.errors[0]; + } + if (response.ok && res.status === 'ok') { + return null; + } + return 'Unknown Error'; + } catch { + return 'Connection Error'; + } +} + +/* + * start new DM channel with user + * query Object with either userId: number or userName: string + * return channel Array on success, error string if not + */ +export async function requestStartDm(query) { + const response = await fetchWithTimeout('api/startdm', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(query), + }); + + try { + const res = await response.json(); + if (res.errors) { + return res.errors[0]; + } + if (response.ok && res.channel) { + const { channel } = res; + return channel; + } + + return 'Unknown Error'; + } catch { + return 'Connection Error'; + } +} + +/* + * set receiving of all DMs on/off + * block true if blocking all dms, false if unblocking + * return error string or null if successful + */ +export async function requestBlockDm(block: boolean) { + const response = await fetchWithTimeout('api/blockdm', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ block }), + }); + + try { + const res = await response.json(); + if (res.errors) { + return res.errors[0]; + } + if (response.ok && res.status === 'ok') { + return null; + } + return 'Unknown Error'; + } catch { + return 'Connection Error'; + } +} diff --git a/src/actions/index.js b/src/actions/index.js index 67d7928..6022355 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -7,6 +7,11 @@ import type { } from './types'; import type { Cell } from '../core/Cell'; import type { ColorIndex } from '../core/Palette'; +import { + requestStartDm, + requestBlock, + requestBlockDm, +} from './fetch'; export function sweetAlert( title: string, @@ -490,8 +495,10 @@ export function receiveMe( ranking, dailyRanking, minecraftname, + blockDm, canvases, channels, + blocked, userlvl, } = me; return { @@ -504,8 +511,10 @@ export function receiveMe( ranking, dailyRanking, minecraftname, + blockDm: !!blockDm, canvases, channels, + blocked, userlvl, }; } @@ -599,6 +608,13 @@ function setChatFetching(fetching: boolean): Action { }; } +function setApiFetching(fetching: boolean): Action { + return { + type: 'SET_API_FETCHING', + fetching, + }; +} + export function fetchChatMessages( cid: number, ): PromiseAction { @@ -608,6 +624,9 @@ export function fetchChatMessages( credentials: 'include', }); + /* + * timeout in order to not spam api requests and get rate limited + */ if (response.ok) { setTimeout(() => { dispatch(setChatFetching(false)); }, 500); const { history } = await response.json(); @@ -755,38 +774,95 @@ export function addChatChannel(channel: Array): Action { }; } +export function blockUser(userId: number, userName: string): Action { + return { + type: 'BLOCK_USER', + userId, + userName, + }; +} + +export function unblockUser(userId: number, userName: string): Action { + return { + type: 'UNBLOCK_USER', + userId, + userName, + }; +} + +export function blockingDm(blockDm: boolean): Action { + return { + type: 'SET_BLOCKING_DM', + blockDm, + }; +} + +/* + * query: Object with either userId: number or userName: string + */ export function startDm(query): PromiseAction { return async (dispatch) => { - const response = await fetch('api/startdm', { - method: 'POST', - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(query), - }); - - try { - const res = await response.json(); - if (res.errors) { - dispatch(sweetAlert( - 'Direct Message Error', - res.errors[0], - 'error', - 'OK', - )); + dispatch(setApiFetching(true)); + const res = await requestStartDm(query); + if (typeof res === 'string') { + dispatch(sweetAlert( + 'Direct Message Error', + res, + 'error', + 'OK', + )); + } else { + const channelId = res[0]; + if (channelId) { + dispatch(addChatChannel(res)); + dispatch(setChatChannel(channelId)); } - if (response.ok) { - const { channel } = res; - const channelId = channel[0]; - if (channelId) { - await dispatch(addChatChannel(channel)); - dispatch(setChatChannel(channelId)); - } - } - } catch { - dispatch(notify('Couldn\'t start DM')); } + dispatch(setApiFetching(false)); + }; +} + +export function setUserBlock( + userId: number, + userName: string, + block: boolean, +) { + return async (dispatch) => { + dispatch(setApiFetching(true)); + const res = await requestBlock(userId, block); + if (res) { + dispatch(sweetAlert( + 'User Block Error', + res, + 'error', + 'OK', + )); + } else if (block) { + dispatch(blockUser(userId, userName)); + } else { + dispatch(unblockUser(userId, userName)); + } + dispatch(setApiFetching(false)); + }; +} + +export function setBlockingDm( + block: boolean, +) { + return async (dispatch) => { + dispatch(setApiFetching(true)); + const res = await requestBlockDm(block); + if (res) { + dispatch(sweetAlert( + 'Blocking DMs Error', + res, + 'error', + 'OK', + )); + } else { + dispatch(blockingDm(block)); + } + dispatch(setApiFetching(false)); }; } diff --git a/src/actions/types.js b/src/actions/types.js index 5a086d7..982f819 100644 --- a/src/actions/types.js +++ b/src/actions/types.js @@ -74,6 +74,9 @@ export type Action = | { type: 'SET_CHAT_FETCHING', fetching: boolean } | { type: 'SET_CHAT_INPUT_MSG', message: string } | { type: 'ADD_CHAT_INPUT_MSG', message: string } + | { type: 'BLOCK_USER', userId: number, userName: string } + | { type: 'UNBLOCK_USER', userId: number, userName: string } + | { type: 'SET_BLOCKING_DM', blockDm: boolean } | { type: 'RECEIVE_ME', name: string, waitSeconds: number, @@ -84,8 +87,10 @@ export type Action = ranking: number, dailyRanking: number, minecraftname: string, + blockDm: boolean, canvases: Object, - channels: Object, + channels: Array, + blocked: Array, userlvl: number, } | { type: 'RECEIVE_STATS', totalRanking: Object, totalDailyRanking: Object } diff --git a/src/components/Chat.jsx b/src/components/Chat.jsx index 16cf684..57eea03 100644 --- a/src/components/Chat.jsx +++ b/src/components/Chat.jsx @@ -39,10 +39,12 @@ const Chat = ({ setChannel, fetchMessages, fetching, + blocked, }) => { const listRef = useRef(); const [selection, setSelection] = useState(null); const [nameRegExp, setNameRegExp] = useState(null); + const [blockedIds, setBlockedIds] = useState([]); const { stayScrolled } = useStayScrolled(listRef, { initialScroll: Infinity, @@ -75,6 +77,14 @@ const Chat = ({ setNameRegExp(regExp); }, [ownName]); + useEffect(() => { + const bl = []; + for (let i = 0; i < blocked.length; i += 1) { + bl.push(blocked[i][0]); + } + setBlockedIds(bl); + }, [blocked.length]); + function handleSubmit(e) { e.preventDefault(); const msg = inputMessage.trim(); @@ -102,71 +112,74 @@ const Chat = ({ } return ( -
- - {(ownName) ? ( -
-
handleSubmit(e)} - style={{ display: 'flex', flexDirection: 'row' }} - > - setInputMessage(e.target.value)} - id="chatmsginput" - maxLength="200" - type="text" - placeholder="Chat here" - /> - - - -
- ) : ( -
+
+
    { setSelection(saveSelection); }} + role="presentation" > - You must be logged in to chat -
- )} + { + (!channelMessages.length) + && ( + + ) + } + { + channelMessages.map((message) => ((blockedIds.includes(message[3])) + ? null : ( + + ))) + } + + {(ownName) ? ( +
+
handleSubmit(e)} + style={{ display: 'flex', flexDirection: 'row' }} + > + setInputMessage(e.target.value)} + id="chatmsginput" + maxLength="200" + type="text" + placeholder="Chat here" + /> + + + +
+ ) : ( +
+ You must be logged in to chat +
+ )} +
); }; @@ -177,13 +190,17 @@ function mapStateToProps(state: State) { const { channels, messages, - fetching, inputMessage, + blocked, } = state.chat; + const { + fetchingChat: fetching, + } = state.fetching; return { channels, messages, fetching, + blocked, inputMessage, chatChannel, ownName: name, diff --git a/src/components/ChatModal.jsx b/src/components/ChatModal.jsx index c0c955a..12d1e05 100644 --- a/src/components/ChatModal.jsx +++ b/src/components/ChatModal.jsx @@ -18,17 +18,10 @@ const ChatModal = () => ( right: 10, }} > -

-

Chat with other people here

-

diff --git a/src/components/MdToggleButton.jsx b/src/components/MdToggleButton.jsx index 6fa2e5c..4971fe2 100644 --- a/src/components/MdToggleButton.jsx +++ b/src/components/MdToggleButton.jsx @@ -10,10 +10,10 @@ const MdToggleButton = ({ value, onToggle }) => ( } activeLabel={} - thumbAnimateRange={[-10, 36]} value={value} onToggle={onToggle} /> ); +// thumbAnimateRange={[-10, 36]} export default MdToggleButton; diff --git a/src/components/SocialSettings.jsx b/src/components/SocialSettings.jsx new file mode 100644 index 0000000..4d64dec --- /dev/null +++ b/src/components/SocialSettings.jsx @@ -0,0 +1,116 @@ +/* + * Change Mail Form + * @flow + */ + +import React from 'react'; +import { connect } from 'react-redux'; + +import { + setBlockingDm, + setUserBlock, +} from '../actions'; +import MdToggleButtonHover from './MdToggleButtonHover'; + +const SocialSettings = ({ + blocked, + fetching, + blockDm, + setBlockDm, + unblock, + done, +}) => ( +
+
+ + Block all Private Messages + + { + if (!fetching) { + setBlockDm(!blockDm); + } + }} + /> +
+
+

Unblock Users

+ { + (blocked.length) ? ( + + { + blocked.map((bl) => ( +
{ + if (!fetching) { + unblock(bl[0], bl[1]); + } + }} + > + {bl[1]} +
+ )) + } +
+ ) + : ( +

You have no users blocked

+ ) + } +
+ +
+); + +function mapStateToProps(state: State) { + const { blocked } = state.chat; + const { blockDm } = state.user; + const { fetchingApi: fetching } = state.fetching; + return { + blocked, + blockDm, + fetching, + }; +} + +function mapDispatchToProps(dispatch) { + return { + setBlockDm(block) { + dispatch(setBlockingDm(block)); + }, + unblock(userId, userName) { + dispatch(setUserBlock(userId, userName, false)); + }, + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(SocialSettings); diff --git a/src/components/UserArea.jsx b/src/components/UserArea.jsx index 26c9f9f..30f11d9 100644 --- a/src/components/UserArea.jsx +++ b/src/components/UserArea.jsx @@ -13,6 +13,7 @@ import ChangePassword from './ChangePassword'; import ChangeName from './ChangeName'; import ChangeMail from './ChangeMail'; import DeleteAccount from './DeleteAccount'; +import SocialSettings from './SocialSettings'; import { numberToString } from '../core/utils'; @@ -45,6 +46,7 @@ class UserArea extends React.Component { changeMailExtended, changePasswdExtended, deleteAccountExtended, + socialSettingsExtended, } = this.state; return (

@@ -84,6 +86,7 @@ class UserArea extends React.Component { changeMailExtended: false, changePasswdExtended: false, deleteAccountExtended: false, + socialSettingsExtended: false, })} > Change Username | {(mailreg) @@ -98,6 +101,7 @@ class UserArea extends React.Component { changeMailExtended: true, changePasswdExtended: false, deleteAccountExtended: false, + socialSettingsExtended: false, })} > Change Mail | @@ -111,6 +115,7 @@ class UserArea extends React.Component { changeMailExtended: false, changePasswdExtended: true, deleteAccountExtended: false, + socialSettingsExtended: false, })} > Change Password | Delete Account ) +
( + this.setState({ + changeNameExtended: false, + changeMailExtended: false, + changePasswdExtended: false, + deleteAccountExtended: false, + socialSettingsExtended: true, + })} + > Social Settings )

+

{(changePasswdExtended) && ( { this.setState({ deleteAccountExtended: false }); }} /> )} + {(socialSettingsExtended) + && ( + { this.setState({ socialSettingsExtended: false }); }} + /> + )} {(typeof window.hcaptcha !== 'undefined') && ( { const wrapperRef = useRef(null); @@ -53,6 +54,10 @@ const UserContextMenu = ({ >

{ + block(uid, name); + close(); + }} > Block
@@ -113,6 +118,9 @@ function mapDispatchToProps(dispatch) { dm(userId) { dispatch(startDm({ userId })); }, + block(userId, userName) { + dispatch(setUserBlock(userId, userName, true)); + }, close() { dispatch(hideContextMenu()); }, diff --git a/src/data/models/RegUser.js b/src/data/models/RegUser.js index 4d94786..bd745aa 100644 --- a/src/data/models/RegUser.js +++ b/src/data/models/RegUser.js @@ -64,6 +64,12 @@ const RegUser = Model.define('User', { defaultValue: false, }, + blockDm: { + type: DataType.BOOLEAN, + allowNull: false, + defaultValue: false, + }, + discordid: { type: DataType.CHAR(18), allowNull: true, diff --git a/src/data/models/User.js b/src/data/models/User.js index 07d96dc..8531f8d 100644 --- a/src/data/models/User.js +++ b/src/data/models/User.js @@ -197,6 +197,7 @@ class User { mailVerified: regUser.mailVerified, mcVerified: regUser.mcVerified, minecraftname: regUser.minecraftname, + blockDm: regUser.blockDm, totalPixels: regUser.totalPixels, dailyTotalPixels: regUser.dailyTotalPixels, ranking: regUser.ranking, diff --git a/src/reducers/canvas.js b/src/reducers/canvas.js index db8f6ad..7838c7c 100644 --- a/src/reducers/canvas.js +++ b/src/reducers/canvas.js @@ -32,7 +32,6 @@ export type CanvasState = { view: Cell, scale: number, viewscale: number, - fetchs: number, isHistoricalView: boolean, historicalDate: string, historicalTime: string, @@ -137,7 +136,6 @@ function getViewFromURL(canvases: Object) { const initialState: CanvasState = { ...getViewFromURL(DEFAULT_CANVASES), - fetchs: 0, isHistoricalView: false, historicalDate: null, historicalTime: null, @@ -242,35 +240,6 @@ export default function canvasReducer( }; } - case 'REQUEST_BIG_CHUNK': { - const { - fetchs, - } = state; - - return { - ...state, - fetchs: fetchs + 1, - }; - } - - case 'RECEIVE_BIG_CHUNK': { - const { fetchs } = state; - - return { - ...state, - fetchs: fetchs + 1, - }; - } - - case 'RECEIVE_BIG_CHUNK_FAILURE': { - const { fetchs } = state; - - return { - ...state, - fetchs: fetchs + 1, - }; - } - case 'SELECT_COLOR': { return { ...state, diff --git a/src/reducers/chat.js b/src/reducers/chat.js index 707eb57..8fe3230 100644 --- a/src/reducers/chat.js +++ b/src/reducers/chat.js @@ -6,19 +6,19 @@ import type { Action } from '../actions/types'; export type ChatState = { inputMessage: string, - // [[cid, name], [cid2, name2],...] + // [[cid, name, type, lastMessage], [cid2, name2, type2, lastMessage2],...] channels: Array, - // { cid: [message1,message2,message2,...]} + // [[userId, userName], [userId2, userName2],...] + blocked: Array, + // { cid: [message1,message2,message3,...]} messages: Object, - // if currently fetching messages - fetching: boolean, } const initialState: ChatState = { inputMessage: '', channels: [], + blocked: [], messages: {}, - fetching: false, }; export default function chat( @@ -30,6 +30,27 @@ export default function chat( return { ...state, channels: action.channels, + blocked: action.blocked, + }; + } + + case 'BLOCK_USER': { + const { userId, userName } = action; + return { + ...state, + blocked: [ + ...state.blocked, + [userId, userName], + ], + }; + } + + case 'UNBLOCK_USER': { + const { userId } = action; + const blocked = state.blocked.filter((bl) => (bl[0] !== userId)); + return { + ...state, + blocked, }; } @@ -45,14 +66,6 @@ export default function chat( }; } - case 'SET_CHAT_FETCHING': { - const { fetching } = action; - return { - ...state, - fetching, - }; - } - case 'SET_CHAT_INPUT_MSG': { const { message } = action; return { diff --git a/src/reducers/fetching.js b/src/reducers/fetching.js new file mode 100644 index 0000000..2d6d798 --- /dev/null +++ b/src/reducers/fetching.js @@ -0,0 +1,74 @@ +/* + * keeps track of some api fetching states + * + * @flow + */ + +import type { Action } from '../actions/types'; + +export type FetchingState = { + fetchingChunks: number, + fetchingChat: boolean, + fetchinApi: boolean, +} + +const initialState: FetchingState = { + fetchingChunks: 0, + fetchingChat: false, + fetchinApi: false, +}; + +export default function fetching( + state: FetchingState = initialState, + action: Action, +): FetchingState { + switch (action.type) { + case 'SET_CHAT_FETCHING': { + const { fetching: fetchingChat } = action; + return { + ...state, + fetchingChat, + }; + } + + case 'SET_API_FETCHING': { + const { fetching: fetchinApi } = action; + return { + ...state, + fetchinApi, + }; + } + + case 'REQUEST_BIG_CHUNK': { + const { + fetchingChunks, + } = state; + + return { + ...state, + fetchingChunks: fetchingChunks + 1, + }; + } + + case 'RECEIVE_BIG_CHUNK': { + const { fetchingChunks } = state; + + return { + ...state, + fetchingChunks: fetchingChunks - 1, + }; + } + + case 'RECEIVE_BIG_CHUNK_FAILURE': { + const { fetchingChunks } = state; + + return { + ...state, + fetchingChunks: fetchingChunks - 1, + }; + } + + default: + return state; + } +} diff --git a/src/reducers/index.js b/src/reducers/index.js index a7812be..ff068f3 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -9,6 +9,7 @@ import modal from './modal'; import user from './user'; import chat from './chat'; import contextMenu from './contextMenu'; +import fetching from './fetching'; import type { AudioState } from './audio'; import type { CanvasState } from './canvas'; @@ -17,6 +18,7 @@ import type { ModalState } from './modal'; import type { UserState } from './user'; import type { ChatState } from './chat'; import type { ContextMenuState } from './contextMenu'; +import type { FetchingState } from './fetching'; export type State = { audio: AudioState, @@ -26,6 +28,7 @@ export type State = { user: UserState, chat: ChatState, contextMenu: ContextMenuState, + fetching: FetchingState, }; const config = { @@ -37,6 +40,7 @@ const config = { 'modal', 'chat', 'contextMenu', + 'fetching', ], }; @@ -48,4 +52,5 @@ export default persistCombineReducers(config, { user, chat, contextMenu, + fetching, }); diff --git a/src/reducers/user.js b/src/reducers/user.js index e662fc9..64fdf66 100644 --- a/src/reducers/user.js +++ b/src/reducers/user.js @@ -24,6 +24,8 @@ export type UserState = { totalDailyRanking: Object, // minecraft minecraftname: string, + // blocking all Dms + blockDm: boolean, // if user is using touchscreen isOnMobile: boolean, // small notifications for received cooldown @@ -45,6 +47,7 @@ const initialState: UserState = { totalRanking: {}, totalDailyRanking: {}, minecraftname: null, + blockDm: false, isOnMobile: false, notification: null, userlvl: 0, @@ -139,6 +142,7 @@ export default function user( ranking, dailyRanking, minecraftname, + blockDm, userlvl, } = action; const messages = (action.messages) ? action.messages : []; @@ -152,6 +156,7 @@ export default function user( ranking, dailyRanking, minecraftname, + blockDm, userlvl, }; } @@ -173,6 +178,14 @@ export default function user( }; } + case 'SET_BLOCKING_DM': { + const { blockDm } = action; + return { + ...state, + blockDm, + }; + } + case 'SET_MINECRAFT_NAME': { const { minecraftname } = action; return { diff --git a/src/routes/api/block.js b/src/routes/api/block.js index d84fc2e..f5f1659 100644 --- a/src/routes/api/block.js +++ b/src/routes/api/block.js @@ -8,11 +8,12 @@ import type { Request, Response } from 'express'; import logger from '../../core/logger'; -import { RegUser, UserBlock } from '../../data/models'; +import { RegUser, UserBlock, Channel } from '../../data/models'; async function block(req: Request, res: Response) { let userId = parseInt(req.body.userId, 10); let { userName } = req.body; + const { block: blocking } = req.body; const { user } = req; const errors = []; @@ -23,6 +24,9 @@ async function block(req: Request, res: Response) { } query.id = userId; } + if (typeof blocking !== 'boolean') { + errors.push('Not defined if blocking or unblocking'); + } if (userName) { query.name = userName; } @@ -33,7 +37,7 @@ async function block(req: Request, res: Response) { errors.push('You are not logged in'); } if (user && userId && user.id === userId) { - errors.push('You can not DM yourself.'); + errors.push('You can not block yourself.'); } if (errors.length) { res.status(400); @@ -61,14 +65,53 @@ async function block(req: Request, res: Response) { userId = targetUser.id; userName = targetUser.name; - const ret = await UserBlock.findOrCreate({ + let ret = null; + if (blocking) { + ret = await UserBlock.findOrCreate({ + where: { + uid: user.id, + buid: userId, + }, + raw: true, + attributes: ['uid'], + }); + } else { + ret = await UserBlock.destroy({ + where: { + uid: user.id, + buid: userId, + }, + }); + } + + /* + * delete possible dm channel + */ + let dmu1id = null; + let dmu2id = null; + if (user.id > userId) { + dmu1id = userId; + dmu2id = user.id; + } else { + dmu1id = user.id; + dmu2id = userId; + } + + // TODO test if this removes association too + const channel = await Channel.findOne({ where: { - uid: user.id, - buid: userId, + type: 1, + dmu1id, + dmu2id, }, - raw: true, - attributes: ['uid'], }); + if (channel) { + const channelId = channel.id; + channel.destroy(); + } + + // TODO notify websocket + if (ret) { res.json({ status: 'ok', @@ -76,10 +119,10 @@ async function block(req: Request, res: Response) { } else { res.status(502); res.json({ - errors: ['Could not block user'], + errors: ['Could not (un)block user'], }); logger.info( - `User ${user.getName()} blocked ${userName}`, + `User ${user.getName()} (un)blocked ${userName}`, ); } } diff --git a/src/routes/api/blockdm.js b/src/routes/api/blockdm.js new file mode 100644 index 0000000..f135eaa --- /dev/null +++ b/src/routes/api/blockdm.js @@ -0,0 +1,46 @@ +/* + * + * block all private messages + * + * @flow + */ + +import type { Request, Response } from 'express'; + +import logger from '../../core/logger'; + +async function blockdm(req: Request, res: Response) { + const { block } = req.body; + const { user } = req; + + const errors = []; + if (typeof block !== 'boolean') { + errors.push('Not defined if blocking or unblocking'); + } + if (!user || !user.regUser) { + errors.push('You are not logged in'); + } + if (errors.length) { + res.status(400); + res.json({ + errors, + }); + return; + } + + logger.info( + `User ${user.getName()} (un)blocked all dms`, + ); + + await user.regUser.update({ + blockDm: block, + }); + + // TODO notify websocket + + res.json({ + status: 'ok', + }); +} + +export default blockdm; diff --git a/src/routes/api/index.js b/src/routes/api/index.js index 6030b6c..42fa401 100644 --- a/src/routes/api/index.js +++ b/src/routes/api/index.js @@ -20,6 +20,7 @@ import history from './history'; import chatHistory from './chathistory'; import startDm from './startdm'; import block from './block'; +import blockdm from './blockdm'; const router = express.Router(); @@ -86,6 +87,8 @@ router.post('/startdm', startDm); router.post('/block', block); +router.post('/blockdm', blockdm); + router.use('/auth', auth(passport)); export default router; diff --git a/src/routes/api/startdm.js b/src/routes/api/startdm.js index 5f69bec..67160b4 100644 --- a/src/routes/api/startdm.js +++ b/src/routes/api/startdm.js @@ -49,6 +49,7 @@ async function startDm(req: Request, res: Response) { attributes: [ 'id', 'name', + 'blockDm', ], raw: true, }); @@ -59,6 +60,12 @@ async function startDm(req: Request, res: Response) { }); return; } + if (blockDm) { + res.status(401); + res.json({ + errors: ['Target user doesn\'t allo DMs'], + }); + } userId = targetUser.id; userName = targetUser.name; diff --git a/src/styles/default.css b/src/styles/default.css index 95967c8..0ff8afb 100644 --- a/src/styles/default.css +++ b/src/styles/default.css @@ -74,6 +74,8 @@ a:hover { padding: 4px; margin-top: 4px; border-width: 1px; + margin-left: 10px; + margin-right: 10px; } .tab-list { @@ -552,6 +554,27 @@ tr:nth-child(even) { opacity: 0; } +.unblocklist { + display: inline-block; + margin: 5px 0px 15px 0px; + width: 95%; + max-height: 250px; + overflow-y: auto; + max-width: 340px; + border: thin solid; +} + +.unblocklist > div { + padding: 4px; + font-weight: bold; +} + +.unblocklist > div:hover { + cursor: pointer; + font-weight: bold; + background-color: #db0000; +} + .actionbuttons { vertical-align: text-bottom; cursor: pointer;