ユーザーグループ

Resolve #3218
This commit is contained in:
syuilo
2019-05-18 20:36:33 +09:00
parent 61f54f8f74
commit c7cc3dcdfd
65 changed files with 1797 additions and 638 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

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

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

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

View File

@ -80,5 +80,5 @@ export default define(meta, async (ps, me) => {
}
// Push the user
pushUserToUserList(user, userList);
await pushUserToUserList(user, userList);
});

View File

@ -23,4 +23,6 @@ export const kinds = [
'write:pages',
'write:page-likes',
'read:page-likes',
'read:user-groups',
'write:user-groups',
];

View File

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

View File

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