Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance: “つながりの公開範囲”がフォロー・フォロワー個別設定できるように #12702

Merged
merged 5 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
- Feat: TL上からノートが見えなくなるワードミュートであるハードミュートを追加
- Enhance: アイコンデコレーションを複数設定できるように
- Enhance: アイコンデコレーションの位置を微調整できるように
- Enhance: つながりの公開範囲をフォロー/フォロワーで個別に設定可能に #12072
- Fix: MFM `$[unixtime ]` に不正な値を入力した際に発生する各種エラーを修正

### Client
Expand Down
4 changes: 2 additions & 2 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -884,8 +884,8 @@ export interface Locale {
"classic": string;
"muteThread": string;
"unmuteThread": string;
"ffVisibility": string;
"ffVisibilityDescription": string;
"followingVisibility": string;
"followersVisibility": string;
"continueThread": string;
"deleteAccountConfirm": string;
"incorrectPassword": string;
Expand Down
4 changes: 2 additions & 2 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -881,8 +881,8 @@ makeReactionsPublicDescription: "あなたがしたリアクション一覧を
classic: "クラシック"
muteThread: "スレッドをミュート"
unmuteThread: "スレッドのミュートを解除"
ffVisibility: "つながりの公開範囲"
ffVisibilityDescription: "自分のフォロー/フォロワー情報の公開範囲を設定できます。"
followingVisibility: "フォローの公開範囲"
followersVisibility: "フォロワーの公開範囲"
continueThread: "さらにスレッドを見る"
deleteAccountConfirm: "アカウントが削除されます。よろしいですか?"
incorrectPassword: "パスワードが間違っています。"
Expand Down
35 changes: 35 additions & 0 deletions packages/backend/migration/1702718871541-ffVisibility.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/

export class ffVisibility1702718871541 {
constructor() {
this.name = 'ffVisibility1702718871541';
}
async up(queryRunner) {
await queryRunner.query(`CREATE TYPE "public"."user_profile_followingvisibility_enum" AS ENUM('public', 'followers', 'private')`);
await queryRunner.query(`CREATE CAST ("public"."user_profile_ffvisibility_enum" AS "public"."user_profile_followingvisibility_enum") WITH INOUT AS ASSIGNMENT`);
await queryRunner.query(`CREATE TYPE "public"."user_profile_followersVisibility_enum" AS ENUM('public', 'followers', 'private')`);
await queryRunner.query(`CREATE CAST ("public"."user_profile_ffvisibility_enum" AS "public"."user_profile_followersVisibility_enum") WITH INOUT AS ASSIGNMENT`);
await queryRunner.query(`ALTER TABLE "user_profile" ADD "followingVisibility" "public"."user_profile_followingvisibility_enum" NOT NULL DEFAULT 'public'`);
await queryRunner.query(`ALTER TABLE "user_profile" ADD "followersVisibility" "public"."user_profile_followersVisibility_enum" NOT NULL DEFAULT 'public'`);
await queryRunner.query(`UPDATE "user_profile" SET "followingVisibility" = "ffVisibility"`);
await queryRunner.query(`UPDATE "user_profile" SET "followersVisibility" = "ffVisibility"`);
await queryRunner.query(`DROP CAST ("public"."user_profile_ffvisibility_enum" AS "public"."user_profile_followersVisibility_enum")`);
await queryRunner.query(`DROP CAST ("public"."user_profile_ffvisibility_enum" AS "public"."user_profile_followingvisibility_enum")`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "ffVisibility"`);
await queryRunner.query(`DROP TYPE "public"."user_profile_ffvisibility_enum"`);
}
async down(queryRunner) {
await queryRunner.query(`CREATE TYPE "public"."user_profile_ffvisibility_enum" AS ENUM('public', 'followers', 'private')`);
await queryRunner.query(`ALTER TABLE "user_profile" ADD "ffVisibility" "public"."user_profile_ffvisibility_enum" NOT NULL DEFAULT 'public'`);
await queryRunner.query(`CREATE CAST ("public"."user_profile_followingvisibility_enum" AS "public"."user_profile_ffvisibility_enum") WITH INOUT AS ASSIGNMENT`);
await queryRunner.query(`UPDATE "user_profile" SET ffVisibility = "user_profile"."followingVisibility"`);
await queryRunner.query(`DROP CAST ("public"."user_profile_followingvisibility_enum" AS "public"."user_profile_ffvisibility_enum")`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "followersVisibility"`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "followingVisibility"`);
await queryRunner.query(`DROP TYPE "public"."user_profile_followersVisibility_enum"`);
await queryRunner.query(`DROP TYPE "public"."user_profile_followingvisibility_enum"`);
}
}
11 changes: 6 additions & 5 deletions packages/backend/src/core/entities/UserEntityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,13 +332,13 @@ export class UserEntityService implements OnModuleInit {
const profile = opts.detail ? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) : null;

const followingCount = profile == null ? null :
(profile.ffVisibility === 'public') || isMe ? user.followingCount :
(profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount :
(profile.followingVisibility === 'public') || isMe ? user.followingCount :
(profile.followingVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount :
null;

const followersCount = profile == null ? null :
(profile.ffVisibility === 'public') || isMe ? user.followersCount :
(profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
(profile.followersVisibility === 'public') || isMe ? user.followersCount :
(profile.followersVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
null;

const isModerator = isMe && opts.detail ? this.roleService.isModerator(user) : null;
Expand Down Expand Up @@ -417,7 +417,8 @@ export class UserEntityService implements OnModuleInit {
pinnedPageId: profile!.pinnedPageId,
pinnedPage: profile!.pinnedPageId ? this.pageEntityService.pack(profile!.pinnedPageId, me) : null,
publicReactions: profile!.publicReactions,
ffVisibility: profile!.ffVisibility,
followersVisibility: profile!.followersVisibility,
followingVisibility: profile!.followingVisibility,
twoFactorEnabled: profile!.twoFactorEnabled,
usePasswordLessLogin: profile!.usePasswordLessLogin,
securityKeys: profile!.twoFactorEnabled
Expand Down
12 changes: 9 additions & 3 deletions packages/backend/src/models/UserProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
import { obsoleteNotificationTypes, ffVisibility, notificationTypes } from '@/types.js';
import { obsoleteNotificationTypes, followingVisibilities, followersVisibilities, notificationTypes } from '@/types.js';
import { id } from './util/id.js';
import { MiUser } from './User.js';
import { MiPage } from './Page.js';
Expand Down Expand Up @@ -94,10 +94,16 @@ export class MiUserProfile {
public publicReactions: boolean;

@Column('enum', {
enum: ffVisibility,
enum: followingVisibilities,
default: 'public',
})
public ffVisibility: typeof ffVisibility[number];
public followingVisibility: typeof followingVisibilities[number];

@Column('enum', {
enum: followersVisibilities,
default: 'public',
})
public followersVisibility: typeof followersVisibilities[number];

@Column('varchar', {
length: 128, nullable: true,
Expand Down
7 changes: 6 additions & 1 deletion packages/backend/src/models/json-schema/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,12 @@ export const packedUserDetailedNotMeOnlySchema = {
type: 'boolean',
nullable: false, optional: false,
},
ffVisibility: {
followingVisibility: {
type: 'string',
nullable: false, optional: false,
enum: ['public', 'followers', 'private'],
},
followersVisibility: {
type: 'string',
nullable: false, optional: false,
enum: ['public', 'followers', 'private'],
Expand Down
8 changes: 4 additions & 4 deletions packages/backend/src/server/ActivityPubServerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,11 +195,11 @@ export class ActivityPubServerService {
//#region Check ff visibility
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });

if (profile.ffVisibility === 'private') {
if (profile.followersVisibility === 'private') {
reply.code(403);
reply.header('Cache-Control', 'public, max-age=30');
return;
} else if (profile.ffVisibility === 'followers') {
} else if (profile.followersVisibility === 'followers') {
reply.code(403);
reply.header('Cache-Control', 'public, max-age=30');
return;
Expand Down Expand Up @@ -287,11 +287,11 @@ export class ActivityPubServerService {
//#region Check ff visibility
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });

if (profile.ffVisibility === 'private') {
if (profile.followingVisibility === 'private') {
reply.code(403);
reply.header('Cache-Control', 'public, max-age=30');
return;
} else if (profile.ffVisibility === 'followers') {
} else if (profile.followingVisibility === 'followers') {
reply.code(403);
reply.header('Cache-Control', 'public, max-age=30');
return;
Expand Down
6 changes: 4 additions & 2 deletions packages/backend/src/server/api/endpoints/i/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ export const paramDef = {
receiveAnnouncementEmail: { type: 'boolean' },
alwaysMarkNsfw: { type: 'boolean' },
autoSensitive: { type: 'boolean' },
ffVisibility: { type: 'string', enum: ['public', 'followers', 'private'] },
followingVisibility: { type: 'string', enum: ['public', 'followers', 'private'] },
followersVisibility: { type: 'string', enum: ['public', 'followers', 'private'] },
pinnedPageId: { type: 'string', format: 'misskey:id', nullable: true },
mutedWords: muteWords,
hardMutedWords: muteWords,
Expand Down Expand Up @@ -241,7 +242,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (ps.lang !== undefined) profileUpdates.lang = ps.lang;
if (ps.location !== undefined) profileUpdates.location = ps.location;
if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
if (ps.ffVisibility !== undefined) profileUpdates.ffVisibility = ps.ffVisibility;
if (ps.followingVisibility !== undefined) profileUpdates.followingVisibility = ps.followingVisibility;
if (ps.followersVisibility !== undefined) profileUpdates.followersVisibility = ps.followersVisibility;

function checkMuteWordCount(mutedWords: (string[] | string)[], limit: number) {
// TODO: ちゃんと数える
Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/server/api/endpoints/users/followers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-

const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });

if (profile.ffVisibility === 'private') {
if (profile.followersVisibility === 'private') {
if (me == null || (me.id !== user.id)) {
throw new ApiError(meta.errors.forbidden);
}
} else if (profile.ffVisibility === 'followers') {
} else if (profile.followersVisibility === 'followers') {
if (me == null) {
throw new ApiError(meta.errors.forbidden);
} else if (me.id !== user.id) {
Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/server/api/endpoints/users/following.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-

const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });

if (profile.ffVisibility === 'private') {
if (profile.followingVisibility === 'private') {
if (me == null || (me.id !== user.id)) {
throw new ApiError(meta.errors.forbidden);
}
} else if (profile.ffVisibility === 'followers') {
} else if (profile.followingVisibility === 'followers') {
if (me == null) {
throw new ApiError(meta.errors.forbidden);
} else if (me.id !== user.id) {
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/server/web/FeedService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export class FeedService {
title: `${author.name} (@${user.username}@${this.config.host})`,
updated: notes.length !== 0 ? this.idService.parse(notes[0].id).date : undefined,
generator: 'Misskey',
description: `${user.notesCount} Notes, ${profile.ffVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.ffVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`,
description: `${user.notesCount} Notes, ${profile.followingVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.followersVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`,
link: author.link,
image: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
feedLinks: {
Expand Down
3 changes: 2 additions & 1 deletion packages/backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as

export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const;

export const ffVisibility = ['public', 'followers', 'private'] as const;
export const followingVisibilities = ['public', 'followers', 'private'] as const;
export const followersVisibilities = ['public', 'followers', 'private'] as const;

export const moderationLogTypes = [
'updateServerSettings',
Expand Down
Loading
Loading