Merge branch 'misskey-develop' into merge/2025-03-24
# Conflicts: # package.json # packages/backend/src/core/AccountMoveService.ts # packages/frontend/src/components/MkDateSeparatedList.vue # packages/misskey-js/etc/misskey-js.api.md # pnpm-lock.yaml
This commit is contained in:
commit
3eeb53ff63
74 changed files with 622 additions and 242 deletions
|
|
@ -24,6 +24,7 @@ import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
|||
import InstanceChart from '@/core/chart/charts/instance.js';
|
||||
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
|
||||
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
||||
@Injectable()
|
||||
export class AccountMoveService {
|
||||
|
|
@ -64,6 +65,7 @@ export class AccountMoveService {
|
|||
private relayService: RelayService,
|
||||
private queueService: QueueService,
|
||||
private systemAccountService: SystemAccountService,
|
||||
private roleService: RoleService,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -123,6 +125,7 @@ export class AccountMoveService {
|
|||
this.copyBlocking(src, dst),
|
||||
this.copyMutings(src, dst),
|
||||
this.deleteScheduledNotes(src),
|
||||
this.copyRoles(src, dst),
|
||||
this.updateLists(src, dst),
|
||||
]);
|
||||
} catch {
|
||||
|
|
@ -220,6 +223,32 @@ export class AccountMoveService {
|
|||
});
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async copyRoles(src: ThinUser, dst: ThinUser): Promise<void> {
|
||||
// Insert new roles with the same values except userId
|
||||
// role service may have cache for roles so retrieve roles from service
|
||||
const [oldRoleAssignments, roles] = await Promise.all([
|
||||
this.roleService.getUserAssigns(src.id),
|
||||
this.roleService.getRoles(),
|
||||
]);
|
||||
|
||||
if (oldRoleAssignments.length === 0) return;
|
||||
|
||||
// No promise all since the only async operation is writing to the database
|
||||
for (const oldRoleAssignment of oldRoleAssignments) {
|
||||
const role = roles.find(x => x.id === oldRoleAssignment.roleId);
|
||||
if (role == null) continue; // Very unlikely however removing role may cause this case
|
||||
if (!role.preserveAssignmentOnMoveAccount) continue;
|
||||
|
||||
try {
|
||||
await this.roleService.assign(dst.id, role.id, oldRoleAssignment.expiresAt);
|
||||
} catch (e) {
|
||||
if (e instanceof RoleService.AlreadyAssignedError) continue;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update lists while moving accounts.
|
||||
* - No removal of the old account from the lists
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ export class ChatService {
|
|||
text?: string | null;
|
||||
file?: MiDriveFile | null;
|
||||
uri?: string | null;
|
||||
}): Promise<Packed<'ChatMessageLite'>> {
|
||||
}): Promise<Packed<'ChatMessageLiteFor1on1'>> {
|
||||
if (fromUser.id === toUser.id) {
|
||||
throw new Error('yourself');
|
||||
}
|
||||
|
|
@ -210,7 +210,7 @@ export class ChatService {
|
|||
text?: string | null;
|
||||
file?: MiDriveFile | null;
|
||||
uri?: string | null;
|
||||
}): Promise<Packed<'ChatMessageLite'>> {
|
||||
}): Promise<Packed<'ChatMessageLiteForRoom'>> {
|
||||
const memberships = (await this.chatRoomMembershipsRepository.findBy({ roomId: toRoom.id })).map(m => ({
|
||||
userId: m.userId,
|
||||
isMuted: m.isMuted,
|
||||
|
|
|
|||
|
|
@ -639,6 +639,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||
isModerator: values.isModerator,
|
||||
isExplorable: values.isExplorable,
|
||||
asBadge: values.asBadge,
|
||||
preserveAssignmentOnMoveAccount: values.preserveAssignmentOnMoveAccount,
|
||||
canEditMembersByModerator: values.canEditMembersByModerator,
|
||||
displayOrder: values.displayOrder,
|
||||
policies: values.policies,
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ export class ChatEntityService {
|
|||
packedFiles: Map<MiChatMessage['fileId'], Packed<'DriveFile'> | null>;
|
||||
};
|
||||
},
|
||||
): Promise<Packed<'ChatMessageLite'>> {
|
||||
): Promise<Packed<'ChatMessageLiteFor1on1'>> {
|
||||
const packedFiles = options?._hint_?.packedFiles;
|
||||
|
||||
const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src });
|
||||
|
|
@ -147,7 +147,7 @@ export class ChatEntityService {
|
|||
createdAt: this.idService.parse(message.id).date.toISOString(),
|
||||
text: message.text,
|
||||
fromUserId: message.fromUserId,
|
||||
toUserId: message.toUserId,
|
||||
toUserId: message.toUserId!,
|
||||
fileId: message.fileId,
|
||||
file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null,
|
||||
reactions,
|
||||
|
|
@ -177,7 +177,7 @@ export class ChatEntityService {
|
|||
packedUsers: Map<MiUser['id'], Packed<'UserLite'>>;
|
||||
};
|
||||
},
|
||||
): Promise<Packed<'ChatMessageLite'>> {
|
||||
): Promise<Packed<'ChatMessageLiteForRoom'>> {
|
||||
const packedFiles = options?._hint_?.packedFiles;
|
||||
const packedUsers = options?._hint_?.packedUsers;
|
||||
|
||||
|
|
@ -199,7 +199,7 @@ export class ChatEntityService {
|
|||
text: message.text,
|
||||
fromUserId: message.fromUserId,
|
||||
fromUser: packedUsers?.get(message.fromUserId) ?? await this.userEntityService.pack(message.fromUser ?? message.fromUserId),
|
||||
toRoomId: message.toRoomId,
|
||||
toRoomId: message.toRoomId!,
|
||||
fileId: message.fileId,
|
||||
file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null,
|
||||
reactions,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import type { MiRole } from '@/models/Role.js';
|
|||
import { bindThis } from '@/decorators.js';
|
||||
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { Packed } from '@/misc/json-schema.js';
|
||||
|
||||
@Injectable()
|
||||
export class RoleEntityService {
|
||||
|
|
@ -31,7 +32,7 @@ export class RoleEntityService {
|
|||
public async pack(
|
||||
src: MiRole['id'] | MiRole,
|
||||
me?: { id: MiUser['id'] } | null | undefined,
|
||||
) {
|
||||
): Promise<Packed<'Role'>> {
|
||||
const role = typeof src === 'object' ? src : await this.rolesRepository.findOneByOrFail({ id: src });
|
||||
|
||||
const assignedCount = await this.roleAssignmentsRepository.createQueryBuilder('assign')
|
||||
|
|
@ -67,6 +68,7 @@ export class RoleEntityService {
|
|||
isModerator: role.isModerator,
|
||||
isExplorable: role.isExplorable,
|
||||
asBadge: role.asBadge,
|
||||
preserveAssignmentOnMoveAccount: role.preserveAssignmentOnMoveAccount,
|
||||
canEditMembersByModerator: role.canEditMembersByModerator,
|
||||
displayOrder: role.displayOrder,
|
||||
policies: policies,
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ import {
|
|||
} from '@/models/json-schema/meta.js';
|
||||
import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js';
|
||||
import { packedAbuseReportNotificationRecipientSchema } from '@/models/json-schema/abuse-report-notification-recipient.js';
|
||||
import { packedChatMessageSchema, packedChatMessageLiteSchema } from '@/models/json-schema/chat-message.js';
|
||||
import { packedChatMessageSchema, packedChatMessageLiteSchema, packedChatMessageLiteForRoomSchema, packedChatMessageLiteFor1on1Schema } 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';
|
||||
|
|
@ -126,6 +126,8 @@ export const refs = {
|
|||
AbuseReportNotificationRecipient: packedAbuseReportNotificationRecipientSchema,
|
||||
ChatMessage: packedChatMessageSchema,
|
||||
ChatMessageLite: packedChatMessageLiteSchema,
|
||||
ChatMessageLiteFor1on1: packedChatMessageLiteFor1on1Schema,
|
||||
ChatMessageLiteForRoom: packedChatMessageLiteForRoomSchema,
|
||||
ChatRoom: packedChatRoomSchema,
|
||||
ChatRoomInvitation: packedChatRoomInvitationSchema,
|
||||
ChatRoomMembership: packedChatRoomMembershipSchema,
|
||||
|
|
|
|||
|
|
@ -248,6 +248,11 @@ export class MiRole {
|
|||
})
|
||||
public isExplorable: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public preserveAssignmentOnMoveAccount: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ export const packedChatMessageSchema = {
|
|||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
optional: true, nullable: true,
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserLite',
|
||||
},
|
||||
},
|
||||
|
|
@ -144,3 +144,113 @@ export const packedChatMessageLiteSchema = {
|
|||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const packedChatMessageLiteFor1on1Schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
fromUserId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
toUserId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
fileId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
file: {
|
||||
type: 'object',
|
||||
optional: true, nullable: true,
|
||||
ref: 'DriveFile',
|
||||
},
|
||||
reactions: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
reaction: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const packedChatMessageLiteForRoomSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
fromUserId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
fromUser: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserLite',
|
||||
},
|
||||
toRoomId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
fileId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
file: {
|
||||
type: 'object',
|
||||
optional: true, nullable: true,
|
||||
ref: 'DriveFile',
|
||||
},
|
||||
reactions: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
reaction: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserLite',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -397,6 +397,11 @@ export const packedRoleSchema = {
|
|||
optional: false, nullable: false,
|
||||
example: false,
|
||||
},
|
||||
preserveAssignmentOnMoveAccount: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
example: false,
|
||||
},
|
||||
canEditMembersByModerator: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export const paramDef = {
|
|||
isAdministrator: { type: 'boolean' },
|
||||
isExplorable: { type: 'boolean', default: false }, // optional for backward compatibility
|
||||
asBadge: { type: 'boolean' },
|
||||
preserveAssignmentOnMoveAccount: { type: 'boolean' },
|
||||
canEditMembersByModerator: { type: 'boolean' },
|
||||
displayOrder: { type: 'number' },
|
||||
policies: {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export const paramDef = {
|
|||
isAdministrator: { type: 'boolean' },
|
||||
isExplorable: { type: 'boolean' },
|
||||
asBadge: { type: 'boolean' },
|
||||
preserveAssignmentOnMoveAccount: { type: 'boolean' },
|
||||
canEditMembersByModerator: { type: 'boolean' },
|
||||
displayOrder: { type: 'number' },
|
||||
policies: {
|
||||
|
|
@ -78,6 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
isAdministrator: ps.isAdministrator,
|
||||
isExplorable: ps.isExplorable,
|
||||
asBadge: ps.asBadge,
|
||||
preserveAssignmentOnMoveAccount: ps.preserveAssignmentOnMoveAccount,
|
||||
canEditMembersByModerator: ps.canEditMembersByModerator,
|
||||
displayOrder: ps.displayOrder,
|
||||
policies: ps.policies,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export const meta = {
|
|||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatMessageLite',
|
||||
ref: 'ChatMessageLiteForRoom',
|
||||
},
|
||||
|
||||
errors: {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export const meta = {
|
|||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatMessageLite',
|
||||
ref: 'ChatMessageLiteFor1on1',
|
||||
},
|
||||
|
||||
errors: {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export const meta = {
|
|||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
requiredRolePolicy: 'canChat',
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export const meta = {
|
|||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
requiredRolePolicy: 'canChat',
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export const meta = {
|
|||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatMessageLite',
|
||||
ref: 'ChatMessageLiteForRoom',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export const meta = {
|
|||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
requiredRolePolicy: 'canChat',
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export const meta = {
|
|||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatMessageLite',
|
||||
ref: 'ChatMessageLiteFor1on1',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ class BubbleTimelineChannel extends Channel {
|
|||
if (note.channelId != null) return;
|
||||
if (note.user.host == null) return;
|
||||
if (!this.instance.bubbleInstances.includes(note.user.host)) return;
|
||||
if (note.user.requireSigninToViewContents && this.user == null) return;
|
||||
|
||||
if (isRenotePacked(note) && !isQuotePacked(note) && !this.withRenotes) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ class GlobalTimelineChannel extends Channel {
|
|||
|
||||
if (note.visibility !== 'public') return;
|
||||
if (note.channelId != null) return;
|
||||
if (note.user.requireSigninToViewContents && this.user == null) return;
|
||||
|
||||
if (isRenotePacked(note) && !isQuotePacked(note) && !this.withRenotes) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ class LocalTimelineChannel extends Channel {
|
|||
if (note.user.host !== null) return;
|
||||
if (note.visibility !== 'public') return;
|
||||
if (note.channelId != null) return;
|
||||
if (note.user.requireSigninToViewContents && this.user == null) return;
|
||||
|
||||
// 関係ない返信は除外
|
||||
if (note.reply && this.user && !this.following[note.userId]?.withReplies && !this.withReplies) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue