From 3439a29c142bd7f47d4331eebfa7d0bdfae0e362 Mon Sep 17 00:00:00 2001
From: kakkokari-gtyih <daisho7308+f@gmail.com>
Date: Thu, 9 Nov 2023 21:20:33 +0900
Subject: [PATCH] (refactor) better types

---
 .../backend/src/core/NoteCreateService.ts     | 36 +----------
 packages/backend/src/models/NoteSchedule.ts   |  7 +--
 .../api/endpoints/notes/schedule/create.ts    | 60 ++++++++-----------
 packages/backend/src/types.ts                 | 39 ++++++++++++
 4 files changed, 67 insertions(+), 75 deletions(-)

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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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<T> = {
 	[K in keyof T]:
 		T[K] extends Date