@ -1,21 +1,33 @@
|
||||
import { publishMainStream } from '../../../services/stream';
|
||||
import { publishMainStream, publishGroupMessagingStream } from '../../../services/stream';
|
||||
import { publishMessagingStream } from '../../../services/stream';
|
||||
import { publishMessagingIndexStream } from '../../../services/stream';
|
||||
import { User } from '../../../models/entities/user';
|
||||
import { MessagingMessage } from '../../../models/entities/messaging-message';
|
||||
import { MessagingMessages } from '../../../models';
|
||||
import { MessagingMessages, UserGroupJoinings, Users } from '../../../models';
|
||||
import { In } from 'typeorm';
|
||||
import { IdentifiableError } from '../../../misc/identifiable-error';
|
||||
import { UserGroup } from '../../../models/entities/user-group';
|
||||
|
||||
/**
|
||||
* Mark messages as read
|
||||
*/
|
||||
export default async (
|
||||
export async function readUserMessagingMessage(
|
||||
userId: User['id'],
|
||||
otherpartyId: User['id'],
|
||||
messageIds: MessagingMessage['id'][]
|
||||
) => {
|
||||
) {
|
||||
if (messageIds.length === 0) return;
|
||||
|
||||
const messages = await MessagingMessages.find({
|
||||
id: In(messageIds)
|
||||
});
|
||||
|
||||
for (const message of messages) {
|
||||
if (message.recipientId !== userId) {
|
||||
throw new IdentifiableError('e140a4bf-49ce-4fb6-b67c-b78dadf6b52f', 'Access denied (user).');
|
||||
}
|
||||
}
|
||||
|
||||
// Update documents
|
||||
await MessagingMessages.update({
|
||||
id: In(messageIds),
|
||||
@ -30,14 +42,62 @@ export default async (
|
||||
publishMessagingStream(otherpartyId, userId, 'read', messageIds);
|
||||
publishMessagingIndexStream(userId, 'read', messageIds);
|
||||
|
||||
// Calc count of my unread messages
|
||||
const count = await MessagingMessages.count({
|
||||
recipientId: userId,
|
||||
isRead: false
|
||||
});
|
||||
|
||||
if (count == 0) {
|
||||
if (!Users.getHasUnreadMessagingMessage(userId)) {
|
||||
// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
|
||||
publishMainStream(userId, 'readAllMessagingMessages');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark messages as read
|
||||
*/
|
||||
export async function readGroupMessagingMessage(
|
||||
userId: User['id'],
|
||||
groupId: UserGroup['id'],
|
||||
messageIds: MessagingMessage['id'][]
|
||||
) {
|
||||
if (messageIds.length === 0) return;
|
||||
|
||||
// check joined
|
||||
const joining = await UserGroupJoinings.findOne({
|
||||
userId: userId,
|
||||
userGroupId: groupId
|
||||
});
|
||||
|
||||
if (joining == null) {
|
||||
throw new IdentifiableError('930a270c-714a-46b2-b776-ad27276dc569', 'Access denied (group).');
|
||||
}
|
||||
|
||||
const messages = await MessagingMessages.find({
|
||||
id: In(messageIds)
|
||||
});
|
||||
|
||||
const reads = [];
|
||||
|
||||
for (const message of messages) {
|
||||
if (message.userId === userId) continue;
|
||||
if (message.reads.includes(userId)) continue;
|
||||
|
||||
// Update document
|
||||
await MessagingMessages.createQueryBuilder().update()
|
||||
.set({
|
||||
reads: (() => `array_append("reads", '${joining.userId}')`) as any
|
||||
})
|
||||
.where('id = :id', { id: message.id })
|
||||
.execute();
|
||||
|
||||
reads.push(message.id);
|
||||
}
|
||||
|
||||
// Publish event
|
||||
publishGroupMessagingStream(groupId, 'read', {
|
||||
ids: reads,
|
||||
userId: userId
|
||||
});
|
||||
publishMessagingIndexStream(userId, 'read', reads);
|
||||
|
||||
if (!Users.getHasUnreadMessagingMessage(userId)) {
|
||||
// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
|
||||
publishMainStream(userId, 'readAllMessagingMessages');
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import $ from 'cafy';
|
||||
import define from '../../define';
|
||||
import { MessagingMessage } from '../../../../models/entities/messaging-message';
|
||||
import { MessagingMessages, Mutings } from '../../../../models';
|
||||
import { MessagingMessages, Mutings, UserGroupJoinings } from '../../../../models';
|
||||
import { Brackets } from 'typeorm';
|
||||
import { types, bool } from '../../../../misc/schema';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': 'Messagingの履歴を取得します。',
|
||||
'ja-JP': 'トークの履歴を取得します。',
|
||||
'en-US': 'Show messaging history.'
|
||||
},
|
||||
|
||||
@ -21,6 +21,11 @@ export const meta = {
|
||||
limit: {
|
||||
validator: $.optional.num.range(1, 100),
|
||||
default: 10
|
||||
},
|
||||
|
||||
group: {
|
||||
validator: $.optional.bool,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
@ -40,26 +45,46 @@ export default define(meta, async (ps, user) => {
|
||||
muterId: user.id,
|
||||
});
|
||||
|
||||
const groups = ps.group ? await UserGroupJoinings.find({
|
||||
userId: user.id,
|
||||
}).then(xs => xs.map(x => x.userGroupId)) : [];
|
||||
|
||||
if (ps.group && groups.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const history: MessagingMessage[] = [];
|
||||
|
||||
for (let i = 0; i < ps.limit!; i++) {
|
||||
const found = history.map(m => (m.userId === user.id) ? m.recipientId : m.userId);
|
||||
const found = ps.group
|
||||
? history.map(m => m.groupId!)
|
||||
: history.map(m => (m.userId === user.id) ? m.recipientId! : m.userId!);
|
||||
|
||||
const query = MessagingMessages.createQueryBuilder('message')
|
||||
.where(new Brackets(qb => { qb
|
||||
.where(`message.userId = :userId`, { userId: user.id })
|
||||
.orWhere(`message.recipientId = :userId`, { userId: user.id });
|
||||
}))
|
||||
.orderBy('message.createdAt', 'DESC');
|
||||
|
||||
if (found.length > 0) {
|
||||
query.andWhere(`message.userId NOT IN (:...found)`, { found: found });
|
||||
query.andWhere(`message.recipientId NOT IN (:...found)`, { found: found });
|
||||
}
|
||||
if (ps.group) {
|
||||
query.where(`message.groupId IN (:...groups)`, { groups: groups });
|
||||
|
||||
if (mute.length > 0) {
|
||||
query.andWhere(`message.userId NOT IN (:...mute)`, { mute: mute.map(m => m.muteeId) });
|
||||
query.andWhere(`message.recipientId NOT IN (:...mute)`, { mute: mute.map(m => m.muteeId) });
|
||||
if (found.length > 0) {
|
||||
query.andWhere(`message.groupId NOT IN (:...found)`, { found: found });
|
||||
}
|
||||
} else {
|
||||
query.where(new Brackets(qb => { qb
|
||||
.where(`message.userId = :userId`, { userId: user.id })
|
||||
.orWhere(`message.recipientId = :userId`, { userId: user.id });
|
||||
}));
|
||||
query.andWhere(`message.groupId IS NULL`);
|
||||
|
||||
if (found.length > 0) {
|
||||
query.andWhere(`message.userId NOT IN (:...found)`, { found: found });
|
||||
query.andWhere(`message.recipientId NOT IN (:...found)`, { found: found });
|
||||
}
|
||||
|
||||
if (mute.length > 0) {
|
||||
query.andWhere(`message.userId NOT IN (:...mute)`, { mute: mute.map(m => m.muteeId) });
|
||||
query.andWhere(`message.recipientId NOT IN (:...mute)`, { mute: mute.map(m => m.muteeId) });
|
||||
}
|
||||
}
|
||||
|
||||
const message = await query.getOne();
|
||||
|
@ -1,16 +1,17 @@
|
||||
import $ from 'cafy';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import read from '../../common/read-messaging-message';
|
||||
import define from '../../define';
|
||||
import { ApiError } from '../../error';
|
||||
import { getUser } from '../../common/getters';
|
||||
import { MessagingMessages } from '../../../../models';
|
||||
import { MessagingMessages, UserGroups, UserGroupJoinings } from '../../../../models';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { types, bool } from '../../../../misc/schema';
|
||||
import { Brackets } from 'typeorm';
|
||||
import { readUserMessagingMessage, readGroupMessagingMessage } from '../../common/read-messaging-message';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': '指定したユーザーとのMessagingのメッセージ一覧を取得します。',
|
||||
'ja-JP': 'トークメッセージ一覧を取得します。',
|
||||
'en-US': 'Get messages of messaging.'
|
||||
},
|
||||
|
||||
@ -22,13 +23,21 @@ export const meta = {
|
||||
|
||||
params: {
|
||||
userId: {
|
||||
validator: $.type(ID),
|
||||
validator: $.optional.type(ID),
|
||||
desc: {
|
||||
'ja-JP': '対象のユーザーのID',
|
||||
'en-US': 'Target user ID'
|
||||
}
|
||||
},
|
||||
|
||||
groupId: {
|
||||
validator: $.optional.type(ID),
|
||||
desc: {
|
||||
'ja-JP': '対象のグループのID',
|
||||
'en-US': 'Target group ID'
|
||||
}
|
||||
},
|
||||
|
||||
limit: {
|
||||
validator: $.optional.num.range(1, 100),
|
||||
default: 10
|
||||
@ -64,27 +73,85 @@ export const meta = {
|
||||
code: 'NO_SUCH_USER',
|
||||
id: '11795c64-40ea-4198-b06e-3c873ed9039d'
|
||||
},
|
||||
|
||||
noSuchGroup: {
|
||||
message: 'No such group.',
|
||||
code: 'NO_SUCH_GROUP',
|
||||
id: 'c4d9f88c-9270-4632-b032-6ed8cee36f7f'
|
||||
},
|
||||
|
||||
groupAccessDenied: {
|
||||
message: 'You can not read messages of groups that you have not joined.',
|
||||
code: 'GROUP_ACCESS_DENIED',
|
||||
id: 'a053a8dd-a491-4718-8f87-50775aad9284'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
// Fetch recipient
|
||||
const recipient = await getUser(ps.userId).catch(e => {
|
||||
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
|
||||
throw e;
|
||||
});
|
||||
if (ps.userId != null) {
|
||||
// Fetch recipient (user)
|
||||
const recipient = await getUser(ps.userId).catch(e => {
|
||||
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
|
||||
throw e;
|
||||
});
|
||||
|
||||
const query = makePaginationQuery(MessagingMessages.createQueryBuilder('message'), ps.sinceId, ps.untilId)
|
||||
.andWhere(`(message.userId = :meId AND message.recipientId = :recipientId) OR (message.userId = :recipientId AND message.recipientId = :meId)`, { meId: user.id, recipientId: recipient.id });
|
||||
const query = makePaginationQuery(MessagingMessages.createQueryBuilder('message'), ps.sinceId, ps.untilId)
|
||||
.andWhere(new Brackets(qb => { qb
|
||||
.where(new Brackets(qb => { qb
|
||||
.where('message.userId = :meId')
|
||||
.andWhere('message.recipientId = :recipientId');
|
||||
}))
|
||||
.orWhere(new Brackets(qb => { qb
|
||||
.where('message.userId = :recipientId')
|
||||
.andWhere('message.recipientId = :meId');
|
||||
}));
|
||||
}))
|
||||
.setParameter('meId', user.id)
|
||||
.setParameter('recipientId', recipient.id);
|
||||
|
||||
const messages = await query.getMany();
|
||||
const messages = await query.take(ps.limit!).getMany();
|
||||
|
||||
// Mark all as read
|
||||
if (ps.markAsRead) {
|
||||
read(user.id, recipient.id, messages.map(x => x.id));
|
||||
// Mark all as read
|
||||
if (ps.markAsRead) {
|
||||
readUserMessagingMessage(user.id, recipient.id, messages.map(x => x.id));
|
||||
}
|
||||
|
||||
return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, {
|
||||
populateRecipient: false
|
||||
})));
|
||||
} else if (ps.groupId != null) {
|
||||
// Fetch recipient (group)
|
||||
const recipientGroup = await UserGroups.findOne(ps.groupId);
|
||||
|
||||
if (recipientGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
|
||||
// check joined
|
||||
const joining = await UserGroupJoinings.findOne({
|
||||
userId: user.id,
|
||||
userGroupId: recipientGroup.id
|
||||
});
|
||||
|
||||
if (joining == null) {
|
||||
throw new ApiError(meta.errors.groupAccessDenied);
|
||||
}
|
||||
|
||||
const query = makePaginationQuery(MessagingMessages.createQueryBuilder('message'), ps.sinceId, ps.untilId)
|
||||
.andWhere(`message.groupId = :groupId`, { groupId: recipientGroup.id });
|
||||
|
||||
const messages = await query.take(ps.limit!).getMany();
|
||||
|
||||
// Mark all as read
|
||||
if (ps.markAsRead) {
|
||||
readGroupMessagingMessage(user.id, recipientGroup.id, messages.map(x => x.id));
|
||||
}
|
||||
|
||||
return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, {
|
||||
populateGroup: false
|
||||
})));
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, {
|
||||
populateRecipient: false
|
||||
})));
|
||||
});
|
||||
|
@ -1,19 +1,22 @@
|
||||
import $ from 'cafy';
|
||||
import { ID } from '../../../../../misc/cafy-id';
|
||||
import { publishMainStream } from '../../../../../services/stream';
|
||||
import { publishMainStream, publishGroupMessagingStream } from '../../../../../services/stream';
|
||||
import { publishMessagingStream, publishMessagingIndexStream } from '../../../../../services/stream';
|
||||
import pushSw from '../../../../../services/push-notification';
|
||||
import define from '../../../define';
|
||||
import { ApiError } from '../../../error';
|
||||
import { getUser } from '../../../common/getters';
|
||||
import { MessagingMessages, DriveFiles, Mutings } from '../../../../../models';
|
||||
import { MessagingMessages, DriveFiles, Mutings, UserGroups, UserGroupJoinings } from '../../../../../models';
|
||||
import { MessagingMessage } from '../../../../../models/entities/messaging-message';
|
||||
import { genId } from '../../../../../misc/gen-id';
|
||||
import { types, bool } from '../../../../../misc/schema';
|
||||
import { User } from '../../../../../models/entities/user';
|
||||
import { UserGroup } from '../../../../../models/entities/user-group';
|
||||
import { Not } from 'typeorm';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': '指定したユーザーへMessagingのメッセージを送信します。',
|
||||
'ja-JP': 'トークメッセージを送信します。',
|
||||
'en-US': 'Create a message of messaging.'
|
||||
},
|
||||
|
||||
@ -25,13 +28,21 @@ export const meta = {
|
||||
|
||||
params: {
|
||||
userId: {
|
||||
validator: $.type(ID),
|
||||
validator: $.optional.type(ID),
|
||||
desc: {
|
||||
'ja-JP': '対象のユーザーのID',
|
||||
'en-US': 'Target user ID'
|
||||
}
|
||||
},
|
||||
|
||||
groupId: {
|
||||
validator: $.optional.type(ID),
|
||||
desc: {
|
||||
'ja-JP': '対象のグループのID',
|
||||
'en-US': 'Target group ID'
|
||||
}
|
||||
},
|
||||
|
||||
text: {
|
||||
validator: $.optional.str.pipe(MessagingMessages.isValidText)
|
||||
},
|
||||
@ -60,6 +71,18 @@ export const meta = {
|
||||
id: '11795c64-40ea-4198-b06e-3c873ed9039d'
|
||||
},
|
||||
|
||||
noSuchGroup: {
|
||||
message: 'No such group.',
|
||||
code: 'NO_SUCH_GROUP',
|
||||
id: 'c94e2a5d-06aa-4914-8fa6-6a42e73d6537'
|
||||
},
|
||||
|
||||
groupAccessDenied: {
|
||||
message: 'You can not send messages to groups that you have not joined.',
|
||||
code: 'GROUP_ACCESS_DENIED',
|
||||
id: 'd96b3cca-5ad1-438b-ad8b-02f931308fbd'
|
||||
},
|
||||
|
||||
noSuchFile: {
|
||||
message: 'No such file.',
|
||||
code: 'NO_SUCH_FILE',
|
||||
@ -75,16 +98,38 @@ export const meta = {
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
// Myself
|
||||
if (ps.userId === user.id) {
|
||||
throw new ApiError(meta.errors.recipientIsYourself);
|
||||
}
|
||||
let recipientUser: User | undefined;
|
||||
let recipientGroup: UserGroup | undefined;
|
||||
|
||||
// Fetch recipient
|
||||
const recipient = await getUser(ps.userId).catch(e => {
|
||||
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
|
||||
throw e;
|
||||
});
|
||||
if (ps.userId != null) {
|
||||
// Myself
|
||||
if (ps.userId === user.id) {
|
||||
throw new ApiError(meta.errors.recipientIsYourself);
|
||||
}
|
||||
|
||||
// Fetch recipient (user)
|
||||
recipientUser = await getUser(ps.userId).catch(e => {
|
||||
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
|
||||
throw e;
|
||||
});
|
||||
} else if (ps.groupId != null) {
|
||||
// Fetch recipient (group)
|
||||
recipientGroup = await UserGroups.findOne(ps.groupId);
|
||||
|
||||
if (recipientGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
|
||||
// check joined
|
||||
const joining = await UserGroupJoinings.findOne({
|
||||
userId: user.id,
|
||||
userGroupId: recipientGroup.id
|
||||
});
|
||||
|
||||
if (joining == null) {
|
||||
throw new ApiError(meta.errors.groupAccessDenied);
|
||||
}
|
||||
}
|
||||
|
||||
let file = null;
|
||||
if (ps.fileId != null) {
|
||||
@ -107,32 +152,49 @@ export default define(meta, async (ps, user) => {
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
fileId: file ? file.id : null,
|
||||
recipientId: recipient.id,
|
||||
recipientId: recipientUser ? recipientUser.id : null,
|
||||
groupId: recipientGroup ? recipientGroup.id : null,
|
||||
text: ps.text ? ps.text.trim() : null,
|
||||
userId: user.id,
|
||||
isRead: false
|
||||
isRead: false,
|
||||
reads: [] as any[]
|
||||
} as MessagingMessage);
|
||||
|
||||
const messageObj = await MessagingMessages.pack(message);
|
||||
|
||||
// 自分のストリーム
|
||||
publishMessagingStream(message.userId, message.recipientId, 'message', messageObj);
|
||||
publishMessagingIndexStream(message.userId, 'message', messageObj);
|
||||
publishMainStream(message.userId, 'messagingMessage', messageObj);
|
||||
if (recipientUser) {
|
||||
// 自分のストリーム
|
||||
publishMessagingStream(message.userId, recipientUser.id, 'message', messageObj);
|
||||
publishMessagingIndexStream(message.userId, 'message', messageObj);
|
||||
publishMainStream(message.userId, 'messagingMessage', messageObj);
|
||||
|
||||
// 相手のストリーム
|
||||
publishMessagingStream(message.recipientId, message.userId, 'message', messageObj);
|
||||
publishMessagingIndexStream(message.recipientId, 'message', messageObj);
|
||||
publishMainStream(message.recipientId, 'messagingMessage', messageObj);
|
||||
// 相手のストリーム
|
||||
publishMessagingStream(recipientUser.id, message.userId, 'message', messageObj);
|
||||
publishMessagingIndexStream(recipientUser.id, 'message', messageObj);
|
||||
publishMainStream(recipientUser.id, 'messagingMessage', messageObj);
|
||||
} else if (recipientGroup) {
|
||||
// グループのストリーム
|
||||
publishGroupMessagingStream(recipientGroup.id, 'message', messageObj);
|
||||
|
||||
// メンバーのストリーム
|
||||
const joinings = await UserGroupJoinings.find({ userGroupId: recipientGroup.id });
|
||||
for (const joining of joinings) {
|
||||
publishMessagingIndexStream(joining.userId, 'message', messageObj);
|
||||
publishMainStream(joining.userId, 'messagingMessage', messageObj);
|
||||
}
|
||||
}
|
||||
|
||||
// 2秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する
|
||||
setTimeout(async () => {
|
||||
const freshMessage = await MessagingMessages.findOne({ id: message.id });
|
||||
const freshMessage = await MessagingMessages.findOne(message.id);
|
||||
if (freshMessage == null) return; // メッセージが削除されている場合もある
|
||||
if (!freshMessage.isRead) {
|
||||
|
||||
if (recipientUser) {
|
||||
if (freshMessage.isRead) return; // 既読
|
||||
|
||||
//#region ただしミュートされているなら発行しない
|
||||
const mute = await Mutings.find({
|
||||
muterId: recipient.id,
|
||||
muterId: recipientUser.id,
|
||||
});
|
||||
const mutedUserIds = mute.map(m => m.muteeId.toString());
|
||||
if (mutedUserIds.indexOf(user.id) != -1) {
|
||||
@ -140,8 +202,15 @@ export default define(meta, async (ps, user) => {
|
||||
}
|
||||
//#endregion
|
||||
|
||||
publishMainStream(message.recipientId, 'unreadMessagingMessage', messageObj);
|
||||
pushSw(message.recipientId, 'unreadMessagingMessage', messageObj);
|
||||
publishMainStream(recipientUser.id, 'unreadMessagingMessage', messageObj);
|
||||
pushSw(recipientUser.id, 'unreadMessagingMessage', messageObj);
|
||||
} else if (recipientGroup) {
|
||||
const joinings = await UserGroupJoinings.find({ userGroupId: recipientGroup.id, userId: Not(user.id) });
|
||||
for (const joining of joinings) {
|
||||
if (freshMessage.reads.includes(joining.userId)) return; // 既読
|
||||
publishMainStream(joining.userId, 'unreadMessagingMessage', messageObj);
|
||||
pushSw(joining.userId, 'unreadMessagingMessage', messageObj);
|
||||
}
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import $ from 'cafy';
|
||||
import { ID } from '../../../../../misc/cafy-id';
|
||||
import define from '../../../define';
|
||||
import { publishMessagingStream } from '../../../../../services/stream';
|
||||
import { publishMessagingStream, publishGroupMessagingStream } from '../../../../../services/stream';
|
||||
import * as ms from 'ms';
|
||||
import { ApiError } from '../../../error';
|
||||
import { MessagingMessages } from '../../../../../models';
|
||||
@ -10,7 +10,7 @@ export const meta = {
|
||||
stability: 'stable',
|
||||
|
||||
desc: {
|
||||
'ja-JP': '指定したメッセージを削除します。',
|
||||
'ja-JP': '指定したトークメッセージを削除します。',
|
||||
'en-US': 'Delete a message.'
|
||||
},
|
||||
|
||||
@ -57,6 +57,10 @@ export default define(meta, async (ps, user) => {
|
||||
|
||||
await MessagingMessages.delete(message.id);
|
||||
|
||||
publishMessagingStream(message.userId, message.recipientId, 'deleted', message.id);
|
||||
publishMessagingStream(message.recipientId, message.userId, 'deleted', message.id);
|
||||
if (message.recipientId) {
|
||||
publishMessagingStream(message.userId, message.recipientId, 'deleted', message.id);
|
||||
publishMessagingStream(message.recipientId, message.userId, 'deleted', message.id);
|
||||
} else if (message.groupId) {
|
||||
publishGroupMessagingStream(message.groupId, 'deleted', message.id);
|
||||
}
|
||||
});
|
||||
|
@ -1,13 +1,13 @@
|
||||
import $ from 'cafy';
|
||||
import { ID } from '../../../../../misc/cafy-id';
|
||||
import read from '../../../common/read-messaging-message';
|
||||
import define from '../../../define';
|
||||
import { ApiError } from '../../../error';
|
||||
import { MessagingMessages } from '../../../../../models';
|
||||
import { readUserMessagingMessage, readGroupMessagingMessage } from '../../../common/read-messaging-message';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': '指定した自分宛てのメッセージを既読にします。',
|
||||
'ja-JP': '指定した自分宛てのトークメッセージを既読にします。',
|
||||
'en-US': 'Mark as read a message of messaging.'
|
||||
},
|
||||
|
||||
@ -39,12 +39,21 @@ export const meta = {
|
||||
export default define(meta, async (ps, user) => {
|
||||
const message = await MessagingMessages.findOne({
|
||||
id: ps.messageId,
|
||||
recipientId: user.id
|
||||
});
|
||||
|
||||
if (message == null) {
|
||||
throw new ApiError(meta.errors.noSuchMessage);
|
||||
}
|
||||
|
||||
read(user.id, message.userId, [message.id]);
|
||||
if (message.recipientId) {
|
||||
await readUserMessagingMessage(user.id, message.recipientId, [message.id]).catch(e => {
|
||||
if (e.id === 'e140a4bf-49ce-4fb6-b67c-b78dadf6b52f') throw new ApiError(meta.errors.noSuchMessage);
|
||||
throw e;
|
||||
});
|
||||
} else if (message.groupId) {
|
||||
await readGroupMessagingMessage(user.id, message.groupId, [message.id]).catch(e => {
|
||||
if (e.id === '930a270c-714a-46b2-b776-ad27276dc569') throw new ApiError(meta.errors.noSuchMessage);
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
51
src/server/api/endpoints/users/groups/create.ts
Normal file
51
src/server/api/endpoints/users/groups/create.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import $ from 'cafy';
|
||||
import define from '../../../define';
|
||||
import { UserGroups, UserGroupJoinings } from '../../../../../models';
|
||||
import { genId } from '../../../../../misc/gen-id';
|
||||
import { UserGroup } from '../../../../../models/entities/user-group';
|
||||
import { types, bool } from '../../../../../misc/schema';
|
||||
import { UserGroupJoining } from '../../../../../models/entities/user-group-joining';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': 'ユーザーグループを作成します。',
|
||||
'en-US': 'Create a user group.'
|
||||
},
|
||||
|
||||
tags: ['groups'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:user-groups',
|
||||
|
||||
params: {
|
||||
name: {
|
||||
validator: $.str.range(1, 100)
|
||||
}
|
||||
},
|
||||
|
||||
res: {
|
||||
type: types.object,
|
||||
optional: bool.false, nullable: bool.false,
|
||||
ref: 'UserGroup',
|
||||
},
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
const userGroup = await UserGroups.save({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
userId: user.id,
|
||||
name: ps.name,
|
||||
} as UserGroup);
|
||||
|
||||
// Push the owner
|
||||
await UserGroupJoinings.save({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
userId: user.id,
|
||||
userGroupId: userGroup.id
|
||||
} as UserGroupJoining);
|
||||
|
||||
return await UserGroups.pack(userGroup);
|
||||
});
|
49
src/server/api/endpoints/users/groups/delete.ts
Normal file
49
src/server/api/endpoints/users/groups/delete.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import $ from 'cafy';
|
||||
import { ID } from '../../../../../misc/cafy-id';
|
||||
import define from '../../../define';
|
||||
import { ApiError } from '../../../error';
|
||||
import { UserGroups } from '../../../../../models';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': '指定したユーザーグループを削除します。',
|
||||
'en-US': 'Delete a user group'
|
||||
},
|
||||
|
||||
tags: ['groups'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:user-groups',
|
||||
|
||||
params: {
|
||||
groupId: {
|
||||
validator: $.type(ID),
|
||||
desc: {
|
||||
'ja-JP': '対象となるユーザーグループのID',
|
||||
'en-US': 'ID of target user group'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: 'No such group.',
|
||||
code: 'NO_SUCH_GROUP',
|
||||
id: '63dbd64c-cd77-413f-8e08-61781e210b38'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
const userGroup = await UserGroups.findOne({
|
||||
id: ps.groupId,
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
if (userGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
|
||||
await UserGroups.delete(userGroup.id);
|
||||
});
|
33
src/server/api/endpoints/users/groups/joined.ts
Normal file
33
src/server/api/endpoints/users/groups/joined.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import define from '../../../define';
|
||||
import { UserGroups, UserGroupJoinings } from '../../../../../models';
|
||||
import { types, bool } from '../../../../../misc/schema';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': '自分の所属するユーザーグループ一覧を取得します。'
|
||||
},
|
||||
|
||||
tags: ['groups', 'account'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:user-groups',
|
||||
|
||||
res: {
|
||||
type: types.array,
|
||||
optional: bool.false, nullable: bool.false,
|
||||
items: {
|
||||
type: types.object,
|
||||
optional: bool.false, nullable: bool.false,
|
||||
ref: 'UserGroup',
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, me) => {
|
||||
const joinings = await UserGroupJoinings.find({
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
return await Promise.all(joinings.map(x => UserGroups.pack(x.userGroupId)));
|
||||
});
|
33
src/server/api/endpoints/users/groups/owned.ts
Normal file
33
src/server/api/endpoints/users/groups/owned.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import define from '../../../define';
|
||||
import { UserGroups } from '../../../../../models';
|
||||
import { types, bool } from '../../../../../misc/schema';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': '自分の作成したユーザーグループ一覧を取得します。'
|
||||
},
|
||||
|
||||
tags: ['groups', 'account'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:user-groups',
|
||||
|
||||
res: {
|
||||
type: types.array,
|
||||
optional: bool.false, nullable: bool.false,
|
||||
items: {
|
||||
type: types.object,
|
||||
optional: bool.false, nullable: bool.false,
|
||||
ref: 'UserGroup',
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, me) => {
|
||||
const userGroups = await UserGroups.find({
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
return await Promise.all(userGroups.map(x => UserGroups.pack(x)));
|
||||
});
|
68
src/server/api/endpoints/users/groups/pull.ts
Normal file
68
src/server/api/endpoints/users/groups/pull.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import $ from 'cafy';
|
||||
import { ID } from '../../../../../misc/cafy-id';
|
||||
import define from '../../../define';
|
||||
import { ApiError } from '../../../error';
|
||||
import { getUser } from '../../../common/getters';
|
||||
import { UserGroups, UserGroupJoinings } from '../../../../../models';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': '指定したユーザーグループから指定したユーザーを削除します。',
|
||||
'en-US': 'Remove a user to a user group.'
|
||||
},
|
||||
|
||||
tags: ['groups', 'users'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:user-groups',
|
||||
|
||||
params: {
|
||||
groupId: {
|
||||
validator: $.type(ID),
|
||||
},
|
||||
|
||||
userId: {
|
||||
validator: $.type(ID),
|
||||
desc: {
|
||||
'ja-JP': '対象のユーザーのID',
|
||||
'en-US': 'Target user ID'
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: 'No such group.',
|
||||
code: 'NO_SUCH_GROUP',
|
||||
id: '4662487c-05b1-4b78-86e5-fd46998aba74'
|
||||
},
|
||||
|
||||
noSuchUser: {
|
||||
message: 'No such user.',
|
||||
code: 'NO_SUCH_USER',
|
||||
id: '0b5cc374-3681-41da-861e-8bc1146f7a55'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, me) => {
|
||||
// Fetch the group
|
||||
const userGroup = await UserGroups.findOne({
|
||||
id: ps.groupId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
if (userGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
|
||||
// Fetch the user
|
||||
const user = await getUser(ps.userId).catch(e => {
|
||||
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
|
||||
throw e;
|
||||
});
|
||||
|
||||
// Pull the user
|
||||
await UserGroupJoinings.delete({ userId: user.id });
|
||||
});
|
90
src/server/api/endpoints/users/groups/push.ts
Normal file
90
src/server/api/endpoints/users/groups/push.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import $ from 'cafy';
|
||||
import { ID } from '../../../../../misc/cafy-id';
|
||||
import define from '../../../define';
|
||||
import { ApiError } from '../../../error';
|
||||
import { getUser } from '../../../common/getters';
|
||||
import { UserGroups, UserGroupJoinings } from '../../../../../models';
|
||||
import { genId } from '../../../../../misc/gen-id';
|
||||
import { UserGroupJoining } from '../../../../../models/entities/user-group-joining';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': '指定したユーザーグループに指定したユーザーを追加します。',
|
||||
'en-US': 'Add a user to a user group.'
|
||||
},
|
||||
|
||||
tags: ['groups', 'users'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:user-groups',
|
||||
|
||||
params: {
|
||||
groupId: {
|
||||
validator: $.type(ID),
|
||||
},
|
||||
|
||||
userId: {
|
||||
validator: $.type(ID),
|
||||
desc: {
|
||||
'ja-JP': '対象のユーザーのID',
|
||||
'en-US': 'Target user ID'
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: 'No such group.',
|
||||
code: 'NO_SUCH_GROUP',
|
||||
id: '583f8bc0-8eee-4b78-9299-1e14fc91e409'
|
||||
},
|
||||
|
||||
noSuchUser: {
|
||||
message: 'No such user.',
|
||||
code: 'NO_SUCH_USER',
|
||||
id: 'da52de61-002c-475b-90e1-ba64f9cf13a8'
|
||||
},
|
||||
|
||||
alreadyAdded: {
|
||||
message: 'That user has already been added to that group.',
|
||||
code: 'ALREADY_ADDED',
|
||||
id: '7e35c6a0-39b2-4488-aea6-6ee20bd5da2c'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, me) => {
|
||||
// Fetch the group
|
||||
const userGroup = await UserGroups.findOne({
|
||||
id: ps.groupId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
if (userGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
|
||||
// Fetch the user
|
||||
const user = await getUser(ps.userId).catch(e => {
|
||||
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
|
||||
throw e;
|
||||
});
|
||||
|
||||
const exist = await UserGroupJoinings.findOne({
|
||||
userGroupId: userGroup.id,
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
if (exist) {
|
||||
throw new ApiError(meta.errors.alreadyAdded);
|
||||
}
|
||||
|
||||
// Push the user
|
||||
await UserGroupJoinings.save({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
userId: user.id,
|
||||
userGroupId: userGroup.id
|
||||
} as UserGroupJoining);
|
||||
});
|
53
src/server/api/endpoints/users/groups/show.ts
Normal file
53
src/server/api/endpoints/users/groups/show.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import $ from 'cafy';
|
||||
import { ID } from '../../../../../misc/cafy-id';
|
||||
import define from '../../../define';
|
||||
import { ApiError } from '../../../error';
|
||||
import { UserGroups } from '../../../../../models';
|
||||
import { types, bool } from '../../../../../misc/schema';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': '指定したユーザーグループの情報を取得します。',
|
||||
'en-US': 'Show a user group.'
|
||||
},
|
||||
|
||||
tags: ['groups', 'account'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:user-groups',
|
||||
|
||||
params: {
|
||||
groupId: {
|
||||
validator: $.type(ID),
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
type: types.object,
|
||||
optional: bool.false, nullable: bool.false,
|
||||
ref: 'UserGroup',
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: 'No such group.',
|
||||
code: 'NO_SUCH_GROUP',
|
||||
id: 'ea04751e-9b7e-487b-a509-330fb6bd6b9b'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, me) => {
|
||||
// Fetch the group
|
||||
const userGroup = await UserGroups.findOne({
|
||||
id: ps.groupId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
if (userGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
|
||||
return await UserGroups.pack(userGroup);
|
||||
});
|
@ -80,5 +80,5 @@ export default define(meta, async (ps, me) => {
|
||||
}
|
||||
|
||||
// Push the user
|
||||
pushUserToUserList(user, userList);
|
||||
await pushUserToUserList(user, userList);
|
||||
});
|
||||
|
@ -23,4 +23,6 @@ export const kinds = [
|
||||
'write:pages',
|
||||
'write:page-likes',
|
||||
'read:page-likes',
|
||||
'read:user-groups',
|
||||
'write:user-groups',
|
||||
];
|
||||
|
@ -13,6 +13,7 @@ import { packedBlockingSchema } from '../../../models/repositories/blocking';
|
||||
import { packedNoteReactionSchema } from '../../../models/repositories/note-reaction';
|
||||
import { packedHashtagSchema } from '../../../models/repositories/hashtag';
|
||||
import { packedPageSchema } from '../../../models/repositories/page';
|
||||
import { packedUserGroupSchema } from '../../../models/repositories/user-group';
|
||||
|
||||
export function convertSchemaToOpenApiSchema(schema: Schema) {
|
||||
const res: any = schema;
|
||||
@ -66,6 +67,7 @@ export const schemas = {
|
||||
|
||||
User: convertSchemaToOpenApiSchema(packedUserSchema),
|
||||
UserList: convertSchemaToOpenApiSchema(packedUserListSchema),
|
||||
UserGroup: convertSchemaToOpenApiSchema(packedUserGroupSchema),
|
||||
App: convertSchemaToOpenApiSchema(packedAppSchema),
|
||||
MessagingMessage: convertSchemaToOpenApiSchema(packedMessagingMessageSchema),
|
||||
Note: convertSchemaToOpenApiSchema(packedNoteSchema),
|
||||
|
@ -1,20 +1,39 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import read from '../../common/read-messaging-message';
|
||||
import { readUserMessagingMessage, readGroupMessagingMessage } from '../../common/read-messaging-message';
|
||||
import Channel from '../channel';
|
||||
import { UserGroupJoinings } from '../../../../models';
|
||||
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'messaging';
|
||||
public static shouldShare = false;
|
||||
public static requireCredential = true;
|
||||
|
||||
private otherpartyId: string;
|
||||
private otherpartyId: string | null;
|
||||
private groupId: string | null;
|
||||
|
||||
@autobind
|
||||
public async init(params: any) {
|
||||
this.otherpartyId = params.otherparty as string;
|
||||
this.groupId = params.group as string;
|
||||
|
||||
// Check joining
|
||||
if (this.groupId) {
|
||||
const joining = await UserGroupJoinings.findOne({
|
||||
userId: this.user!.id,
|
||||
userGroupId: this.groupId
|
||||
});
|
||||
|
||||
if (joining == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const subCh = this.otherpartyId
|
||||
? `messagingStream:${this.user!.id}-${this.otherpartyId}`
|
||||
: `messagingStream:${this.groupId}`;
|
||||
|
||||
// Subscribe messaging stream
|
||||
this.subscriber.on(`messagingStream:${this.user!.id}-${this.otherpartyId}`, data => {
|
||||
this.subscriber.on(subCh, data => {
|
||||
this.send(data);
|
||||
});
|
||||
}
|
||||
@ -23,7 +42,11 @@ export default class extends Channel {
|
||||
public onMessage(type: string, body: any) {
|
||||
switch (type) {
|
||||
case 'read':
|
||||
read(this.user!.id, this.otherpartyId, [body.id]);
|
||||
if (this.otherpartyId) {
|
||||
readUserMessagingMessage(this.user!.id, this.otherpartyId, [body.id]);
|
||||
} else if (this.groupId) {
|
||||
readGroupMessagingMessage(this.user!.id, this.groupId, [body.id]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user