diff --git a/CHANGELOG.md b/CHANGELOG.md index 22e09b2cac..b629fa4611 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ You should also include the user name that made the change. ### Improvements - ユーザーごとにRenoteをミュートできるように - ノートごとに絵文字リアクションを受け取るか設定できるように +- ノート検索の利用可否をロールで制御可能に(デフォルトでオフ) - ロールの並び順を設定可能に - 指定した文字列を含む投稿の公開範囲をホームにできるように - enhance(client): 設定から自分のロールを確認できるように diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 992595734f..feb09b9054 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1261,6 +1261,7 @@ _role: rateLimitFactor: "レートリミット" descriptionOfRateLimitFactor: "小さいほど制限が緩和され、大きいほど制限が強化されます。" canHideAds: "広告の非表示" + canSearchNotes: "ノート検索の利用可否" _condition: isLocal: "ローカルユーザー" isRemote: "リモートユーザー" diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 7149591198..4775196c6f 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -21,6 +21,7 @@ export type RolePolicies = { canPublicNote: boolean; canInvite: boolean; canManageCustomEmojis: boolean; + canSearchNotes: boolean; canHideAds: boolean; driveCapacityMb: number; pinLimit: number; @@ -40,6 +41,7 @@ export const DEFAULT_POLICIES: RolePolicies = { canPublicNote: true, canInvite: false, canManageCustomEmojis: false, + canSearchNotes: false, canHideAds: false, driveCapacityMb: 100, pinLimit: 5, @@ -264,6 +266,7 @@ export class RoleService implements OnApplicationShutdown { canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)), canInvite: calc('canInvite', vs => vs.some(v => v === true)), canManageCustomEmojis: calc('canManageCustomEmojis', vs => vs.some(v => v === true)), + canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)), canHideAds: calc('canHideAds', vs => vs.some(v => v === true)), driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)), pinLimit: calc('pinLimit', vs => Math.max(...vs)), diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index ef47a3004d..5db5b6267f 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -6,6 +6,8 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; +import { RoleService } from '@/core/RoleService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['notes'], @@ -23,6 +25,11 @@ export const meta = { }, errors: { + unavailable: { + message: 'Search of notes unavailable.', + code: 'UNAVAILABLE', + id: '0b44998d-77aa-4427-80d0-d2c9b8523011', + }, }, } as const; @@ -59,8 +66,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private noteEntityService: NoteEntityService, private queryService: QueryService, + private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { + const policies = await this.roleService.getUserPolicies(me ? me.id : null); + if (!policies.canSearchNotes) { + throw new ApiError(meta.errors.unavailable); + } + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId); if (ps.userId) { diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts index 46ebc7d6a3..1d1b8fcea4 100644 --- a/packages/frontend/src/const.ts +++ b/packages/frontend/src/const.ts @@ -53,6 +53,7 @@ export const ROLE_POLICIES = [ 'canPublicNote', 'canInvite', 'canManageCustomEmojis', + 'canSearchNotes', 'canHideAds', 'driveCapacityMb', 'pinLimit', diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 408bbc6460..873ff02feb 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -187,6 +187,26 @@ </div> </MkFolder> + <MkFolder v-if="matchQuery([i18n.ts._role._options.canSearchNotes, 'canSearchNotes'])"> + <template #label>{{ i18n.ts._role._options.canSearchNotes }}</template> + <template #suffix> + <span v-if="role.policies.canSearchNotes.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span> + <span v-else>{{ role.policies.canSearchNotes.value ? i18n.ts.yes : i18n.ts.no }}</span> + <span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canSearchNotes)"></i></span> + </template> + <div class="_gaps"> + <MkSwitch v-model="role.policies.canSearchNotes.useDefault" :readonly="readonly"> + <template #label>{{ i18n.ts._role.useBaseValue }}</template> + </MkSwitch> + <MkSwitch v-model="role.policies.canSearchNotes.value" :disabled="role.policies.canSearchNotes.useDefault" :readonly="readonly"> + <template #label>{{ i18n.ts.enable }}</template> + </MkSwitch> + <MkRange v-model="role.policies.canSearchNotes.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <template #label>{{ i18n.ts._role.priority }}</template> + </MkRange> + </div> + </MkFolder> + <MkFolder v-if="matchQuery([i18n.ts._role._options.driveCapacity, 'driveCapacityMb'])"> <template #label>{{ i18n.ts._role._options.driveCapacity }}</template> <template #suffix> diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index 25d8f3ad6e..351a44bec0 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -55,6 +55,14 @@ </MkSwitch> </MkFolder> + <MkFolder> + <template #label>{{ i18n.ts._role._options.canSearchNotes }}</template> + <template #suffix>{{ policies.canSearchNotes ? i18n.ts.yes : i18n.ts.no }}</template> + <MkSwitch v-model="policies.canSearchNotes"> + <template #label>{{ i18n.ts.enable }}</template> + </MkSwitch> + </MkFolder> + <MkFolder> <template #label>{{ i18n.ts._role._options.driveCapacity }}</template> <template #suffix>{{ policies.driveCapacityMb }}MB</template> @@ -167,25 +175,7 @@ import { definePageMetadata } from '@/scripts/page-metadata'; import { instance } from '@/instance'; import { useRouter } from '@/router'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; - -const ROLE_POLICIES = [ - 'gtlAvailable', - 'ltlAvailable', - 'canPublicNote', - 'canInvite', - 'canManageCustomEmojis', - 'canHideAds', - 'driveCapacityMb', - 'pinLimit', - 'antennaLimit', - 'wordMuteLimit', - 'webhookLimit', - 'clipLimit', - 'noteEachClipsLimit', - 'userListLimit', - 'userEachUserListsLimit', - 'rateLimitFactor', -] as const; +import { ROLE_POLICIES } from '@/const'; const router = useRouter();