wip
This commit is contained in:
@@ -54,14 +54,14 @@ export class ChatService {
|
||||
public async createMessage(params: {
|
||||
fromUser: { id: MiUser['id']; host: MiUser['host']; };
|
||||
toUser?: MiUser | null;
|
||||
//toGroup?: MiUserGroup | null;
|
||||
//toRoom?: MiUserRoom | null;
|
||||
text?: string | null;
|
||||
file?: MiDriveFile | null;
|
||||
uri?: string | null;
|
||||
}) {
|
||||
const { fromUser, toUser /*toGroup*/ } = params;
|
||||
const { fromUser, toUser /*toRoom*/ } = params;
|
||||
|
||||
if (toUser == null /*&& toGroup == null*/) {
|
||||
if (toUser == null /*&& toRoom == null*/) {
|
||||
throw new Error('recipient is required');
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ export class ChatService {
|
||||
id: this.idService.gen(),
|
||||
fromUserId: fromUser.id,
|
||||
toUserId: toUser ? toUser.id : null,
|
||||
//toGroupId: recipientGroup ? recipientGroup.id : null,
|
||||
//toRoomId: recipientRoom ? recipientRoom.id : null,
|
||||
text: params.text ? params.text.trim() : null,
|
||||
fileId: params.file ? params.file.id : null,
|
||||
reads: [],
|
||||
@@ -102,12 +102,12 @@ export class ChatService {
|
||||
// 相手のストリーム
|
||||
this.globalEventService.publishChatStream(toUser.id, fromUser.id, 'message', packedMessage);
|
||||
}
|
||||
}/* else if (toGroup) {
|
||||
}/* else if (toRoom) {
|
||||
// グループのストリーム
|
||||
this.globalEventService.publishGroupChatStream(toGroup.id, 'message', messageObj);
|
||||
this.globalEventService.publishRoomChatStream(toRoom.id, 'message', messageObj);
|
||||
|
||||
// メンバーのストリーム
|
||||
const joinings = await this.userGroupJoiningsRepository.findBy({ userGroupId: toGroup.id });
|
||||
const joinings = await this.userRoomJoiningsRepository.findBy({ userRoomId: toRoom.id });
|
||||
for (const joining of joinings) {
|
||||
this.globalEventService.publishChatIndexStream(joining.userId, 'message', messageObj);
|
||||
this.globalEventService.publishMainStream(joining.userId, 'chatMessage', messageObj);
|
||||
@@ -124,8 +124,8 @@ export class ChatService {
|
||||
const packedMessageForTo = await this.chatMessageEntityService.pack(inserted, toUser);
|
||||
this.globalEventService.publishMainStream(toUser.id, 'newChatMessage', packedMessageForTo);
|
||||
this.pushNotificationService.pushNotification(toUser.id, 'newChatMessage', packedMessageForTo);
|
||||
}/* else if (toGroup) {
|
||||
const joinings = await this.userGroupJoiningsRepository.findBy({ userGroupId: toGroup.id, userId: Not(fromUser.id) });
|
||||
}/* else if (toRoom) {
|
||||
const joinings = await this.userRoomJoiningsRepository.findBy({ userRoomId: toRoom.id, userId: Not(fromUser.id) });
|
||||
for (const joining of joinings) {
|
||||
if (freshMessage.reads.includes(joining.userId)) return; // 既読
|
||||
this.globalEventService.publishMainStream(joining.userId, 'newChatMessage', messageObj);
|
||||
@@ -186,28 +186,28 @@ export class ChatService {
|
||||
const activity = this.apRendererService.addContext(this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${message.id}`), fromUser));
|
||||
this.queueService.deliver(fromUser, activity, toUser.inbox);
|
||||
}
|
||||
}/* else if (message.groupId) {
|
||||
this.globalEventService.publishGroupChatStream(message.groupId, 'deleted', message.id);
|
||||
}/* else if (message.roomId) {
|
||||
this.globalEventService.publishRoomChatStream(message.roomId, 'deleted', message.id);
|
||||
}*/
|
||||
}
|
||||
|
||||
/*
|
||||
@bindThis
|
||||
public async readGroupChatMessage(
|
||||
public async readRoomChatMessage(
|
||||
userId: MiUser['id'],
|
||||
groupId: MiUserGroup['id'],
|
||||
roomId: MiUserRoom['id'],
|
||||
messageIds: MiChatMessage['id'][],
|
||||
) {
|
||||
if (messageIds.length === 0) return;
|
||||
|
||||
// check joined
|
||||
const joining = await this.userGroupJoiningsRepository.findOneBy({
|
||||
const joining = await this.userRoomJoiningsRepository.findOneBy({
|
||||
userId: userId,
|
||||
userGroupId: groupId,
|
||||
userRoomId: roomId,
|
||||
});
|
||||
|
||||
if (joining == null) {
|
||||
throw new IdentifiableError('930a270c-714a-46b2-b776-ad27276dc569', 'Access denied (group).');
|
||||
throw new IdentifiableError('930a270c-714a-46b2-b776-ad27276dc569', 'Access denied (room).');
|
||||
}
|
||||
|
||||
const messages = await this.chatMessagesRepository.findBy({
|
||||
@@ -232,7 +232,7 @@ export class ChatService {
|
||||
}
|
||||
|
||||
// Publish event
|
||||
this.globalEventService.publishGroupChatStream(groupId, 'read', {
|
||||
this.globalEventService.publishRoomChatStream(roomId, 'read', {
|
||||
ids: reads,
|
||||
userId: userId,
|
||||
});
|
||||
@@ -245,14 +245,14 @@ export class ChatService {
|
||||
} else {
|
||||
// そのグループにおいて未読がなければイベント発行
|
||||
const unreadExist = await this.chatMessagesRepository.createQueryBuilder('message')
|
||||
.where('message.groupId = :groupId', { groupId: groupId })
|
||||
.where('message.roomId = :roomId', { roomId: roomId })
|
||||
.andWhere('message.userId != :userId', { userId: userId })
|
||||
.andWhere('NOT (:userId = ANY(message.reads))', { userId: userId })
|
||||
.andWhere('message.createdAt > :joinedAt', { joinedAt: joining.createdAt }) // 自分が加入する前の会話については、未読扱いしない
|
||||
.getOne().then(x => x != null);
|
||||
|
||||
if (!unreadExist) {
|
||||
this.pushNotificationService.pushNotification(userId, 'readAllChatMessagesOfARoom', { groupId });
|
||||
this.pushNotificationService.pushNotification(userId, 'readAllChatMessagesOfARoom', { roomId });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -300,7 +300,7 @@ export class ChatService {
|
||||
.where('message.fromUserId = :meId', { meId: meId })
|
||||
.orWhere('message.toUserId = :meId', { meId: meId });
|
||||
}))
|
||||
.andWhere('message.groupId IS NULL')
|
||||
.andWhere('message.roomId IS NULL')
|
||||
.andWhere(`message.fromUserId NOT IN (${ mutingQuery.getQuery() })`)
|
||||
.andWhere(`message.toUserId NOT IN (${ mutingQuery.getQuery() })`);
|
||||
|
||||
@@ -324,27 +324,27 @@ export class ChatService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async groupHistory(meId: MiUser['id'], limit: number) {
|
||||
public async roomHistory(meId: MiUser['id'], limit: number) {
|
||||
/*
|
||||
const groups = await this.userGroupJoiningsRepository.findBy({
|
||||
const rooms = await this.userRoomJoiningsRepository.findBy({
|
||||
userId: meId,
|
||||
}).then(xs => xs.map(x => x.userGroupId));
|
||||
}).then(xs => xs.map(x => x.userRoomId));
|
||||
|
||||
if (groups.length === 0) {
|
||||
if (rooms.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const history: MiChatMessage[] = [];
|
||||
|
||||
for (let i = 0; i < limit; i++) {
|
||||
const found = history.map(m => m.groupId!);
|
||||
const found = history.map(m => m.roomId!);
|
||||
|
||||
const query = this.chatMessagesRepository.createQueryBuilder('message')
|
||||
.orderBy('message.id', 'DESC')
|
||||
.where('message.groupId IN (:...groups)', { groups: groups });
|
||||
.where('message.roomId IN (:...rooms)', { rooms: rooms });
|
||||
|
||||
if (found.length > 0) {
|
||||
query.andWhere('message.groupId NOT IN (:...found)', { found: found });
|
||||
query.andWhere('message.roomId NOT IN (:...found)', { found: found });
|
||||
}
|
||||
|
||||
const message = await query.getOne();
|
||||
|
@@ -7,6 +7,7 @@ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typ
|
||||
import { id } from './util/id.js';
|
||||
import { MiUser } from './User.js';
|
||||
import { MiDriveFile } from './DriveFile.js';
|
||||
import { MiChatRoom } from './ChatRoom.js';
|
||||
|
||||
@Entity('chat_message')
|
||||
export class MiChatMessage {
|
||||
@@ -37,19 +38,17 @@ export class MiChatMessage {
|
||||
@JoinColumn()
|
||||
public toUser: MiUser | null;
|
||||
|
||||
/*
|
||||
@Index()
|
||||
@Column({
|
||||
...id(), nullable: true,
|
||||
})
|
||||
public toGroupId: MiUserGroup['id'] | null;
|
||||
public toRoomId: MiChatRoom['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiUserGroup, {
|
||||
@ManyToOne(type => MiChatRoom, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public toGroup: MiUserGroup | null;
|
||||
*/
|
||||
public toRoom: MiChatRoom | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 4096, nullable: true,
|
||||
|
31
packages/backend/src/models/ChatRoom.ts
Normal file
31
packages/backend/src/models/ChatRoom.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { id } from './util/id.js';
|
||||
import { MiUser } from './User.js';
|
||||
|
||||
@Entity('chat_room')
|
||||
export class MiChatRoom {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256,
|
||||
})
|
||||
public name: string;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
})
|
||||
public ownerId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public owner: MiUser | null;
|
||||
}
|
39
packages/backend/src/models/ChatRoomMembership.ts
Normal file
39
packages/backend/src/models/ChatRoomMembership.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { id } from './util/id.js';
|
||||
import { MiUser } from './User.js';
|
||||
import { MiChatRoom } from './ChatRoom.js';
|
||||
|
||||
@Entity('chat_room_membership')
|
||||
export class MiChatRoomMembership {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: MiUser | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
})
|
||||
public roomId: MiChatRoom['id'];
|
||||
|
||||
@ManyToOne(type => MiChatRoom, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public room: MiChatRoom | null;
|
||||
}
|
@@ -76,6 +76,8 @@ import { MiFlash } from '@/models/Flash.js';
|
||||
import { MiFlashLike } from '@/models/FlashLike.js';
|
||||
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
|
||||
import { MiChatMessage } from '@/models/ChatMessage.js';
|
||||
import { MiChatRoom } from '@/models/ChatRoom.js';
|
||||
import { MiChatRoomMembership } from '@/models/ChatRoomMembership.js';
|
||||
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
|
||||
import { MiReversiGame } from '@/models/ReversiGame.js';
|
||||
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
|
||||
@@ -193,6 +195,8 @@ export {
|
||||
MiFlashLike,
|
||||
MiUserMemo,
|
||||
MiChatMessage,
|
||||
MiChatRoom,
|
||||
MiChatRoomMembership,
|
||||
MiBubbleGameRecord,
|
||||
MiReversiGame,
|
||||
};
|
||||
@@ -266,5 +270,7 @@ export type FlashsRepository = Repository<MiFlash> & MiRepository<MiFlash>;
|
||||
export type FlashLikesRepository = Repository<MiFlashLike> & MiRepository<MiFlashLike>;
|
||||
export type UserMemoRepository = Repository<MiUserMemo> & MiRepository<MiUserMemo>;
|
||||
export type ChatMessagesRepository = Repository<MiChatMessage> & MiRepository<MiChatMessage>;
|
||||
export type ChatRoomsRepository = Repository<MiChatRoom> & MiRepository<MiChatRoom>;
|
||||
export type ChatRoomMembershipsRepository = Repository<MiChatRoomMembership> & MiRepository<MiChatRoomMembership>;
|
||||
export type BubbleGameRecordsRepository = Repository<MiBubbleGameRecord> & MiRepository<MiBubbleGameRecord>;
|
||||
export type ReversiGamesRepository = Repository<MiReversiGame> & MiRepository<MiReversiGame>;
|
||||
|
@@ -77,6 +77,8 @@ import { MiFlash } from '@/models/Flash.js';
|
||||
import { MiFlashLike } from '@/models/FlashLike.js';
|
||||
import { MiUserMemo } from '@/models/UserMemo.js';
|
||||
import { MiChatMessage } from '@/models/ChatMessage.js';
|
||||
import { MiChatRoom } from '@/models/ChatRoom.js';
|
||||
import { MiChatRoomMembership } from '@/models/ChatRoomMembership.js';
|
||||
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
|
||||
import { MiReversiGame } from '@/models/ReversiGame.js';
|
||||
|
||||
@@ -238,6 +240,8 @@ export const entities = [
|
||||
MiFlashLike,
|
||||
MiUserMemo,
|
||||
MiChatMessage,
|
||||
MiChatRoom,
|
||||
MiChatRoomMembership,
|
||||
MiBubbleGameRecord,
|
||||
MiReversiGame,
|
||||
...charts,
|
||||
|
@@ -45,15 +45,15 @@ export const meta = {
|
||||
id: '11795c64-40ea-4198-b06e-3c873ed9039d',
|
||||
},
|
||||
|
||||
noSuchGroup: {
|
||||
message: 'No such group.',
|
||||
code: 'NO_SUCH_GROUP',
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
code: 'NO_SUCH_ROOM',
|
||||
id: 'c94e2a5d-06aa-4914-8fa6-6a42e73d6537',
|
||||
},
|
||||
|
||||
groupAccessDenied: {
|
||||
message: 'You can not send messages to groups that you have not joined.',
|
||||
code: 'GROUP_ACCESS_DENIED',
|
||||
roomAccessDenied: {
|
||||
message: 'You can not send messages to rooms that you have not joined.',
|
||||
code: 'ROOM_ACCESS_DENIED',
|
||||
id: 'd96b3cca-5ad1-438b-ad8b-02f931308fbd',
|
||||
},
|
||||
|
||||
@@ -130,22 +130,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
text: ps.text,
|
||||
file: file,
|
||||
});
|
||||
}/* else if (ps.groupId != null) {
|
||||
// Fetch recipient (group)
|
||||
recipientGroup = await this.userGroupsRepository.findOneBy({ id: ps.groupId! });
|
||||
}/* else if (ps.roomId != null) {
|
||||
// Fetch recipient (room)
|
||||
recipientRoom = await this.userRoomsRepository.findOneBy({ id: ps.roomId! });
|
||||
|
||||
if (recipientGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
if (recipientRoom == null) {
|
||||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
|
||||
// check joined
|
||||
const joining = await this.userGroupJoiningsRepository.findOneBy({
|
||||
const joining = await this.userRoomJoiningsRepository.findOneBy({
|
||||
userId: me.id,
|
||||
userGroupId: recipientGroup.id,
|
||||
userRoomId: recipientRoom.id,
|
||||
});
|
||||
|
||||
if (joining == null) {
|
||||
throw new ApiError(meta.errors.groupAccessDenied);
|
||||
throw new ApiError(meta.errors.roomAccessDenied);
|
||||
}
|
||||
}*/
|
||||
});
|
||||
|
@@ -35,7 +35,7 @@ export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
group: { type: 'boolean', default: false },
|
||||
room: { type: 'boolean', default: false },
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -46,7 +46,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const history = ps.group ? await this.chatService.groupHistory(me.id, ps.limit) : await this.chatService.userHistory(me.id, ps.limit);
|
||||
const history = ps.room ? await this.chatService.roomHistory(me.id, ps.limit) : await this.chatService.userHistory(me.id, ps.limit);
|
||||
|
||||
return await this.chatMessageEntityService.packMany(history, me);
|
||||
});
|
||||
|
@@ -35,15 +35,15 @@ export const meta = {
|
||||
id: '11795c64-40ea-4198-b06e-3c873ed9039d',
|
||||
},
|
||||
|
||||
noSuchGroup: {
|
||||
message: 'No such group.',
|
||||
code: 'NO_SUCH_GROUP',
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
code: 'NO_SUCH_ROOM',
|
||||
id: 'c4d9f88c-9270-4632-b032-6ed8cee36f7f',
|
||||
},
|
||||
|
||||
groupAccessDenied: {
|
||||
message: 'You can not read messages of groups that you have not joined.',
|
||||
code: 'GROUP_ACCESS_DENIED',
|
||||
roomAccessDenied: {
|
||||
message: 'You can not read messages of rooms that you have not joined.',
|
||||
code: 'ROOM_ACCESS_DENIED',
|
||||
id: 'a053a8dd-a491-4718-8f87-50775aad9284',
|
||||
},
|
||||
},
|
||||
@@ -76,36 +76,36 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
const messages = await this.chatService.userTimeline(me.id, other.id, ps.sinceId, ps.untilId, ps.limit);
|
||||
|
||||
return await this.chatMessageEntityService.packLiteMany(messages);
|
||||
}/* else if (ps.groupId != null) {
|
||||
// Fetch recipient (group)
|
||||
const recipientGroup = await this.userGroupRepository.findOneBy({ id: ps.groupId });
|
||||
}/* else if (ps.roomId != null) {
|
||||
// Fetch recipient (room)
|
||||
const recipientRoom = await this.userRoomRepository.findOneBy({ id: ps.roomId });
|
||||
|
||||
if (recipientGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
if (recipientRoom == null) {
|
||||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
|
||||
// check joined
|
||||
const joining = await this.userGroupJoiningsRepository.findOneBy({
|
||||
const joining = await this.userRoomJoiningsRepository.findOneBy({
|
||||
userId: me.id,
|
||||
userGroupId: recipientGroup.id,
|
||||
userRoomId: recipientRoom.id,
|
||||
});
|
||||
|
||||
if (joining == null) {
|
||||
throw new ApiError(meta.errors.groupAccessDenied);
|
||||
throw new ApiError(meta.errors.roomAccessDenied);
|
||||
}
|
||||
|
||||
const query = this.queryService.makePaginationQuery(this.messagingMessagesRepository.createQueryBuilder('message'), ps.sinceId, ps.untilId)
|
||||
.andWhere('message.groupId = :groupId', { groupId: recipientGroup.id });
|
||||
.andWhere('message.roomId = :roomId', { roomId: recipientRoom.id });
|
||||
|
||||
const messages = await query.take(ps.limit).getMany();
|
||||
|
||||
// Mark all as read
|
||||
if (ps.markAsRead) {
|
||||
this.messagingService.readGroupMessagingMessage(me.id, recipientGroup.id, messages.map(x => x.id));
|
||||
this.messagingService.readRoomMessagingMessage(me.id, recipientRoom.id, messages.map(x => x.id));
|
||||
}
|
||||
|
||||
return await Promise.all(messages.map(message => this.messagingMessageEntityService.pack(message, me, {
|
||||
populateGroup: false,
|
||||
populateRoom: false,
|
||||
})));
|
||||
}*/
|
||||
});
|
||||
|
Reference in New Issue
Block a user