From fd8f8162e186981ca2acf9d3b1caef523c748691 Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Sat, 19 Feb 2022 23:21:28 +0900
Subject: [PATCH] =?UTF-8?q?SchemaType=E3=81=AE=E5=9E=8B=E8=A8=88=E7=AE=97?=
 =?UTF-8?q?=E9=87=8F=E3=82=92=E5=89=8A=E6=B8=9B=20(#8332)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* schema typeの型計算量を削減

* reduce some type error

* wip

* fix

* clean up

* more shrink
---
 packages/backend/src/misc/schema.ts           | 47 ++++++-------------
 .../backend/src/models/repositories/app.ts    |  2 +-
 .../endpoints/admin/announcements/create.ts   |  2 +-
 .../server/api/endpoints/i/gallery/likes.ts   | 28 ++++++-----
 .../src/server/api/endpoints/i/page-likes.ts  | 29 +++++++-----
 .../src/server/api/endpoints/my/apps.ts       | 41 +---------------
 .../src/server/api/endpoints/sw/register.ts   |  8 ++--
 .../backend/src/server/api/stream/types.ts    |  1 +
 .../src/services/create-system-user.ts        |  4 +-
 9 files changed, 57 insertions(+), 105 deletions(-)

diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts
index fbe5a1e421..5909969bbf 100644
--- a/packages/backend/src/misc/schema.ts
+++ b/packages/backend/src/misc/schema.ts
@@ -63,14 +63,7 @@ export const refs = {
 	Emoji: packedEmojiSchema,
 };
 
-// Packed = SchemaTypeDef<typeof refs[x]>; とすると展開されてマウスホバー時に型表示が使い物にならなくなる
-// ObjType<r['properties']>を指定すると(なぜか)展開されずにPacked<'Hoge'>と表示される
-type PackedDef<r extends { properties?: Obj; oneOf?: ReadonlyArray<Schema>; allOf?: ReadonlyArray<Schema> }> =
-	r['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<r['allOf']>> :
-	r['oneOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<r['oneOf']> :
-	r['properties'] extends Obj ? ObjType<r['properties'], any> :
-	never;
-export type Packed<x extends keyof typeof refs> = PackedDef<typeof refs[x]>;
+export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;
 
 type TypeStringef = 'null' | 'boolean' | 'integer' | 'number' | 'string' | 'array' | 'object' | 'any';
 type StringDefToType<T extends TypeStringef> =
@@ -107,31 +100,20 @@ export interface Schema extends OfSchema {
 	readonly minLength?: number;
 }
 
-type OptionalPropertyNames<T extends Obj> = {
-	[K in keyof T]: T[K]['optional'] extends true ? K : never
-}[keyof T];
-
-type NonOptionalPropertyNames<T extends Obj> = {
-	[K in keyof T]: T[K]['optional'] extends false ? K : never
-}[keyof T];
-
-type DefaultPropertyNames<T extends Obj> = {
-	[K in keyof T]: T[K]['default'] extends null ? K :
-		T[K]['default'] extends string ? K :
-		T[K]['default'] extends number ? K :
-		T[K]['default'] extends boolean ? K :
-		T[K]['default'] extends Record<string, unknown> ? K :
-		never
-}[keyof T];
+type RequiredPropertyNames<s extends Obj> = {
+	[K in keyof s]:
+		// K is not optional
+		s[K]['optional'] extends false ? K :
+		// K has default value
+		s[K]['default'] extends null | string | number | boolean | Record<string, unknown> ? K : never
+}[keyof s];
 
 export interface Obj { [key: string]: Schema; }
 
-export type ObjType<s extends Obj, RequiredProps extends ReadonlyArray<keyof s>> =
+export type ObjType<s extends Obj, RequiredProps extends keyof s> =
 	{ -readonly [P in keyof s]?: SchemaType<s[P]> } &
-	{ -readonly [P in RequiredProps[number]]: SchemaType<s[P]> } &
-	{ -readonly [P in OptionalPropertyNames<s>]?: SchemaType<s[P]> } &
-	{ -readonly [P in NonOptionalPropertyNames<s>]: SchemaType<s[P]> } &
-	{ -readonly [P in DefaultPropertyNames<s>]: SchemaType<s[P]> };
+	{ -readonly [P in RequiredProps]: SchemaType<s[P]> } &
+	{ -readonly [P in RequiredPropertyNames<s>]: SchemaType<s[P]> };
 
 type NullOrUndefined<p extends Schema, T> =
 	p['nullable'] extends true
@@ -142,11 +124,12 @@ type NullOrUndefined<p extends Schema, T> =
 			? (T | undefined)
 			: T;
 
-// 共用体型を交差型にする型 https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
+// https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
+// Get intersection from union 
 type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
 
 // https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552
-// 単純にSchemaTypeDef<X>で判定するだけではダメ
+// To get union, we use `Foo extends any ? Hoge<Foo> : never`
 type UnionSchemaType<a extends readonly any[], X extends Schema = a[number]> = X extends any ? SchemaType<X> : never;
 type ArrayUnion<T> = T extends any ? Array<T> : never; 
 
@@ -163,7 +146,7 @@ export type SchemaTypeDef<p extends Schema> =
 	p['type'] extends 'boolean' ? boolean :
 	p['type'] extends 'object' ? (
 		p['ref'] extends keyof typeof refs ? Packed<p['ref']> :
-		p['properties'] extends NonNullable<Obj> ? ObjType<p['properties'], NonNullable<p['required']>> :
+		p['properties'] extends NonNullable<Obj> ? ObjType<p['properties'], NonNullable<p['required']>[number]> :
 		p['anyOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['anyOf']> & Partial<UnionToIntersection<UnionSchemaType<p['anyOf']>>> :
 		p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> :
 		any
diff --git a/packages/backend/src/models/repositories/app.ts b/packages/backend/src/models/repositories/app.ts
index 6bac4d9598..5576c67236 100644
--- a/packages/backend/src/models/repositories/app.ts
+++ b/packages/backend/src/models/repositories/app.ts
@@ -32,7 +32,7 @@ export class AppRepository extends Repository<App> {
 			...(me ? {
 				isAuthorized: await AccessTokens.count({
 					appId: app.id,
-					userId: me,
+					userId: me.id,
 				}).then(count => count > 0),
 			} : {}),
 		};
diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts
index 8d2bda9781..295d99513d 100644
--- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts
@@ -65,5 +65,5 @@ export default define(meta, paramDef, async (ps) => {
 		imageUrl: ps.imageUrl,
 	}).then(x => Announcements.findOneOrFail(x.identifiers[0]));
 
-	return announcement;
+	return Object.assign({}, announcement, { createdAt: announcement.createdAt.toISOString(), updatedAt: null });
 });
diff --git a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts
index dc862a6b05..7578ed3216 100644
--- a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts
+++ b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts
@@ -10,20 +10,24 @@ export const meta = {
 	kind: 'read:gallery-likes',
 
 	res: {
-		type: 'object',
+		type: 'array',
 		optional: false, nullable: false,
-		properties: {
-			id: {
-				type: 'string',
-				optional: false, nullable: false,
-				format: 'id',
+		items: {
+			type: 'object',
+			optional: false, nullable: false,
+			properties: {
+				id: {
+					type: 'string',
+					optional: false, nullable: false,
+					format: 'id',
+				},
+				post: {
+					type: 'object',
+					optional: false, nullable: false,
+					ref: 'GalleryPost',
+				},
 			},
-			page: {
-				type: 'object',
-				optional: false, nullable: false,
-				ref: 'GalleryPost',
-			},
-		},
+		}
 	},
 } as const;
 
diff --git a/packages/backend/src/server/api/endpoints/i/page-likes.ts b/packages/backend/src/server/api/endpoints/i/page-likes.ts
index e66bc616c2..60ac3ccde1 100644
--- a/packages/backend/src/server/api/endpoints/i/page-likes.ts
+++ b/packages/backend/src/server/api/endpoints/i/page-likes.ts
@@ -10,20 +10,23 @@ export const meta = {
 	kind: 'read:page-likes',
 
 	res: {
-		type: 'object',
+		type: 'array',
 		optional: false, nullable: false,
-		properties: {
-			id: {
-				type: 'string',
-				optional: false, nullable: false,
-				format: 'id',
+		items: {
+			type: 'object',
+			properties: {
+				id: {
+					type: 'string',
+					optional: false, nullable: false,
+					format: 'id',
+				},
+				page: {
+					type: 'object',
+					optional: false, nullable: false,
+					ref: 'Page',
+				},
 			},
-			page: {
-				type: 'object',
-				optional: false, nullable: false,
-				ref: 'Page',
-			},
-		},
+		}
 	},
 } as const;
 
@@ -47,5 +50,5 @@ export default define(meta, paramDef, async (ps, user) => {
 		.take(ps.limit)
 		.getMany();
 
-	return await PageLikes.packMany(likes, user);
+	return PageLikes.packMany(likes, user);
 });
diff --git a/packages/backend/src/server/api/endpoints/my/apps.ts b/packages/backend/src/server/api/endpoints/my/apps.ts
index 60e5014110..8dee11fb93 100644
--- a/packages/backend/src/server/api/endpoints/my/apps.ts
+++ b/packages/backend/src/server/api/endpoints/my/apps.ts
@@ -12,46 +12,7 @@ export const meta = {
 		items: {
 			type: 'object',
 			optional: false, nullable: false,
-			properties: {
-				id: {
-					type: 'string',
-					optional: false, nullable: false,
-				},
-				name: {
-					type: 'string',
-					optional: false, nullable: false,
-				},
-				callbackUrl: {
-					type: 'string',
-					optional: false, nullable: false,
-				},
-				permission: {
-					type: 'array',
-					optional: false, nullable: false,
-					items: {
-						type: 'string',
-						optional: false, nullable: false,
-					},
-				},
-				secret: {
-					type: 'string',
-					optional: true, nullable: false,
-				},
-				isAuthorized: {
-					type: 'object',
-					optional: true, nullable: false,
-					properties: {
-						appId: {
-							type: 'string',
-							optional: false, nullable: false,
-						},
-						userId: {
-							type: 'string',
-							optional: false, nullable: false,
-						},
-					},
-				},
-			},
+			ref: 'App',
 		},
 	},
 } as const;
diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts
index 9091c9481a..459b0dd969 100644
--- a/packages/backend/src/server/api/endpoints/sw/register.ts
+++ b/packages/backend/src/server/api/endpoints/sw/register.ts
@@ -14,12 +14,12 @@ export const meta = {
 		properties: {
 			state: {
 				type: 'string',
-				optional: false, nullable: false,
+				optional: true, nullable: false,
 				enum: ['already-subscribed', 'subscribed'],
 			},
 			key: {
 				type: 'string',
-				optional: false, nullable: false,
+				optional: false, nullable: true,
 			},
 		},
 	},
@@ -49,7 +49,7 @@ export default define(meta, paramDef, async (ps, user) => {
 
 	if (exist != null) {
 		return {
-			state: 'already-subscribed',
+			state: 'already-subscribed' as const,
 			key: instance.swPublicKey,
 		};
 	}
@@ -64,7 +64,7 @@ export default define(meta, paramDef, async (ps, user) => {
 	});
 
 	return {
-		state: 'subscribed',
+		state: 'subscribed' as const,
 		key: instance.swPublicKey,
 	};
 });
diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts
index e2f1c6fc9c..921856b38d 100644
--- a/packages/backend/src/server/api/stream/types.ts
+++ b/packages/backend/src/server/api/stream/types.ts
@@ -84,6 +84,7 @@ export interface MainStreamTypes {
 	};
 	driveFileCreated: Packed<'DriveFile'>;
 	readAntenna: Antenna;
+	receiveFollowRequest: Packed<'User'>;
 }
 
 export interface DriveStreamTypes {
diff --git a/packages/backend/src/services/create-system-user.ts b/packages/backend/src/services/create-system-user.ts
index 82130dd593..2e9ec178fd 100644
--- a/packages/backend/src/services/create-system-user.ts
+++ b/packages/backend/src/services/create-system-user.ts
@@ -4,7 +4,7 @@ import generateNativeUserToken from '../server/api/common/generate-native-user-t
 import { genRsaKeyPair } from '@/misc/gen-key-pair';
 import { User } from '@/models/entities/user';
 import { UserProfile } from '@/models/entities/user-profile';
-import { getConnection } from 'typeorm';
+import { getConnection, ObjectLiteral } from 'typeorm';
 import { genId } from '@/misc/gen-id';
 import { UserKeypair } from '@/models/entities/user-keypair';
 import { UsedUsername } from '@/models/entities/used-username';
@@ -21,7 +21,7 @@ export async function createSystemUser(username: string) {
 
 	const keyPair = await genRsaKeyPair(4096);
 
-	let account!: User;
+	let account!: User | ObjectLiteral;
 
 	// Start transaction
 	await getConnection().transaction(async transactionalEntityManager => {