Merge branch 'develop' into reimplement-rate-limit
This commit is contained in:
		
						commit
						fce656b5e9
					
				|  | @ -2,3 +2,7 @@ contact_links: | |||
|   - name: 💬 Misskey official Discord | ||||
|     url: https://discord.gg/Wp8gVStHW3 | ||||
|     about: Chat freely about Misskey | ||||
|   # 仮 | ||||
|   - name: 💬 Start discussion | ||||
|     url: https://github.com/misskey-dev/misskey/discussions | ||||
|     about: The official forum to join conversation and ask question | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ | |||
| - Fix: 配信停止したインスタンス一覧が見れなくなる問題を修正 | ||||
| 
 | ||||
| ### Client | ||||
| - `/about#federation` ページなどで各インスタンスのチャートが表示されなくなっていた問題を修正 | ||||
| - Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968) | ||||
| 
 | ||||
| ### Server | ||||
|  |  | |||
|  | @ -82,34 +82,14 @@ import { MiReversiGame } from '@/models/ReversiGame.js'; | |||
| import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js'; | ||||
| 
 | ||||
| export interface MiRepository<T extends ObjectLiteral> { | ||||
| 	createTableColumnNames(this: Repository<T> & MiRepository<T>, queryBuilder: InsertQueryBuilder<T>): string[]; | ||||
| 	createTableColumnNamesWithPrimaryKey(this: Repository<T> & MiRepository<T>, queryBuilder: InsertQueryBuilder<T>): string[]; | ||||
| 	createTableColumnNames(this: Repository<T> & MiRepository<T>): string[]; | ||||
| 	insertOne(this: Repository<T> & MiRepository<T>, entity: QueryDeepPartialEntity<T>, findOptions?: Pick<FindOneOptions<T>, 'relations'>): Promise<T>; | ||||
| 	selectAliasColumnNames(this: Repository<T> & MiRepository<T>, queryBuilder: InsertQueryBuilder<T>, builder: SelectQueryBuilder<T>): void; | ||||
| } | ||||
| 
 | ||||
| export const miRepository = { | ||||
| 	createTableColumnNames(queryBuilder) { | ||||
| 		// @ts-expect-error -- protected
 | ||||
| 		const insertedColumns = queryBuilder.getInsertedColumns(); | ||||
| 		if (insertedColumns.length) { | ||||
| 			return insertedColumns.map(column => column.databaseName); | ||||
| 		} | ||||
| 		if (!queryBuilder.expressionMap.mainAlias?.hasMetadata && !queryBuilder.expressionMap.insertColumns.length) { | ||||
| 			// @ts-expect-error -- protected
 | ||||
| 			const valueSets = queryBuilder.getValueSets(); | ||||
| 			if (valueSets.length === 1) { | ||||
| 				return Object.keys(valueSets[0]); | ||||
| 			} | ||||
| 		} | ||||
| 		return queryBuilder.expressionMap.insertColumns; | ||||
| 	}, | ||||
| 	createTableColumnNamesWithPrimaryKey(queryBuilder) { | ||||
| 		const columnNames = this.createTableColumnNames(queryBuilder); | ||||
| 		if (!columnNames.includes('id')) { | ||||
| 			columnNames.unshift('id'); | ||||
| 		} | ||||
| 		return columnNames; | ||||
| 	createTableColumnNames() { | ||||
| 		return this.metadata.columns.filter(column => column.isSelect && !column.isVirtual).map(column => column.databaseName); | ||||
| 	}, | ||||
| 	async insertOne(entity, findOptions?) { | ||||
| 		const queryBuilder = this.createQueryBuilder().insert().values(entity); | ||||
|  | @ -117,7 +97,7 @@ export const miRepository = { | |||
| 		const mainAlias = queryBuilder.expressionMap.mainAlias!; | ||||
| 		const name = mainAlias.name; | ||||
| 		mainAlias.name = 't'; | ||||
| 		const columnNames = this.createTableColumnNamesWithPrimaryKey(queryBuilder); | ||||
| 		const columnNames = this.createTableColumnNames(); | ||||
| 		queryBuilder.returning(columnNames.reduce((a, c) => `${a}, ${queryBuilder.escape(c)}`, '').slice(2)); | ||||
| 		const builder = this.createQueryBuilder().addCommonTableExpression(queryBuilder, 'cte', { columnNames }); | ||||
| 		// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 | ||||
|  | @ -138,7 +118,7 @@ export const miRepository = { | |||
| 			selectOrAddSelect = (selection, selectionAliasName) => builder.addSelect(selection, selectionAliasName); | ||||
| 			return builder.select(selection, selectionAliasName); | ||||
| 		}; | ||||
| 		for (const columnName of this.createTableColumnNamesWithPrimaryKey(queryBuilder)) { | ||||
| 		for (const columnName of this.createTableColumnNames()) { | ||||
| 			selectOrAddSelect(`${builder.alias}.${columnName}`, `${builder.alias}_${columnName}`); | ||||
| 		} | ||||
| 	}, | ||||
|  |  | |||
|  | @ -125,6 +125,35 @@ export function file(isSensitive = false) { | |||
| 	}; | ||||
| } | ||||
| 
 | ||||
| export function federationInstance(): entities.FederationInstance { | ||||
| 	return { | ||||
| 		id: 'someinstanceid', | ||||
| 		firstRetrievedAt: '2021-01-01T00:00:00.000Z', | ||||
| 		host: 'misskey-hub.net', | ||||
| 		usersCount: 10, | ||||
| 		notesCount: 20, | ||||
| 		followingCount: 5, | ||||
| 		followersCount: 15, | ||||
| 		isNotResponding: false, | ||||
| 		isSuspended: false, | ||||
| 		suspensionState: 'none', | ||||
| 		isBlocked: false, | ||||
| 		softwareName: 'misskey', | ||||
| 		softwareVersion: '2024.5.0', | ||||
| 		openRegistrations: false, | ||||
| 		name: 'Misskey Hub', | ||||
| 		description: '', | ||||
| 		maintainerName: '', | ||||
| 		maintainerEmail: '', | ||||
| 		isSilenced: false, | ||||
| 		iconUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true', | ||||
| 		faviconUrl: '', | ||||
| 		themeColor: '', | ||||
| 		infoUpdatedAt: '', | ||||
| 		latestRequestReceivedAt: '', | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| export function userDetailed(id = 'someuserid', username = 'miskist', host = 'misskey-hub.net', name = 'Misskey User'): entities.UserDetailed { | ||||
| 	return { | ||||
| 		id, | ||||
|  |  | |||
|  | @ -403,6 +403,7 @@ function toStories(component: string): Promise<string> { | |||
| 		glob('src/components/MkSignupServerRules.vue'), | ||||
| 		glob('src/components/MkUserSetupDialog.vue'), | ||||
| 		glob('src/components/MkUserSetupDialog.*.vue'), | ||||
| 		glob('src/components/MkInstanceCardMini.vue'), | ||||
| 		glob('src/components/MkInviteCode.vue'), | ||||
| 		glob('src/pages/user/home.vue'), | ||||
| 	]); | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ function getChartArray(seed: string, limit: number, option?: { accumulate?: bool | |||
| 	return array; | ||||
| } | ||||
| 
 | ||||
| function getChartResolver(fields: string[], option?: { accumulate?: boolean, mulMap?: Record<string, number> }): HttpResponseResolver<PathParams, DefaultBodyType, JsonBodyType> { | ||||
| export function getChartResolver(fields: string[], option?: { accumulate?: boolean, mulMap?: Record<string, number> }): HttpResponseResolver<PathParams, DefaultBodyType, JsonBodyType> { | ||||
| 	return ({ request }) => { | ||||
| 		action(`GET ${request.url}`)(); | ||||
| 		const limitParam = new URL(request.url).searchParams.get('limit'); | ||||
|  |  | |||
|  | @ -0,0 +1,64 @@ | |||
| /* | ||||
|  * SPDX-FileCopyrightText: syuilo and misskey-project | ||||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
| 
 | ||||
| /* eslint-disable @typescript-eslint/explicit-function-return-type */ | ||||
| import { StoryObj } from '@storybook/vue3'; | ||||
| import { HttpResponse, http } from 'msw'; | ||||
| import { federationInstance } from '../../.storybook/fakes.js'; | ||||
| import { commonHandlers } from '../../.storybook/mocks.js'; | ||||
| import MkInstanceCardMini from './MkInstanceCardMini.vue'; | ||||
| import { getChartResolver } from './MkChart.stories.impl.js'; | ||||
| export const Default = { | ||||
| 	render(args) { | ||||
| 		return { | ||||
| 			components: { | ||||
| 				MkInstanceCardMini, | ||||
| 			}, | ||||
| 			setup() { | ||||
| 				return { | ||||
| 					args, | ||||
| 				}; | ||||
| 			}, | ||||
| 			computed: { | ||||
| 				props() { | ||||
| 					return { | ||||
| 						...this.args, | ||||
| 					}; | ||||
| 				}, | ||||
| 			}, | ||||
| 			template: '<MkInstanceCardMini v-bind="props" />', | ||||
| 		}; | ||||
| 	}, | ||||
| 	args: { | ||||
| 		instance: federationInstance(), | ||||
| 	}, | ||||
| 	parameters: { | ||||
| 		layout: 'centered', | ||||
| 		msw: { | ||||
| 			handlers: [ | ||||
| 				...commonHandlers, | ||||
| 				http.get('/undefined/preview.webp', async ({ request }) => { | ||||
| 					const urlStr = new URL(request.url).searchParams.get('url'); | ||||
| 					if (urlStr == null) { | ||||
| 						return new HttpResponse(null, { status: 404 }); | ||||
| 					} | ||||
| 					const url = new URL(urlStr); | ||||
| 
 | ||||
| 					if (url.href.startsWith('https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/')) { | ||||
| 						const image = await (await fetch(`client-assets/${url.pathname.split('/').pop()}`)).blob(); | ||||
| 						return new HttpResponse(image, { | ||||
| 							headers: { | ||||
| 								'Content-Type': 'image/jpeg', | ||||
| 							}, | ||||
| 						}); | ||||
| 					} else { | ||||
| 						return new HttpResponse(null, { status: 404 }); | ||||
| 					} | ||||
| 				}), | ||||
| 				http.get('/api/charts/instance', getChartResolver(['requests.received'])), | ||||
| 			], | ||||
| 		}, | ||||
| 	}, | ||||
| } satisfies StoryObj<typeof MkInstanceCardMini>; | ||||
|  | @ -29,8 +29,8 @@ const chartValues = ref<number[] | null>(null); | |||
| 
 | ||||
| misskeyApiGet('charts/instance', { host: props.instance.host, limit: 16 + 1, span: 'day' }).then(res => { | ||||
| 	// 今日のぶんの値はまだ途中の値であり、それも含めると大抵の場合前日よりも下降しているようなグラフになってしまうため今日は弾く | ||||
| 	res['requests.received'].splice(0, 1); | ||||
| 	chartValues.value = res['requests.received']; | ||||
| 	res.requests.received.splice(0, 1); | ||||
| 	chartValues.value = res.requests.received; | ||||
| }); | ||||
| 
 | ||||
| function getInstanceIcon(instance): string { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue