Merge branch 'develop' into reimplement-rate-limit

This commit is contained in:
syuilo 2024-06-22 19:42:11 +09:00 committed by GitHub
commit 0265d913e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 76 additions and 163 deletions

View File

@ -88,7 +88,7 @@ jobs:
if [ "$BRANCH" = "misskey-dev:$HEAD_REF" ]; then if [ "$BRANCH" = "misskey-dev:$HEAD_REF" ]; then
BRANCH="$HEAD_REF" BRANCH="$HEAD_REF"
fi fi
pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static --branch-name $BRANCH $(echo "$CHROMATIC_PARAMETER") pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static --branch-name "$BRANCH" $(echo "$CHROMATIC_PARAMETER")
env: env:
HEAD_REF: ${{ github.event.pull_request.head.ref }} HEAD_REF: ${{ github.event.pull_request.head.ref }}
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}

View File

@ -8,15 +8,23 @@
- Fix: `/about#federation` ページなどで各インスタンスのチャートが表示されなくなっていた問題を修正 - Fix: `/about#federation` ページなどで各インスタンスのチャートが表示されなくなっていた問題を修正
- Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968) - Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968)
- Fix: リバーシの対局を正しく共有できないことがある問題を修正 - Fix: リバーシの対局を正しく共有できないことがある問題を修正
- Fix: コントロールパネルでベースロールのポリシーを編集してもUI上では変更が反映されない問題を修正
### Server ### Server
- チャート生成時にinstance.suspentionStateに置き換えられたinstance.isSuspendedが参照されてしまう問題を修正 - チャート生成時にinstance.suspentionStateに置き換えられたinstance.isSuspendedが参照されてしまう問題を修正
- Feat: レートリミット制限に引っかかったときに`Retry-After`ヘッダーを返すように (#13949) - Feat: レートリミット制限に引っかかったときに`Retry-After`ヘッダーを返すように (#13949)
- Fix: ユーザーのフィードページのMFMをHTMLに展開するように (#14006) - Fix: ユーザーのフィードページのMFMをHTMLに展開するように (#14006)
- Fix: アンテナ・クリップ・リスト・ウェブフックがロールポリシーの上限より一つ多く作れてしまうのを修正 (#14036) - Fix: アンテナ・クリップ・リスト・ウェブフックがロールポリシーの上限より一つ多く作れてしまうのを修正 (#14036)
- Enhance: エンドポイント`clips/update`の必須項目を`clipId`のみに
- Enhance: エンドポイント`admin/roles/update`の必須項目を`roleId`のみに
- Enhance: エンドポイント`pages/update`の必須項目を`pageId`のみに
- Enhance: エンドポイント`gallery/posts/update`の必須項目を`postId`のみに
- Enhance: エンドポイント`i/webhook/update`の必須項目を`webhookId`のみに
- Enhance: エンドポイント`admin/ad/update`の必須項目を`id`のみに
- Fix: notRespondingSinceが実装される前に不通になったインスタンスが自動的に配信停止にならない (#14059) - Fix: notRespondingSinceが実装される前に不通になったインスタンスが自動的に配信停止にならない (#14059)
- Fix: FTT有効時、タイムライン用エンドポイントで`sinceId`にキャッシュ内最古のものより古いものを指定した場合に正しく結果が返ってこない問題を修正 - Fix: FTT有効時、タイムライン用エンドポイントで`sinceId`にキャッシュ内最古のものより古いものを指定した場合に正しく結果が返ってこない問題を修正
- Fix: レートリミットのfactorが二回適用されて二乗の効果がある問題を修正 (#13997) - Fix: レートリミットのfactorが二回適用されて二乗の効果がある問題を修正 (#13997)
- Fix: 自分以外のクリップ内のノート個数が見えることがあるのを修正
## 2024.5.0 ## 2024.5.0

View File

@ -53,7 +53,7 @@ export class ClipEntityService {
isPublic: clip.isPublic, isPublic: clip.isPublic,
favoritedCount: await this.clipFavoritesRepository.countBy({ clipId: clip.id }), favoritedCount: await this.clipFavoritesRepository.countBy({ clipId: clip.id }),
isFavorited: meId ? await this.clipFavoritesRepository.exists({ where: { clipId: clip.id, userId: meId } }) : undefined, isFavorited: meId ? await this.clipFavoritesRepository.exists({ where: { clipId: clip.id, userId: meId } }) : undefined,
notesCount: meId ? await this.clipNotesRepository.countBy({ clipId: clip.id }) : undefined, notesCount: (meId === clip.userId) ? await this.clipNotesRepository.countBy({ clipId: clip.id }) : undefined,
}); });
} }

View File

@ -65,44 +65,6 @@ export function maximum(xs: number[]): number {
return Math.max(...xs); return Math.max(...xs);
} }
/**
* Splits an array based on the equivalence relation.
* The concatenation of the result is equal to the argument.
*/
export function groupBy<T>(f: EndoRelation<T>, xs: T[]): T[][] {
const groups = [] as T[][];
for (const x of xs) {
const lastGroup = groups.at(-1);
if (lastGroup !== undefined && f(lastGroup[0], x)) {
lastGroup.push(x);
} else {
groups.push([x]);
}
}
return groups;
}
/**
* Splits an array based on the equivalence relation induced by the function.
* The concatenation of the result is equal to the argument.
*/
export function groupOn<T, S>(f: (x: T) => S, xs: T[]): T[][] {
return groupBy((a, b) => f(a) === f(b), xs);
}
export function groupByX<T>(collections: T[], keySelector: (x: T) => string) {
return collections.reduce((obj: Record<string, T[]>, item: T) => {
const key = keySelector(item);
if (!Object.prototype.hasOwnProperty.call(obj, key)) {
obj[key] = [];
}
obj[key].push(item);
return obj;
}, {});
}
/** /**
* Compare two arrays by lexicographical order * Compare two arrays by lexicographical order
*/ */

View File

@ -40,7 +40,7 @@ export const paramDef = {
startsAt: { type: 'integer' }, startsAt: { type: 'integer' },
dayOfWeek: { type: 'integer' }, dayOfWeek: { type: 'integer' },
}, },
required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'dayOfWeek'], required: ['id'],
} as const; } as const;
@Injectable() @Injectable()
@ -63,8 +63,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
ratio: ps.ratio, ratio: ps.ratio,
memo: ps.memo, memo: ps.memo,
imageUrl: ps.imageUrl, imageUrl: ps.imageUrl,
expiresAt: new Date(ps.expiresAt), expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : undefined,
startsAt: new Date(ps.startsAt), startsAt: ps.startsAt ? new Date(ps.startsAt) : undefined,
dayOfWeek: ps.dayOfWeek, dayOfWeek: ps.dayOfWeek,
}); });

View File

@ -6,7 +6,6 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import type { RolesRepository } from '@/models/_.js'; import type { RolesRepository } from '@/models/_.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { ApiError } from '@/server/api/error.js'; import { ApiError } from '@/server/api/error.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
@ -50,19 +49,6 @@ export const paramDef = {
}, },
required: [ required: [
'roleId', 'roleId',
'name',
'description',
'color',
'iconUrl',
'target',
'condFormula',
'isPublic',
'isModerator',
'isAdministrator',
'asBadge',
'canEditMembersByModerator',
'displayOrder',
'policies',
], ],
} as const; } as const;

View File

@ -3,7 +3,7 @@
* 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 { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
import { ClipService } from '@/core/ClipService.js'; import { ClipService } from '@/core/ClipService.js';
@ -41,7 +41,7 @@ export const paramDef = {
isPublic: { type: 'boolean' }, isPublic: { type: 'boolean' },
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 }, description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
}, },
required: ['clipId', 'name'], required: ['clipId'],
} as const; } as const;
@Injectable() @Injectable()

View File

@ -47,7 +47,7 @@ export const paramDef = {
} }, } },
isSensitive: { type: 'boolean', default: false }, isSensitive: { type: 'boolean', default: false },
}, },
required: ['postId', 'title', 'fileIds'], required: ['postId'],
} as const; } as const;
@Injectable() @Injectable()
@ -62,15 +62,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private galleryPostEntityService: GalleryPostEntityService, private galleryPostEntityService: GalleryPostEntityService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const files = (await Promise.all(ps.fileIds.map(fileId => let files: Array<MiDriveFile> | undefined;
this.driveFilesRepository.findOneBy({
id: fileId,
userId: me.id,
}),
))).filter(x => x != null);
if (files.length === 0) { if (ps.fileIds) {
throw new Error(); files = (await Promise.all(ps.fileIds.map(fileId =>
this.driveFilesRepository.findOneBy({
id: fileId,
userId: me.id,
}),
))).filter(x => x != null);
if (files.length === 0) {
throw new Error();
}
} }
await this.galleryPostsRepository.update({ await this.galleryPostsRepository.update({
@ -81,7 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
title: ps.title, title: ps.title,
description: ps.description, description: ps.description,
isSensitive: ps.isSensitive, isSensitive: ps.isSensitive,
fileIds: files.map(file => file.id), fileIds: files ? files.map(file => file.id) : undefined,
}); });
const post = await this.galleryPostsRepository.findOneByOrFail({ id: ps.postId }); const post = await this.galleryPostsRepository.findOneByOrFail({ id: ps.postId });

View File

@ -34,13 +34,13 @@ export const paramDef = {
webhookId: { type: 'string', format: 'misskey:id' }, webhookId: { type: 'string', format: 'misskey:id' },
name: { type: 'string', minLength: 1, maxLength: 100 }, name: { type: 'string', minLength: 1, maxLength: 100 },
url: { type: 'string', minLength: 1, maxLength: 1024 }, url: { type: 'string', minLength: 1, maxLength: 1024 },
secret: { type: 'string', maxLength: 1024, default: '' }, secret: { type: 'string', nullable: true, maxLength: 1024 },
on: { type: 'array', items: { on: { type: 'array', items: {
type: 'string', enum: webhookEventTypes, type: 'string', enum: webhookEventTypes,
} }, } },
active: { type: 'boolean' }, active: { type: 'boolean' },
}, },
required: ['webhookId', 'name', 'url', 'on', 'active'], required: ['webhookId'],
} as const; } as const;
// TODO: ロジックをサービスに切り出す // TODO: ロジックをサービスに切り出す
@ -66,7 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
await this.webhooksRepository.update(webhook.id, { await this.webhooksRepository.update(webhook.id, {
name: ps.name, name: ps.name,
url: ps.url, url: ps.url,
secret: ps.secret, secret: ps.secret === null ? '' : ps.secret,
on: ps.on, on: ps.on,
active: ps.active, active: ps.active,
}); });

View File

@ -70,7 +70,7 @@ export const paramDef = {
alignCenter: { type: 'boolean' }, alignCenter: { type: 'boolean' },
hideTitleWhenPinned: { type: 'boolean' }, hideTitleWhenPinned: { type: 'boolean' },
}, },
required: ['pageId', 'title', 'name', 'content', 'variables', 'script'], required: ['pageId'],
} as const; } as const;
@Injectable() @Injectable()
@ -91,9 +91,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.accessDenied); throw new ApiError(meta.errors.accessDenied);
} }
let eyeCatchingImage = null;
if (ps.eyeCatchingImageId != null) { if (ps.eyeCatchingImageId != null) {
eyeCatchingImage = await this.driveFilesRepository.findOneBy({ const eyeCatchingImage = await this.driveFilesRepository.findOneBy({
id: ps.eyeCatchingImageId, id: ps.eyeCatchingImageId,
userId: me.id, userId: me.id,
}); });
@ -116,23 +115,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
await this.pagesRepository.update(page.id, { await this.pagesRepository.update(page.id, {
updatedAt: new Date(), updatedAt: new Date(),
title: ps.title, title: ps.title,
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing name: ps.name,
name: ps.name === undefined ? page.name : ps.name,
summary: ps.summary === undefined ? page.summary : ps.summary, summary: ps.summary === undefined ? page.summary : ps.summary,
content: ps.content, content: ps.content,
variables: ps.variables, variables: ps.variables,
script: ps.script, script: ps.script,
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing alignCenter: ps.alignCenter,
alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter, hideTitleWhenPinned: ps.hideTitleWhenPinned,
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing font: ps.font,
hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned, eyeCatchingImageId: ps.eyeCatchingImageId,
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
font: ps.font === undefined ? page.font : ps.font,
eyeCatchingImageId: ps.eyeCatchingImageId === null
? null
: ps.eyeCatchingImageId === undefined
? page.eyeCatchingImageId
: eyeCatchingImage!.id,
}); });
}); });
} }

View File

@ -243,7 +243,7 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
import { instance } from '@/instance.js'; import { instance, fetchInstance } from '@/instance.js';
import MkFoldableSection from '@/components/MkFoldableSection.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue';
import { ROLE_POLICIES } from '@/const.js'; import { ROLE_POLICIES } from '@/const.js';
import { useRouter } from '@/router/supplier.js'; import { useRouter } from '@/router/supplier.js';
@ -267,6 +267,7 @@ async function updateBaseRole() {
await os.apiWithDialog('admin/roles/update-default-policies', { await os.apiWithDialog('admin/roles/update-default-policies', {
policies, policies,
}); });
fetchInstance(true);
} }
function create() { function create() {

View File

@ -77,44 +77,6 @@ export function maximum(xs: number[]): number {
return Math.max(...xs); return Math.max(...xs);
} }
/**
* Splits an array based on the equivalence relation.
* The concatenation of the result is equal to the argument.
*/
export function groupBy<T>(f: EndoRelation<T>, xs: T[]): T[][] {
const groups = [] as T[][];
for (const x of xs) {
const lastGroup = groups.at(-1);
if (lastGroup !== undefined && f(lastGroup[0], x)) {
lastGroup.push(x);
} else {
groups.push([x]);
}
}
return groups;
}
/**
* Splits an array based on the equivalence relation induced by the function.
* The concatenation of the result is equal to the argument.
*/
export function groupOn<T, S>(f: (x: T) => S, xs: T[]): T[][] {
return groupBy((a, b) => f(a) === f(b), xs);
}
export function groupByX<T>(collections: T[], keySelector: (x: T) => string) {
return collections.reduce((obj: Record<string, T[]>, item: T) => {
const key = keySelector(item);
if (typeof obj[key] === 'undefined') {
obj[key] = [];
}
obj[key].push(item);
return obj;
}, {});
}
/** /**
* Compare two arrays by lexicographical order * Compare two arrays by lexicographical order
*/ */

View File

@ -5881,15 +5881,15 @@ export type operations = {
'application/json': { 'application/json': {
/** Format: misskey:id */ /** Format: misskey:id */
id: string; id: string;
memo: string; memo?: string;
url: string; url?: string;
imageUrl: string; imageUrl?: string;
place: string; place?: string;
priority: string; priority?: string;
ratio: number; ratio?: number;
expiresAt: number; expiresAt?: number;
startsAt: number; startsAt?: number;
dayOfWeek: number; dayOfWeek?: number;
}; };
}; };
}; };
@ -9744,21 +9744,21 @@ export type operations = {
'application/json': { 'application/json': {
/** Format: misskey:id */ /** Format: misskey:id */
roleId: string; roleId: string;
name: string; name?: string;
description: string; description?: string;
color: string | null; color?: string | null;
iconUrl: string | null; iconUrl?: string | null;
/** @enum {string} */ /** @enum {string} */
target: 'manual' | 'conditional'; target?: 'manual' | 'conditional';
condFormula: Record<string, never>; condFormula?: Record<string, never>;
isPublic: boolean; isPublic?: boolean;
isModerator: boolean; isModerator?: boolean;
isAdministrator: boolean; isAdministrator?: boolean;
isExplorable?: boolean; isExplorable?: boolean;
asBadge: boolean; asBadge?: boolean;
canEditMembersByModerator: boolean; canEditMembersByModerator?: boolean;
displayOrder: number; displayOrder?: number;
policies: Record<string, never>; policies?: Record<string, never>;
}; };
}; };
}; };
@ -13400,7 +13400,7 @@ export type operations = {
'application/json': { 'application/json': {
/** Format: misskey:id */ /** Format: misskey:id */
clipId: string; clipId: string;
name: string; name?: string;
isPublic?: boolean; isPublic?: boolean;
description?: string | null; description?: string | null;
}; };
@ -16247,9 +16247,9 @@ export type operations = {
'application/json': { 'application/json': {
/** Format: misskey:id */ /** Format: misskey:id */
postId: string; postId: string;
title: string; title?: string;
description?: string | null; description?: string | null;
fileIds: string[]; fileIds?: string[];
/** @default false */ /** @default false */
isSensitive?: boolean; isSensitive?: boolean;
}; };
@ -20030,12 +20030,11 @@ export type operations = {
'application/json': { 'application/json': {
/** Format: misskey:id */ /** Format: misskey:id */
webhookId: string; webhookId: string;
name: string; name?: string;
url: string; url?: string;
/** @default */ secret?: string | null;
secret?: string; on?: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[];
on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[]; active?: boolean;
active: boolean;
}; };
}; };
}; };
@ -23404,16 +23403,16 @@ export type operations = {
'application/json': { 'application/json': {
/** Format: misskey:id */ /** Format: misskey:id */
pageId: string; pageId: string;
title: string; title?: string;
name: string; name?: string;
summary?: string | null; summary?: string | null;
content: { content?: {
[key: string]: unknown; [key: string]: unknown;
}[]; }[];
variables: { variables?: {
[key: string]: unknown; [key: string]: unknown;
}[]; }[];
script: string; script?: string;
/** Format: misskey:id */ /** Format: misskey:id */
eyeCatchingImageId?: string | null; eyeCatchingImageId?: string | null;
/** @enum {string} */ /** @enum {string} */