From 8bacd4aa279a1fbc484e0fede23e4ecf804f0f31 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Thu, 24 Oct 2024 20:25:42 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=E3=83=95=E3=82=A9=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=83=BB=E3=83=95=E3=82=A9=E3=83=AD=E3=83=AF=E3=83=BC=E3=81=AE?= =?UTF-8?q?=E5=85=AC=E9=96=8B=E7=8A=B6=E6=B3=81=E3=81=AB=E3=82=88=E3=81=A3?= =?UTF-8?q?=E3=81=A6=E3=81=AF=E3=83=95=E3=82=A9=E3=83=AD=E3=83=BC=E9=96=A2?= =?UTF-8?q?=E9=80=A3=E3=81=AE=E3=83=81=E3=83=A3=E3=83=BC=E3=83=88=E3=82=92?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=81=A7=E3=81=8D=E3=81=AA=E3=81=84=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/endpoints/charts/user/following.ts | 59 +++++++++++++++++-- packages/frontend/src/components/MkChart.vue | 18 +++--- .../src/pages/user/activity.following.vue | 4 +- packages/frontend/src/pages/user/activity.vue | 8 ++- 4 files changed, 74 insertions(+), 15 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/charts/user/following.ts b/packages/backend/src/server/api/endpoints/charts/user/following.ts index 0a019ce4fb..711a2fd082 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/following.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/following.ts @@ -3,19 +3,29 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Injectable, Inject } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import type { UserProfilesRepository } from '@/models/_.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { RoleService } from '@/core/RoleService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { getJsonSchema } from '@/core/chart/core.js'; import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js'; import { schema } from '@/core/chart/charts/entities/per-user-following.js'; +import { ApiError } from '@/server/api/error.js'; export const meta = { tags: ['charts', 'users', 'following'], res: getJsonSchema(schema), - allowGet: true, - cacheSec: 60 * 60, + errors: { + ffIsMarkedAsPrivate: { + message: 'This user\'s followings and/or followers is marked as private.', + code: 'FF_IS_MARKED_AS_PRIVATE', + id: 'f9c54d7f-d4c2-4d3c-9a8g-a70daac86512', + }, + }, } as const; export const paramDef = { @@ -32,10 +42,51 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + private roleService: RoleService, + private userEntityService: UserEntityService, private perUserFollowingChart: PerUserFollowingChart, ) { super(meta, paramDef, async (ps, me) => { - return await this.perUserFollowingChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); + const done = async () => { + return await this.perUserFollowingChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); + }; + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: ps.userId }); + + if (profile.followingVisibility === 'public' && profile.followersVisibility === 'public') { + done(); + } + + const iAmModerator = await this.roleService.isModerator(me); + + if (iAmModerator) { + done(); + } + + if ( + (profile.followingVisibility === 'private' || profile.followersVisibility === 'private') && + (me != null && profile.userId === me.id) + ) { + done(); + } + + if ( + me != null && ( + (profile.followingVisibility === 'followers' && profile.followersVisibility === 'followers') || + (profile.followingVisibility === 'followers' && profile.followersVisibility === 'public') || + (profile.followingVisibility === 'public' && profile.followersVisibility === 'followers') + ) + ) { + const relations = await this.userEntityService.getRelation(me.id, ps.userId); + if (relations.following) { + done(); + } + } + + throw new ApiError(meta.errors.ffIsMarkedAsPrivate); }); } } diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue index d05f4921f6..cbe08b977b 100644 --- a/packages/frontend/src/components/MkChart.vue +++ b/packages/frontend/src/components/MkChart.vue @@ -53,7 +53,7 @@ export type ChartSrc = import { onMounted, ref, shallowRef, watch } from 'vue'; import { Chart } from 'chart.js'; import * as Misskey from 'misskey-js'; -import { misskeyApiGet } from '@/scripts/misskey-api.js'; +import { misskeyApiGet, misskeyApi } from '@/scripts/misskey-api.js'; import { defaultStore } from '@/store.js'; import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; import { chartVLine } from '@/scripts/chart-vline.js'; @@ -758,8 +758,10 @@ const fetchPerUserPvChart = async (): Promise => { }; const fetchPerUserFollowingChart = async (): Promise => { - const raw = await misskeyApiGet('charts/user/following', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); - return { + const raw = await misskeyApi('charts/user/following', { userId: props.args?.user?.id!, limit: props.limit, span: props.span }).catch(() => { + return null; + }); + return raw != null ? { series: [{ name: 'Local', type: 'area', @@ -769,12 +771,14 @@ const fetchPerUserFollowingChart = async (): Promise => { type: 'area', data: format(raw.remote.followings.total), }], - }; + } : raw; }; const fetchPerUserFollowersChart = async (): Promise => { - const raw = await misskeyApiGet('charts/user/following', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); - return { + const raw = await misskeyApi('charts/user/following', { userId: props.args?.user?.id!, limit: props.limit, span: props.span }).catch(() => { + return null; + }); + return raw != null ? { series: [{ name: 'Local', type: 'area', @@ -784,7 +788,7 @@ const fetchPerUserFollowersChart = async (): Promise => { type: 'area', data: format(raw.remote.followers.total), }], - }; + } : raw; }; const fetchPerUserDriveChart = async (): Promise => { diff --git a/packages/frontend/src/pages/user/activity.following.vue b/packages/frontend/src/pages/user/activity.following.vue index aa2c791c76..15963f0aaa 100644 --- a/packages/frontend/src/pages/user/activity.following.vue +++ b/packages/frontend/src/pages/user/activity.following.vue @@ -32,7 +32,7 @@ const props = defineProps<{ user: Misskey.entities.User; }>(); -const chartEl = shallowRef(null); +const chartEl = shallowRef(); const legendEl = shallowRef>(); const now = new Date(); let chartInstance: Chart = null; @@ -88,7 +88,7 @@ async function renderChart() { }, extra); } - chartInstance = new Chart(chartEl.value, { + chartInstance = new Chart(chartEl.value!, { type: 'bar', data: { datasets: [ diff --git a/packages/frontend/src/pages/user/activity.vue b/packages/frontend/src/pages/user/activity.vue index 994bd52705..f555d72132 100644 --- a/packages/frontend/src/pages/user/activity.vue +++ b/packages/frontend/src/pages/user/activity.vue @@ -14,7 +14,10 @@ SPDX-License-Identifier: AGPL-3.0-only - + @@ -33,9 +36,10 @@ import XNotes from './activity.notes.vue'; import XFollowing from './activity.following.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; import MkHeatmap from '@/components/MkHeatmap.vue'; +import { isFollowersVisibleForMe, isFollowingVisibleForMe } from '@/scripts/isFfVisibleForMe.js'; const props = defineProps<{ - user: Misskey.entities.User; + user: Misskey.entities.UserDetailed; }>();