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 1/6] =?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; }>(); From 2ebf79d85566b4fc166875a626468c8c7280a62a Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Thu, 24 Oct 2024 20:27:23 +0900 Subject: [PATCH 2/6] build misskey-js with types --- packages/misskey-js/src/autogen/types.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 560960f018..c438c05108 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -1227,13 +1227,6 @@ export type paths = { post: operations['charts___user___drive']; }; '/charts/user/following': { - /** - * charts/user/following - * @description No description provided. - * - * **Credential required**: *No* - */ - get: operations['charts___user___following']; /** * charts/user/following * @description No description provided. From 18380f8e01d1432c9c7cd9000b2f72b67f010f60 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Thu, 24 Oct 2024 20:29:52 +0900 Subject: [PATCH 3/6] refactor --- .../src/server/api/endpoints/charts/user/following.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 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 711a2fd082..15b318fa89 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/following.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/following.ts @@ -60,16 +60,13 @@ export default class extends Endpoint { // eslint- done(); } - const iAmModerator = await this.roleService.isModerator(me); - - if (iAmModerator) { + if (me != null && me.id === ps.userId) { done(); } - if ( - (profile.followingVisibility === 'private' || profile.followersVisibility === 'private') && - (me != null && profile.userId === me.id) - ) { + const iAmModerator = await this.roleService.isModerator(me); + + if (iAmModerator) { done(); } From 8f04bacef38bc44c39c59a82f0f0b9a193548dba Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Thu, 24 Oct 2024 20:30:35 +0900 Subject: [PATCH 4/6] refactor --- .../server/api/endpoints/charts/user/following.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 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 15b318fa89..4462e4a5bb 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/following.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/following.ts @@ -54,20 +54,20 @@ export default class extends Endpoint { // eslint- return await this.perUserFollowingChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId); }; + if (me != null && me.id === ps.userId) { + return await done(); + } + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: ps.userId }); if (profile.followingVisibility === 'public' && profile.followersVisibility === 'public') { - done(); - } - - if (me != null && me.id === ps.userId) { - done(); + return await done(); } const iAmModerator = await this.roleService.isModerator(me); if (iAmModerator) { - done(); + return await done(); } if ( @@ -79,7 +79,7 @@ export default class extends Endpoint { // eslint- ) { const relations = await this.userEntityService.getRelation(me.id, ps.userId); if (relations.following) { - done(); + return await done(); } } From 0866960342401bfb800530c65ebba2fe0d406ab5 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Thu, 24 Oct 2024 20:31:28 +0900 Subject: [PATCH 5/6] fix --- .../backend/src/server/api/endpoints/charts/user/following.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4462e4a5bb..67efc83246 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/following.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/following.ts @@ -23,7 +23,7 @@ export const meta = { 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', + id: '52e90f27-3dfd-441e-a1f2-ca6ac7068040', }, }, } as const; From 950c67979a3348ea76e3fee47e67e4caa0e15321 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Thu, 24 Oct 2024 21:29:41 +0900 Subject: [PATCH 6/6] lint --- packages/frontend/src/components/MkChart.vue | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue index cbe08b977b..2302e6c16a 100644 --- a/packages/frontend/src/components/MkChart.vue +++ b/packages/frontend/src/components/MkChart.vue @@ -761,7 +761,8 @@ const fetchPerUserFollowingChart = async (): Promise => { const raw = await misskeyApi('charts/user/following', { userId: props.args?.user?.id!, limit: props.limit, span: props.span }).catch(() => { return null; }); - return raw != null ? { + if (raw == null) return null; + return { series: [{ name: 'Local', type: 'area', @@ -771,14 +772,15 @@ const fetchPerUserFollowingChart = async (): Promise => { type: 'area', data: format(raw.remote.followings.total), }], - } : raw; + }; }; const fetchPerUserFollowersChart = async (): Promise => { const raw = await misskeyApi('charts/user/following', { userId: props.args?.user?.id!, limit: props.limit, span: props.span }).catch(() => { return null; }); - return raw != null ? { + if (raw == null) return null; + return { series: [{ name: 'Local', type: 'area', @@ -788,7 +790,7 @@ const fetchPerUserFollowersChart = async (): Promise => { type: 'area', data: format(raw.remote.followers.total), }], - } : raw; + }; }; const fetchPerUserDriveChart = async (): Promise => {