add blocking of Users

This commit is contained in:
HF 2020-11-24 16:44:33 +01:00
parent 5318b521e7
commit 26ed411129
21 changed files with 721 additions and 157 deletions

120
src/actions/fetch.js Normal file
View File

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

View File

@ -7,6 +7,11 @@ import type {
} from './types'; } from './types';
import type { Cell } from '../core/Cell'; import type { Cell } from '../core/Cell';
import type { ColorIndex } from '../core/Palette'; import type { ColorIndex } from '../core/Palette';
import {
requestStartDm,
requestBlock,
requestBlockDm,
} from './fetch';
export function sweetAlert( export function sweetAlert(
title: string, title: string,
@ -490,8 +495,10 @@ export function receiveMe(
ranking, ranking,
dailyRanking, dailyRanking,
minecraftname, minecraftname,
blockDm,
canvases, canvases,
channels, channels,
blocked,
userlvl, userlvl,
} = me; } = me;
return { return {
@ -504,8 +511,10 @@ export function receiveMe(
ranking, ranking,
dailyRanking, dailyRanking,
minecraftname, minecraftname,
blockDm: !!blockDm,
canvases, canvases,
channels, channels,
blocked,
userlvl, userlvl,
}; };
} }
@ -599,6 +608,13 @@ function setChatFetching(fetching: boolean): Action {
}; };
} }
function setApiFetching(fetching: boolean): Action {
return {
type: 'SET_API_FETCHING',
fetching,
};
}
export function fetchChatMessages( export function fetchChatMessages(
cid: number, cid: number,
): PromiseAction { ): PromiseAction {
@ -608,6 +624,9 @@ export function fetchChatMessages(
credentials: 'include', credentials: 'include',
}); });
/*
* timeout in order to not spam api requests and get rate limited
*/
if (response.ok) { if (response.ok) {
setTimeout(() => { dispatch(setChatFetching(false)); }, 500); setTimeout(() => { dispatch(setChatFetching(false)); }, 500);
const { history } = await response.json(); 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 { export function startDm(query): PromiseAction {
return async (dispatch) => { return async (dispatch) => {
const response = await fetch('api/startdm', { dispatch(setApiFetching(true));
method: 'POST', const res = await requestStartDm(query);
credentials: 'include', if (typeof res === 'string') {
headers: { dispatch(sweetAlert(
'Content-Type': 'application/json', 'Direct Message Error',
}, res,
body: JSON.stringify(query), 'error',
}); 'OK',
));
try { } else {
const res = await response.json(); const channelId = res[0];
if (res.errors) { if (channelId) {
dispatch(sweetAlert( dispatch(addChatChannel(res));
'Direct Message Error', dispatch(setChatChannel(channelId));
res.errors[0],
'error',
'OK',
));
} }
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));
}; };
} }

View File

@ -74,6 +74,9 @@ export type Action =
| { type: 'SET_CHAT_FETCHING', fetching: boolean } | { type: 'SET_CHAT_FETCHING', fetching: boolean }
| { type: 'SET_CHAT_INPUT_MSG', message: string } | { type: 'SET_CHAT_INPUT_MSG', message: string }
| { type: 'ADD_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', | { type: 'RECEIVE_ME',
name: string, name: string,
waitSeconds: number, waitSeconds: number,
@ -84,8 +87,10 @@ export type Action =
ranking: number, ranking: number,
dailyRanking: number, dailyRanking: number,
minecraftname: string, minecraftname: string,
blockDm: boolean,
canvases: Object, canvases: Object,
channels: Object, channels: Array,
blocked: Array,
userlvl: number, userlvl: number,
} }
| { type: 'RECEIVE_STATS', totalRanking: Object, totalDailyRanking: Object } | { type: 'RECEIVE_STATS', totalRanking: Object, totalDailyRanking: Object }

View File

@ -39,10 +39,12 @@ const Chat = ({
setChannel, setChannel,
fetchMessages, fetchMessages,
fetching, fetching,
blocked,
}) => { }) => {
const listRef = useRef(); const listRef = useRef();
const [selection, setSelection] = useState(null); const [selection, setSelection] = useState(null);
const [nameRegExp, setNameRegExp] = useState(null); const [nameRegExp, setNameRegExp] = useState(null);
const [blockedIds, setBlockedIds] = useState([]);
const { stayScrolled } = useStayScrolled(listRef, { const { stayScrolled } = useStayScrolled(listRef, {
initialScroll: Infinity, initialScroll: Infinity,
@ -75,6 +77,14 @@ const Chat = ({
setNameRegExp(regExp); setNameRegExp(regExp);
}, [ownName]); }, [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) { function handleSubmit(e) {
e.preventDefault(); e.preventDefault();
const msg = inputMessage.trim(); const msg = inputMessage.trim();
@ -102,71 +112,74 @@ const Chat = ({
} }
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}> <div style={{ display: 'relative', width: '100%', height: '100%' }}>
<ul <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
className="chatarea" <ul
ref={listRef} className="chatarea"
style={{ flexGrow: 1 }} ref={listRef}
onMouseUp={() => { setSelection(saveSelection); }} style={{ flexGrow: 1 }}
role="presentation" onMouseUp={() => { setSelection(saveSelection); }}
> role="presentation"
{
(!channelMessages.length)
&& (
<ChatMessage
name="info"
msgArray={splitChatMessage('Start chatting here', nameRegExp)}
country="xx"
uid={0}
/>
)
}
{
channelMessages.map((message) => (
<ChatMessage
name={message[0]}
msgArray={splitChatMessage(message[1], nameRegExp)}
country={message[2]}
uid={message[3]}
/>
))
}
</ul>
{(ownName) ? (
<div classNam="chatinput">
<form
onSubmit={(e) => handleSubmit(e)}
style={{ display: 'flex', flexDirection: 'row' }}
>
<input
style={{ flexGrow: 1, minWidth: 40 }}
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
id="chatmsginput"
maxLength="200"
type="text"
placeholder="Chat here"
/>
<button
style={{ flexGrow: 0 }}
type="submit"
>
</button>
<ChannelDropDown />
</form>
</div>
) : (
<div
className="modallink"
onClick={open}
style={{ textAlign: 'center', fontSize: 13 }}
role="button"
tabIndex={0}
> >
You must be logged in to chat {
</div> (!channelMessages.length)
)} && (
<ChatMessage
name="info"
msgArray={splitChatMessage('Start chatting here', nameRegExp)}
country="xx"
uid={0}
/>
)
}
{
channelMessages.map((message) => ((blockedIds.includes(message[3]))
? null : (
<ChatMessage
name={message[0]}
msgArray={splitChatMessage(message[1], nameRegExp)}
country={message[2]}
uid={message[3]}
/>
)))
}
</ul>
{(ownName) ? (
<div classNam="chatinput">
<form
onSubmit={(e) => handleSubmit(e)}
style={{ display: 'flex', flexDirection: 'row' }}
>
<input
style={{ flexGrow: 1, minWidth: 40 }}
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
id="chatmsginput"
maxLength="200"
type="text"
placeholder="Chat here"
/>
<button
style={{ flexGrow: 0 }}
type="submit"
>
</button>
<ChannelDropDown />
</form>
</div>
) : (
<div
className="modallink"
onClick={open}
style={{ textAlign: 'center', fontSize: 13 }}
role="button"
tabIndex={0}
>
You must be logged in to chat
</div>
)}
</div>
</div> </div>
); );
}; };
@ -177,13 +190,17 @@ function mapStateToProps(state: State) {
const { const {
channels, channels,
messages, messages,
fetching,
inputMessage, inputMessage,
blocked,
} = state.chat; } = state.chat;
const {
fetchingChat: fetching,
} = state.fetching;
return { return {
channels, channels,
messages, messages,
fetching, fetching,
blocked,
inputMessage, inputMessage,
chatChannel, chatChannel,
ownName: name, ownName: name,

View File

@ -18,17 +18,10 @@ const ChatModal = () => (
right: 10, right: 10,
}} }}
> >
<p style={{ textAlign: 'center' }}>
<p className="modaltext">Chat with other people here</p>
</p>
<div <div
className="inarea" className="inarea"
style={{ style={{
position: 'absolute', height: '95%',
bottom: 10,
top: 50,
left: 10,
right: 10,
}} }}
> >
<Chat /> <Chat />

View File

@ -10,10 +10,10 @@ const MdToggleButton = ({ value, onToggle }) => (
<ToggleButton <ToggleButton
inactiveLabel={<MdClose />} inactiveLabel={<MdClose />}
activeLabel={<MdCheck />} activeLabel={<MdCheck />}
thumbAnimateRange={[-10, 36]}
value={value} value={value}
onToggle={onToggle} onToggle={onToggle}
/> />
); );
// thumbAnimateRange={[-10, 36]}
export default MdToggleButton; export default MdToggleButton;

View File

@ -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,
}) => (
<div className="inarea">
<div
style={{
display: 'flex',
flexWrap: 'nowrap',
margin: 10,
}}
>
<span
style={{
flex: 'auto',
textAlign: 'left',
}}
className="modaltitle"
>
Block all Private Messages
</span>
<MdToggleButtonHover
value={blockDm}
onToggle={() => {
if (!fetching) {
setBlockDm(!blockDm);
}
}}
/>
</div>
<div className="modaldivider" />
<p
style={{
textAlign: 'left',
marginLeft: 10,
}}
className="modaltitle"
>Unblock Users</p>
{
(blocked.length) ? (
<span
className="unblocklist"
>
{
blocked.map((bl) => (
<div
role="button"
tabIndex={0}
onClick={() => {
if (!fetching) {
unblock(bl[0], bl[1]);
}
}}
>
{bl[1]}
</div>
))
}
</span>
)
: (
<p className="modaltext">You have no users blocked</p>
)
}
<div className="modaldivider" />
<button
type="button"
onClick={done}
style={{ margin: 10 }}
>
Done
</button>
</div>
);
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);

