summaryrefslogtreecommitdiff
path: root/packages/backend
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-03-31 15:01:56 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2023-03-31 15:01:56 +0900
commit9bc5d52e413dcf35e85613e0c5a5b319ba95017a (patch)
treebadac56bd198b281c9e3a4cc662d1533b8076389 /packages/backend
parentrefactor (diff)
downloadmisskey-9bc5d52e413dcf35e85613e0c5a5b319ba95017a.tar.gz
misskey-9bc5d52e413dcf35e85613e0c5a5b319ba95017a.tar.bz2
misskey-9bc5d52e413dcf35e85613e0c5a5b319ba95017a.zip
feat: チャンネルにノートをピン留めできるように
Resolve #7740
Diffstat (limited to 'packages/backend')
-rw-r--r--packages/backend/migration/1680238118084-channelNotePining.js11
-rw-r--r--packages/backend/src/core/entities/ChannelEntityService.ts22
-rw-r--r--packages/backend/src/di-symbols.ts1
-rw-r--r--packages/backend/src/models/RepositoryModule.ts10
-rw-r--r--packages/backend/src/models/entities/Channel.ts5
-rw-r--r--packages/backend/src/models/entities/ChannelNotePining.ts35
-rw-r--r--packages/backend/src/models/index.ts3
-rw-r--r--packages/backend/src/models/json-schema/channel.ts8
-rw-r--r--packages/backend/src/postgres.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/channels/show.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/channels/update.ts11
11 files changed, 54 insertions, 56 deletions
diff --git a/packages/backend/migration/1680238118084-channelNotePining.js b/packages/backend/migration/1680238118084-channelNotePining.js
new file mode 100644
index 0000000000..126eae87ea
--- /dev/null
+++ b/packages/backend/migration/1680238118084-channelNotePining.js
@@ -0,0 +1,11 @@
+export class channelNotePining1680238118084 {
+ name = 'channelNotePining1680238118084'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "channel" ADD "pinnedNoteIds" character varying(128) array NOT NULL DEFAULT '{}'`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "channel" DROP COLUMN "pinnedNoteIds"`);
+ }
+}
diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts
index 97bf66095c..72e9b25544 100644
--- a/packages/backend/src/core/entities/ChannelEntityService.ts
+++ b/packages/backend/src/core/entities/ChannelEntityService.ts
@@ -1,13 +1,14 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { ChannelFavoritesRepository, ChannelFollowingsRepository, ChannelsRepository, DriveFilesRepository, NoteUnreadsRepository } from '@/models/index.js';
+import type { ChannelFavoritesRepository, ChannelFollowingsRepository, ChannelsRepository, DriveFilesRepository, NoteUnreadsRepository, NotesRepository } from '@/models/index.js';
import type { Packed } from '@/misc/json-schema.js';
import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js';
import type { Channel } from '@/models/entities/Channel.js';
import { bindThis } from '@/decorators.js';
-import { UserEntityService } from './UserEntityService.js';
import { DriveFileEntityService } from './DriveFileEntityService.js';
+import { NoteEntityService } from './NoteEntityService.js';
+import { In } from 'typeorm';
@Injectable()
export class ChannelEntityService {
@@ -21,13 +22,16 @@ export class ChannelEntityService {
@Inject(DI.channelFavoritesRepository)
private channelFavoritesRepository: ChannelFavoritesRepository,
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
+
@Inject(DI.noteUnreadsRepository)
private noteUnreadsRepository: NoteUnreadsRepository,
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
- private userEntityService: UserEntityService,
+ private noteEntityService: NoteEntityService,
private driveFileEntityService: DriveFileEntityService,
) {
}
@@ -36,6 +40,7 @@ export class ChannelEntityService {
public async pack(
src: Channel['id'] | Channel,
me?: { id: User['id'] } | null | undefined,
+ detailed?: boolean,
): Promise<Packed<'Channel'>> {
const channel = typeof src === 'object' ? src : await this.channelsRepository.findOneByOrFail({ id: src });
const meId = me ? me.id : null;
@@ -54,6 +59,12 @@ export class ChannelEntityService {
channelId: channel.id,
}) : null;
+ const pinnedNotes = channel.pinnedNoteIds.length > 0 ? await this.notesRepository.find({
+ where: {
+ id: In(channel.pinnedNoteIds),
+ },
+ }) : [];
+
return {
id: channel.id,
createdAt: channel.createdAt.toISOString(),
@@ -62,6 +73,7 @@ export class ChannelEntityService {
description: channel.description,
userId: channel.userId,
bannerUrl: banner ? this.driveFileEntityService.getPublicUrl(banner) : null,
+ pinnedNoteIds: channel.pinnedNoteIds,
usersCount: channel.usersCount,
notesCount: channel.notesCount,
@@ -70,6 +82,10 @@ export class ChannelEntityService {
isFavorited: favorite != null,
hasUnreadNote,
} : {}),
+
+ ...(detailed ? {
+ pinnedNotes: await this.noteEntityService.packMany(pinnedNotes, me),
+ } : {}),
};
}
}
diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts
index 6da31b9a4b..4f475a03ad 100644
--- a/packages/backend/src/di-symbols.ts
+++ b/packages/backend/src/di-symbols.ts
@@ -62,7 +62,6 @@ export const DI = {
channelsRepository: Symbol('channelsRepository'),
channelFollowingsRepository: Symbol('channelFollowingsRepository'),
channelFavoritesRepository: Symbol('channelFavoritesRepository'),
- channelNotePiningsRepository: Symbol('channelNotePiningsRepository'),
registryItemsRepository: Symbol('registryItemsRepository'),
webhooksRepository: Symbol('webhooksRepository'),
adsRepository: Symbol('adsRepository'),
diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts
index a007a1ae87..da7faf9ffb 100644
--- a/packages/backend/src/models/RepositoryModule.ts
+++ b/packages/backend/src/models/RepositoryModule.ts
@@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, AntennaNote, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, ChannelNotePining, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite } from './index.js';
+import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, AntennaNote, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite } from './index.js';
import type { DataSource } from 'typeorm';
import type { Provider } from '@nestjs/common';
@@ -346,12 +346,6 @@ const $channelFavoritesRepository: Provider = {
inject: [DI.db],
};
-const $channelNotePiningsRepository: Provider = {
- provide: DI.channelNotePiningsRepository,
- useFactory: (db: DataSource) => db.getRepository(ChannelNotePining),
- inject: [DI.db],
-};
-
const $registryItemsRepository: Provider = {
provide: DI.registryItemsRepository,
useFactory: (db: DataSource) => db.getRepository(RegistryItem),
@@ -467,7 +461,6 @@ const $roleAssignmentsRepository: Provider = {
$channelsRepository,
$channelFollowingsRepository,
$channelFavoritesRepository,
- $channelNotePiningsRepository,
$registryItemsRepository,
$webhooksRepository,
$adsRepository,
@@ -536,7 +529,6 @@ const $roleAssignmentsRepository: Provider = {
$channelsRepository,
$channelFollowingsRepository,
$channelFavoritesRepository,
- $channelNotePiningsRepository,
$registryItemsRepository,
$webhooksRepository,
$adsRepository,
diff --git a/packages/backend/src/models/entities/Channel.ts b/packages/backend/src/models/entities/Channel.ts
index a6e32d54f7..2d346fdf9d 100644
--- a/packages/backend/src/models/entities/Channel.ts
+++ b/packages/backend/src/models/entities/Channel.ts
@@ -59,6 +59,11 @@ export class Channel {
@JoinColumn()
public banner: DriveFile | null;
+ @Column('varchar', {
+ array: true, length: 128, default: '{}',
+ })
+ public pinnedNoteIds: string[];
+
@Index()
@Column('integer', {
default: 0,
diff --git a/packages/backend/src/models/entities/ChannelNotePining.ts b/packages/backend/src/models/entities/ChannelNotePining.ts
deleted file mode 100644
index ab5796626a..0000000000
--- a/packages/backend/src/models/entities/ChannelNotePining.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
-import { id } from '../id.js';
-import { Note } from './Note.js';
-import { Channel } from './Channel.js';
-
-@Entity()
-@Index(['channelId', 'noteId'], { unique: true })
-export class ChannelNotePining {
- @PrimaryColumn(id())
- public id: string;
-
- @Column('timestamp with time zone', {
- comment: 'The created date of the ChannelNotePining.',
- })
- public createdAt: Date;
-
- @Index()
- @Column(id())
- public channelId: Channel['id'];
-
- @ManyToOne(type => Channel, {
- onDelete: 'CASCADE',
- })
- @JoinColumn()
- public channel: Channel | null;
-
- @Column(id())
- public noteId: Note['id'];
-
- @ManyToOne(type => Note, {
- onDelete: 'CASCADE',
- })
- @JoinColumn()
- public note: Note | null;
-}
diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts
index 872cbbcbac..79bd014cea 100644
--- a/packages/backend/src/models/index.ts
+++ b/packages/backend/src/models/index.ts
@@ -11,7 +11,6 @@ import { AuthSession } from '@/models/entities/AuthSession.js';
import { Blocking } from '@/models/entities/Blocking.js';
import { ChannelFollowing } from '@/models/entities/ChannelFollowing.js';
import { ChannelFavorite } from '@/models/entities/ChannelFavorite.js';
-import { ChannelNotePining } from '@/models/entities/ChannelNotePining.js';
import { Clip } from '@/models/entities/Clip.js';
import { ClipNote } from '@/models/entities/ClipNote.js';
import { ClipFavorite } from '@/models/entities/ClipFavorite.js';
@@ -81,7 +80,6 @@ export {
Blocking,
ChannelFollowing,
ChannelFavorite,
- ChannelNotePining,
Clip,
ClipNote,
ClipFavorite,
@@ -150,7 +148,6 @@ export type AuthSessionsRepository = Repository<AuthSession>;
export type BlockingsRepository = Repository<Blocking>;
export type ChannelFollowingsRepository = Repository<ChannelFollowing>;
export type ChannelFavoritesRepository = Repository<ChannelFavorite>;
-export type ChannelNotePiningsRepository = Repository<ChannelNotePining>;
export type ClipsRepository = Repository<Clip>;
export type ClipNotesRepository = Repository<ClipNote>;
export type ClipFavoritesRepository = Repository<ClipFavorite>;
diff --git a/packages/backend/src/models/json-schema/channel.ts b/packages/backend/src/models/json-schema/channel.ts
index f90e959926..745b39a6ba 100644
--- a/packages/backend/src/models/json-schema/channel.ts
+++ b/packages/backend/src/models/json-schema/channel.ts
@@ -51,5 +51,13 @@ export const packedChannelSchema = {
nullable: true, optional: false,
format: 'id',
},
+ pinnedNoteIds: {
+ type: 'array',
+ nullable: false, optional: false,
+ items: {
+ type: 'string',
+ format: 'id',
+ },
+ },
},
} as const;
diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts
index edcf344561..cbe3814a24 100644
--- a/packages/backend/src/postgres.ts
+++ b/packages/backend/src/postgres.ts
@@ -19,7 +19,6 @@ import { AuthSession } from '@/models/entities/AuthSession.js';
import { Blocking } from '@/models/entities/Blocking.js';
import { ChannelFollowing } from '@/models/entities/ChannelFollowing.js';
import { ChannelFavorite } from '@/models/entities/ChannelFavorite.js';
-import { ChannelNotePining } from '@/models/entities/ChannelNotePining.js';
import { Clip } from '@/models/entities/Clip.js';
import { ClipNote } from '@/models/entities/ClipNote.js';
import { ClipFavorite } from '@/models/entities/ClipFavorite.js';
@@ -177,7 +176,6 @@ export const entities = [
Channel,
ChannelFollowing,
ChannelFavorite,
- ChannelNotePining,
RegistryItem,
Ad,
PasswordResetRequest,
diff --git a/packages/backend/src/server/api/endpoints/channels/show.ts b/packages/backend/src/server/api/endpoints/channels/show.ts
index 8718615db2..070d14631e 100644
--- a/packages/backend/src/server/api/endpoints/channels/show.ts
+++ b/packages/backend/src/server/api/endpoints/channels/show.ts
@@ -51,7 +51,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
throw new ApiError(meta.errors.noSuchChannel);
}
- return await this.channelEntityService.pack(channel, me);
+ return await this.channelEntityService.pack(channel, me, true);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts
index a86cc2565a..084b3f919e 100644
--- a/packages/backend/src/server/api/endpoints/channels/update.ts
+++ b/packages/backend/src/server/api/endpoints/channels/update.ts
@@ -3,8 +3,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import type { DriveFilesRepository, ChannelsRepository } from '@/models/index.js';
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
import { DI } from '@/di-symbols.js';
-import { ApiError } from '../../error.js';
import { RoleService } from '@/core/RoleService.js';
+import { ApiError } from '../../error.js';
export const meta = {
tags: ['channels'],
@@ -47,6 +47,12 @@ export const paramDef = {
name: { type: 'string', minLength: 1, maxLength: 128 },
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
bannerId: { type: 'string', format: 'misskey:id', nullable: true },
+ pinnedNoteIds: {
+ type: 'array',
+ items: {
+ type: 'string', format: 'misskey:id',
+ },
+ },
},
required: ['channelId'],
} as const;
@@ -64,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private channelEntityService: ChannelEntityService,
private roleService: RoleService,
- ) {
+ ) {
super(meta, paramDef, async (ps, me) => {
const channel = await this.channelsRepository.findOneBy({
id: ps.channelId,
@@ -97,6 +103,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
await this.channelsRepository.update(channel.id, {
...(ps.name !== undefined ? { name: ps.name } : {}),
...(ps.description !== undefined ? { description: ps.description } : {}),
+ ...(ps.pinnedNoteIds !== undefined ? { pinnedNoteIds: ps.pinnedNoteIds } : {}),
...(banner ? { bannerId: banner.id } : {}),
});