diff --git a/package.json b/package.json index 0ed73f56b3..79d1e33a58 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2023.11.0", + "version": "2023.11.2", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/backend/migration/1699337454434-schedulenote.js b/packages/backend/migration/1699337454434-schedulenote.js new file mode 100644 index 0000000000..213da993b0 --- /dev/null +++ b/packages/backend/migration/1699337454434-schedulenote.js @@ -0,0 +1,11 @@ +export class Schedulenote1699337454434 { + name = 'Schedulenote1699337454434' + + async up(queryRunner) { + await queryRunner.query(`CREATE TABLE "note_schedule" ("id" character varying(32) NOT NULL, "note" jsonb NOT NULL, "userId" character varying(260) NOT NULL, CONSTRAINT "PK_3a1ae2db41988f4994268218436" PRIMARY KEY ("id"))`); + } + + async down(queryRunner) { + await queryRunner.query(`DROP TABLE "note_schedule"`); + } +} diff --git a/packages/backend/src/models/NoteSchedule.ts b/packages/backend/src/models/NoteSchedule.ts index 557133e8fa..aec1bb52b9 100644 --- a/packages/backend/src/models/NoteSchedule.ts +++ b/packages/backend/src/models/NoteSchedule.ts @@ -14,16 +14,13 @@ import type { MiDriveFile } from './DriveFile.js'; @Entity('note_schedule') export class MiNoteSchedule { @PrimaryColumn(id()) - public noteId: MiNote['id']; + public id: string; - @Column('string') - public note:{ apEmojis: any[] | undefined; visibility: any; apMentions: any[] | undefined; visibleUsers: MiUser[]; channel: null | MiChannel; poll: { multiple: any; choices: any; expiresAt: Date | null } | undefined; renote: null | MiNote; localOnly: any; cw: any; apHashtags: any[] | undefined; reactionAcceptance: any; files: MiDriveFile[]; text: any; reply: null | MiNote }; + @Column('jsonb') + public note:{createdAt?: Date | undefined ; apEmojis: any[] | undefined; visibility: any; apMentions: any[] | undefined; visibleUsers: MiUser[]; channel: null | MiChannel; poll: { multiple: any; choices: any; expiresAt: Date | null } | undefined; renote: null | MiNote; localOnly: any; cw: any; apHashtags: any[] | undefined; reactionAcceptance: any; files: MiDriveFile[]; text: any; reply: null | MiNote }; - @Column('string') - public user: MiUser & {host: null;uri: null;}; - - @Column('timestamp with time zone', { - nullable: true, + @Column('varchar', { + length: 260, }) - public expiresAt: Date; + public userId: MiUser['id']; } diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index 866fdfe6d4..61fac96349 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -5,7 +5,74 @@ import { Module } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiAvatarDecoration, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook } from './_.js'; +import { + MiAbuseUserReport, + MiAccessToken, + MiAd, + MiAnnouncement, + MiAnnouncementRead, + MiAntenna, + MiApp, + MiAuthSession, + MiAvatarDecoration, + MiBlocking, + MiChannel, + MiChannelFavorite, + MiChannelFollowing, + MiClip, + MiClipFavorite, + MiClipNote, + MiDriveFile, + MiDriveFolder, + MiEmoji, + MiFlash, + MiFlashLike, + MiFollowRequest, + MiFollowing, + MiGalleryLike, + MiGalleryPost, + MiHashtag, + MiInstance, + MiMeta, + MiModerationLog, + MiMuting, + MiNote, + MiNoteFavorite, + MiNoteReaction, + MiNoteThreadMuting, + MiNoteUnread, + MiPage, + MiPageLike, + MiPasswordResetRequest, + MiPoll, + MiPollVote, + MiPromoNote, + MiPromoRead, + MiRegistrationTicket, + MiRegistryItem, + MiRelay, + MiRenoteMuting, + MiRetentionAggregation, + MiRole, + MiRoleAssignment, + MiSignin, + MiSwSubscription, + MiUsedUsername, + MiUser, + MiUserIp, + MiUserKeypair, + MiUserList, + MiUserListFavorite, + MiUserListMembership, + MiUserMemo, + MiUserNotePining, + MiUserPending, + MiUserProfile, + MiUserPublickey, + MiUserSecurityKey, + MiWebhook, + MiNoteSchedule, +} from './_.js'; import type { DataSource } from 'typeorm'; import type { Provider } from '@nestjs/common'; @@ -21,6 +88,12 @@ const $notesRepository: Provider = { inject: [DI.db], }; +const $noteScheduleRepository: Provider = { + provide: DI.noteScheduleRepository, + useFactory: (db: DataSource) => db.getRepository(MiNoteSchedule), + inject: [DI.db], +}; + const $announcementsRepository: Provider = { provide: DI.announcementsRepository, useFactory: (db: DataSource) => db.getRepository(MiAnnouncement), @@ -405,6 +478,7 @@ const $userMemosRepository: Provider = { providers: [ $usersRepository, $notesRepository, + $noteScheduleRepository, $announcementsRepository, $announcementReadsRepository, $appsRepository, @@ -472,6 +546,7 @@ const $userMemosRepository: Provider = { exports: [ $usersRepository, $notesRepository, + $noteScheduleRepository, $announcementsRepository, $announcementReadsRepository, $appsRepository, diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index cd611839a4..002b5c30aa 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -76,6 +76,7 @@ import { MiRoleAssignment } from '@/models/RoleAssignment.js'; import { MiFlash } from '@/models/Flash.js'; import { MiFlashLike } from '@/models/FlashLike.js'; import { MiUserMemo } from '@/models/UserMemo.js'; +import { MiNoteSchedule } from '@/models/NoteSchedule.js'; import { Config } from '@/config.js'; import MisskeyLogger from '@/logger.js'; @@ -149,6 +150,7 @@ export const entities = [ MiRenoteMuting, MiBlocking, MiNote, + MiNoteSchedule, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, diff --git a/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts b/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts index b4e3e0686f..6e4432f9c3 100644 --- a/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts +++ b/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts @@ -7,6 +7,8 @@ import { Inject, Injectable } from '@nestjs/common'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { NoteCreateService } from '@/core/NoteCreateService.js'; +import type { NoteScheduleRepository, UsersRepository } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { ScheduleNotePostJobData } from '../types.js'; @@ -16,6 +18,12 @@ export class ScheduleNotePostProcessorService { private logger: Logger; constructor( + @Inject(DI.noteScheduleRepository) + private noteScheduleRepository: NoteScheduleRepository, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + private noteCreateService: NoteCreateService, private queueLoggerService: QueueLoggerService, ) { @@ -24,7 +32,15 @@ export class ScheduleNotePostProcessorService { @bindThis public async process(job: Bull.Job): Promise { - job.data.note.createdAt = new Date(); - await this.noteCreateService.create(job.data.user, job.data.note); + this.noteScheduleRepository.findOneBy({ id: job.data.scheduleNoteId }).then(async (data) => { + if (!data) { + this.logger.warn(`Schedule note ${job.data.scheduleNoteId} not found`); + } else { + data.note.createdAt = new Date(); + const me = await this.usersRepository.findOneByOrFail({ id: data.userId }); + await this.noteCreateService.create(me, data.note); + await this.noteScheduleRepository.remove(data); + } + }); } } diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts index 9a0608dab1..cc69c77d08 100644 --- a/packages/backend/src/queue/types.ts +++ b/packages/backend/src/queue/types.ts @@ -109,28 +109,7 @@ export type EndedPollNotificationJobData = { }; export type ScheduleNotePostJobData = { - note: { - name?: string | null; - text?: string | null; - reply?: MiNote | null; - renote?: MiNote | null; - files?: MiDriveFile[] | null; - poll?: IPoll | null; - schedule?: MiNoteSchedule | null; - localOnly?: boolean | null; - reactionAcceptance?: MiNote['reactionAcceptance']; - cw?: string | null; - visibility?: string; - visibleUsers?: MinimumUser[] | null; - channel?: MiChannel | null; - apMentions?: MinimumUser[] | null; - apHashtags?: string[] | null; - apEmojis?: string[] | null; - uri?: string | null; - url?: string | null; - app?: MiApp | null; - }; - user: MiUser & {host: null, uri: null}; + scheduleNoteId: MiNote['id']; } type MinimumUser = { diff --git a/packages/backend/src/server/api/endpoints/notes/create-schedule.ts b/packages/backend/src/server/api/endpoints/notes/create-schedule.ts index 3fbfaf36fa..d3e57d6048 100644 --- a/packages/backend/src/server/api/endpoints/notes/create-schedule.ts +++ b/packages/backend/src/server/api/endpoints/notes/create-schedule.ts @@ -7,7 +7,14 @@ import ms from 'ms'; import { DataSource, In } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import type { MiUser } from '@/models/User.js'; -import type { UsersRepository, NotesRepository, BlockingsRepository, DriveFilesRepository, ChannelsRepository } from '@/models/_.js'; +import type { + UsersRepository, + NotesRepository, + BlockingsRepository, + DriveFilesRepository, + ChannelsRepository, + NoteScheduleRepository, +} from '@/models/_.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; import type { MiNote } from '@/models/Note.js'; import type { MiChannel } from '@/models/Channel.js'; @@ -18,6 +25,8 @@ import { NoteCreateService } from '@/core/NoteCreateService.js'; import { DI } from '@/di-symbols.js'; import { isPureRenote } from '@/misc/is-pure-renote.js'; import { QueueService } from '@/core/QueueService.js'; +import { MiNoteSchedule } from '@/models/_.js'; +import { IdService } from '@/core/IdService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -194,6 +203,9 @@ export default class extends Endpoint { // eslint- @Inject(DI.notesRepository) private notesRepository: NotesRepository, + @Inject(DI.noteScheduleRepository) + private noteScheduleRepository: NoteScheduleRepository, + @Inject(DI.blockingsRepository) private blockingsRepository: BlockingsRepository, @@ -203,9 +215,8 @@ export default class extends Endpoint { // eslint- @Inject(DI.channelsRepository) private channelsRepository: ChannelsRepository, - private noteEntityService: NoteEntityService, private queueService: QueueService, - private noteCreateService: NoteCreateService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { let visibleUsers: MiUser[] = []; @@ -328,8 +339,28 @@ export default class extends Endpoint { // eslint- } } } - - const note = { + type NoteType = { + createdAt?: Date | undefined; + apEmojis: any[] | undefined; + visibility: any; + apMentions: any[] | undefined; + visibleUsers: MiUser[]; + channel: null | MiChannel; + poll: { + multiple: any; + choices: any; + expiresAt: Date | null; + } | undefined; + renote: null | MiNote; + localOnly: any; + cw: any; + apHashtags: any[] | undefined; + reactionAcceptance: any; + files: MiDriveFile[]; + text: any; + reply: null | MiNote; + }; + const note:NoteType = { files: files, poll: ps.poll ? { choices: ps.poll.choices, @@ -351,10 +382,17 @@ export default class extends Endpoint { // eslint- }; if (ps.schedule && ps.schedule.expiresAt) { + me.token = null; + const noteId = this.idService.gen(new Date().getTime()); + await this.noteScheduleRepository.insert({ + id: noteId, + note: note, + userId: me.id, + }); + const delay = new Date(ps.schedule.expiresAt).getTime() - Date.now(); await this.queueService.ScheduleNotePostQueue.add(String(delay), { - note: note, - user: me, + scheduleNoteId: noteId, }, { delay, removeOnComplete: true,