View File

@ -13,6 +13,7 @@ import ChangePassword from './ChangePassword';
import ChangeName from './ChangeName'; import ChangeName from './ChangeName';
import ChangeMail from './ChangeMail'; import ChangeMail from './ChangeMail';
import DeleteAccount from './DeleteAccount'; import DeleteAccount from './DeleteAccount';
import SocialSettings from './SocialSettings';
import { numberToString } from '../core/utils'; import { numberToString } from '../core/utils';
@ -45,6 +46,7 @@ class UserArea extends React.Component {
changeMailExtended, changeMailExtended,
changePasswdExtended, changePasswdExtended,
deleteAccountExtended, deleteAccountExtended,
socialSettingsExtended,
} = this.state; } = this.state;
return ( return (
<p style={{ textAlign: 'center' }}> <p style={{ textAlign: 'center' }}>
@ -84,6 +86,7 @@ class UserArea extends React.Component {
changeMailExtended: false, changeMailExtended: false,
changePasswdExtended: false, changePasswdExtended: false,
deleteAccountExtended: false, deleteAccountExtended: false,
socialSettingsExtended: false,
})} })}
> Change Username</span> | > Change Username</span> |
{(mailreg) {(mailreg)
@ -98,6 +101,7 @@ class UserArea extends React.Component {
changeMailExtended: true, changeMailExtended: true,
changePasswdExtended: false, changePasswdExtended: false,
deleteAccountExtended: false, deleteAccountExtended: false,
socialSettingsExtended: false,
})} })}
> Change Mail</span> | > Change Mail</span> |
</span> </span>
@ -111,6 +115,7 @@ class UserArea extends React.Component {
changeMailExtended: false, changeMailExtended: false,
changePasswdExtended: true, changePasswdExtended: true,
deleteAccountExtended: false, deleteAccountExtended: false,
socialSettingsExtended: false,
})} })}
> Change Password</span> | > Change Password</span> |
<span <span
@ -122,9 +127,24 @@ class UserArea extends React.Component {
changeMailExtended: false, changeMailExtended: false,
changePasswdExtended: false, changePasswdExtended: false,
deleteAccountExtended: true, deleteAccountExtended: true,
socialSettingsExtended: false,
})} })}
> Delete Account</span> ) > Delete Account</span> )
<br />(
<span
role="button"
tabIndex={-1}
className="modallink"
onClick={() => this.setState({
changeNameExtended: false,
changeMailExtended: false,
changePasswdExtended: false,
deleteAccountExtended: false,
socialSettingsExtended: true,
})}
> Social Settings</span> )
</p> </p>
<p className="modaltext" />
{(changePasswdExtended) {(changePasswdExtended)
&& ( && (
<ChangePassword <ChangePassword
@ -156,6 +176,12 @@ class UserArea extends React.Component {
done={() => { this.setState({ deleteAccountExtended: false }); }} done={() => { this.setState({ deleteAccountExtended: false }); }}
/> />
)} )}
{(socialSettingsExtended)
&& (
<SocialSettings
done={() => { this.setState({ socialSettingsExtended: false }); }}
/>
)}
{(typeof window.hcaptcha !== 'undefined') {(typeof window.hcaptcha !== 'undefined')
&& ( && (
<img <img

View File

@ -12,10 +12,10 @@ import {
hideContextMenu, hideContextMenu,
addToChatInputMessage, addToChatInputMessage,
startDm, startDm,
setUserBlock,
} from '../actions'; } from '../actions';
import type { State } from '../reducers'; import type { State } from '../reducers';
const UserContextMenu = ({ const UserContextMenu = ({
xPos, xPos,
yPos, yPos,
@ -23,6 +23,7 @@ const UserContextMenu = ({
name, name,
addToInput, addToInput,
dm, dm,
block,
close, close,
}) => { }) => {
const wrapperRef = useRef(null); const wrapperRef = useRef(null);
@ -53,6 +54,10 @@ const UserContextMenu = ({
> >
<div <div
style={{ borderBottom: 'thin solid' }} style={{ borderBottom: 'thin solid' }}
onClick={() => {
block(uid, name);
close();
}}
> >
Block Block
</div> </div>
@ -113,6 +118,9 @@ function mapDispatchToProps(dispatch) {
dm(userId) { dm(userId) {
dispatch(startDm({ userId })); dispatch(startDm({ userId }));
}, },
block(userId, userName) {
dispatch(setUserBlock(userId, userName, true));
},
close() { close() {
dispatch(hideContextMenu()); dispatch(hideContextMenu());
}, },

View File

@ -64,6 +64,12 @@ const RegUser = Model.define('User', {
defaultValue: false, defaultValue: false,
}, },
blockDm: {
type: DataType.BOOLEAN,
allowNull: false,
defaultValue: false,
},
discordid: { discordid: {
type: DataType.CHAR(18), type: DataType.CHAR(18),
allowNull: true, allowNull: true,

View File

@ -197,6 +197,7 @@ class User {
mailVerified: regUser.mailVerified, mailVerified: regUser.mailVerified,
mcVerified: regUser.mcVerified, mcVerified: regUser.mcVerified,
minecraftname: regUser.minecraftname, minecraftname: regUser.minecraftname,
blockDm: regUser.blockDm,
totalPixels: regUser.totalPixels, totalPixels: regUser.totalPixels,
dailyTotalPixels: regUser.dailyTotalPixels, dailyTotalPixels: regUser.dailyTotalPixels,
ranking: regUser.ranking, ranking: regUser.ranking,

View File

@ -32,7 +32,6 @@ export type CanvasState = {
view: Cell, view: Cell,
scale: number, scale: number,
viewscale: number, viewscale: number,
fetchs: number,
isHistoricalView: boolean, isHistoricalView: boolean,
historicalDate: string, historicalDate: string,
historicalTime: string, historicalTime: string,
@ -137,7 +136,6 @@ function getViewFromURL(canvases: Object) {
const initialState: CanvasState = { const initialState: CanvasState = {
...getViewFromURL(DEFAULT_CANVASES), ...getViewFromURL(DEFAULT_CANVASES),
fetchs: 0,
isHistoricalView: false, isHistoricalView: false,
historicalDate: null, historicalDate: null,
historicalTime: 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': { case 'SELECT_COLOR': {
return { return {
...state, ...state,

View File

@ -6,19 +6,19 @@ import type { Action } from '../actions/types';
export type ChatState = { export type ChatState = {
inputMessage: string, inputMessage: string,
// [[cid, name], [cid2, name2],...] // [[cid, name, type, lastMessage], [cid2, name2, type2, lastMessage2],...]
channels: Array, channels: Array,
// { cid: [message1,message2,message2,...]} // [[userId, userName], [userId2, userName2],...]
blocked: Array,
// { cid: [message1,message2,message3,...]}
messages: Object, messages: Object,
// if currently fetching messages
fetching: boolean,
} }
const initialState: ChatState = { const initialState: ChatState = {
inputMessage: '', inputMessage: '',
channels: [], channels: [],
blocked: [],
messages: {}, messages: {},
fetching: false,
}; };
export default function chat( export default function chat(
@ -30,6 +30,27 @@ export default function chat(
return { return {
...state, ...state,
channels: action.channels, 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': { case 'SET_CHAT_INPUT_MSG': {
const { message } = action; const { message } = action;
return { return {

74
src/reducers/fetching.js Normal file
View File

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

View File

@ -9,6 +9,7 @@ import modal from './modal';
import user from './user'; import user from './user';
import chat from './chat'; import chat from './chat';
import contextMenu from './contextMenu'; import contextMenu from './contextMenu';
import fetching from './fetching';
import type { AudioState } from './audio'; import type { AudioState } from './audio';
import type { CanvasState } from './canvas'; import type { CanvasState } from './canvas';
@ -17,6 +18,7 @@ import type { ModalState } from './modal';
import type { UserState } from './user'; import type { UserState } from './user';
import type { ChatState } from './chat'; import type { ChatState } from './chat';
import type { ContextMenuState } from './contextMenu'; import type { ContextMenuState } from './contextMenu';
import type { FetchingState } from './fetching';
export type State = { export type State = {
audio: AudioState, audio: AudioState,
@ -26,6 +28,7 @@ export type State = {
user: UserState, user: UserState,
chat: ChatState, chat: ChatState,
contextMenu: ContextMenuState, contextMenu: ContextMenuState,
fetching: FetchingState,
}; };
const config = { const config = {
@ -37,6 +40,7 @@ const config = {
'modal', 'modal',
'chat', 'chat',
'contextMenu', 'contextMenu',
'fetching',
], ],
}; };
@ -48,4 +52,5 @@ export default persistCombineReducers(config, {
user, user,
chat, chat,
contextMenu, contextMenu,
fetching,
}); });

View File

@ -24,6 +24,8 @@ export type UserState = {
totalDailyRanking: Object, totalDailyRanking: Object,
// minecraft // minecraft
minecraftname: string, minecraftname: string,
// blocking all Dms
blockDm: boolean,
// if user is using touchscreen // if user is using touchscreen
isOnMobile: boolean, isOnMobile: boolean,
// small notifications for received cooldown // small notifications for received cooldown
@ -45,6 +47,7 @@ const initialState: UserState = {
totalRanking: {}, totalRanking: {},
totalDailyRanking: {}, totalDailyRanking: {},
minecraftname: null, minecraftname: null,
blockDm: false,
isOnMobile: false, isOnMobile: false,
notification: null, notification: null,
userlvl: 0, userlvl: 0,
@ -139,6 +142,7 @@ export default function user(
ranking, ranking,
dailyRanking, dailyRanking,
minecraftname, minecraftname,
blockDm,
userlvl, userlvl,
} = action; } = action;
const messages = (action.messages) ? action.messages : []; const messages = (action.messages) ? action.messages : [];
@ -152,6 +156,7 @@ export default function user(
ranking, ranking,
dailyRanking, dailyRanking,
minecraftname, minecraftname,
blockDm,
userlvl, userlvl,
}; };
} }
@ -173,6 +178,14 @@ export default function user(
}; };
} }
case 'SET_BLOCKING_DM': {
const { blockDm } = action;
return {
...state,
blockDm,
};
}
case 'SET_MINECRAFT_NAME': { case 'SET_MINECRAFT_NAME': {
const { minecraftname } = action; const { minecraftname } = action;
return { return {

View File

@ -8,11 +8,12 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import logger from '../../core/logger'; 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) { async function block(req: Request, res: Response) {
let userId = parseInt(req.body.userId, 10); let userId = parseInt(req.body.userId, 10);
let { userName } = req.body; let { userName } = req.body;
const { block: blocking } = req.body;
const { user } = req; const { user } = req;
const errors = []; const errors = [];
@ -23,6 +24,9 @@ async function block(req: Request, res: Response) {
} }
query.id = userId; query.id = userId;
} }
if (typeof blocking !== 'boolean') {
errors.push('Not defined if blocking or unblocking');
}
if (userName) { if (userName) {
query.name = userName; query.name = userName;
} }
@ -33,7 +37,7 @@ async function block(req: Request, res: Response) {
errors.push('You are not logged in'); errors.push('You are not logged in');
} }
if (user && userId && user.id === userId) { if (user && userId && user.id === userId) {
errors.push('You can not DM yourself.'); errors.push('You can not block yourself.');
} }
if (errors.length) { if (errors.length) {
res.status(400); res.status(400);
@ -61,14 +65,53 @@ async function block(req: Request, res: Response) {
userId = targetUser.id; userId = targetUser.id;
userName = targetUser.name; 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: { where: {
uid: user.id, type: 1,
buid: userId, dmu1id,
dmu2id,
}, },
raw: true,
attributes: ['uid'],
}); });
if (channel) {
const channelId = channel.id;
channel.destroy();
}
// TODO notify websocket
if (ret) { if (ret) {
res.json({ res.json({
status: 'ok', status: 'ok',
@ -76,10 +119,10 @@ async function block(req: Request, res: Response) {
} else { } else {
res.status(502); res.status(502);
res.json({ res.json({
errors: ['Could not block user'], errors: ['Could not (un)block user'],
}); });
logger.info( logger.info(
`User ${user.getName()} blocked ${userName}`, `User ${user.getName()} (un)blocked ${userName}`,
); );
} }
} }

46
src/routes/api/blockdm.js Normal file
View File

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

View File

@ -20,6 +20,7 @@ import history from './history';
import chatHistory from './chathistory'; import chatHistory from './chathistory';
import startDm from './startdm'; import startDm from './startdm';
import block from './block'; import block from './block';
import blockdm from './blockdm';
const router = express.Router(); const router = express.Router();
@ -86,6 +87,8 @@ router.post('/startdm', startDm);
router.post('/block', block); router.post('/block', block);
router.post('/blockdm', blockdm);
router.use('/auth', auth(passport)); router.use('/auth', auth(passport));
export default router; export default router;

View File

@ -49,6 +49,7 @@ async function startDm(req: Request, res: Response) {
attributes: [ attributes: [
'id', 'id',
'name', 'name',
'blockDm',
], ],
raw: true, raw: true,
}); });
@ -59,6 +60,12 @@ async function startDm(req: Request, res: Response) {
}); });
return; return;
} }
if (blockDm) {
res.status(401);
res.json({
errors: ['Target user doesn\'t allo DMs'],
});
}
userId = targetUser.id; userId = targetUser.id;
userName = targetUser.name; userName = targetUser.name;

View File

@ -74,6 +74,8 @@ a:hover {
padding: 4px; padding: 4px;
margin-top: 4px; margin-top: 4px;
border-width: 1px; border-width: 1px;
margin-left: 10px;
margin-right: 10px;
} }
.tab-list { .tab-list {
@ -552,6 +554,27 @@ tr:nth-child(even) {
opacity: 0; 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 { .actionbuttons {
vertical-align: text-bottom; vertical-align: text-bottom;
cursor: pointer; cursor: pointer;