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

View File

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

View File

@ -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 (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<ul
className="chatarea"
ref={listRef}
style={{ flexGrow: 1 }}
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}
<div style={{ display: 'relative', width: '100%', height: '100%' }}>
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<ul
className="chatarea"
ref={listRef}
style={{ flexGrow: 1 }}
onMouseUp={() => { setSelection(saveSelection); }}
role="presentation"
>
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>
);
};
@ -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,

View File

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

View File

@ -10,10 +10,10 @@ const MdToggleButton = ({ value, onToggle }) => (
<ToggleButton
inactiveLabel={<MdClose />}
activeLabel={<MdCheck />}
thumbAnimateRange={[-10, 36]}
value={value}
onToggle={onToggle}
/>
);
// thumbAnimateRange={[-10, 36]}
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 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 (
<p style={{ textAlign: 'center' }}>
@ -84,6 +86,7 @@ class UserArea extends React.Component {
changeMailExtended: false,
changePasswdExtended: false,
deleteAccountExtended: false,
socialSettingsExtended: false,
})}
> Change Username</span> |
{(mailreg)
@ -98,6 +101,7 @@ class UserArea extends React.Component {
changeMailExtended: true,
changePasswdExtended: false,
deleteAccountExtended: false,
socialSettingsExtended: false,
})}
> Change Mail</span> |
</span>
@ -111,6 +115,7 @@ class UserArea extends React.Component {
changeMailExtended: false,
changePasswdExtended: true,
deleteAccountExtended: false,
socialSettingsExtended: false,
})}
> Change Password</span> |
<span
@ -122,9 +127,24 @@ class UserArea extends React.Component {
changeMailExtended: false,
changePasswdExtended: false,
deleteAccountExtended: true,
socialSettingsExtended: false,
})}
> 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 className="modaltext" />
{(changePasswdExtended)
&& (
<ChangePassword
@ -156,6 +176,12 @@ class UserArea extends React.Component {
done={() => { this.setState({ deleteAccountExtended: false }); }}
/>
)}
{(socialSettingsExtended)
&& (
<SocialSettings
done={() => { this.setState({ socialSettingsExtended: false }); }}
/>
)}
{(typeof window.hcaptcha !== 'undefined')
&& (
<img

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

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

View File

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