parent
							
								
									7be4b2145b
								
							
						
					
					
						commit
						58e83f8e4f
					
				
					 24 changed files with 146 additions and 46 deletions
				
			
		|  | @ -12,6 +12,7 @@ You should also include the user name that made the change. | ||||||
| ## 12.x.x (unreleased) | ## 12.x.x (unreleased) | ||||||
| 
 | 
 | ||||||
| ### Improvements | ### Improvements | ||||||
|  | - Server: Allow GET method for some endpoints @syuilo | ||||||
| - Server: Add rate limit to i/notifications @tamaina | - Server: Add rate limit to i/notifications @tamaina | ||||||
| - Client: Improve control panel @syuilo | - Client: Improve control panel @syuilo | ||||||
| - Client: Show warning in control panel when there is an unresolved abuse report @syuilo | - Client: Show warning in control panel when there is an unresolved abuse report @syuilo | ||||||
|  |  | ||||||
|  | @ -6,7 +6,11 @@ import call from './call.js'; | ||||||
| import { ApiError } from './error.js'; | import { ApiError } from './error.js'; | ||||||
| 
 | 
 | ||||||
| export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise<void>((res) => { | export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise<void>((res) => { | ||||||
| 	const body = ctx.request.body; | 	const body = ctx.is('multipart/form-data') | ||||||
|  | 		? (ctx.req as any).body | ||||||
|  | 		: ctx.method === 'GET' | ||||||
|  | 			? ctx.query | ||||||
|  | 			: ctx.request.body; | ||||||
| 
 | 
 | ||||||
| 	const reply = (x?: any, y?: ApiError) => { | 	const reply = (x?: any, y?: ApiError) => { | ||||||
| 		if (x == null) { | 		if (x == null) { | ||||||
|  | @ -33,6 +37,9 @@ export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise<void>((res | ||||||
| 	authenticate(body['i']).then(([user, app]) => { | 	authenticate(body['i']).then(([user, app]) => { | ||||||
| 		// API invoking
 | 		// API invoking
 | ||||||
| 		call(endpoint.name, user, app, body, ctx).then((res: any) => { | 		call(endpoint.name, user, app, body, ctx).then((res: any) => { | ||||||
|  | 			if (ctx.method === 'GET' && endpoint.meta.cacheSec && !body['i'] && !user) { | ||||||
|  | 				ctx.set('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`); | ||||||
|  | 			} | ||||||
| 			reply(res); | 			reply(res); | ||||||
| 		}).catch((e: ApiError) => { | 		}).catch((e: ApiError) => { | ||||||
| 			reply(e.httpStatusCode ? e.httpStatusCode : e.kind === 'client' ? 400 : 500, e); | 			reply(e.httpStatusCode ? e.httpStatusCode : e.kind === 'client' ? 400 : 500, e); | ||||||
|  |  | ||||||
|  | @ -94,7 +94,7 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Cast non JSON input
 | 	// Cast non JSON input
 | ||||||
| 	if (ep.meta.requireFile && ep.params.properties) { | 	if ((ep.meta.requireFile || ctx?.method === 'GET') && ep.params.properties) { | ||||||
| 		for (const k of Object.keys(ep.params.properties)) { | 		for (const k of Object.keys(ep.params.properties)) { | ||||||
| 			const param = ep.params.properties![k]; | 			const param = ep.params.properties![k]; | ||||||
| 			if (['boolean', 'number', 'integer'].includes(param.type ?? '') && typeof data[k] === 'string') { | 			if (['boolean', 'number', 'integer'].includes(param.type ?? '') && typeof data[k] === 'string') { | ||||||
|  |  | ||||||
|  | @ -701,6 +701,16 @@ export interface IEndpointMeta { | ||||||
| 	readonly kind?: string; | 	readonly kind?: string; | ||||||
| 
 | 
 | ||||||
| 	readonly description?: string; | 	readonly description?: string; | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * GETでのリクエストを許容するか否か | ||||||
|  | 	 */ | ||||||
|  | 	readonly allowGet?: boolean; | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * 正常応答をキャッシュ (Cache-Control: public) する秒数 | ||||||
|  | 	 */ | ||||||
|  | 	readonly cacheSec?: number; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface IEndpoint { | export interface IEndpoint { | ||||||
|  |  | ||||||
|  | @ -1,11 +1,14 @@ | ||||||
| import define from '../../define.js'; |  | ||||||
| import { getJsonSchema } from '@/services/chart/core.js'; | import { getJsonSchema } from '@/services/chart/core.js'; | ||||||
| import { activeUsersChart } from '@/services/chart/index.js'; | import { activeUsersChart } from '@/services/chart/index.js'; | ||||||
|  | import define from '../../define.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['charts', 'users'], | 	tags: ['charts', 'users'], | ||||||
| 
 | 
 | ||||||
| 	res: getJsonSchema(activeUsersChart.schema), | 	res: getJsonSchema(activeUsersChart.schema), | ||||||
|  | 
 | ||||||
|  | 	allowGet: true, | ||||||
|  | 	cacheSec: 60 * 60, | ||||||
| } as const; | } as const; | ||||||
| 
 | 
 | ||||||
| export const paramDef = { | export const paramDef = { | ||||||
|  |  | ||||||
|  | @ -1,11 +1,14 @@ | ||||||
| import define from '../../define.js'; |  | ||||||
| import { getJsonSchema } from '@/services/chart/core.js'; | import { getJsonSchema } from '@/services/chart/core.js'; | ||||||
| import { apRequestChart } from '@/services/chart/index.js'; | import { apRequestChart } from '@/services/chart/index.js'; | ||||||
|  | import define from '../../define.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['charts'], | 	tags: ['charts'], | ||||||
| 
 | 
 | ||||||
| 	res: getJsonSchema(apRequestChart.schema), | 	res: getJsonSchema(apRequestChart.schema), | ||||||
|  | 
 | ||||||
|  | 	allowGet: true, | ||||||
|  | 	cacheSec: 60 * 60, | ||||||
| } as const; | } as const; | ||||||
| 
 | 
 | ||||||
| export const paramDef = { | export const paramDef = { | ||||||
|  |  | ||||||
|  | @ -1,11 +1,14 @@ | ||||||
| import define from '../../define.js'; |  | ||||||
| import { getJsonSchema } from '@/services/chart/core.js'; | import { getJsonSchema } from '@/services/chart/core.js'; | ||||||
| import { driveChart } from '@/services/chart/index.js'; | import { driveChart } from '@/services/chart/index.js'; | ||||||
|  | import define from '../../define.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['charts', 'drive'], | 	tags: ['charts', 'drive'], | ||||||
| 
 | 
 | ||||||
| 	res: getJsonSchema(driveChart.schema), | 	res: getJsonSchema(driveChart.schema), | ||||||
|  | 
 | ||||||
|  | 	allowGet: true, | ||||||
|  | 	cacheSec: 60 * 60, | ||||||
| } as const; | } as const; | ||||||
| 
 | 
 | ||||||
| export const paramDef = { | export const paramDef = { | ||||||
|  |  | ||||||
|  | @ -1,11 +1,14 @@ | ||||||
| import define from '../../define.js'; |  | ||||||
| import { getJsonSchema } from '@/services/chart/core.js'; | import { getJsonSchema } from '@/services/chart/core.js'; | ||||||
| import { federationChart } from '@/services/chart/index.js'; | import { federationChart } from '@/services/chart/index.js'; | ||||||
|  | import define from '../../define.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['charts'], | 	tags: ['charts'], | ||||||
| 
 | 
 | ||||||
| 	res: getJsonSchema(federationChart.schema), | 	res: getJsonSchema(federationChart.schema), | ||||||
|  | 
 | ||||||
|  | 	allowGet: true, | ||||||
|  | 	cacheSec: 60 * 60, | ||||||
| } as const; | } as const; | ||||||
| 
 | 
 | ||||||
| export const paramDef = { | export const paramDef = { | ||||||
|  |  | ||||||
|  | @ -1,11 +1,14 @@ | ||||||
| import define from '../../define.js'; |  | ||||||
| import { getJsonSchema } from '@/services/chart/core.js'; | import { getJsonSchema } from '@/services/chart/core.js'; | ||||||
| import { hashtagChart } from '@/services/chart/index.js'; | import { hashtagChart } from '@/services/chart/index.js'; | ||||||
|  | import define from '../../define.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['charts', 'hashtags'], | 	tags: ['charts', 'hashtags'], | ||||||
| 
 | 
 | ||||||
| 	res: getJsonSchema(hashtagChart.schema), | 	res: getJsonSchema(hashtagChart.schema), | ||||||
|  | 
 | ||||||
|  | 	allowGet: true, | ||||||
|  | 	cacheSec: 60 * 60, | ||||||
| } as const; | } as const; | ||||||
| 
 | 
 | ||||||
| export const paramDef = { | export const paramDef = { | ||||||
|  |  | ||||||
|  | @ -1,11 +1,14 @@ | ||||||
| import define from '../../define.js'; |  | ||||||
| import { getJsonSchema } from '@/services/chart/core.js'; | import { getJsonSchema } from '@/services/chart/core.js'; | ||||||
| import { instanceChart } from '@/services/chart/index.js'; | import { instanceChart } from '@/services/chart/index.js'; | ||||||
|  | import define from '../../define.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['charts'], | 	tags: ['charts'], | ||||||
| 
 | 
 | ||||||
| 	res: getJsonSchema(instanceChart.schema), | 	res: getJsonSchema(instanceChart.schema), | ||||||
|  | 
 | ||||||
|  | 	allowGet: true, | ||||||
|  | 	cacheSec: 60 * 60, | ||||||
| } as const; | } as const; | ||||||
| 
 | 
 | ||||||
| export const paramDef = { | export const paramDef = { | ||||||
|  |  | ||||||
|  | @ -1,11 +1,14 @@ | ||||||
| import define from '../../define.js'; |  | ||||||
| import { getJsonSchema } from '@/services/chart/core.js'; | import { getJsonSchema } from '@/services/chart/core.js'; | ||||||
| import { notesChart } from '@/services/chart/index.js'; | import { notesChart } from '@/services/chart/index.js'; | ||||||
|  | import define from '../../define.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['charts', 'notes'], | 	tags: ['charts', 'notes'], | ||||||
| 
 | 
 | ||||||
| 	res: getJsonSchema(notesChart.schema), | 	res: getJsonSchema(notesChart.schema), | ||||||
|  | 
 | ||||||
|  | 	allowGet: true, | ||||||
|  | 	cacheSec: 60 * 60, | ||||||
| } as const; | } as const; | ||||||
| 
 | 
 | ||||||
| export const paramDef = { | export const paramDef = { | ||||||
|  |  | ||||||
|  | @ -1,11 +1,14 @@ | ||||||
| import define from '../../../define.js'; |  | ||||||
| import { getJsonSchema } from '@/services/chart/core.js'; | import { getJsonSchema } from '@/services/chart/core.js'; | ||||||
| import { perUserDriveChart } from '@/services/chart/index.js'; | import { perUserDriveChart } from '@/services/chart/index.js'; | ||||||
|  | import define from '../../../define.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['charts', 'drive', 'users'], | 	tags: ['charts', 'drive', 'users'], | ||||||
| 
 | 
 | ||||||
| 	res: getJsonSchema(perUserDriveChart.schema), | 	res: getJsonSchema(perUserDriveChart.schema), | ||||||
|  | 
 | ||||||
|  | 	allowGet: true, | ||||||
|  | 	cacheSec: 60 * 60, | ||||||
| } as const; | } as const; | ||||||
| 
 | 
 | ||||||
| export const paramDef = { | export const paramDef = { | ||||||
|  |  | ||||||
|  | @ -6,6 +6,9 @@ export const meta = { | ||||||
| 	tags: ['charts', 'users', 'following'], | 	tags: ['charts', 'users', 'following'], | ||||||
| 
 | 
 | ||||||
| 	res: getJsonSchema(perUserFollowingChart.schema), | 	res: getJsonSchema(perUserFollowingChart.schema), | ||||||
|  | 
 | ||||||
|  | 	allowGet: true, | ||||||
|  | 	cacheSec: 60 * 60, | ||||||
| } as const; | } as const; | ||||||
| 
 | 
 | ||||||
| export const paramDef = { | export const paramDef = { | ||||||
|  |  | ||||||
|  | @ -1,11 +1,14 @@ | ||||||
| import define from '../../../define.js'; |  | ||||||
| import { getJsonSchema } from '@/services/chart/core.js'; | import { getJsonSchema } from '@/services/chart/core.js'; | ||||||
| import { perUserNotesChart } from '@/services/chart/index.js'; | import { perUserNotesChart } from '@/services/chart/index.js'; | ||||||
|  | import define from '../../../define.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['charts', 'users', 'notes'], | 	tags: ['charts', 'users', 'notes'], | ||||||
| 
 | 
 | ||||||
| 	res: getJsonSchema(perUserNotesChart.schema), | 	res: getJsonSchema(perUserNotesChart.schema), | ||||||
|  | 
 | ||||||
|  | 	allowGet: true, | ||||||
|  | 	cacheSec: 60 * 60, | ||||||
| } as const; | } as const; | ||||||
| 
 | 
 | ||||||
| export const paramDef = { | export const paramDef = { | ||||||
|  |  | ||||||
|  | @ -1,11 +1,14 @@ | ||||||
| import define from '../../../define.js'; |  | ||||||
| import { getJsonSchema } from '@/services/chart/core.js'; | import { getJsonSchema } from '@/services/chart/core.js'; | ||||||
| import { perUserReactionsChart } from '@/services/chart/index.js'; | import { perUserReactionsChart } from '@/services/chart/index.js'; | ||||||
|  | import define from '../../../define.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['charts', 'users', 'reactions'], | 	tags: ['charts', 'users', 'reactions'], | ||||||
| 
 | 
 | ||||||
| 	res: getJsonSchema(perUserReactionsChart.schema), | 	res: getJsonSchema(perUserReactionsChart.schema), | ||||||
|  | 
 | ||||||
|  | 	allowGet: true, | ||||||
|  | 	cacheSec: 60 * 60, | ||||||
| } as const; | } as const; | ||||||
| 
 | 
 | ||||||
| export const paramDef = { | export const paramDef = { | ||||||
|  |  | ||||||
|  | @ -1,11 +1,14 @@ | ||||||
| import define from '../../define.js'; |  | ||||||
| import { getJsonSchema } from '@/services/chart/core.js'; | import { getJsonSchema } from '@/services/chart/core.js'; | ||||||
| import { usersChart } from '@/services/chart/index.js'; | import { usersChart } from '@/services/chart/index.js'; | ||||||
|  | import define from '../../define.js'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['charts', 'users'], | 	tags: ['charts', 'users'], | ||||||
| 
 | 
 | ||||||
| 	res: getJsonSchema(usersChart.schema), | 	res: getJsonSchema(usersChart.schema), | ||||||
|  | 
 | ||||||
|  | 	allowGet: true, | ||||||
|  | 	cacheSec: 60 * 60, | ||||||
| } as const; | } as const; | ||||||
| 
 | 
 | ||||||
| export const paramDef = { | export const paramDef = { | ||||||
|  |  | ||||||
|  | @ -8,6 +8,8 @@ import multer from '@koa/multer'; | ||||||
| import bodyParser from 'koa-bodyparser'; | import bodyParser from 'koa-bodyparser'; | ||||||
| import cors from '@koa/cors'; | import cors from '@koa/cors'; | ||||||
| 
 | 
 | ||||||
|  | import { Instances, AccessTokens, Users } from '@/models/index.js'; | ||||||
|  | import config from '@/config/index.js'; | ||||||
| import endpoints from './endpoints.js'; | import endpoints from './endpoints.js'; | ||||||
| import handler from './api-handler.js'; | import handler from './api-handler.js'; | ||||||
| import signup from './private/signup.js'; | import signup from './private/signup.js'; | ||||||
|  | @ -16,8 +18,6 @@ import signupPending from './private/signup-pending.js'; | ||||||
| import discord from './service/discord.js'; | import discord from './service/discord.js'; | ||||||
| import github from './service/github.js'; | import github from './service/github.js'; | ||||||
| import twitter from './service/twitter.js'; | import twitter from './service/twitter.js'; | ||||||
| import { Instances, AccessTokens, Users } from '@/models/index.js'; |  | ||||||
| import config from '@/config/index.js'; |  | ||||||
| 
 | 
 | ||||||
| // Init app
 | // Init app
 | ||||||
| const app = new Koa(); | const app = new Koa(); | ||||||
|  | @ -56,11 +56,24 @@ for (const endpoint of endpoints) { | ||||||
| 	if (endpoint.meta.requireFile) { | 	if (endpoint.meta.requireFile) { | ||||||
| 		router.post(`/${endpoint.name}`, upload.single('file'), handler.bind(null, endpoint)); | 		router.post(`/${endpoint.name}`, upload.single('file'), handler.bind(null, endpoint)); | ||||||
| 	} else { | 	} else { | ||||||
|  | 		// 後方互換性のため
 | ||||||
| 		if (endpoint.name.includes('-')) { | 		if (endpoint.name.includes('-')) { | ||||||
| 			// 後方互換性のため
 |  | ||||||
| 			router.post(`/${endpoint.name.replace(/-/g, '_')}`, handler.bind(null, endpoint)); | 			router.post(`/${endpoint.name.replace(/-/g, '_')}`, handler.bind(null, endpoint)); | ||||||
|  | 
 | ||||||
|  | 			if (endpoint.meta.allowGet) { | ||||||
|  | 				router.get(`/${endpoint.name.replace(/-/g, '_')}`, handler.bind(null, endpoint)); | ||||||
|  | 			} else { | ||||||
|  | 				router.get(`/${endpoint.name.replace(/-/g, '_')}`, async ctx => { ctx.status = 405; }); | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
|  | 
 | ||||||
| 		router.post(`/${endpoint.name}`, handler.bind(null, endpoint)); | 		router.post(`/${endpoint.name}`, handler.bind(null, endpoint)); | ||||||
|  | 
 | ||||||
|  | 		if (endpoint.meta.allowGet) { | ||||||
|  | 			router.get(`/${endpoint.name}`, handler.bind(null, endpoint)); | ||||||
|  | 		} else { | ||||||
|  | 			router.get(`/${endpoint.name}`, async ctx => { ctx.status = 405; }); | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -342,7 +342,7 @@ const exportData = () => { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const fetchFederationChart = async (): Promise<typeof chartData> => { | const fetchFederationChart = async (): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.api('charts/federation', { limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/federation', { limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
| 		series: [{ | 		series: [{ | ||||||
| 			name: 'Received', | 			name: 'Received', | ||||||
|  | @ -392,7 +392,7 @@ const fetchFederationChart = async (): Promise<typeof chartData> => { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const fetchApRequestChart = async (): Promise<typeof chartData> => { | const fetchApRequestChart = async (): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.api('charts/ap-request', { limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/ap-request', { limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
| 		series: [{ | 		series: [{ | ||||||
| 			name: 'In', | 			name: 'In', | ||||||
|  | @ -414,7 +414,7 @@ const fetchApRequestChart = async (): Promise<typeof chartData> => { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const fetchNotesChart = async (type: string): Promise<typeof chartData> => { | const fetchNotesChart = async (type: string): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.api('charts/notes', { limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/notes', { limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
| 		series: [{ | 		series: [{ | ||||||
| 			name: 'All', | 			name: 'All', | ||||||
|  | @ -461,7 +461,7 @@ const fetchNotesChart = async (type: string): Promise<typeof chartData> => { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const fetchNotesTotalChart = async (): Promise<typeof chartData> => { | const fetchNotesTotalChart = async (): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.api('charts/notes', { limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/notes', { limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
| 		series: [{ | 		series: [{ | ||||||
| 			name: 'Combined', | 			name: 'Combined', | ||||||
|  | @ -480,7 +480,7 @@ const fetchNotesTotalChart = async (): Promise<typeof chartData> => { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const fetchUsersChart = async (total: boolean): Promise<typeof chartData> => { | const fetchUsersChart = async (total: boolean): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.api('charts/users', { limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/users', { limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
| 		series: [{ | 		series: [{ | ||||||
| 			name: 'Combined', | 			name: 'Combined', | ||||||
|  | @ -508,7 +508,7 @@ const fetchUsersChart = async (total: boolean): Promise<typeof chartData> => { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const fetchActiveUsersChart = async (): Promise<typeof chartData> => { | const fetchActiveUsersChart = async (): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.api('charts/active-users', { limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/active-users', { limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
| 		series: [{ | 		series: [{ | ||||||
| 			name: 'Read & Write', | 			name: 'Read & Write', | ||||||
|  | @ -560,7 +560,7 @@ const fetchActiveUsersChart = async (): Promise<typeof chartData> => { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const fetchDriveChart = async (): Promise<typeof chartData> => { | const fetchDriveChart = async (): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.api('charts/drive', { limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/drive', { limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
| 		bytes: true, | 		bytes: true, | ||||||
| 		series: [{ | 		series: [{ | ||||||
|  | @ -596,7 +596,7 @@ const fetchDriveChart = async (): Promise<typeof chartData> => { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const fetchDriveFilesChart = async (): Promise<typeof chartData> => { | const fetchDriveFilesChart = async (): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.api('charts/drive', { limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/drive', { limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
| 		series: [{ | 		series: [{ | ||||||
| 			name: 'All', | 			name: 'All', | ||||||
|  | @ -631,7 +631,7 @@ const fetchDriveFilesChart = async (): Promise<typeof chartData> => { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const fetchInstanceRequestsChart = async (): Promise<typeof chartData> => { | const fetchInstanceRequestsChart = async (): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
| 		series: [{ | 		series: [{ | ||||||
| 			name: 'In', | 			name: 'In', | ||||||
|  | @ -653,7 +653,7 @@ const fetchInstanceRequestsChart = async (): Promise<typeof chartData> => { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const fetchInstanceUsersChart = async (total: boolean): Promise<typeof chartData> => { | const fetchInstanceUsersChart = async (total: boolean): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
| 		series: [{ | 		series: [{ | ||||||
| 			name: 'Users', | 			name: 'Users', | ||||||
|  | @ -668,7 +668,7 @@ const fetchInstanceUsersChart = async (total: boolean): Promise<typeof chartData | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const fetchInstanceNotesChart = async (total: boolean): Promise<typeof chartData> => { | const fetchInstanceNotesChart = async (total: boolean): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
| 		series: [{ | 		series: [{ | ||||||
| 			name: 'Notes', | 			name: 'Notes', | ||||||
|  | @ -683,7 +683,7 @@ const fetchInstanceNotesChart = async (total: boolean): Promise<typeof chartData | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const fetchInstanceFfChart = async (total: boolean): Promise<typeof chartData> => { | const fetchInstanceFfChart = async (total: boolean): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
| 		series: [{ | 		series: [{ | ||||||
| 			name: 'Following', | 			name: 'Following', | ||||||
|  | @ -706,7 +706,7 @@ const fetchInstanceFfChart = async (total: boolean): Promise<typeof chartData> = | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof chartData> => { | const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
| 		bytes: true, | 		bytes: true, | ||||||
| 		series: [{ | 		series: [{ | ||||||
|  | @ -722,7 +722,7 @@ const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof char | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof chartData> => { | const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
| 		series: [{ | 		series: [{ | ||||||
| 			name: 'Drive files', | 			name: 'Drive files', | ||||||
|  | @ -737,7 +737,7 @@ const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof char | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const fetchPerUserNotesChart = async (): Promise<typeof chartData> => { | const fetchPerUserNotesChart = async (): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.api('charts/user/notes', { userId: props.args.user.id, limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/user/notes', { userId: props.args.user.id, limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
| 		series: [...(props.args.withoutAll ? [] : [{ | 		series: [...(props.args.withoutAll ? [] : [{ | ||||||
| 			name: 'All', | 			name: 'All', | ||||||
|  | @ -769,7 +769,7 @@ const fetchPerUserNotesChart = async (): Promise<typeof chartData> => { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const fetchPerUserFollowingChart = async (): Promise<typeof chartData> => { | const fetchPerUserFollowingChart = async (): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.api('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
| 		series: [{ | 		series: [{ | ||||||
| 			name: 'Local', | 			name: 'Local', | ||||||
|  | @ -784,7 +784,7 @@ const fetchPerUserFollowingChart = async (): Promise<typeof chartData> => { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const fetchPerUserFollowersChart = async (): Promise<typeof chartData> => { | const fetchPerUserFollowersChart = async (): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.api('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
| 		series: [{ | 		series: [{ | ||||||
| 			name: 'Local', | 			name: 'Local', | ||||||
|  | @ -799,7 +799,7 @@ const fetchPerUserFollowersChart = async (): Promise<typeof chartData> => { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const fetchPerUserDriveChart = async (): Promise<typeof chartData> => { | const fetchPerUserDriveChart = async (): Promise<typeof chartData> => { | ||||||
| 	const raw = await os.api('charts/user/drive', { userId: props.args.user.id, limit: props.limit, span: props.span }); | 	const raw = await os.apiGet('charts/user/drive', { userId: props.args.user.id, limit: props.limit, span: props.span }); | ||||||
| 	return { | 	return { | ||||||
| 		series: [{ | 		series: [{ | ||||||
| 			name: 'Inc', | 			name: 'Inc', | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ const props = defineProps<{ | ||||||
| 
 | 
 | ||||||
| const chart = $ref(null); | const chart = $ref(null); | ||||||
| 
 | 
 | ||||||
| os.api('charts/instance', { host: props.instance.host, limit: 16, span: 'day' }).then(res => { | os.apiGet('charts/instance', { host: props.instance.host, limit: 16, span: 'day' }).then(res => { | ||||||
| 	chart = res; | 	chart = res; | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -21,7 +21,7 @@ const props = defineProps<{ | ||||||
| 
 | 
 | ||||||
| const chart = $ref(null); | const chart = $ref(null); | ||||||
| 
 | 
 | ||||||
| os.api('charts/user/notes', { userId: props.user.id, limit: 16, span: 'day' }).then(res => { | os.apiGet('charts/user/notes', { userId: props.user.id, limit: 16, span: 'day' }).then(res => { | ||||||
| 	chart = res; | 	chart = res; | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -52,6 +52,39 @@ export const api = ((endpoint: string, data: Record<string, any> = {}, token?: s | ||||||
| 	return promise; | 	return promise; | ||||||
| }) as typeof apiClient.request; | }) as typeof apiClient.request; | ||||||
| 
 | 
 | ||||||
|  | export const apiGet = ((endpoint: string, data: Record<string, any> = {}) => { | ||||||
|  | 	pendingApiRequestsCount.value++; | ||||||
|  | 
 | ||||||
|  | 	const onFinally = () => { | ||||||
|  | 		pendingApiRequestsCount.value--; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	const query = new URLSearchParams(data); | ||||||
|  | 
 | ||||||
|  | 	const promise = new Promise((resolve, reject) => { | ||||||
|  | 		// Send request
 | ||||||
|  | 		fetch(`${apiUrl}/${endpoint}?${query}`, { | ||||||
|  | 			method: 'GET', | ||||||
|  | 			credentials: 'omit', | ||||||
|  | 			cache: 'default', | ||||||
|  | 		}).then(async (res) => { | ||||||
|  | 			const body = res.status === 204 ? null : await res.json(); | ||||||
|  | 
 | ||||||
|  | 			if (res.status === 200) { | ||||||
|  | 				resolve(body); | ||||||
|  | 			} else if (res.status === 204) { | ||||||
|  | 				resolve(); | ||||||
|  | 			} else { | ||||||
|  | 				reject(body.error); | ||||||
|  | 			} | ||||||
|  | 		}).catch(reject); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	promise.then(onFinally, onFinally); | ||||||
|  | 
 | ||||||
|  | 	return promise; | ||||||
|  | }) as typeof apiClient.request; | ||||||
|  | 
 | ||||||
| export const apiWithDialog = (( | export const apiWithDialog = (( | ||||||
| 	endpoint: string, | 	endpoint: string, | ||||||
| 	data: Record<string, any> = {}, | 	data: Record<string, any> = {}, | ||||||
|  |  | ||||||
|  | @ -90,11 +90,11 @@ onMounted(async () => { | ||||||
| 	os.api('stats', {}).then(statsResponse => { | 	os.api('stats', {}).then(statsResponse => { | ||||||
| 		stats = statsResponse; | 		stats = statsResponse; | ||||||
| 
 | 
 | ||||||
| 		os.api('charts/users', { limit: 2, span: 'day' }).then(chart => { | 		os.apiGet('charts/users', { limit: 2, span: 'day' }).then(chart => { | ||||||
| 			usersComparedToThePrevDay = stats.originalUsersCount - chart.local.total[1]; | 			usersComparedToThePrevDay = stats.originalUsersCount - chart.local.total[1]; | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 		os.api('charts/notes', { limit: 2, span: 'day' }).then(chart => { | 		os.apiGet('charts/notes', { limit: 2, span: 'day' }).then(chart => { | ||||||
| 			notesComparedToThePrevDay = stats.originalNotesCount - chart.local.total[1]; | 			notesComparedToThePrevDay = stats.originalNotesCount - chart.local.total[1]; | ||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
|  | @ -15,12 +15,12 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'; | import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'; | ||||||
| import { GetFormResultType } from '@/scripts/form'; |  | ||||||
| import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; | import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; | ||||||
| import * as os from '@/os'; |  | ||||||
| import MkContainer from '@/components/ui/container.vue'; |  | ||||||
| import XCalendar from './activity.calendar.vue'; | import XCalendar from './activity.calendar.vue'; | ||||||
| import XChart from './activity.chart.vue'; | import XChart from './activity.chart.vue'; | ||||||
|  | import { GetFormResultType } from '@/scripts/form'; | ||||||
|  | import * as os from '@/os'; | ||||||
|  | import MkContainer from '@/components/ui/container.vue'; | ||||||
| import { $i } from '@/account'; | import { $i } from '@/account'; | ||||||
| 
 | 
 | ||||||
| const name = 'activity'; | const name = 'activity'; | ||||||
|  | @ -67,7 +67,7 @@ const toggleView = () => { | ||||||
| 	save(); | 	save(); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| os.api('charts/user/notes', { | os.apiGet('charts/user/notes', { | ||||||
| 	userId: $i.id, | 	userId: $i.id, | ||||||
| 	span: 'day', | 	span: 'day', | ||||||
| 	limit: 7 * 21, | 	limit: 7 * 21, | ||||||
|  | @ -76,7 +76,7 @@ os.api('charts/user/notes', { | ||||||
| 		total: res.diffs.normal[i] + res.diffs.reply[i] + res.diffs.renote[i], | 		total: res.diffs.normal[i] + res.diffs.reply[i] + res.diffs.renote[i], | ||||||
| 		notes: res.diffs.normal[i], | 		notes: res.diffs.normal[i], | ||||||
| 		replies: res.diffs.reply[i], | 		replies: res.diffs.reply[i], | ||||||
| 		renotes: res.diffs.renote[i] | 		renotes: res.diffs.renote[i], | ||||||
| 	})); | 	})); | ||||||
| 	fetching.value = false; | 	fetching.value = false; | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -20,8 +20,8 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { onMounted, onUnmounted, ref } from 'vue'; | import { onMounted, onUnmounted, ref } from 'vue'; | ||||||
| import { GetFormResultType } from '@/scripts/form'; |  | ||||||
| import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; | import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; | ||||||
|  | import { GetFormResultType } from '@/scripts/form'; | ||||||
| import MkContainer from '@/components/ui/container.vue'; | import MkContainer from '@/components/ui/container.vue'; | ||||||
| import MkMiniChart from '@/components/mini-chart.vue'; | import MkMiniChart from '@/components/mini-chart.vue'; | ||||||
| import * as os from '@/os'; | import * as os from '@/os'; | ||||||
|  | @ -56,9 +56,9 @@ const fetching = ref(true); | ||||||
| const fetch = async () => { | const fetch = async () => { | ||||||
| 	const fetchedInstances = await os.api('federation/instances', { | 	const fetchedInstances = await os.api('federation/instances', { | ||||||
| 		sort: '+lastCommunicatedAt', | 		sort: '+lastCommunicatedAt', | ||||||
| 		limit: 5 | 		limit: 5, | ||||||
| 	}); | 	}); | ||||||
| 	const fetchedCharts = await Promise.all(fetchedInstances.map(i => os.api('charts/instance', { host: i.host, limit: 16, span: 'hour' }))); | 	const fetchedCharts = await Promise.all(fetchedInstances.map(i => os.apiGet('charts/instance', { host: i.host, limit: 16, span: 'hour' }))); | ||||||
| 	instances.value = fetchedInstances; | 	instances.value = fetchedInstances; | ||||||
| 	charts.value = fetchedCharts; | 	charts.value = fetchedCharts; | ||||||
| 	fetching.value = false; | 	fetching.value = false; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue