This commit is contained in:
syuilo
2025-03-23 16:51:35 +09:00
parent 9e23531adc
commit b98d3cbc29
25 changed files with 1836 additions and 5 deletions

View File

@@ -16,13 +16,15 @@ import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { PushNotificationService } from '@/core/PushNotificationService.js';
import { bindThis } from '@/decorators.js';
import type { ChatApprovalsRepository, ChatMessagesRepository, ChatRoomInvitationsRepository, ChatRoomMembershipsRepository, ChatRoomsRepository, MiChatMessage, MiChatRoom, MiDriveFile, MiUser, MutingsRepository, UsersRepository } from '@/models/_.js';
import type { ChatApprovalsRepository, ChatMessagesRepository, ChatRoomInvitationsRepository, ChatRoomMembershipsRepository, ChatRoomsRepository, MiChatMessage, MiChatRoom, MiChatRoomMembership, MiDriveFile, MiUser, MutingsRepository, UsersRepository } from '@/models/_.js';
import { UserBlockingService } from '@/core/UserBlockingService.js';
import { QueryService } from '@/core/QueryService.js';
import { RoleService } from '@/core/RoleService.js';
import { UserFollowingService } from '@/core/UserFollowingService.js';
import { MiChatRoomInvitation } from '@/models/ChatRoomInvitation.js';
const MAX_ROOM_MEMBERS = 30;
@Injectable()
export class ChatService {
constructor(
@@ -463,6 +465,22 @@ export class ChatService {
await this.chatRoomsRepository.delete(room.id);
}
@bindThis
public async findMyRoomById(ownerId: MiUser['id'], roomId: MiChatRoom['id']) {
return this.chatRoomsRepository.findOneBy({ id: roomId, ownerId: ownerId });
}
@bindThis
public async findRoomById(roomId: MiChatRoom['id']) {
return this.chatRoomsRepository.findOneBy({ id: roomId });
}
@bindThis
public async isRoomMember(roomId: MiChatRoom['id'], userId: MiUser['id']) {
const membership = await this.chatRoomMembershipsRepository.findOneBy({ roomId, userId });
return membership != null;
}
@bindThis
public async createRoomInvitation(inviterId: MiUser['id'], roomId: MiChatRoom['id'], inviteeId: MiUser['id']) {
if (inviterId === inviteeId) {
@@ -471,6 +489,16 @@ export class ChatService {
const room = await this.chatRoomsRepository.findOneByOrFail({ id: roomId, ownerId: inviterId });
const existingInvitation = await this.chatRoomInvitationsRepository.findOneBy({ roomId, userId: inviteeId });
if (existingInvitation) {
throw new Error('already invited');
}
const membershipsCount = await this.chatRoomMembershipsRepository.countBy({ roomId });
if (membershipsCount >= MAX_ROOM_MEMBERS) {
throw new Error('room is full');
}
// TODO: cehck block
const invitation = {
@@ -483,4 +511,80 @@ export class ChatService {
return created;
}
@bindThis
public async getOwnedRoomsWithPagination(ownerId: MiUser['id'], limit: number, sinceId?: MiChatRoom['id'] | null, untilId?: MiChatRoom['id'] | null) {
const query = this.queryService.makePaginationQuery(this.chatRoomsRepository.createQueryBuilder('room'), sinceId, untilId)
.where('room.ownerId = :ownerId', { ownerId });
const rooms = await query.take(limit).getMany();
return rooms;
}
@bindThis
public async getReceivedRoomInvitationsWithPagination(userId: MiUser['id'], limit: number, sinceId?: MiChatRoomInvitation['id'] | null, untilId?: MiChatRoomInvitation['id'] | null) {
const query = this.queryService.makePaginationQuery(this.chatRoomInvitationsRepository.createQueryBuilder('invitation'), sinceId, untilId)
.where('invitation.userId = :userId', { userId });
const invitations = await query.take(limit).getMany();
return invitations;
}
@bindThis
public async joinToRoom(userId: MiUser['id'], roomId: MiChatRoom['id']) {
const invitation = await this.chatRoomInvitationsRepository.findOneByOrFail({ roomId, userId });
const membershipsCount = await this.chatRoomMembershipsRepository.countBy({ roomId });
if (membershipsCount >= MAX_ROOM_MEMBERS) {
throw new Error('room is full');
}
const membership = {
id: this.idService.gen(),
roomId: roomId,
userId: userId,
} satisfies Partial<MiChatRoomMembership>;
// TODO: transaction
await this.chatRoomMembershipsRepository.insertOne(membership);
await this.chatRoomInvitationsRepository.delete(invitation.id);
}
@bindThis
public async rejectRoomInvitation(userId: MiUser['id'], roomId: MiChatRoom['id']) {
const invitation = await this.chatRoomInvitationsRepository.findOneByOrFail({ roomId, userId });
await this.chatRoomInvitationsRepository.delete(invitation.id);
}
@bindThis
public async leaveRoom(userId: MiUser['id'], roomId: MiChatRoom['id']) {
const membership = await this.chatRoomMembershipsRepository.findOneByOrFail({ roomId, userId });
await this.chatRoomMembershipsRepository.delete(membership.id);
}
@bindThis
public async updateRoom(room: MiChatRoom, params: {
name?: string;
}): Promise<MiChatRoom> {
return this.chatRoomsRepository.createQueryBuilder().update()
.set(params)
.where('id = :id', { id: room.id })
.returning('*')
.execute()
.then((response) => {
return response.raw[0];
});
}
@bindThis
public async getRoomMembershipsWithPagination(roomId: MiChatRoom['id'], limit: number, sinceId?: MiChatRoomMembership['id'] | null, untilId?: MiChatRoomMembership['id'] | null) {
const query = this.queryService.makePaginationQuery(this.chatRoomMembershipsRepository.createQueryBuilder('membership'), sinceId, untilId)
.where('membership.roomId = :roomId', { roomId });
const memberships = await query.take(limit).getMany();
return memberships;
}
}

View File

@@ -5,7 +5,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { MiUser, ChatMessagesRepository, MiChatMessage, ChatRoomsRepository, MiChatRoom } from '@/models/_.js';
import type { MiUser, ChatMessagesRepository, MiChatMessage, ChatRoomsRepository, MiChatRoom, MiChatRoomInvitation, ChatRoomInvitationsRepository, MiChatRoomMembership, ChatRoomMembershipsRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/json-schema.js';
import type { } from '@/models/Blocking.js';
@@ -23,6 +23,12 @@ export class ChatEntityService {
@Inject(DI.chatRoomsRepository)
private chatRoomsRepository: ChatRoomsRepository,
@Inject(DI.chatRoomInvitationsRepository)
private chatRoomInvitationsRepository: ChatRoomInvitationsRepository,
@Inject(DI.chatRoomMembershipsRepository)
private chatRoomMembershipsRepository: ChatRoomMembershipsRepository,
private userEntityService: UserEntityService,
private driveFileEntityService: DriveFileEntityService,
private idService: IdService,
@@ -206,4 +212,72 @@ export class ChatEntityService {
return Promise.all(rooms.map(room => this.packRoom(room, me, { _hint_: { packedOwners } })));
}
@bindThis
public async packRoomInvitation(
src: MiChatRoomInvitation['id'] | MiChatRoomInvitation,
me: { id: MiUser['id'] },
options?: {
_hint_?: {
packedRooms: Map<MiChatRoomInvitation['roomId'], Packed<'ChatRoom'>>;
packedUsers: Map<MiChatRoomInvitation['id'], Packed<'UserLite'>>;
};
},
): Promise<Packed<'ChatRoomInvitation'>> {
const invitation = typeof src === 'object' ? src : await this.chatRoomInvitationsRepository.findOneByOrFail({ id: src });
return {
id: invitation.id,
createdAt: this.idService.parse(invitation.id).date.toISOString(),
roomId: invitation.roomId,
room: options?._hint_?.packedRooms.get(invitation.roomId) ?? await this.packRoom(invitation.room ?? invitation.roomId, me),
userId: invitation.userId,
user: options?._hint_?.packedUsers.get(invitation.userId) ?? await this.userEntityService.pack(invitation.user ?? invitation.userId, me),
};
}
@bindThis
public async packRoomInvitations(
invitations: MiChatRoomInvitation[],
me: { id: MiUser['id'] },
) {
if (invitations.length === 0) return [];
return Promise.all(invitations.map(invitation => this.packRoomInvitation(invitation, me)));
}
@bindThis
public async packRoomMembership(
src: MiChatRoomMembership['id'] | MiChatRoomMembership,
me: { id: MiUser['id'] },
options?: {
_hint_?: {
packedUsers: Map<MiChatRoomMembership['id'], Packed<'UserLite'>>;
};
},
): Promise<Packed<'ChatRoomMembership'>> {
const membership = typeof src === 'object' ? src : await this.chatRoomMembershipsRepository.findOneByOrFail({ id: src });
return {
id: membership.id,
createdAt: this.idService.parse(membership.id).date.toISOString(),
userId: membership.userId,
user: options?._hint_?.packedUsers.get(membership.userId) ?? await this.userEntityService.pack(membership.user ?? membership.userId, me),
};
}
@bindThis
public async packRoomMemberships(
memberships: MiChatRoomMembership[],
me: { id: MiUser['id'] },
) {
if (memberships.length === 0) return [];
const users = memberships.map(x => x.user ?? x.userId);
const packedUsers = await this.userEntityService.packMany(users, me)
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(memberships.map(membership => this.packRoomMembership(membership, me, { _hint_: { packedUsers } })));
}
}

View File

@@ -65,6 +65,8 @@ import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.j
import { packedAbuseReportNotificationRecipientSchema } from '@/models/json-schema/abuse-report-notification-recipient.js';
import { packedChatMessageSchema, packedChatMessageLiteSchema } from '@/models/json-schema/chat-message.js';
import { packedChatRoomSchema } from '@/models/json-schema/chat-room.js';
import { packedChatRoomInvitationSchema } from '@/models/json-schema/chat-room-invitation.js';
import { packedChatRoomMembershipSchema } from '@/models/json-schema/chat-room-membership.js';
export const refs = {
UserLite: packedUserLiteSchema,
@@ -125,6 +127,8 @@ export const refs = {
ChatMessage: packedChatMessageSchema,
ChatMessageLite: packedChatMessageLiteSchema,
ChatRoom: packedChatRoomSchema,
ChatRoomInvitation: packedChatRoomInvitationSchema,
ChatRoomMembership: packedChatRoomMembershipSchema,
};
export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;

View File

@@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export const packedChatRoomInvitationSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
},
createdAt: {
type: 'string',
format: 'date-time',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: false,
},
user: {
type: 'object',
optional: false, nullable: false,
ref: 'UserLite',
},
roomId: {
type: 'string',
optional: false, nullable: false,
},
room: {
type: 'object',
optional: false, nullable: false,
ref: 'ChatRoom',
},
},
} as const;

View File

@@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export const packedChatRoomMembershipSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
},
createdAt: {
type: 'string',
format: 'date-time',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: false,
},
user: {
type: 'object',
optional: false, nullable: false,
ref: 'UserLite',
},
},
} as const;

View File

@@ -400,5 +400,16 @@ export * as 'chat/messages/create' from './endpoints/chat/messages/create.js';
export * as 'chat/messages/delete' from './endpoints/chat/messages/delete.js';
export * as 'chat/messages/show' from './endpoints/chat/messages/show.js';
export * as 'chat/messages/timeline' from './endpoints/chat/messages/timeline.js';
export * as 'chat/rooms/create' from './endpoints/chat/rooms/create.js';
export * as 'chat/rooms/delete' from './endpoints/chat/rooms/delete.js';
export * as 'chat/rooms/join' from './endpoints/chat/rooms/join.js';
export * as 'chat/rooms/leave' from './endpoints/chat/rooms/leave.js';
export * as 'chat/rooms/show' from './endpoints/chat/rooms/show.js';
export * as 'chat/rooms/owned' from './endpoints/chat/rooms/owned.js';
export * as 'chat/rooms/update' from './endpoints/chat/rooms/update.js';
export * as 'chat/rooms/members' from './endpoints/chat/rooms/members.js';
export * as 'chat/rooms/invitations/create' from './endpoints/chat/rooms/invitations/create.js';
export * as 'chat/rooms/invitations/reject' from './endpoints/chat/rooms/invitations/reject.js';
export * as 'chat/rooms/invitations/inbox' from './endpoints/chat/rooms/invitations/inbox.js';
export * as 'chat/history' from './endpoints/chat/history.js';
export * as 'v2/admin/emoji/list' from './endpoints/v2/admin/emoji/list.js';

View File

@@ -6,9 +6,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { GetterService } from '@/server/api/GetterService.js';
import { ChatService } from '@/core/ChatService.js';
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
import { ApiError } from '@/server/api/error.js';
export const meta = {

View File

@@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ChatService } from '@/core/ChatService.js';
import { ApiError } from '@/server/api/error.js';
export const meta = {
tags: ['chat'],
requireCredential: true,
kind: 'write:chat',
res: {
},
errors: {
noSuchRoom: {
message: 'No such room.',
code: 'NO_SUCH_ROOM',
id: 'd4e3753d-97bf-4a19-ab8e-21080fbc0f4b',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
roomId: { type: 'string', format: 'misskey:id' },
},
required: ['roomId'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private chatService: ChatService,
) {
super(meta, paramDef, async (ps, me) => {
const room = await this.chatService.findMyRoomById(me.id, ps.roomId);
if (room == null) {
throw new ApiError(meta.errors.noSuchRoom);
}
await this.chatService.deleteRoom(room);
});
}
}

View File

@@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '@/server/api/error.js';
import { ChatService } from '@/core/ChatService.js';
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
export const meta = {
tags: ['chat'],
requireCredential: true,
requiredRolePolicy: 'canChat',
prohibitMoved: true,
kind: 'write:chat',
limit: {
duration: ms('1day'),
max: 50,
},
res: {
type: 'object',
optional: false, nullable: false,
ref: 'ChatRoomInvitation',
},
errors: {
noSuchRoom: {
message: 'No such room.',
code: 'NO_SUCH_ROOM',
id: '916f9507-49ba-4e90-b57f-1fd4deaa47a5',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
roomId: { type: 'string', format: 'misskey:id' },
userId: { type: 'string', format: 'misskey:id' },
},
required: ['roomId', 'userId'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private chatService: ChatService,
private chatEntityService: ChatEntityService,
) {
super(meta, paramDef, async (ps, me) => {
const room = await this.chatService.findMyRoomById(me.id, ps.roomId);
if (room == null) {
throw new ApiError(meta.errors.noSuchRoom);
}
const invitation = await this.chatService.createRoomInvitation(ps.userId, room.id, ps.userId);
return await this.chatEntityService.packRoomInvitation(invitation, me);
});
}
}

View File

@@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ChatService } from '@/core/ChatService.js';
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
import { ApiError } from '@/server/api/error.js';
export const meta = {
tags: ['chat'],
requireCredential: true,
kind: 'read:chat',
res: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'ChatRoomInvitation',
},
},
errors: {
},
} as const;
export const paramDef = {
type: 'object',
properties: {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
},
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private chatEntityService: ChatEntityService,
private chatService: ChatService,
) {
super(meta, paramDef, async (ps, me) => {
const invitations = await this.chatService.getReceivedRoomInvitationsWithPagination(me.id, ps.limit, ps.sinceId, ps.untilId);
return this.chatEntityService.packRoomInvitations(invitations, me);
});
}
}

View File

@@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ChatService } from '@/core/ChatService.js';
import { ApiError } from '@/server/api/error.js';
export const meta = {
tags: ['chat'],
requireCredential: true,
kind: 'write:chat',
res: {
},
errors: {
noSuchRoom: {
message: 'No such room.',
code: 'NO_SUCH_ROOM',
id: '5130557e-5a11-4cfb-9cc5-fe60cda5de0d',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
roomId: { type: 'string', format: 'misskey:id' },
},
required: ['roomId'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private chatService: ChatService,
) {
super(meta, paramDef, async (ps, me) => {
await this.chatService.rejectRoomInvitation(me.id, ps.roomId);
});
}
}

View File

@@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ChatService } from '@/core/ChatService.js';
import { ApiError } from '@/server/api/error.js';
export const meta = {
tags: ['chat'],
requireCredential: true,
kind: 'write:chat',
res: {
},
errors: {
noSuchRoom: {
message: 'No such room.',
code: 'NO_SUCH_ROOM',
id: '84416476-5ce8-4a2c-b568-9569f1b10733',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
roomId: { type: 'string', format: 'misskey:id' },
},
required: ['roomId'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private chatService: ChatService,
) {
super(meta, paramDef, async (ps, me) => {
await this.chatService.joinToRoom(me.id, ps.roomId);
});
}
}

View File

@@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ChatService } from '@/core/ChatService.js';
import { ApiError } from '@/server/api/error.js';
export const meta = {
tags: ['chat'],
requireCredential: true,
kind: 'write:chat',
res: {
},
errors: {
noSuchRoom: {
message: 'No such room.',
code: 'NO_SUCH_ROOM',
id: 'cb7f3179-50e8-4389-8c30-dbe2650a67c9',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
roomId: { type: 'string', format: 'misskey:id' },
},
required: ['roomId'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private chatService: ChatService,
) {
super(meta, paramDef, async (ps, me) => {
await this.chatService.leaveRoom(me.id, ps.roomId);
});
}
}

View File

@@ -0,0 +1,71 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ChatService } from '@/core/ChatService.js';
import { ApiError } from '@/server/api/error.js';
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
export const meta = {
tags: ['chat'],
requireCredential: true,
kind: 'write:chat',
res: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'ChatRoomMembership',
},
},
errors: {
noSuchRoom: {
message: 'No such room.',
code: 'NO_SUCH_ROOM',
id: '7b9fe84c-eafc-4d21-bf89-485458ed2c18',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
roomId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
},
required: ['roomId'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private chatService: ChatService,
private chatEntityService: ChatEntityService,
) {
super(meta, paramDef, async (ps, me) => {
const room = await this.chatService.findRoomById(ps.roomId);
if (room == null) {
throw new ApiError(meta.errors.noSuchRoom);
}
if (!(await this.chatService.isRoomMember(room.id, me.id)) && room.ownerId !== me.id) {
throw new ApiError(meta.errors.noSuchRoom);
}
const memberships = await this.chatService.getRoomMembershipsWithPagination(room.id, ps.limit, ps.sinceId, ps.untilId);
return this.chatEntityService.packRoomMemberships(memberships, me);
});
}
}

