This commit is contained in:
syuilo 2025-02-21 20:34:20 +09:00
parent dde1230235
commit 271ca3f5c9
11 changed files with 55 additions and 72 deletions

4
locales/index.d.ts vendored
View File

@ -10058,6 +10058,10 @@ export interface Locale extends ILocale {
* 稿 * 稿
*/ */
"deleteGalleryPost": string; "deleteGalleryPost": string;
/**
*
*/
"updateProxyAccountDescription": string;
}; };
"_fileViewer": { "_fileViewer": {
/** /**

View File

@ -2664,6 +2664,7 @@ _moderationLogTypes:
deletePage: "ページを削除" deletePage: "ページを削除"
deleteFlash: "Playを削除" deleteFlash: "Playを削除"
deleteGalleryPost: "ギャラリーの投稿を削除" deleteGalleryPost: "ギャラリーの投稿を削除"
updateProxyAccountDescription: "プロキシアカウントの説明を更新"
_fileViewer: _fileViewer:
title: "ファイルの詳細" title: "ファイルの詳細"

View File

@ -133,7 +133,7 @@ const $meta: Provider = {
for (const key in body.after) { for (const key in body.after) {
(meta as any)[key] = (body.after as any)[key]; (meta as any)[key] = (body.after as any)[key];
} }
meta.proxyAccount = null; // joinなカラムは通常取ってこないので meta.rootUser = null; // joinなカラムは通常取ってこないので
break; break;
} }
default: default:

View File

@ -20,10 +20,10 @@ import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ProxyAccountService } from '@/core/ProxyAccountService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import InstanceChart from '@/core/chart/charts/instance.js'; import InstanceChart from '@/core/chart/charts/instance.js';
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js'; import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
import { SystemAccountService } from '@/core/SystemAccountService.js';
@Injectable() @Injectable()
export class AccountMoveService { export class AccountMoveService {
@ -55,12 +55,12 @@ export class AccountMoveService {
private apRendererService: ApRendererService, private apRendererService: ApRendererService,
private apDeliverManagerService: ApDeliverManagerService, private apDeliverManagerService: ApDeliverManagerService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private proxyAccountService: ProxyAccountService,
private perUserFollowingChart: PerUserFollowingChart, private perUserFollowingChart: PerUserFollowingChart,
private federatedInstanceService: FederatedInstanceService, private federatedInstanceService: FederatedInstanceService,
private instanceChart: InstanceChart, private instanceChart: InstanceChart,
private relayService: RelayService, private relayService: RelayService,
private queueService: QueueService, private queueService: QueueService,
private systemAccountService: SystemAccountService,
) { ) {
} }
@ -126,11 +126,11 @@ export class AccountMoveService {
} }
// follow the new account // follow the new account
const proxy = await this.proxyAccountService.fetch(); const proxy = await this.systemAccountService.fetch('proxy');
const followings = await this.followingsRepository.findBy({ const followings = await this.followingsRepository.findBy({
followeeId: src.id, followeeId: src.id,
followerHost: IsNull(), // follower is local followerHost: IsNull(), // follower is local
followerId: proxy ? Not(proxy.id) : undefined, followerId: Not(proxy.id),
}); });
const followJobs = followings.map(following => ({ const followJobs = followings.map(following => ({
from: { id: following.followerId }, from: { id: following.followerId },
@ -250,10 +250,8 @@ export class AccountMoveService {
// Have the proxy account follow the new account in the same way as UserListService.push // Have the proxy account follow the new account in the same way as UserListService.push
if (this.userEntityService.isRemoteUser(dst)) { if (this.userEntityService.isRemoteUser(dst)) {
const proxy = await this.proxyAccountService.fetch(); const proxy = await this.systemAccountService.fetch('proxy');
if (proxy) { this.queueService.createFollowJob([{ from: { id: proxy.id }, to: { id: dst.id } }]);
this.queueService.createFollowJob([{ from: { id: proxy.id }, to: { id: dst.id } }]);
}
} }
} }

View File

@ -53,7 +53,7 @@ export class MetaService implements OnApplicationShutdown {
case 'metaUpdated': { case 'metaUpdated': {
this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
...(body.after), ...(body.after),
proxyAccount: null, // joinなカラムは通常取ってこないので rootUser: null, // joinなカラムは通常取ってこないので
}; };
break; break;
} }

View File

@ -9,7 +9,7 @@ import { DataSource, IsNull } from 'typeorm';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import { MiLocalUser, MiUser } from '@/models/User.js'; import { MiLocalUser, MiUser } from '@/models/User.js';
import { MiSystemAccount, MiUsedUsername, MiUserKeypair, MiUserProfile, type UsersRepository, type SystemAccountsRepository } from '@/models/_.js'; import { MiSystemAccount, MiUsedUsername, MiUserKeypair, MiUserProfile, type UsersRepository, type SystemAccountsRepository } from '@/models/_.js';
import type { UserProfilesRepository } from '@/models/_.js'; import type { MiMeta, UserProfilesRepository } from '@/models/_.js';
import { MemoryKVCache } from '@/misc/cache.js'; import { MemoryKVCache } from '@/misc/cache.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -27,6 +27,9 @@ export class SystemAccountService {
@Inject(DI.db) @Inject(DI.db)
private db: DataSource, private db: DataSource,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.systemAccountsRepository) @Inject(DI.systemAccountsRepository)
private systemAccountsRepository: SystemAccountsRepository, private systemAccountsRepository: SystemAccountsRepository,
@ -64,6 +67,7 @@ export class SystemAccountService {
} else { } else {
const created = await this.createCorrespondingUser(type, { const created = await this.createCorrespondingUser(type, {
username: `system.${type}`, username: `system.${type}`,
name: this.meta.name,
}); });
this.cache.set(type, created); this.cache.set(type, created);
return created; return created;
@ -72,8 +76,8 @@ export class SystemAccountService {
@bindThis @bindThis
private async createCorrespondingUser(type: typeof SYSTEM_ACCOUNT_TYPES[number], extra: { private async createCorrespondingUser(type: typeof SYSTEM_ACCOUNT_TYPES[number], extra: {
username: string; username: MiUser['username'];
name?: string; name?: MiUser['name'];
}): Promise<MiLocalUser> { }): Promise<MiLocalUser> {
const password = randomUUID(); const password = randomUUID();
@ -139,7 +143,7 @@ export class SystemAccountService {
@bindThis @bindThis
public async updateCorrespondingUserProfile(type: typeof SYSTEM_ACCOUNT_TYPES[number], extra: { public async updateCorrespondingUserProfile(type: typeof SYSTEM_ACCOUNT_TYPES[number], extra: {
name?: string; name?: string;
description?: string; description?: MiUserProfile['description'];
}): Promise<MiLocalUser> { }): Promise<MiLocalUser> {
const user = await this.fetch(type); const user = await this.fetch(type);

View File

@ -15,11 +15,11 @@ import type { GlobalEvents } from '@/core/GlobalEventService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ProxyAccountService } from '@/core/ProxyAccountService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { QueueService } from '@/core/QueueService.js'; import { QueueService } from '@/core/QueueService.js';
import { RedisKVCache } from '@/misc/cache.js'; import { RedisKVCache } from '@/misc/cache.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
import { SystemAccountService } from '@/core/SystemAccountService.js';
@Injectable() @Injectable()
export class UserListService implements OnApplicationShutdown, OnModuleInit { export class UserListService implements OnApplicationShutdown, OnModuleInit {
@ -43,8 +43,8 @@ export class UserListService implements OnApplicationShutdown, OnModuleInit {
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
private idService: IdService, private idService: IdService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private proxyAccountService: ProxyAccountService,
private queueService: QueueService, private queueService: QueueService,
private systemAccountService: SystemAccountService,
) { ) {
this.membersCache = new RedisKVCache<Set<string>>(this.redisClient, 'userListMembers', { this.membersCache = new RedisKVCache<Set<string>>(this.redisClient, 'userListMembers', {
lifetime: 1000 * 60 * 30, // 30m lifetime: 1000 * 60 * 30, // 30m
@ -111,10 +111,8 @@ export class UserListService implements OnApplicationShutdown, OnModuleInit {
// このインスタンス内にこのリモートユーザーをフォローしているユーザーがいなくても投稿を受け取るためにダミーのユーザーがフォローしたということにする // このインスタンス内にこのリモートユーザーをフォローしているユーザーがいなくても投稿を受け取るためにダミーのユーザーがフォローしたということにする
if (this.userEntityService.isRemoteUser(target)) { if (this.userEntityService.isRemoteUser(target)) {
const proxy = await this.proxyAccountService.fetch(); const proxy = await this.systemAccountService.fetch('proxy');
if (proxy) { this.queueService.createFollowJob([{ from: { id: proxy.id }, to: { id: target.id } }]);
this.queueService.createFollowJob([{ from: { id: proxy.id }, to: { id: target.id } }]);
}
} }
} }

View File

@ -3,18 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { Inject, Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import type {
UsersRepository,
} from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js';
import { import {
descriptionSchema, descriptionSchema,
nameSchema,
} from '@/models/User.js'; } from '@/models/User.js';
import { ApiError } from '@/server/api/error.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js';
import { SystemAccountService } from '@/core/SystemAccountService.js'; import { SystemAccountService } from '@/core/SystemAccountService.js';
@ -24,52 +17,30 @@ export const meta = {
requireCredential: true, requireCredential: true,
requireModerator: true, requireModerator: true,
kind: 'write:admin:update-proxy-account', kind: 'write:admin:account',
errors: {
accessDenied: {
message: 'Only administrators can edit members of the role.',
code: 'ACCESS_DENIED',
id: '101f9105-27cb-489c-842a-69b6d2092c03',
},
},
res: { res: {
type: 'object', type: 'object',
nullable: false, optional: false, nullable: false, optional: false,
ref: 'UserDetailed', ref: 'UserDetailed',
}, },
required: [],
} as const; } as const;
export const paramDef = { export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
name: { ...nameSchema, nullable: true },
description: { ...descriptionSchema, nullable: true }, description: { ...descriptionSchema, nullable: true },
avatarId: { type: 'string', format: 'misskey:id', nullable: true },
bannerId: { type: 'string', format: 'misskey:id', nullable: true },
}, },
} as const; } as const;
@Injectable() @Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor( constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
private roleService: RoleService,
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
private moderationLogService: ModerationLogService, private moderationLogService: ModerationLogService,
private systemAccountService: SystemAccountService, private systemAccountService: SystemAccountService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const _me = await this.usersRepository.findOneByOrFail({ id: me.id });
if (!await this.roleService.isModerator(_me)) {
throw new ApiError(meta.errors.accessDenied);
}
const proxy = await this.systemAccountService.updateCorrespondingUserProfile('proxy', { const proxy = await this.systemAccountService.updateCorrespondingUserProfile('proxy', {
description: ps.description, description: ps.description,
}); });
@ -78,11 +49,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
schema: 'MeDetailed', schema: 'MeDetailed',
}); });
this.moderationLogService.log(me, 'updateUser', { if (ps.description !== undefined) {
userId: proxy.id, this.moderationLogService.log(me, 'updateProxyAccountDescription', {
userUsername: proxy.username, before: null, //TODO
userHost: proxy.host, after: ps.description,
}); });
}
return updated; return updated;
}); });

View File

@ -122,6 +122,7 @@ export const moderationLogTypes = [
'deletePage', 'deletePage',
'deleteFlash', 'deleteFlash',
'deleteGalleryPost', 'deleteGalleryPost',
'updateProxyAccountDescription',
] as const; ] as const;
export type ModerationLogPayloads = { export type ModerationLogPayloads = {
@ -374,25 +375,29 @@ export type ModerationLogPayloads = {
postUserUsername: string; postUserUsername: string;
post: any; post: any;
}; };
updateProxyAccountDescription: {
before: string | null;
after: string | null;
};
}; };
export type Serialized<T> = { export type Serialized<T> = {
[K in keyof T]: [K in keyof T]:
T[K] extends Date T[K] extends Date
? string ? string
: T[K] extends (Date | null) : T[K] extends (Date | null)
? (string | null) ? (string | null)
: T[K] extends Record<string, any> : T[K] extends Record<string, any>
? Serialized<T[K]> ? Serialized<T[K]>
: T[K] extends (Record<string, any> | null) : T[K] extends (Record<string, any> | null)
? (Serialized<T[K]> | null) ? (Serialized<T[K]> | null)
: T[K] extends (Record<string, any> | undefined) : T[K] extends (Record<string, any> | undefined)
? (Serialized<T[K]> | undefined) ? (Serialized<T[K]> | undefined)
: T[K]; : T[K];
}; };
export type FilterUnionByProperty< export type FilterUnionByProperty<
Union, Union,
Property extends string | number | symbol, Property extends string | number | symbol,
Condition Condition,
> = Union extends Record<Property, Condition> ? Union : never; > = Union extends Record<Property, Condition> ? Union : never;

View File

@ -170,6 +170,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/> <CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/>
</div> </div>
</template> </template>
<template v-else-if="log.type === 'updateProxyAccountDescription'">
<div :class="$style.diff">
<CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/>
</div>
</template>
<details> <details>
<summary>raw</summary> <summary>raw</summary>

View File

@ -8282,8 +8282,6 @@ export type operations = {
sensitiveMediaDetectionSensitivity: string; sensitiveMediaDetectionSensitivity: string;
setSensitiveFlagAutomatically: boolean; setSensitiveFlagAutomatically: boolean;
enableSensitiveMediaDetectionForVideos: boolean; enableSensitiveMediaDetectionForVideos: boolean;
/** Format: id */
proxyAccountId: string | null;
email: string | null; email: string | null;
smtpSecure: boolean; smtpSecure: boolean;
smtpHost: string | null; smtpHost: string | null;
@ -10623,8 +10621,6 @@ export type operations = {
sensitiveMediaDetectionSensitivity?: 'medium' | 'low' | 'high' | 'veryLow' | 'veryHigh'; sensitiveMediaDetectionSensitivity?: 'medium' | 'low' | 'high' | 'veryLow' | 'veryHigh';
setSensitiveFlagAutomatically?: boolean; setSensitiveFlagAutomatically?: boolean;
enableSensitiveMediaDetectionForVideos?: boolean; enableSensitiveMediaDetectionForVideos?: boolean;
/** Format: misskey:id */
proxyAccountId?: string | null;
maintainerName?: string | null; maintainerName?: string | null;
maintainerEmail?: string | null; maintainerEmail?: string | null;
langs?: string[]; langs?: string[];