Merge branch 'develop' into reimplement-rate-limit
This commit is contained in:
commit
0265d913e5
|
@ -88,7 +88,7 @@ jobs:
|
|||
if [ "$BRANCH" = "misskey-dev:$HEAD_REF" ]; then
|
||||
BRANCH="$HEAD_REF"
|
||||
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:
|
||||
HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
||||
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
|
|
|
@ -8,15 +8,23 @@
|
|||
- Fix: `/about#federation` ページなどで各インスタンスのチャートが表示されなくなっていた問題を修正
|
||||
- Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968)
|
||||
- Fix: リバーシの対局を正しく共有できないことがある問題を修正
|
||||
- Fix: コントロールパネルでベースロールのポリシーを編集してもUI上では変更が反映されない問題を修正
|
||||
|
||||
### Server
|
||||
- チャート生成時にinstance.suspentionStateに置き換えられたinstance.isSuspendedが参照されてしまう問題を修正
|
||||
- Feat: レートリミット制限に引っかかったときに`Retry-After`ヘッダーを返すように (#13949)
|
||||
- Fix: ユーザーのフィードページのMFMをHTMLに展開するように (#14006)
|
||||
- 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: FTT有効時、タイムライン用エンドポイントで`sinceId`にキャッシュ内最古のものより古いものを指定した場合に正しく結果が返ってこない問題を修正
|
||||
- Fix: レートリミットのfactorが二回適用されて二乗の効果がある問題を修正 (#13997)
|
||||
- Fix: 自分以外のクリップ内のノート個数が見えることがあるのを修正
|
||||
|
||||
## 2024.5.0
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ export class ClipEntityService {
|
|||
isPublic: clip.isPublic,
|
||||
favoritedCount: await this.clipFavoritesRepository.countBy({ clipId: clip.id }),
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -65,44 +65,6 @@ export function maximum(xs: number[]): number {
|
|||
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
|
||||
*/
|
||||
|
|
|
@ -40,7 +40,7 @@ export const paramDef = {
|
|||
startsAt: { type: 'integer' },
|
||||
dayOfWeek: { type: 'integer' },
|
||||
},
|
||||
required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'dayOfWeek'],
|
||||
required: ['id'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
|
@ -63,8 +63,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
ratio: ps.ratio,
|
||||
memo: ps.memo,
|
||||
imageUrl: ps.imageUrl,
|
||||
expiresAt: new Date(ps.expiresAt),
|
||||
startsAt: new Date(ps.startsAt),
|
||||
expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : undefined,
|
||||
startsAt: ps.startsAt ? new Date(ps.startsAt) : undefined,
|
||||
dayOfWeek: ps.dayOfWeek,
|
||||
});
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { RolesRepository } from '@/models/_.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
@ -50,19 +49,6 @@ export const paramDef = {
|
|||
},
|
||||
required: [
|
||||
'roleId',
|
||||
'name',
|
||||
'description',
|
||||
'color',
|
||||
'iconUrl',
|
||||
'target',
|
||||
'condFormula',
|
||||
'isPublic',
|
||||
'isModerator',
|
||||
'isAdministrator',
|
||||
'asBadge',
|
||||
'canEditMembersByModerator',
|
||||
'displayOrder',
|
||||
'policies',
|
||||
],
|
||||
} as const;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* 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 { ClipEntityService } from '@/core/entities/ClipEntityService.js';
|
||||
import { ClipService } from '@/core/ClipService.js';
|
||||
|
@ -41,7 +41,7 @@ export const paramDef = {
|
|||
isPublic: { type: 'boolean' },
|
||||
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
|
||||
},
|
||||
required: ['clipId', 'name'],
|
||||
required: ['clipId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
|
|
|
@ -47,7 +47,7 @@ export const paramDef = {
|
|||
} },
|
||||
isSensitive: { type: 'boolean', default: false },
|
||||
},
|
||||
required: ['postId', 'title', 'fileIds'],
|
||||
required: ['postId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
|
@ -62,15 +62,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private galleryPostEntityService: GalleryPostEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const files = (await Promise.all(ps.fileIds.map(fileId =>
|
||||
this.driveFilesRepository.findOneBy({
|
||||
id: fileId,
|
||||
userId: me.id,
|
||||
}),
|
||||
))).filter(x => x != null);
|
||||
let files: Array<MiDriveFile> | undefined;
|
||||
|
||||
if (files.length === 0) {
|
||||
throw new Error();
|
||||
if (ps.fileIds) {
|
||||
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({
|
||||
|
@ -81,7 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
title: ps.title,
|
||||
description: ps.description,
|
||||
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 });
|
||||
|
|
|
@ -34,13 +34,13 @@ export const paramDef = {
|
|||
webhookId: { type: 'string', format: 'misskey:id' },
|
||||
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||
url: { type: 'string', minLength: 1, maxLength: 1024 },
|
||||
secret: { type: 'string', maxLength: 1024, default: '' },
|
||||
secret: { type: 'string', nullable: true, maxLength: 1024 },
|
||||
on: { type: 'array', items: {
|
||||
type: 'string', enum: webhookEventTypes,
|
||||
} },
|
||||
active: { type: 'boolean' },
|
||||
},
|
||||
required: ['webhookId', 'name', 'url', 'on', 'active'],
|
||||
required: ['webhookId'],
|
||||
} as const;
|
||||
|
||||
// TODO: ロジックをサービスに切り出す
|
||||
|
@ -66,7 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
await this.webhooksRepository.update(webhook.id, {
|
||||
name: ps.name,
|
||||
url: ps.url,
|
||||
secret: ps.secret,
|
||||
secret: ps.secret === null ? '' : ps.secret,
|
||||
on: ps.on,
|
||||
active: ps.active,
|
||||
});
|
||||
|
|
|
@ -70,7 +70,7 @@ export const paramDef = {
|
|||
alignCenter: { type: 'boolean' },
|
||||
hideTitleWhenPinned: { type: 'boolean' },
|
||||
},
|
||||
required: ['pageId', 'title', 'name', 'content', 'variables', 'script'],
|
||||
required: ['pageId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
|
@ -91,9 +91,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new ApiError(meta.errors.accessDenied);
|
||||
}
|
||||
|
||||
let eyeCatchingImage = null;
|
||||
if (ps.eyeCatchingImageId != null) {
|
||||
eyeCatchingImage = await this.driveFilesRepository.findOneBy({
|
||||
const eyeCatchingImage = await this.driveFilesRepository.findOneBy({
|
||||
id: ps.eyeCatchingImageId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
@ -116,23 +115,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
await this.pagesRepository.update(page.id, {
|
||||
updatedAt: new Date(),
|
||||
title: ps.title,
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
name: ps.name === undefined ? page.name : ps.name,
|
||||
name: ps.name,
|
||||
summary: ps.summary === undefined ? page.summary : ps.summary,
|
||||
content: ps.content,
|
||||
variables: ps.variables,
|
||||
script: ps.script,
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter,
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned,
|
||||
// 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,
|
||||
alignCenter: ps.alignCenter,
|
||||
hideTitleWhenPinned: ps.hideTitleWhenPinned,
|
||||
font: ps.font,
|
||||
eyeCatchingImageId: ps.eyeCatchingImageId,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -243,7 +243,7 @@ import * as os from '@/os.js';
|
|||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { i18n } from '@/i18n.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 { ROLE_POLICIES } from '@/const.js';
|
||||
import { useRouter } from '@/router/supplier.js';
|
||||
|
@ -267,6 +267,7 @@ async function updateBaseRole() {
|
|||
await os.apiWithDialog('admin/roles/update-default-policies', {
|
||||
policies,
|
||||
});
|
||||
fetchInstance(true);
|
||||
}
|
||||
|
||||
function create() {
|
||||
|
|
|
@ -77,44 +77,6 @@ export function maximum(xs: number[]): number {
|
|||
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
|
||||
*/
|
||||
|
|
|
@ -5881,15 +5881,15 @@ export type operations = {
|
|||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
id: string;
|
||||
memo: string;
|
||||
url: string;
|
||||
imageUrl: string;
|
||||
place: string;
|
||||
priority: string;
|
||||
ratio: number;
|
||||
expiresAt: number;
|
||||
startsAt: number;
|
||||
dayOfWeek: number;
|
||||
memo?: string;
|
||||
url?: string;
|
||||
imageUrl?: string;
|
||||
place?: string;
|
||||
priority?: string;
|
||||
ratio?: number;
|
||||
expiresAt?: number;
|
||||
startsAt?: number;
|
||||
dayOfWeek?: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -9744,21 +9744,21 @@ export type operations = {
|
|||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
roleId: string;
|
||||
name: string;
|
||||
description: string;
|
||||
color: string | null;
|
||||
iconUrl: string | null;
|
||||
name?: string;
|
||||
description?: string;
|
||||
color?: string | null;
|
||||
iconUrl?: string | null;
|
||||
/** @enum {string} */
|
||||
target: 'manual' | 'conditional';
|
||||
condFormula: Record<string, never>;
|
||||
isPublic: boolean;
|
||||
isModerator: boolean;
|
||||
isAdministrator: boolean;
|
||||
target?: 'manual' | 'conditional';
|
||||
condFormula?: Record<string, never>;
|
||||
isPublic?: boolean;
|
||||
isModerator?: boolean;
|
||||
isAdministrator?: boolean;
|
||||
isExplorable?: boolean;
|
||||
asBadge: boolean;
|
||||
canEditMembersByModerator: boolean;
|
||||
displayOrder: number;
|
||||
policies: Record<string, never>;
|
||||
asBadge?: boolean;
|
||||
canEditMembersByModerator?: boolean;
|
||||
displayOrder?: number;
|
||||
policies?: Record<string, never>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -13400,7 +13400,7 @@ export type operations = {
|
|||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
clipId: string;
|
||||
name: string;
|
||||
name?: string;
|
||||
isPublic?: boolean;
|
||||
description?: string | null;
|
||||
};
|
||||
|
@ -16247,9 +16247,9 @@ export type operations = {
|
|||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
postId: string;
|
||||
title: string;
|
||||
title?: string;
|
||||
description?: string | null;
|
||||
fileIds: string[];
|
||||
fileIds?: string[];
|
||||
/** @default false */
|
||||
isSensitive?: boolean;
|
||||
};
|
||||
|
@ -20030,12 +20030,11 @@ export type operations = {
|
|||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
webhookId: string;
|
||||
name: string;
|
||||
url: string;
|
||||
/** @default */
|
||||
secret?: string;
|
||||
on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[];
|
||||
active: boolean;
|
||||
name?: string;
|
||||
url?: string;
|
||||
secret?: string | null;
|
||||
on?: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[];
|
||||
active?: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -23404,16 +23403,16 @@ export type operations = {
|
|||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
pageId: string;
|
||||
title: string;
|
||||
name: string;
|
||||
title?: string;
|
||||
name?: string;
|
||||
summary?: string | null;
|
||||
content: {
|
||||
content?: {
|
||||
[key: string]: unknown;
|
||||
}[];
|
||||
variables: {
|
||||
variables?: {
|
||||
[key: string]: unknown;
|
||||
}[];
|
||||
script: string;
|
||||
script?: string;
|
||||
/** Format: misskey:id */
|
||||
eyeCatchingImageId?: string | null;
|
||||
/** @enum {string} */
|
||||
|
|
Loading…
Reference in New Issue