View File

@@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ChatService } from '@/core/ChatService.js';
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
import { ApiError } from '@/server/api/error.js';
export const meta = {
tags: ['chat'],
requireCredential: true,
kind: 'read:chat',
res: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'ChatRoom',
},
},
errors: {
},
} as const;
export const paramDef = {
type: 'object',
properties: {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
},
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private chatEntityService: ChatEntityService,
private chatService: ChatService,
) {
super(meta, paramDef, async (ps, me) => {
const rooms = await this.chatService.getOwnedRoomsWithPagination(me.id, ps.limit, ps.sinceId, ps.untilId);
return this.chatEntityService.packRooms(rooms, me);
});
}
}

View File

@@ -0,0 +1,58 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ChatService } from '@/core/ChatService.js';
import { ApiError } from '@/server/api/error.js';
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
export const meta = {
tags: ['chat'],
requireCredential: true,
kind: 'read:chat',
res: {
type: 'object',
optional: false, nullable: false,
ref: 'ChatRoom',
},
errors: {
noSuchRoom: {
message: 'No such room.',
code: 'NO_SUCH_ROOM',
id: '857ae02f-8759-4d20-9adb-6e95fffe4fd7',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
roomId: { type: 'string', format: 'misskey:id' },
},
required: ['roomId'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private chatService: ChatService,
private chatEntityService: ChatEntityService,
) {
super(meta, paramDef, async (ps, me) => {
const room = await this.chatService.findRoomById(ps.roomId);
if (room == null) {
throw new ApiError(meta.errors.noSuchRoom);
}
return this.chatEntityService.packRoom(room, me);
});
}
}

View File

@@ -0,0 +1,63 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ChatService } from '@/core/ChatService.js';
import { ApiError } from '@/server/api/error.js';
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
export const meta = {
tags: ['chat'],
requireCredential: true,
kind: 'write:chat',
res: {
type: 'object',
optional: false, nullable: false,
ref: 'ChatRoom',
},
errors: {
noSuchRoom: {
message: 'No such room.',
code: 'NO_SUCH_ROOM',
id: 'fcdb0f92-bda6-47f9-bd05-343e0e020932',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
roomId: { type: 'string', format: 'misskey:id' },
name: { type: 'string', maxLength: 256 },
},
required: ['roomId'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private chatService: ChatService,
private chatEntityService: ChatEntityService,
) {
super(meta, paramDef, async (ps, me) => {
const room = await this.chatService.findMyRoomById(me.id, ps.roomId);
if (room == null) {
throw new ApiError(meta.errors.noSuchRoom);
}
const updated = await this.chatService.updateRoom(room, {
name: ps.name,
});
return this.chatEntityService.packRoom(updated, me);
});
}
}