diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index da384f7de4..6fb024db53 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -15,19 +15,16 @@ import { extractHashtags } from '@/misc/extract-hashtags.js'; import type { IMentionedRemoteUsers } from '@/models/Note.js'; import { MiNote } from '@/models/Note.js'; import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; -import type { MiDriveFile } from '@/models/DriveFile.js'; -import type { MiApp } from '@/models/App.js'; import { concat } from '@/misc/prelude/array.js'; import { IdService } from '@/core/IdService.js'; import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js'; -import type { IPoll } from '@/models/Poll.js'; import { MiPoll } from '@/models/Poll.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; import { checkWordMute } from '@/misc/check-word-mute.js'; -import type { MiChannel } from '@/models/Channel.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { MemorySingleCache } from '@/misc/cache.js'; import type { MiUserProfile } from '@/models/UserProfile.js'; +import type { MiNoteCreateOption as Option, MiMinimumUser as MinimumUser } from '@/types.js'; import { RelayService } from '@/core/RelayService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { DI } from '@/di-symbols.js'; @@ -57,7 +54,6 @@ import { FeaturedService } from '@/core/FeaturedService.js'; import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; -import { MiNoteSchedule } from '@/models/_.js'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; @@ -117,36 +113,6 @@ class NotificationManager { } } -type MinimumUser = { - id: MiUser['id']; - host: MiUser['host']; - username: MiUser['username']; - uri: MiUser['uri']; -}; - -type Option = { - createdAt?: Date | null; - 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; -}; - @Injectable() export class NoteCreateService implements OnApplicationShutdown { #shutdownController = new AbortController(); diff --git a/packages/backend/src/models/NoteSchedule.ts b/packages/backend/src/models/NoteSchedule.ts index f622893ecf..565d6a6211 100644 --- a/packages/backend/src/models/NoteSchedule.ts +++ b/packages/backend/src/models/NoteSchedule.ts @@ -4,12 +4,9 @@ */ import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm'; -import { noteVisibilities } from '@/types.js'; -import { MiNote } from '@/models/Note.js'; +import type { MiNoteCreateOption } from '@/types.js'; import { id } from './util/id.js'; import { MiUser } from './User.js'; -import { MiChannel } from './Channel.js'; -import type { MiDriveFile } from './DriveFile.js'; @Entity('note_schedule') export class MiNoteSchedule { @@ -17,7 +14,7 @@ export class MiNoteSchedule { public id: string; @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 }; + public note: MiNoteCreateOption; @Index() @Column('varchar', { diff --git a/packages/backend/src/server/api/endpoints/notes/schedule/create.ts b/packages/backend/src/server/api/endpoints/notes/schedule/create.ts index 37c6bd36a6..2d0b6145c7 100644 --- a/packages/backend/src/server/api/endpoints/notes/schedule/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/schedule/create.ts @@ -7,6 +7,9 @@ import ms from 'ms'; import { DataSource, In } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import type { MiUser } from '@/models/User.js'; +import type { MiNote } from '@/models/Note.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; +import type { MiChannel } from '@/models/Channel.js'; import type { UsersRepository, NotesRepository, @@ -15,17 +18,13 @@ import type { 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'; +import type { MiNoteCreateOption } from '@/types.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; -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'; @@ -86,6 +85,12 @@ export const meta = { id: '8a9bfb90-fc7e-4878-a3e8-d97faaf5fb07', }, + specifyScheduleDate: { + message: 'Please specify schedule date.', + code: 'PLEASE_SPECIFY_SCHEDULE_DATE', + id: 'c93a6ad6-f7e2-4156-a0c2-3d03529e5e0f', + }, + noSuchChannel: { message: 'No such channel.', code: 'NO_SUCH_CHANNEL', @@ -212,6 +217,7 @@ export default class extends Endpoint { // eslint- @Inject(DI.channelsRepository) private channelsRepository: ChannelsRepository, + private noteEntityService: NoteEntityService, private queueService: QueueService, private idService: IdService, ) { @@ -336,38 +342,17 @@ export default class extends Endpoint { // eslint- } } } - 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, + const note: MiNoteCreateOption = { + files, poll: ps.poll ? { choices: ps.poll.choices, multiple: ps.poll.multiple ?? false, expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, } : undefined, - text: ps.text ?? undefined, + text: ps.text ?? null, reply, renote, - cw: ps.cw, + cw: ps.cw ?? null, localOnly: ps.localOnly, reactionAcceptance: ps.reactionAcceptance, visibility: ps.visibility, @@ -380,9 +365,9 @@ export default class extends Endpoint { // eslint- if (ps.schedule && ps.schedule.expiresAt) { me.token = null; - const noteId = this.idService.gen(new Date().getTime()); + const scheduleNoteId = this.idService.gen(new Date().getTime()); await this.noteScheduleRepository.insert({ - id: noteId, + id: scheduleNoteId, note: note, userId: me.id, expiresAt: new Date(ps.schedule.expiresAt), @@ -390,14 +375,19 @@ export default class extends Endpoint { // eslint- const delay = new Date(ps.schedule.expiresAt).getTime() - Date.now(); await this.queueService.ScheduleNotePostQueue.add(String(delay), { - scheduleNoteId: noteId, + scheduleNoteId, }, { delay, removeOnComplete: true, }); - } - return ''; + return { + scheduleNoteId, + scheduledNote: note, + }; + } else { + throw new ApiError(meta.errors.specifyScheduleDate); + } }); } } diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index e6dfeb6f8c..ab644bfc03 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -3,6 +3,14 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import type { MiDriveFile } from '@/models/DriveFile.js'; +import type { IPoll } from '@/models/Poll.js'; +import type { MiChannel } from '@/models/Channel.js'; +import type { MiApp } from '@/models/App.js'; +import type { MiUser } from '@/models/User.js'; +import type { MiNote } from '@/models/Note.js'; +import type { MiNoteSchedule } from '@/models/NoteSchedule.js'; + /** * note - 通知オンにしているユーザーが投稿した * follow - フォローされた @@ -239,6 +247,37 @@ export type ModerationLogPayloads = { }; }; +export type MiMinimumUser = { + id: MiUser['id']; + host: MiUser['host']; + username: MiUser['username']; + uri: MiUser['uri']; +}; + +export type MiNoteCreateOption = { + createdAt?: Date | null; + 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?: MiMinimumUser[] | null; + channel?: MiChannel | null; + apMentions?: MiMinimumUser[] | null; + apHashtags?: string[] | null; + apEmojis?: string[] | null; + uri?: string | null; + url?: string | null; + app?: MiApp | null; +}; + + export type Serialized = { [K in keyof T]: T[K] extends Date