From 99b4e5e13c83be4a03bfc819809fd5adc3c246b3 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Thu, 25 May 2023 00:55:33 +0200
Subject: [PATCH 001/198] Implement Meilisearch Indexing

---
 packages/backend/package.json                 |  1 +
 packages/backend/src/config/types.ts          |  6 ++
 packages/backend/src/db/meilisearch.ts        | 61 ++++++++++++++++
 .../src/server/api/endpoints/notes/search.ts  | 70 ++++++++++++++++++-
 packages/backend/src/services/note/create.ts  |  5 ++
 pnpm-lock.yaml                                | 39 +++++++++--
 6 files changed, 177 insertions(+), 5 deletions(-)
 create mode 100644 packages/backend/src/db/meilisearch.ts

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 96edb7f02..968b0af80 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -85,6 +85,7 @@
 		"koa-send": "5.0.1",
 		"koa-slow": "2.1.0",
 		"koa-views": "7.0.2",
+		"meilisearch": "^0.32.4",
 		"mfm-js": "0.23.3",
 		"mime-types": "2.1.35",
 		"multer": "1.4.4-lts.1",
diff --git a/packages/backend/src/config/types.ts b/packages/backend/src/config/types.ts
index 01a98f9f0..b6a449f54 100644
--- a/packages/backend/src/config/types.ts
+++ b/packages/backend/src/config/types.ts
@@ -40,6 +40,12 @@ export type Source = {
 		bucket?: string;
 	};
 
+	meilisearch: {
+		host: string;
+		port: number;
+		apiKey?: string;
+	};
+
 	proxy?: string;
 	proxySmtp?: string;
 	proxyBypassHosts?: string[];
diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
new file mode 100644
index 000000000..756c092e9
--- /dev/null
+++ b/packages/backend/src/db/meilisearch.ts
@@ -0,0 +1,61 @@
+import { MeiliSearch } from 'meilisearch';
+import { dbLogger } from "./logger.js";
+
+import config from "@/config/index.js";
+import {Note} from "@/models/entities/note";
+import {normalizeForSearch} from "@/misc/normalize-for-search";
+
+const logger = dbLogger.createSubLogger("meilisearch", "gray", false);
+
+logger.info("Connecting to MeiliSearch");
+
+const hasConfig =
+	config.meilisearch && (config.meilisearch.host || config.meilisearch.port || config.meilisearch.apiKey);
+
+const host = hasConfig ? config.meilisearch.host ?? "localhost" : "";
+const port = hasConfig ? config.meilisearch.port ?? 7700 : 0;
+const auth = hasConfig ? config.meilisearch.apiKey ?? "" : "";
+
+const client = new MeiliSearch({
+	host: 'http://127.0.0.1:7700',
+	apiKey: 'masterKey',
+})
+
+const posts = client.index('posts');
+
+posts.updateSearchableAttributes(['text']);
+
+logger.info("Connected to MeiliSearch");
+
+
+export type MeilisearchNote = {
+	id: string;
+	text: string;
+	userId: string;
+	userHost: string;
+	channelId: string;
+}
+
+export default hasConfig ? {
+	search: (query : string, limit : number, offset : number) => {
+		logger.info(`Searching for ${query}`);
+
+		return posts.search(query, {
+			limit: limit,
+			offset: offset,
+		});
+	},
+	ingestNote: (note : Note) => {
+		logger.info("Indexing note in MeiliSearch: " + note.id);
+
+		return posts.addDocuments([
+			{
+				id: note.id.toString(),
+				text: note.text,
+				userId: note.userId,
+				userHost: note.userHost,
+				channelId: note.channelId,
+			}
+		])
+	},
+} : null;
diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts
index 93392acdd..0a6737c9b 100644
--- a/packages/backend/src/server/api/endpoints/notes/search.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search.ts
@@ -4,6 +4,7 @@ import { Note } from "@/models/entities/note.js";
 import config from "@/config/index.js";
 import es from "../../../../db/elasticsearch.js";
 import sonic from "../../../../db/sonic.js";
+import meilisearch, {MeilisearchNote} from "../../../../db/meilisearch.js";
 import define from "../../define.js";
 import { makePaginationQuery } from "../../common/make-pagination-query.js";
 import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
@@ -62,7 +63,7 @@ export const paramDef = {
 } as const;
 
 export default define(meta, paramDef, async (ps, me) => {
-	if (es == null && sonic == null) {
+	if (es == null && sonic == null && meilisearch == null) {
 		const query = makePaginationQuery(
 			Notes.createQueryBuilder("note"),
 			ps.sinceId,
@@ -171,6 +172,73 @@ export default define(meta, paramDef, async (ps, me) => {
 		}
 
 		return found;
+	} else if(meilisearch) {
+		let start = 0;
+		const chunkSize = 100;
+
+		// Use meilisearch to fetch and step through all search results that could match the requirements
+		const ids = [];
+		while (true) {
+			const results = await meilisearch.search(ps.query, start, chunkSize);
+
+			start += chunkSize;
+
+			if (results.hits.length === 0) {
+				break;
+			}
+
+			const res = results.hits
+				.filter((key) => {
+					let note = key as MeilisearchNote;
+
+					if (ps.userId && note.userId !== ps.userId) {
+						return false;
+					}
+					if (ps.channelId && note.channelId !== ps.channelId) {
+						return false;
+					}
+					if (ps.sinceId && note.id <= ps.sinceId) {
+						return false;
+					}
+					if (ps.untilId && note.id >= ps.untilId) {
+						return false;
+					}
+					return true;
+				})
+				.map((key) => key.id);
+
+			ids.push(...res);
+		}
+
+		// Sort all the results by note id DESC (newest first)
+		ids.sort((a, b) => b - a);
+
+		// Fetch the notes from the database until we have enough to satisfy the limit
+		start = 0;
+		const found = [];
+		while (found.length < ps.limit && start < ids.length) {
+			const chunk = ids.slice(start, start + chunkSize);
+			const notes: Note[] = await Notes.find({
+				where: {
+					id: In(chunk),
+				},
+				order: {
+					id: "DESC",
+				},
+			});
+
+			// The notes are checked for visibility and muted/blocked users when packed
+			found.push(...(await Notes.packMany(notes, me)));
+			start += chunkSize;
+		}
+
+		// If we have more results than the limit, trim them
+		if (found.length > ps.limit) {
+			found.length = ps.limit;
+		}
+
+		return found;
+
 	} else {
 		const userQuery =
 			ps.userId != null
diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts
index 66c5b8508..158460421 100644
--- a/packages/backend/src/services/note/create.ts
+++ b/packages/backend/src/services/note/create.ts
@@ -67,6 +67,7 @@ import type { UserProfile } from "@/models/entities/user-profile.js";
 import { db } from "@/db/postgre.js";
 import { getActiveWebhooks } from "@/misc/webhook-cache.js";
 import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
+import meilisearch from "@/db/meilisearch";
 
 const mutedWordsCache = new Cache<
 	{ userId: UserProfile["userId"]; mutedWords: UserProfile["mutedWords"] }[]
@@ -776,6 +777,10 @@ export async function index(note: Note): Promise<void> {
 			note.text,
 		);
 	}
+
+	if (meilisearch) {
+		await meilisearch.ingestNote(note);
+	}
 }
 
 async function notifyToWatchersOfRenotee(
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 493e9fc06..48a0e498b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -263,6 +263,9 @@ importers:
       koa-views:
         specifier: 7.0.2
         version: 7.0.2(@types/koa@2.13.5)(ejs@3.1.8)(pug@3.0.2)
+      meilisearch:
+        specifier: ^0.32.4
+        version: 0.32.4
       mfm-js:
         specifier: 0.23.3
         version: 0.23.3
@@ -2819,7 +2822,7 @@ packages:
       '@types/webgl-ext': 0.0.30
       '@webgpu/types': 0.1.16
       long: 4.0.0
-      node-fetch: 2.6.8
+      node-fetch: 2.6.11
       seedrandom: 3.0.5
     transitivePeerDependencies:
       - encoding
@@ -2835,7 +2838,7 @@ packages:
       '@types/webgl-ext': 0.0.30
       '@webgpu/types': 0.1.21
       long: 4.0.0
-      node-fetch: 2.6.8
+      node-fetch: 2.6.11
       seedrandom: 3.0.5
     transitivePeerDependencies:
       - encoding
@@ -2849,7 +2852,7 @@ packages:
     dependencies:
       '@tensorflow/tfjs-core': 3.21.0
       '@types/node-fetch': 2.6.2
-      node-fetch: 2.6.8
+      node-fetch: 2.6.11
       seedrandom: 3.0.5
       string_decoder: 1.3.0
     transitivePeerDependencies:
@@ -2864,7 +2867,7 @@ packages:
     dependencies:
       '@tensorflow/tfjs-core': 4.2.0
       '@types/node-fetch': 2.6.2
-      node-fetch: 2.6.8
+      node-fetch: 2.6.11
       seedrandom: 3.0.5
       string_decoder: 1.3.0
     transitivePeerDependencies:
@@ -5938,6 +5941,14 @@ packages:
       - encoding
     dev: true
 
+  /cross-fetch@3.1.6:
+    resolution: {integrity: sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==}
+    dependencies:
+      node-fetch: 2.6.11
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+
   /cross-spawn@5.1.0:
     resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
     dependencies:
@@ -10386,6 +10397,14 @@ packages:
     engines: {node: '>= 0.6'}
     dev: false
 
+  /meilisearch@0.32.4:
+    resolution: {integrity: sha512-QvPtQ6F2TaqAT9fw072/MDjSCMpQifdtUBFeIk3M5jSnFpeSiv1iwfJWNfP6ByaCgR/s++K1Cqtf9vjcZe7prg==}
+    dependencies:
+      cross-fetch: 3.1.6
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+
   /meow@9.0.0:
     resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==}
     engines: {node: '>=10'}
@@ -10854,6 +10873,18 @@ packages:
     resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
     engines: {node: '>=10.5.0'}
 
+  /node-fetch@2.6.11:
+    resolution: {integrity: sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==}
+    engines: {node: 4.x || >=6.0.0}
+    peerDependencies:
+      encoding: ^0.1.0
+    peerDependenciesMeta:
+      encoding:
+        optional: true
+    dependencies:
+      whatwg-url: 5.0.0
+    dev: false
+
   /node-fetch@2.6.7:
     resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
     engines: {node: 4.x || >=6.0.0}

From 89f1b6357ebfc211b5becefc864fd78f4e361e82 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Thu, 25 May 2023 01:06:03 +0200
Subject: [PATCH 002/198] Meilisearch Config

---
 .config/example.yml |  7 +++++++
 docker-compose.yml  | 16 ++++++++++------
 2 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/.config/example.yml b/.config/example.yml
index 7d8ba32be..7f83446a4 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -82,6 +82,13 @@ redis:
 #  user:
 #  pass:
 
+#   ┌───────────────────────────┐
+#───┘ Meilisearch configuration └─────────────────────────────────────
+#meilisearch:
+#  host: meilisearch
+#  port: 7700
+#  pass:
+
 #   ┌───────────────┐
 #───┘ ID generation └───────────────────────────────────────────
 
diff --git a/docker-compose.yml b/docker-compose.yml
index 5de14d0c8..e1f9c7224 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -40,14 +40,18 @@ services:
     volumes:
       - ./db:/var/lib/postgresql/data
 
-  sonic:
-    restart: unless-stopped
-    image: docker.io/valeriansaliou/sonic:v1.4.0
+  meilisearch:
+    container_name: meilisearch
+    image: getmeili/meilisearch:v0.25.2
+    environment:
+      - MEILI_ENV=${MEILI_ENV:-development}
+    ports:
+      - "7700:7700"
     networks:
-      - calcnet
+      - meilisearch
     volumes:
-      - ./sonic:/var/lib/sonic/store
-      - ./sonic/config.cfg:/etc/sonic.cfg
+      - ./data.ms:/data.ms
+    restart: unless-stopped
 
 networks:
   calcnet:

From 090b5724b478476b590825d4a3624463bd89907f Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Thu, 25 May 2023 02:19:42 +0200
Subject: [PATCH 003/198] Fix wrong parameter ordering

---
 packages/backend/src/db/meilisearch.ts             |  6 ++++--
 .../src/server/api/endpoints/notes/search.ts       | 14 ++++++--------
 packages/backend/src/services/note/create.ts       |  2 +-
 3 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index 756c092e9..f404b4721 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -17,8 +17,8 @@ const port = hasConfig ? config.meilisearch.port ?? 7700 : 0;
 const auth = hasConfig ? config.meilisearch.apiKey ?? "" : "";
 
 const client = new MeiliSearch({
-	host: 'http://127.0.0.1:7700',
-	apiKey: 'masterKey',
+	host: `http://${host}:${port}`,
+	apiKey: auth,
 })
 
 const posts = client.index('posts');
@@ -39,6 +39,8 @@ export type MeilisearchNote = {
 export default hasConfig ? {
 	search: (query : string, limit : number, offset : number) => {
 		logger.info(`Searching for ${query}`);
+		logger.info(`Limit: ${limit}`);
+		logger.info(`Offset: ${offset}`);
 
 		return posts.search(query, {
 			limit: limit,
diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts
index 0a6737c9b..425414561 100644
--- a/packages/backend/src/server/api/endpoints/notes/search.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search.ts
@@ -179,7 +179,7 @@ export default define(meta, paramDef, async (ps, me) => {
 		// Use meilisearch to fetch and step through all search results that could match the requirements
 		const ids = [];
 		while (true) {
-			const results = await meilisearch.search(ps.query, start, chunkSize);
+			const results = await meilisearch.search(ps.query, chunkSize, start);
 
 			start += chunkSize;
 
@@ -188,19 +188,17 @@ export default define(meta, paramDef, async (ps, me) => {
 			}
 
 			const res = results.hits
-				.filter((key) => {
-					let note = key as MeilisearchNote;
-
-					if (ps.userId && note.userId !== ps.userId) {
+				.filter((key: MeilisearchNote) => {
+					if (ps.userId && key.userId !== ps.userId) {
 						return false;
 					}
-					if (ps.channelId && note.channelId !== ps.channelId) {
+					if (ps.channelId && key.channelId !== ps.channelId) {
 						return false;
 					}
-					if (ps.sinceId && note.id <= ps.sinceId) {
+					if (ps.sinceId && key.id <= ps.sinceId) {
 						return false;
 					}
-					if (ps.untilId && note.id >= ps.untilId) {
+					if (ps.untilId && key.id >= ps.untilId) {
 						return false;
 					}
 					return true;
diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts
index 158460421..f6285a61d 100644
--- a/packages/backend/src/services/note/create.ts
+++ b/packages/backend/src/services/note/create.ts
@@ -67,7 +67,7 @@ import type { UserProfile } from "@/models/entities/user-profile.js";
 import { db } from "@/db/postgre.js";
 import { getActiveWebhooks } from "@/misc/webhook-cache.js";
 import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
-import meilisearch from "@/db/meilisearch";
+import meilisearch from "../../db/meilisearch.js";
 
 const mutedWordsCache = new Cache<
 	{ userId: UserProfile["userId"]; mutedWords: UserProfile["mutedWords"] }[]

From 55ce94b951ca5697b9c94c6e90d5d581188f68f8 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Thu, 25 May 2023 09:53:04 +0200
Subject: [PATCH 004/198] Add Meilisearch widget

---
 packages/backend/src/daemons/server-stats.ts  | 11 ++++
 packages/backend/src/db/meilisearch.ts        | 16 ++++--
 .../src/server/api/endpoints/server-info.ts   | 12 +++++
 .../client/src/widgets/server-metric/disk.vue | 18 +++++++
 .../src/widgets/server-metric/index.vue       |  8 ++-
 .../src/widgets/server-metric/meilisearch.vue | 51 +++++++++++++++++++
 6 files changed, 112 insertions(+), 4 deletions(-)
 create mode 100644 packages/client/src/widgets/server-metric/meilisearch.vue

diff --git a/packages/backend/src/daemons/server-stats.ts b/packages/backend/src/daemons/server-stats.ts
index b0bf1288f..c7aaea035 100644
--- a/packages/backend/src/daemons/server-stats.ts
+++ b/packages/backend/src/daemons/server-stats.ts
@@ -1,6 +1,7 @@
 import si from "systeminformation";
 import Xev from "xev";
 import * as osUtils from "os-utils";
+import meilisearch from "../db/meilisearch";
 
 const ev = new Xev();
 
@@ -24,6 +25,7 @@ export default function () {
 		const memStats = await mem();
 		const netStats = await net();
 		const fsStats = await fs();
+		const meilisearchStats = await meilisearchStatus();
 
 		const stats = {
 			cpu: roundCpu(cpu),
@@ -39,6 +41,7 @@ export default function () {
 				r: round(Math.max(0, fsStats.rIO_sec ?? 0)),
 				w: round(Math.max(0, fsStats.wIO_sec ?? 0)),
 			},
+			meilisearch: meilisearchStats
 		};
 		ev.emit("serverStats", stats);
 		log.unshift(stats);
@@ -77,3 +80,11 @@ async function fs() {
 	const data = await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 }));
 	return data || { rIO_sec: 0, wIO_sec: 0 };
 }
+
+async function meilisearchStatus() {
+	if (meilisearch) {
+		return meilisearch.serverStats();
+	} else {
+		return null;
+	}
+}
diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index f404b4721..f3561b807 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -1,4 +1,4 @@
-import { MeiliSearch } from 'meilisearch';
+import {Health, MeiliSearch, Stats } from 'meilisearch';
 import { dbLogger } from "./logger.js";
 
 import config from "@/config/index.js";
@@ -16,7 +16,7 @@ const host = hasConfig ? config.meilisearch.host ?? "localhost" : "";
 const port = hasConfig ? config.meilisearch.port ?? 7700 : 0;
 const auth = hasConfig ? config.meilisearch.apiKey ?? "" : "";
 
-const client = new MeiliSearch({
+const client : MeiliSearch = new MeiliSearch({
 	host: `http://${host}:${port}`,
 	apiKey: auth,
 })
@@ -58,6 +58,16 @@ export default hasConfig ? {
 				userHost: note.userHost,
 				channelId: note.channelId,
 			}
-		])
+		]);
 	},
+	serverStats: async () => {
+		let health : Health = await client.health();
+		let stats: Stats = await client.getStats();
+
+		return {
+			health: health.status,
+			size: stats.databaseSize,
+			indexed_count: stats.indexes["posts"].numberOfDocuments
+		}
+	}
 } : null;
diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts
index 1ce27e262..4b2a4078a 100644
--- a/packages/backend/src/server/api/endpoints/server-info.ts
+++ b/packages/backend/src/server/api/endpoints/server-info.ts
@@ -1,6 +1,7 @@
 import * as os from "node:os";
 import si from "systeminformation";
 import define from "../define.js";
+import meilisearch from "../../../db/meilisearch";
 
 export const meta = {
 	requireCredential: false,
@@ -18,6 +19,7 @@ export const paramDef = {
 export default define(meta, paramDef, async () => {
 	const memStats = await si.mem();
 	const fsStats = await si.fsSize();
+	const meilisearchStats = await meilisearchStatus();
 
 	return {
 		machine: os.hostname(),
@@ -32,5 +34,15 @@ export default define(meta, paramDef, async () => {
 			total: fsStats[0].size,
 			used: fsStats[0].used,
 		},
+		meilisearch: meilisearchStats
+
 	};
 });
+
+async function meilisearchStatus() {
+	if (meilisearch) {
+		return meilisearch.serverStats();
+	} else {
+		return null;
+	}
+}
diff --git a/packages/client/src/widgets/server-metric/disk.vue b/packages/client/src/widgets/server-metric/disk.vue
index 67ea398c1..0457cf7f0 100644
--- a/packages/client/src/widgets/server-metric/disk.vue
+++ b/packages/client/src/widgets/server-metric/disk.vue
@@ -8,6 +8,12 @@
 			<p>Used: {{ bytes(used, 1) }}</p>
 		</div>
 	</div>
+	<br />
+	<div class="ms_stats">
+		<p>MeiliSearch</p>
+
+	</div>
+
 </template>
 
 <script lang="ts" setup>
@@ -26,6 +32,18 @@ const available = $computed(() => props.meta.fs.total - props.meta.fs.used);
 </script>
 
 <style lang="scss" scoped>
+.ms_stats {
+	padding: 16px;
+
+	> div {
+		> p {
+			&:first-child {
+				font-weight: bold;
+				margin-bottom: 4px;
+			}
+		}
+	}
+}
 .zbwaqsat {
 	display: flex;
 	padding: 16px;
diff --git a/packages/client/src/widgets/server-metric/index.vue b/packages/client/src/widgets/server-metric/index.vue
index 96dd3333d..8d36819b5 100644
--- a/packages/client/src/widgets/server-metric/index.vue
+++ b/packages/client/src/widgets/server-metric/index.vue
@@ -38,6 +38,11 @@
 				:connection="connection"
 				:meta="meta"
 			/>
+			<XMeili
+				v-else-if="widgetProps.view === 5"
+				:connection="connection"
+				:meta="meta"
+			/>
 		</div>
 	</MkContainer>
 </template>
@@ -56,6 +61,7 @@ import XNet from "./net.vue";
 import XCpu from "./cpu.vue";
 import XMemory from "./mem.vue";
 import XDisk from "./disk.vue";
+import XMeili from "./meilisearch.vue"
 import MkContainer from "@/components/MkContainer.vue";
 import { GetFormResultType } from "@/scripts/form";
 import * as os from "@/os";
@@ -102,7 +108,7 @@ os.api("server-info", {}).then((res) => {
 });
 
 const toggleView = () => {
-	if (widgetProps.view === 4) {
+	if (widgetProps.view === 5) {
 		widgetProps.view = 0;
 	} else {
 		widgetProps.view++;
diff --git a/packages/client/src/widgets/server-metric/meilisearch.vue b/packages/client/src/widgets/server-metric/meilisearch.vue
new file mode 100644
index 000000000..aa47eb7b6
--- /dev/null
+++ b/packages/client/src/widgets/server-metric/meilisearch.vue
@@ -0,0 +1,51 @@
+<template>
+	<div class="ms_stats">
+		<div>
+			<p><i class="ph-file-search ph-bold ph-lg"></i>MeiliSearch</p>
+			<p>Server Status: {{ available }}</p>
+			<p>Total: {{ bytes(total_size, 1) }}</p>
+			<p>Posts Indexed: {{ index_count }}</p>
+		</div>
+	</div>
+	<br />
+	<div class="ms_stats">
+		<p>MeiliSearch</p>
+
+	</div>
+
+</template>
+
+<script lang="ts" setup>
+import {} from "vue";
+import bytes from "@/filters/bytes";
+
+const props = defineProps<{
+	meta: any; // TODO
+}>();
+
+const total_size = $computed(() => props.meta.meilisearch.total);
+const index_count = $computed(() => props.meta.meilisearch.indexed_count);
+const available = $computed(() => props.meta.meilisearch.available);
+</script>
+
+<style lang="scss" scoped>
+.ms_stats {
+	padding: 16px;
+
+	> div {
+		> p {
+			margin: 0;
+			font-size: 0.8em;
+
+			&:first-child {
+				font-weight: bold;
+				margin-bottom: 4px;
+
+				> i {
+					margin-right: 4px;
+				}
+			}
+		}
+	}
+}
+</style>

From 2abf027d3351debd961c7db132dd833f7d0375be Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Thu, 25 May 2023 10:21:29 +0200
Subject: [PATCH 005/198] Fix property names

---
 .../client/src/widgets/server-metric/disk.vue   | 17 -----------------
 .../src/widgets/server-metric/meilisearch.vue   |  4 ++--
 2 files changed, 2 insertions(+), 19 deletions(-)

diff --git a/packages/client/src/widgets/server-metric/disk.vue b/packages/client/src/widgets/server-metric/disk.vue
index 0457cf7f0..99191e62a 100644
--- a/packages/client/src/widgets/server-metric/disk.vue
+++ b/packages/client/src/widgets/server-metric/disk.vue
@@ -9,11 +9,6 @@
 		</div>
 	</div>
 	<br />
-	<div class="ms_stats">
-		<p>MeiliSearch</p>
-
-	</div>
-
 </template>
 
 <script lang="ts" setup>
@@ -32,18 +27,6 @@ const available = $computed(() => props.meta.fs.total - props.meta.fs.used);
 </script>
 
 <style lang="scss" scoped>
-.ms_stats {
-	padding: 16px;
-
-	> div {
-		> p {
-			&:first-child {
-				font-weight: bold;
-				margin-bottom: 4px;
-			}
-		}
-	}
-}
 .zbwaqsat {
 	display: flex;
 	padding: 16px;
diff --git a/packages/client/src/widgets/server-metric/meilisearch.vue b/packages/client/src/widgets/server-metric/meilisearch.vue
index aa47eb7b6..e45dceab0 100644
--- a/packages/client/src/widgets/server-metric/meilisearch.vue
+++ b/packages/client/src/widgets/server-metric/meilisearch.vue
@@ -23,9 +23,9 @@ const props = defineProps<{
 	meta: any; // TODO
 }>();
 
-const total_size = $computed(() => props.meta.meilisearch.total);
+const total_size = $computed(() => props.meta.meilisearch.size);
 const index_count = $computed(() => props.meta.meilisearch.indexed_count);
-const available = $computed(() => props.meta.meilisearch.available);
+const available = $computed(() => props.meta.meilisearch.health);
 </script>
 
 <style lang="scss" scoped>

From d12e58a038585fdeaca76d2a2a721a9def7ebdda Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Thu, 25 May 2023 10:23:59 +0200
Subject: [PATCH 006/198] Import .js files

---
 packages/backend/src/daemons/server-stats.ts             | 2 +-
 packages/backend/src/server/api/endpoints/server-info.ts | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/backend/src/daemons/server-stats.ts b/packages/backend/src/daemons/server-stats.ts
index c7aaea035..01b9cdcb4 100644
--- a/packages/backend/src/daemons/server-stats.ts
+++ b/packages/backend/src/daemons/server-stats.ts
@@ -1,7 +1,7 @@
 import si from "systeminformation";
 import Xev from "xev";
 import * as osUtils from "os-utils";
-import meilisearch from "../db/meilisearch";
+import meilisearch from "../db/meilisearch.js";
 
 const ev = new Xev();
 
diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts
index 4b2a4078a..3411ba416 100644
--- a/packages/backend/src/server/api/endpoints/server-info.ts
+++ b/packages/backend/src/server/api/endpoints/server-info.ts
@@ -1,7 +1,7 @@
 import * as os from "node:os";
 import si from "systeminformation";
 import define from "../define.js";
-import meilisearch from "../../../db/meilisearch";
+import meilisearch from "../../../db/meilisearch.js";
 
 export const meta = {
 	requireCredential: false,

From 4cf7089e9dec3ed993e32c1bd4a91269e125c1ad Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Thu, 25 May 2023 10:31:22 +0200
Subject: [PATCH 007/198] remove extra header

---
 packages/client/src/widgets/server-metric/meilisearch.vue | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/packages/client/src/widgets/server-metric/meilisearch.vue b/packages/client/src/widgets/server-metric/meilisearch.vue
index e45dceab0..182896cc0 100644
--- a/packages/client/src/widgets/server-metric/meilisearch.vue
+++ b/packages/client/src/widgets/server-metric/meilisearch.vue
@@ -3,16 +3,11 @@
 		<div>
 			<p><i class="ph-file-search ph-bold ph-lg"></i>MeiliSearch</p>
 			<p>Server Status: {{ available }}</p>
-			<p>Total: {{ bytes(total_size, 1) }}</p>
+			<p>Total: {{ bytes(total_size, 2) }}</p>
 			<p>Posts Indexed: {{ index_count }}</p>
 		</div>
 	</div>
 	<br />
-	<div class="ms_stats">
-		<p>MeiliSearch</p>
-
-	</div>
-
 </template>
 
 <script lang="ts" setup>

From 8edac3c654b0e46602a095352963f53deaac351a Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Thu, 25 May 2023 14:15:13 +0200
Subject: [PATCH 008/198] Add basic advanced search + attachment metadata hints

---
 packages/backend/src/db/meilisearch.ts | 46 ++++++++++++++++++++++++--
 1 file changed, 44 insertions(+), 2 deletions(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index f3561b807..407565036 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -3,7 +3,6 @@ import { dbLogger } from "./logger.js";
 
 import config from "@/config/index.js";
 import {Note} from "@/models/entities/note";
-import {normalizeForSearch} from "@/misc/normalize-for-search";
 
 const logger = dbLogger.createSubLogger("meilisearch", "gray", false);
 
@@ -16,7 +15,7 @@ const host = hasConfig ? config.meilisearch.host ?? "localhost" : "";
 const port = hasConfig ? config.meilisearch.port ?? 7700 : 0;
 const auth = hasConfig ? config.meilisearch.apiKey ?? "" : "";
 
-const client : MeiliSearch = new MeiliSearch({
+const client: MeiliSearch = new MeiliSearch({
 	host: `http://${host}:${port}`,
 	apiKey: auth,
 })
@@ -25,6 +24,8 @@ const posts = client.index('posts');
 
 posts.updateSearchableAttributes(['text']);
 
+posts.updateFilterableAttributes(["userId", "userHost", "mediaAttachment"])
+
 logger.info("Connected to MeiliSearch");
 
 
@@ -34,6 +35,7 @@ export type MeilisearchNote = {
 	userId: string;
 	userHost: string;
 	channelId: string;
+	mediaAttachment: string;
 }
 
 export default hasConfig ? {
@@ -42,14 +44,53 @@ export default hasConfig ? {
 		logger.info(`Limit: ${limit}`);
 		logger.info(`Offset: ${offset}`);
 
+		/// Advanced search syntax
+		/// from:user => filter by user + optional domain
+		/// has:image/video/audio/text/file => filter by attachment types
+		/// domain:domain.com => filter by domain
+
+		let constructedFilters: string[] = [];
+
+		let splitSearch = query.split(" ");
+		splitSearch.forEach(term => {
+			if (term.startsWith("has:")) {
+				let fileType = term.slice(4);
+				constructedFilters.push(`mediaAttachment = "${fileType}"`)
+			}
+			if (term.startsWith("from:")) {
+				let user = term.slice(5);
+				constructedFilters.push(`userId = ${user}`)
+			}
+			if (term.startsWith("domain:")) {
+				let domain = term.slice(7);
+				constructedFilters.push(`userHost = ${domain}`)
+			}
+		})
+
 		return posts.search(query, {
 			limit: limit,
 			offset: offset,
+			filter: constructedFilters
 		});
 	},
 	ingestNote: (note : Note) => {
 		logger.info("Indexing note in MeiliSearch: " + note.id);
 
+		let attachmentType = "";
+		if (note.attachedFileTypes.length > 0) {
+			attachmentType = note.attachedFileTypes[0].split("/")[0];
+			switch (attachmentType) {
+				case "image":
+				case "video":
+				case "audio":
+				case "text":
+					break;
+				default:
+					attachmentType = "file"
+					break
+			}
+		}
+
 		return posts.addDocuments([
 			{
 				id: note.id.toString(),
@@ -57,6 +98,7 @@ export default hasConfig ? {
 				userId: note.userId,
 				userHost: note.userHost,
 				channelId: note.channelId,
+				mediaAttachment: attachmentType
 			}
 		]);
 	},

From 44478db3e6d37b33d00de93fa27da4cc447fc4e9 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Thu, 25 May 2023 22:29:47 +0200
Subject: [PATCH 009/198] Add createdAt column, fix username filtering, filter
 advanced search terms correctly

---
 packages/backend/src/db/meilisearch.ts | 43 ++++++++++++++++++++------
 1 file changed, 33 insertions(+), 10 deletions(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index 407565036..49b955672 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -22,20 +22,21 @@ const client: MeiliSearch = new MeiliSearch({
 
 const posts = client.index('posts');
 
-posts.updateSearchableAttributes(['text']);
+posts.updateSearchableAttributes(['text']).catch((e) => logger.error(`Setting searchable attr failed, searches won't work: ${e}`));
 
-posts.updateFilterableAttributes(["userId", "userHost", "mediaAttachment"])
+posts.updateFilterableAttributes(["userName", "userHost", "mediaAttachment", "createdAt"]).catch((e) => logger.error(`Setting filterable attr failed, advanced searches won't work: ${e}`));
 
 logger.info("Connected to MeiliSearch");
 
-
 export type MeilisearchNote = {
 	id: string;
 	text: string;
 	userId: string;
 	userHost: string;
+	userName: string;
 	channelId: string;
 	mediaAttachment: string;
+	createdAt: number
 }
 
 export default hasConfig ? {
@@ -48,23 +49,43 @@ export default hasConfig ? {
 		/// from:user => filter by user + optional domain
 		/// has:image/video/audio/text/file => filter by attachment types
 		/// domain:domain.com => filter by domain
+		/// before:Date => show posts made before Date
+		/// after: Date => show posts made after Date
+
 
 		let constructedFilters: string[] = [];
 
 		let splitSearch = query.split(" ");
-		splitSearch.forEach(term => {
+
+		// Detect search operators and remove them from the actual query
+		splitSearch.filter(term => {
 			if (term.startsWith("has:")) {
 				let fileType = term.slice(4);
 				constructedFilters.push(`mediaAttachment = "${fileType}"`)
-			}
-			if (term.startsWith("from:")) {
+				return false;
+			} else if (term.startsWith("from:")) {
 				let user = term.slice(5);
-				constructedFilters.push(`userId = ${user}`)
-			}
-			if (term.startsWith("domain:")) {
+				constructedFilters.push(`userName = ${user}`)
+				return false;
+			} else if (term.startsWith("domain:")) {
 				let domain = term.slice(7);
 				constructedFilters.push(`userHost = ${domain}`)
+				return false;
+			} else if (term.startsWith("after:")) {
+				let timestamp = term.slice(6);
+				// Try to parse the timestamp as JavaScript Date
+				let date = Date.parse(timestamp);
+				if (isNaN(date)) return false;
+				constructedFilters.push(`createdAt > ${date}`)
+			} else if (term.startsWith("before:")) {
+				let timestamp = term.slice(7);
+				// Try to parse the timestamp as JavaScript Date
+				let date = Date.parse(timestamp);
+				if (isNaN(date)) return false;
+				constructedFilters.push(`createdAt < ${date}`)
 			}
+
+			return true;
 		})
 
 		return posts.search(query, {
@@ -98,7 +119,9 @@ export default hasConfig ? {
 				userId: note.userId,
 				userHost: note.userHost,
 				channelId: note.channelId,
-				mediaAttachment: attachmentType
+				mediaAttachment: attachmentType,
+				userName: note.user?.username,
+				createdAt: note.createdAt.getTime() / 1000 // division by 1000 is necessary because Node returns in ms-accuracy
 			}
 		]);
 	},

From 243b4fb60b73e111fb39461d17705b2c8005449b Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Thu, 25 May 2023 22:37:43 +0200
Subject: [PATCH 010/198] Adjust search term logging + use filtered search
 query

---
 packages/backend/src/db/meilisearch.ts | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index 49b955672..eecbf93e6 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -41,9 +41,6 @@ export type MeilisearchNote = {
 
 export default hasConfig ? {
 	search: (query : string, limit : number, offset : number) => {
-		logger.info(`Searching for ${query}`);
-		logger.info(`Limit: ${limit}`);
-		logger.info(`Offset: ${offset}`);
 
 		/// Advanced search syntax
 		/// from:user => filter by user + optional domain
@@ -88,7 +85,13 @@ export default hasConfig ? {
 			return true;
 		})
 
-		return posts.search(query, {
+		logger.info(`Searching for ${query}`);
+		logger.info(`Limit: ${limit}`);
+		logger.info(`Offset: ${offset}`);
+		logger.info(`Filters: ${constructedFilters}`)
+
+
+		return posts.search(splitSearch.join(" "), {
 			limit: limit,
 			offset: offset,
 			filter: constructedFilters

From 07ef4b5ed1742dcc0702de590870d35deb6dc2f5 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Thu, 25 May 2023 23:25:52 +0200
Subject: [PATCH 011/198] Add search operators to frontend

---
 packages/client/src/scripts/search.ts | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/packages/client/src/scripts/search.ts b/packages/client/src/scripts/search.ts
index 36646d24f..54b300e1d 100644
--- a/packages/client/src/scripts/search.ts
+++ b/packages/client/src/scripts/search.ts
@@ -5,6 +5,13 @@ import { mainRouter } from "@/router";
 export async function search() {
 	const { canceled, result: query } = await os.inputText({
 		title: i18n.ts.search,
+		placeholder: "Enter search terms...",
+		text: "Advanced search operators\n" +
+			"from:user => filter by user\n" +
+			"has:image/video/audio/text/file => filter by attachment types\n" +
+			"domain:domain.com => filter by domain\n" +
+			"before:Date => show posts made before Date\n" +
+			"after:Date => show posts made after Date"
 	});
 	if (canceled || query == null || query === "") return;
 

From 206f7bfc09c41b129d135f71923cef1dd6524629 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Thu, 25 May 2023 23:49:52 +0200
Subject: [PATCH 012/198] Add high performance batch imports

---
 packages/backend/src/db/meilisearch.ts        | 66 +++++++++++--------
 .../processors/background/index-all-notes.ts  | 12 +++-
 packages/backend/src/services/note/create.ts  |  4 +-
 3 files changed, 50 insertions(+), 32 deletions(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index eecbf93e6..abd248e46 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -97,36 +97,48 @@ export default hasConfig ? {
 			filter: constructedFilters
 		});
 	},
-	ingestNote: (note : Note) => {
-		logger.info("Indexing note in MeiliSearch: " + note.id);
-
-		let attachmentType = "";
-		if (note.attachedFileTypes.length > 0) {
-			attachmentType = note.attachedFileTypes[0].split("/")[0];
-			switch (attachmentType) {
-				case "image":
-				case "video":
-				case "audio":
-				case "text":
-					break;
-				default:
-					attachmentType = "file"
-					break
-			}
+	ingestNote: (note: Note | Note[]) => {
+		if (note instanceof Note) {
+			note = [note];
 		}
 
-		return posts.addDocuments([
-			{
-				id: note.id.toString(),
-				text: note.text,
-				userId: note.userId,
-				userHost: note.userHost,
-				channelId: note.channelId,
-				mediaAttachment: attachmentType,
-				userName: note.user?.username,
-				createdAt: note.createdAt.getTime() / 1000 // division by 1000 is necessary because Node returns in ms-accuracy
+		let indexingBatch: MeilisearchNote[] = [];
+
+		note.forEach(note => {
+
+			let attachmentType = "";
+			if (note.attachedFileTypes.length > 0) {
+				attachmentType = note.attachedFileTypes[0].split("/")[0];
+				switch (attachmentType) {
+					case "image":
+					case "video":
+					case "audio":
+					case "text":
+						break;
+					default:
+						attachmentType = "file"
+						break
+				}
 			}
-		]);
+
+			indexingBatch.push({
+					id: note.id.toString(),
+					text: note.text ? note.text : "",
+					userId: note.userId,
+					userHost: note.userHost ? note.userHost : "",
+					channelId: note.channelId ? note.channelId : "",
+					mediaAttachment: attachmentType,
+					userName: note.user?.username ? note.user.username : "",
+					createdAt: note.createdAt.getTime() / 1000 // division by 1000 is necessary because Node returns in ms-accuracy
+				}
+			)
+		});
+
+		let indexingIDs = indexingBatch.map(note => note.id);
+
+		logger.info("Indexing notes in MeiliSearch: " + indexingIDs.join(","));
+
+		return posts.addDocuments(indexingBatch);
 	},
 	serverStats: async () => {
 		let health : Health = await client.health();
diff --git a/packages/backend/src/queue/processors/background/index-all-notes.ts b/packages/backend/src/queue/processors/background/index-all-notes.ts
index 03219199d..9bed4eb73 100644
--- a/packages/backend/src/queue/processors/background/index-all-notes.ts
+++ b/packages/backend/src/queue/processors/background/index-all-notes.ts
@@ -4,7 +4,8 @@ import { queueLogger } from "../../logger.js";
 import { Notes } from "@/models/index.js";
 import { MoreThan } from "typeorm";
 import { index } from "@/services/note/create.js";
-import { Note } from "@/models/entities/note.js";
+import {Note} from "@/models/entities/note.js";
+import meilisearch from "../../../db/meilisearch.js";
 
 const logger = queueLogger.createSubLogger("index-all-notes");
 
@@ -58,11 +59,16 @@ export default async function indexAllNotes(
 
 		for (let i = 0; i < notes.length; i += batch) {
 			const chunk = notes.slice(i, i + batch);
-			await Promise.all(chunk.map((note) => index(note)));
+
+			if (meilisearch) {
+				await meilisearch.ingestNote(chunk)
+			}
+
+			await Promise.all(chunk.map((note) => index(note, true)));
 
 			indexedCount += chunk.length;
 			const pct = (indexedCount / total) * 100;
-			job.update({ indexedCount, cursor, total });
+			job.update({indexedCount, cursor, total});
 			job.progress(+pct.toFixed(1));
 			logger.info(`Indexed notes ${indexedCount}/${total ? total : "?"}`);
 		}
diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts
index f6285a61d..bd54db7e2 100644
--- a/packages/backend/src/services/note/create.ts
+++ b/packages/backend/src/services/note/create.ts
@@ -749,7 +749,7 @@ async function insertNote(
 	}
 }
 
-export async function index(note: Note): Promise<void> {
+export async function index(note: Note, reindexing: boolean): Promise<void> {
 	if (!note.text) return;
 
 	if (config.elasticsearch && es) {
@@ -778,7 +778,7 @@ export async function index(note: Note): Promise<void> {
 		);
 	}
 
-	if (meilisearch) {
+	if (meilisearch && !reindexing) {
 		await meilisearch.ingestNote(note);
 	}
 }

From 755ab39ba0ad442afce0dde138e020cf2213f971 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Thu, 25 May 2023 23:53:08 +0200
Subject: [PATCH 013/198] Fix import

---
 packages/backend/src/db/meilisearch.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index abd248e46..d7b89bfc0 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -2,7 +2,7 @@ import {Health, MeiliSearch, Stats } from 'meilisearch';
 import { dbLogger } from "./logger.js";
 
 import config from "@/config/index.js";
-import {Note} from "@/models/entities/note";
+import {Note} from "@/models/entities/note.js";
 
 const logger = dbLogger.createSubLogger("meilisearch", "gray", false);
 

From 6c7cf90c5b8927bee063683705e678d76c86f0a0 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Fri, 26 May 2023 00:04:07 +0200
Subject: [PATCH 014/198] Specify primary key

---
 packages/backend/src/db/meilisearch.ts | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index d7b89bfc0..bd0514be0 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -138,7 +138,9 @@ export default hasConfig ? {
 
 		logger.info("Indexing notes in MeiliSearch: " + indexingIDs.join(","));
 
-		return posts.addDocuments(indexingBatch);
+		return posts.addDocuments(indexingBatch, {
+			primaryKey: "id"
+		});
 	},
 	serverStats: async () => {
 		let health : Health = await client.health();

From e23ca849251c73a09a78e0bcd76817c35502a718 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Fri, 26 May 2023 00:33:02 +0200
Subject: [PATCH 015/198] Add parsing of host, fix usernames

---
 packages/backend/src/db/meilisearch.ts | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index bd0514be0..c19e6b1ed 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -3,6 +3,7 @@ import { dbLogger } from "./logger.js";
 
 import config from "@/config/index.js";
 import {Note} from "@/models/entities/note.js";
+import * as url from "url";
 
 const logger = dbLogger.createSubLogger("meilisearch", "gray", false);
 
@@ -121,14 +122,14 @@ export default hasConfig ? {
 				}
 			}
 
-			indexingBatch.push({
+			indexingBatch.push(<MeilisearchNote>{
 					id: note.id.toString(),
 					text: note.text ? note.text : "",
 					userId: note.userId,
-					userHost: note.userHost ? note.userHost : "",
+					userHost: note.userHost !== "" ? note.userHost : url.parse(config.host).host,
 					channelId: note.channelId ? note.channelId : "",
 					mediaAttachment: attachmentType,
-					userName: note.user?.username ? note.user.username : "",
+					userName: note.user?.usernameLower ?? "UNKNOWN",
 					createdAt: note.createdAt.getTime() / 1000 // division by 1000 is necessary because Node returns in ms-accuracy
 				}
 			)

From f5aa0c86cfd06527769071c2fe8939b198179092 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Fri, 26 May 2023 02:07:34 +0200
Subject: [PATCH 016/198] Load relationships to populate user fields

---
 packages/backend/src/db/meilisearch.ts        | 26 +++++++++++++------
 .../processors/background/index-all-notes.ts  |  1 +
 2 files changed, 19 insertions(+), 8 deletions(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index c19e6b1ed..acb16e465 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -1,9 +1,12 @@
-import {Health, MeiliSearch, Stats } from 'meilisearch';
-import { dbLogger } from "./logger.js";
+import {Health, MeiliSearch, Stats} from 'meilisearch';
+import {dbLogger} from "./logger.js";
 
 import config from "@/config/index.js";
 import {Note} from "@/models/entities/note.js";
 import * as url from "url";
+import {User} from "@/models/entities/user.js";
+import {Users} from "@/models/index.js";
+
 
 const logger = dbLogger.createSubLogger("meilisearch", "gray", false);
 
@@ -98,14 +101,21 @@ export default hasConfig ? {
 			filter: constructedFilters
 		});
 	},
-	ingestNote: (note: Note | Note[]) => {
-		if (note instanceof Note) {
-			note = [note];
+	ingestNote: async (ingestNotes: Note | Note[]) => {
+		if (ingestNotes instanceof Note) {
+			ingestNotes = [ingestNotes];
 		}
 
 		let indexingBatch: MeilisearchNote[] = [];
 
-		note.forEach(note => {
+		for (let note of ingestNotes) {
+			if (note.user === undefined) {
+				let user = await Users.findOne({
+					where: {
+						id: note.userId
+					}
+				});
+			}
 
 			let attachmentType = "";
 			if (note.attachedFileTypes.length > 0) {
@@ -129,11 +139,11 @@ export default hasConfig ? {
 					userHost: note.userHost !== "" ? note.userHost : url.parse(config.host).host,
 					channelId: note.channelId ? note.channelId : "",
 					mediaAttachment: attachmentType,
-					userName: note.user?.usernameLower ?? "UNKNOWN",
+					userName: note.user?.username ?? "UNKNOWN",
 					createdAt: note.createdAt.getTime() / 1000 // division by 1000 is necessary because Node returns in ms-accuracy
 				}
 			)
-		});
+		}
 
 		let indexingIDs = indexingBatch.map(note => note.id);
 
diff --git a/packages/backend/src/queue/processors/background/index-all-notes.ts b/packages/backend/src/queue/processors/background/index-all-notes.ts
index 9bed4eb73..919c29ff7 100644
--- a/packages/backend/src/queue/processors/background/index-all-notes.ts
+++ b/packages/backend/src/queue/processors/background/index-all-notes.ts
@@ -39,6 +39,7 @@ export default async function indexAllNotes(
 				order: {
 					id: 1,
 				},
+				relations: ["user"]
 			});
 		} catch (e) {
 			logger.error(`Failed to query notes ${e}`);

From 85403efe4ea4b934268f2df3b7a6e66aa39679cb Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Fri, 26 May 2023 02:25:22 +0200
Subject: [PATCH 017/198] Actually assign user object to note

---
 packages/backend/src/db/meilisearch.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index acb16e465..3d2e97692 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -115,6 +115,7 @@ export default hasConfig ? {
 						id: note.userId
 					}
 				});
+				note.user = user;
 			}
 
 			let attachmentType = "";

From de3089937d598811f993fee4afdff1dadcbd9398 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Fri, 26 May 2023 02:31:10 +0200
Subject: [PATCH 018/198] .filter isn't in-place, now reassigning correctly

---
 packages/backend/src/db/meilisearch.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index 3d2e97692..edd077c03 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -59,7 +59,7 @@ export default hasConfig ? {
 		let splitSearch = query.split(" ");
 
 		// Detect search operators and remove them from the actual query
-		splitSearch.filter(term => {
+		splitSearch = splitSearch.filter(term => {
 			if (term.startsWith("has:")) {
 				let fileType = term.slice(4);
 				constructedFilters.push(`mediaAttachment = "${fileType}"`)

From 35b9245dc45bcac107a7b7e32fb783b3347fb84f Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Fri, 26 May 2023 02:38:18 +0200
Subject: [PATCH 019/198] Correct logging statement + add missing returns

---
 packages/backend/src/db/meilisearch.ts | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index edd077c03..25cdc13d4 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -78,18 +78,20 @@ export default hasConfig ? {
 				let date = Date.parse(timestamp);
 				if (isNaN(date)) return false;
 				constructedFilters.push(`createdAt > ${date}`)
+				return false;
 			} else if (term.startsWith("before:")) {
 				let timestamp = term.slice(7);
 				// Try to parse the timestamp as JavaScript Date
 				let date = Date.parse(timestamp);
 				if (isNaN(date)) return false;
 				constructedFilters.push(`createdAt < ${date}`)
+				return false;
 			}
 
 			return true;
 		})
 
-		logger.info(`Searching for ${query}`);
+		logger.info(`Searching for ${splitSearch.join(" ")}`);
 		logger.info(`Limit: ${limit}`);
 		logger.info(`Offset: ${offset}`);
 		logger.info(`Filters: ${constructedFilters}`)

From 3573b6e6bd1876a4b9b489c0e770b0a56c918c2e Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Fri, 26 May 2023 02:46:05 +0200
Subject: [PATCH 020/198] Fix apiKey naming in config

---
 .config/example.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.config/example.yml b/.config/example.yml
index 7f83446a4..0f2eecf90 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -87,7 +87,7 @@ redis:
 #meilisearch:
 #  host: meilisearch
 #  port: 7700
-#  pass:
+#  apiKey:
 
 #   ┌───────────────┐
 #───┘ ID generation └───────────────────────────────────────────

From e7420a5015962480e578158b84783cfb30a90114 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Fri, 26 May 2023 02:51:17 +0200
Subject: [PATCH 021/198] Fix up dockerfile to use 1.0 meili

---
 docker-compose.yml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/docker-compose.yml b/docker-compose.yml
index e1f9c7224..9f503c490 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -8,7 +8,7 @@ services:
     depends_on:
       - db
       - redis
-      - sonic
+      - meilisearch
     ports:
       - "3000:3000"
     networks:
@@ -42,15 +42,15 @@ services:
 
   meilisearch:
     container_name: meilisearch
-    image: getmeili/meilisearch:v0.25.2
+    image: getmeili/meilisearch:v1.1.1
     environment:
       - MEILI_ENV=${MEILI_ENV:-development}
     ports:
       - "7700:7700"
     networks:
-      - meilisearch
+      - calcnet
     volumes:
-      - ./data.ms:/data.ms
+      - ./meili_data:/meili_data
     restart: unless-stopped
 
 networks:

From a8a88af73bbda2b5e6e98a6c3cbcafe4575af328 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@noreply.codeberg.org>
Date: Fri, 26 May 2023 01:06:41 +0000
Subject: [PATCH 022/198] Remove indexing logger

---
 packages/backend/src/db/meilisearch.ts | 2 --
 1 file changed, 2 deletions(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index 25cdc13d4..2364ac7ba 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -150,8 +150,6 @@ export default hasConfig ? {
 
 		let indexingIDs = indexingBatch.map(note => note.id);
 
-		logger.info("Indexing notes in MeiliSearch: " + indexingIDs.join(","));
-
 		return posts.addDocuments(indexingBatch, {
 			primaryKey: "id"
 		});

From 2f20cf476bfdd50bb26408242b52a0e100b1a8cd Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Fri, 26 May 2023 03:06:41 +0200
Subject: [PATCH 023/198] Lint + formatting

---
 packages/backend/src/daemons/server-stats.ts  |   2 +-
 packages/backend/src/db/meilisearch.ts        | 248 ++++++++++--------
 .../processors/background/index-all-notes.ts  |   8 +-
 .../src/server/api/endpoints/notes/search.ts  |   3 +-
 .../src/server/api/endpoints/server-info.ts   |   3 +-
 packages/client/src/scripts/search.ts         |   5 +-
 .../src/widgets/server-metric/index.vue       |   2 +-
 7 files changed, 144 insertions(+), 127 deletions(-)

diff --git a/packages/backend/src/daemons/server-stats.ts b/packages/backend/src/daemons/server-stats.ts
index 01b9cdcb4..4227ce6ee 100644
--- a/packages/backend/src/daemons/server-stats.ts
+++ b/packages/backend/src/daemons/server-stats.ts
@@ -41,7 +41,7 @@ export default function () {
 				r: round(Math.max(0, fsStats.rIO_sec ?? 0)),
 				w: round(Math.max(0, fsStats.wIO_sec ?? 0)),
 			},
-			meilisearch: meilisearchStats
+			meilisearch: meilisearchStats,
 		};
 		ev.emit("serverStats", stats);
 		log.unshift(stats);
diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index 2364ac7ba..75d584f8a 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -1,4 +1,4 @@
-import {Health, MeiliSearch, Stats} from 'meilisearch';
+import {Health, MeiliSearch, Stats} from "meilisearch";
 import {dbLogger} from "./logger.js";
 
 import config from "@/config/index.js";
@@ -7,13 +7,15 @@ import * as url from "url";
 import {User} from "@/models/entities/user.js";
 import {Users} from "@/models/index.js";
 
-
 const logger = dbLogger.createSubLogger("meilisearch", "gray", false);
 
 logger.info("Connecting to MeiliSearch");
 
 const hasConfig =
-	config.meilisearch && (config.meilisearch.host || config.meilisearch.port || config.meilisearch.apiKey);
+	config.meilisearch &&
+	(config.meilisearch.host ||
+		config.meilisearch.port ||
+		config.meilisearch.apiKey);
 
 const host = hasConfig ? config.meilisearch.host ?? "localhost" : "";
 const port = hasConfig ? config.meilisearch.port ?? 7700 : 0;
@@ -22,13 +24,28 @@ const auth = hasConfig ? config.meilisearch.apiKey ?? "" : "";
 const client: MeiliSearch = new MeiliSearch({
 	host: `http://${host}:${port}`,
 	apiKey: auth,
-})
+});
 
-const posts = client.index('posts');
+const posts = client.index("posts");
 
-posts.updateSearchableAttributes(['text']).catch((e) => logger.error(`Setting searchable attr failed, searches won't work: ${e}`));
+posts
+	.updateSearchableAttributes(["text"])
+	.catch((e) =>
+		logger.error(`Setting searchable attr failed, searches won't work: ${e}`),
+	);
 
-posts.updateFilterableAttributes(["userName", "userHost", "mediaAttachment", "createdAt"]).catch((e) => logger.error(`Setting filterable attr failed, advanced searches won't work: ${e}`));
+posts
+	.updateFilterableAttributes([
+		"userName",
+		"userHost",
+		"mediaAttachment",
+		"createdAt",
+	])
+	.catch((e) =>
+		logger.error(
+			`Setting filterable attr failed, advanced searches won't work: ${e}`,
+		),
+	);
 
 logger.info("Connected to MeiliSearch");
 
@@ -40,128 +57,129 @@ export type MeilisearchNote = {
 	userName: string;
 	channelId: string;
 	mediaAttachment: string;
-	createdAt: number
-}
+	createdAt: number;
+};
 
-export default hasConfig ? {
-	search: (query : string, limit : number, offset : number) => {
+export default hasConfig
+	? {
+		search: (query: string, limit: number, offset: number) => {
+			/// Advanced search syntax
+			/// from:user => filter by user + optional domain
+			/// has:image/video/audio/text/file => filter by attachment types
+			/// domain:domain.com => filter by domain
+			/// before:Date => show posts made before Date
+			/// after: Date => show posts made after Date
 
-		/// Advanced search syntax
-		/// from:user => filter by user + optional domain
-		/// has:image/video/audio/text/file => filter by attachment types
-		/// domain:domain.com => filter by domain
-		/// before:Date => show posts made before Date
-		/// after: Date => show posts made after Date
+			let constructedFilters: string[] = [];
 
+			let splitSearch = query.split(" ");
 
-		let constructedFilters: string[] = [];
-
-		let splitSearch = query.split(" ");
-
-		// Detect search operators and remove them from the actual query
-		splitSearch = splitSearch.filter(term => {
-			if (term.startsWith("has:")) {
-				let fileType = term.slice(4);
-				constructedFilters.push(`mediaAttachment = "${fileType}"`)
-				return false;
-			} else if (term.startsWith("from:")) {
-				let user = term.slice(5);
-				constructedFilters.push(`userName = ${user}`)
-				return false;
-			} else if (term.startsWith("domain:")) {
-				let domain = term.slice(7);
-				constructedFilters.push(`userHost = ${domain}`)
-				return false;
-			} else if (term.startsWith("after:")) {
-				let timestamp = term.slice(6);
-				// Try to parse the timestamp as JavaScript Date
-				let date = Date.parse(timestamp);
-				if (isNaN(date)) return false;
-				constructedFilters.push(`createdAt > ${date}`)
-				return false;
-			} else if (term.startsWith("before:")) {
-				let timestamp = term.slice(7);
-				// Try to parse the timestamp as JavaScript Date
-				let date = Date.parse(timestamp);
-				if (isNaN(date)) return false;
-				constructedFilters.push(`createdAt < ${date}`)
-				return false;
-			}
-
-			return true;
-		})
-
-		logger.info(`Searching for ${splitSearch.join(" ")}`);
-		logger.info(`Limit: ${limit}`);
-		logger.info(`Offset: ${offset}`);
-		logger.info(`Filters: ${constructedFilters}`)
-
-
-		return posts.search(splitSearch.join(" "), {
-			limit: limit,
-			offset: offset,
-			filter: constructedFilters
-		});
-	},
-	ingestNote: async (ingestNotes: Note | Note[]) => {
-		if (ingestNotes instanceof Note) {
-			ingestNotes = [ingestNotes];
-		}
-
-		let indexingBatch: MeilisearchNote[] = [];
-
-		for (let note of ingestNotes) {
-			if (note.user === undefined) {
-				let user = await Users.findOne({
-					where: {
-						id: note.userId
-					}
-				});
-				note.user = user;
-			}
-
-			let attachmentType = "";
-			if (note.attachedFileTypes.length > 0) {
-				attachmentType = note.attachedFileTypes[0].split("/")[0];
-				switch (attachmentType) {
-					case "image":
-					case "video":
-					case "audio":
-					case "text":
-						break;
-					default:
-						attachmentType = "file"
-						break
+			// Detect search operators and remove them from the actual query
+			splitSearch = splitSearch.filter((term) => {
+				if (term.startsWith("has:")) {
+					let fileType = term.slice(4);
+					constructedFilters.push(`mediaAttachment = "${fileType}"`);
+					return false;
+				} else if (term.startsWith("from:")) {
+					let user = term.slice(5);
+					constructedFilters.push(`userName = ${user}`);
+					return false;
+				} else if (term.startsWith("domain:")) {
+					let domain = term.slice(7);
+					constructedFilters.push(`userHost = ${domain}`);
+					return false;
+				} else if (term.startsWith("after:")) {
+					let timestamp = term.slice(6);
+					// Try to parse the timestamp as JavaScript Date
+					let date = Date.parse(timestamp);
+					if (isNaN(date)) return false;
+					constructedFilters.push(`createdAt > ${date}`);
+					return false;
+				} else if (term.startsWith("before:")) {
+					let timestamp = term.slice(7);
+					// Try to parse the timestamp as JavaScript Date
+					let date = Date.parse(timestamp);
+					if (isNaN(date)) return false;
+					constructedFilters.push(`createdAt < ${date}`);
+					return false;
 				}
+
+				return true;
+			});
+
+			logger.info(`Searching for ${splitSearch.join(" ")}`);
+			logger.info(`Limit: ${limit}`);
+			logger.info(`Offset: ${offset}`);
+			logger.info(`Filters: ${constructedFilters}`);
+
+			return posts.search(splitSearch.join(" "), {
+				limit: limit,
+				offset: offset,
+				filter: constructedFilters,
+			});
+		},
+		ingestNote: async (ingestNotes: Note | Note[]) => {
+			if (ingestNotes instanceof Note) {
+				ingestNotes = [ingestNotes];
 			}
 
-			indexingBatch.push(<MeilisearchNote>{
+			let indexingBatch: MeilisearchNote[] = [];
+
+			for (let note of ingestNotes) {
+				if (note.user === undefined) {
+					let user = await Users.findOne({
+						where: {
+							id: note.userId,
+						},
+					});
+					note.user = user;
+				}
+
+				let attachmentType = "";
+				if (note.attachedFileTypes.length > 0) {
+					attachmentType = note.attachedFileTypes[0].split("/")[0];
+					switch (attachmentType) {
+						case "image":
+						case "video":
+						case "audio":
+						case "text":
+							break;
+						default:
+							attachmentType = "file";
+							break;
+					}
+				}
+
+				indexingBatch.push(<MeilisearchNote>{
 					id: note.id.toString(),
 					text: note.text ? note.text : "",
 					userId: note.userId,
-					userHost: note.userHost !== "" ? note.userHost : url.parse(config.host).host,
+					userHost:
+						note.userHost !== ""
+							? note.userHost
+							: url.parse(config.host).host,
 					channelId: note.channelId ? note.channelId : "",
 					mediaAttachment: attachmentType,
 					userName: note.user?.username ?? "UNKNOWN",
-					createdAt: note.createdAt.getTime() / 1000 // division by 1000 is necessary because Node returns in ms-accuracy
-				}
-			)
-		}
+					createdAt: note.createdAt.getTime() / 1000, // division by 1000 is necessary because Node returns in ms-accuracy
+				});
+			}
 
-		let indexingIDs = indexingBatch.map(note => note.id);
+			let indexingIDs = indexingBatch.map((note) => note.id);
 
-		return posts.addDocuments(indexingBatch, {
-			primaryKey: "id"
-		});
-	},
-	serverStats: async () => {
-		let health : Health = await client.health();
-		let stats: Stats = await client.getStats();
+			return posts.addDocuments(indexingBatch, {
+				primaryKey: "id",
+			});
+		},
+		serverStats: async () => {
+			let health: Health = await client.health();
+			let stats: Stats = await client.getStats();
 
-		return {
-			health: health.status,
-			size: stats.databaseSize,
-			indexed_count: stats.indexes["posts"].numberOfDocuments
-		}
+			return {
+				health: health.status,
+				size: stats.databaseSize,
+				indexed_count: stats.indexes["posts"].numberOfDocuments,
+			};
+		},
 	}
-} : null;
+	: null;
diff --git a/packages/backend/src/queue/processors/background/index-all-notes.ts b/packages/backend/src/queue/processors/background/index-all-notes.ts
index 919c29ff7..9bc53d2d3 100644
--- a/packages/backend/src/queue/processors/background/index-all-notes.ts
+++ b/packages/backend/src/queue/processors/background/index-all-notes.ts
@@ -3,7 +3,7 @@ import type Bull from "bull";
 import { queueLogger } from "../../logger.js";
 import { Notes } from "@/models/index.js";
 import { MoreThan } from "typeorm";
-import { index } from "@/services/note/create.js";
+import {index} from "@/services/note/create.js";
 import {Note} from "@/models/entities/note.js";
 import meilisearch from "../../../db/meilisearch.js";
 
@@ -33,13 +33,13 @@ export default async function indexAllNotes(
 		try {
 			notes = await Notes.find({
 				where: {
-					...(cursor ? { id: MoreThan(cursor) } : {}),
+					...(cursor ? {id: MoreThan(cursor)} : {}),
 				},
 				take: take,
 				order: {
 					id: 1,
 				},
-				relations: ["user"]
+				relations: ["user"],
 			});
 		} catch (e) {
 			logger.error(`Failed to query notes ${e}`);
@@ -62,7 +62,7 @@ export default async function indexAllNotes(
 			const chunk = notes.slice(i, i + batch);
 
 			if (meilisearch) {
-				await meilisearch.ingestNote(chunk)
+				await meilisearch.ingestNote(chunk);
 			}
 
 			await Promise.all(chunk.map((note) => index(note, true)));
diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts
index 425414561..60f264708 100644
--- a/packages/backend/src/server/api/endpoints/notes/search.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search.ts
@@ -172,7 +172,7 @@ export default define(meta, paramDef, async (ps, me) => {
 		}
 
 		return found;
-	} else if(meilisearch) {
+	} else if (meilisearch) {
 		let start = 0;
 		const chunkSize = 100;
 
@@ -236,7 +236,6 @@ export default define(meta, paramDef, async (ps, me) => {
 		}
 
 		return found;
-
 	} else {
 		const userQuery =
 			ps.userId != null
diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts
index 3411ba416..5ba920301 100644
--- a/packages/backend/src/server/api/endpoints/server-info.ts
+++ b/packages/backend/src/server/api/endpoints/server-info.ts
@@ -34,8 +34,7 @@ export default define(meta, paramDef, async () => {
 			total: fsStats[0].size,
 			used: fsStats[0].used,
 		},
-		meilisearch: meilisearchStats
-
+		meilisearch: meilisearchStats,
 	};
 });
 
diff --git a/packages/client/src/scripts/search.ts b/packages/client/src/scripts/search.ts
index 54b300e1d..15f916cfc 100644
--- a/packages/client/src/scripts/search.ts
+++ b/packages/client/src/scripts/search.ts
@@ -6,12 +6,13 @@ export async function search() {
 	const { canceled, result: query } = await os.inputText({
 		title: i18n.ts.search,
 		placeholder: "Enter search terms...",
-		text: "Advanced search operators\n" +
+		text:
+			"Advanced search operators\n" +
 			"from:user => filter by user\n" +
 			"has:image/video/audio/text/file => filter by attachment types\n" +
 			"domain:domain.com => filter by domain\n" +
 			"before:Date => show posts made before Date\n" +
-			"after:Date => show posts made after Date"
+			"after:Date => show posts made after Date",
 	});
 	if (canceled || query == null || query === "") return;
 
diff --git a/packages/client/src/widgets/server-metric/index.vue b/packages/client/src/widgets/server-metric/index.vue
index 8d36819b5..1eb9f56b8 100644
--- a/packages/client/src/widgets/server-metric/index.vue
+++ b/packages/client/src/widgets/server-metric/index.vue
@@ -61,7 +61,7 @@ import XNet from "./net.vue";
 import XCpu from "./cpu.vue";
 import XMemory from "./mem.vue";
 import XDisk from "./disk.vue";
-import XMeili from "./meilisearch.vue"
+import XMeili from "./meilisearch.vue";
 import MkContainer from "@/components/MkContainer.vue";
 import { GetFormResultType } from "@/scripts/form";
 import * as os from "@/os";

From 856e0359d50553cbe75d0edcb594d2c1294de204 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Fri, 26 May 2023 10:05:46 +0200
Subject: [PATCH 024/198] add sonic back to compose

---
 docker-compose.yml | 42 +++++++++++++++++++++++++++---------------
 1 file changed, 27 insertions(+), 15 deletions(-)

diff --git a/docker-compose.yml b/docker-compose.yml
index 9f503c490..bf4e4fb8d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -40,21 +40,33 @@ services:
     volumes:
       - ./db:/var/lib/postgresql/data
 
-  meilisearch:
-    container_name: meilisearch
-    image: getmeili/meilisearch:v1.1.1
-    environment:
-      - MEILI_ENV=${MEILI_ENV:-development}
-    ports:
-      - "7700:7700"
-    networks:
-      - calcnet
-    volumes:
-      - ./meili_data:/meili_data
-    restart: unless-stopped
+### Only one of the below should be used.
+### Meilisearch is better overall, but resource-intensive. Sonic is a very light full text search engine.
+
+#  meilisearch:
+#    container_name: meilisearch
+#    image: getmeili/meilisearch:v1.1.1
+#    environment:
+#      - MEILI_ENV=${MEILI_ENV:-development}
+#    ports:
+#      - "7700:7700"
+#    networks:
+#      - calcnet
+#    volumes:
+#      - ./meili_data:/meili_data
+#    restart: unless-stopped
+
+#  sonic:
+#    restart: unless-stopped
+#    image: docker.io/valeriansaliou/sonic:v1.4.0
+#    networks:
+#      - calcnet
+#    volumes:
+#      - ./sonic:/var/lib/sonic/store
+#      - ./sonic/config.cfg:/etc/sonic.cfg
 
 networks:
   calcnet:
-    #  web:
-    #    external:
-    #      name: web
+  #  web:
+  #    external:
+  #      name: web

From 591639e570ffc3a2f0abaf96ffa6f6789b39779f Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Fri, 26 May 2023 10:12:52 +0200
Subject: [PATCH 025/198] Add ssl option to MeiliSearch config

---
 .config/example.yml                    | 1 +
 packages/backend/src/config/types.ts   | 2 +-
 packages/backend/src/db/meilisearch.ts | 3 ++-
 3 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/.config/example.yml b/.config/example.yml
index 0f2eecf90..3b35778fa 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -87,6 +87,7 @@ redis:
 #meilisearch:
 #  host: meilisearch
 #  port: 7700
+#  ssl: false
 #  apiKey:
 
 #   ┌───────────────┐
diff --git a/packages/backend/src/config/types.ts b/packages/backend/src/config/types.ts
index b6a449f54..e61ef85ae 100644
--- a/packages/backend/src/config/types.ts
+++ b/packages/backend/src/config/types.ts
@@ -39,11 +39,11 @@ export type Source = {
 		collection?: string;
 		bucket?: string;
 	};
-
 	meilisearch: {
 		host: string;
 		port: number;
 		apiKey?: string;
+		ssl: boolean
 	};
 
 	proxy?: string;
diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index 75d584f8a..a58425c54 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -20,9 +20,10 @@ const hasConfig =
 const host = hasConfig ? config.meilisearch.host ?? "localhost" : "";
 const port = hasConfig ? config.meilisearch.port ?? 7700 : 0;
 const auth = hasConfig ? config.meilisearch.apiKey ?? "" : "";
+const ssl = hasConfig ? config.meilisearch.ssl ?? false : false;
 
 const client: MeiliSearch = new MeiliSearch({
-	host: `http://${host}:${port}`,
+	host: `${ssl ? "https" : "http"}://${host}:${port}`,
 	apiKey: auth,
 });
 

From aa6d3c3b744cb73ad2385ce70df73a33c41f125f Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Fri, 26 May 2023 10:41:16 +0200
Subject: [PATCH 026/198] Remove stray newline

---
 packages/client/src/widgets/server-metric/disk.vue | 1 -
 1 file changed, 1 deletion(-)

diff --git a/packages/client/src/widgets/server-metric/disk.vue b/packages/client/src/widgets/server-metric/disk.vue
index 99191e62a..67ea398c1 100644
--- a/packages/client/src/widgets/server-metric/disk.vue
+++ b/packages/client/src/widgets/server-metric/disk.vue
@@ -8,7 +8,6 @@
 			<p>Used: {{ bytes(used, 1) }}</p>
 		</div>
 	</div>
-	<br />
 </template>
 
 <script lang="ts" setup>

From 182aaf7a57ff09d4c59cd773a1d5d7581decb73b Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Fri, 26 May 2023 10:41:33 +0200
Subject: [PATCH 027/198] Pin meilisearch library

---
 packages/backend/package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 968b0af80..10153116f 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -85,7 +85,7 @@
 		"koa-send": "5.0.1",
 		"koa-slow": "2.1.0",
 		"koa-views": "7.0.2",
-		"meilisearch": "^0.32.4",
+		"meilisearch": "0.32.4",
 		"mfm-js": "0.23.3",
 		"mime-types": "2.1.35",
 		"multer": "1.4.4-lts.1",

From bd2ece3a01f4829791e52a0f321b4e8347f43035 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Fri, 26 May 2023 10:51:11 +0200
Subject: [PATCH 028/198] i18n strings

---
 locales/en-US.yml                             |  5 +
 packages/client/src/scripts/search.ts         |  2 +-
 .../src/widgets/server-metric/meilisearch.vue | 93 ++++++++++---------
 3 files changed, 53 insertions(+), 47 deletions(-)

diff --git a/locales/en-US.yml b/locales/en-US.yml
index 6fe8bccfc..e134a0591 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -5,6 +5,7 @@ introMisskey: "Welcome! Calckey is an open source, decentralized social media pl
   \ that's free forever! \U0001F680"
 monthAndDay: "{month}/{day}"
 search: "Search"
+search_placeholder: "Enter search terms..."
 notifications: "Notifications"
 username: "Username"
 password: "Password"
@@ -1548,6 +1549,10 @@ _widgets:
   userList: "User list"
   _userList:
     chooseList: "Select a list"
+  meiliStatus: "Server Status"
+  meiliSize: "Index size"
+  meiliIndexCount: "Indexed posts"
+
 _cw:
   hide: "Hide"
   show: "Show content"
diff --git a/packages/client/src/scripts/search.ts b/packages/client/src/scripts/search.ts
index 15f916cfc..c7b8582ac 100644
--- a/packages/client/src/scripts/search.ts
+++ b/packages/client/src/scripts/search.ts
@@ -5,7 +5,7 @@ import { mainRouter } from "@/router";
 export async function search() {
 	const { canceled, result: query } = await os.inputText({
 		title: i18n.ts.search,
-		placeholder: "Enter search terms...",
+		placeholder: i18n.ts.search_placeholder,
 		text:
 			"Advanced search operators\n" +
 			"from:user => filter by user\n" +
diff --git a/packages/client/src/widgets/server-metric/meilisearch.vue b/packages/client/src/widgets/server-metric/meilisearch.vue
index 182896cc0..cb50c0301 100644
--- a/packages/client/src/widgets/server-metric/meilisearch.vue
+++ b/packages/client/src/widgets/server-metric/meilisearch.vue
@@ -1,46 +1,47 @@
-<template>
-	<div class="ms_stats">
-		<div>
-			<p><i class="ph-file-search ph-bold ph-lg"></i>MeiliSearch</p>
-			<p>Server Status: {{ available }}</p>
-			<p>Total: {{ bytes(total_size, 2) }}</p>
-			<p>Posts Indexed: {{ index_count }}</p>
-		</div>
-	</div>
-	<br />
-</template>
-
-<script lang="ts" setup>
-import {} from "vue";
-import bytes from "@/filters/bytes";
-
-const props = defineProps<{
-	meta: any; // TODO
-}>();
-
-const total_size = $computed(() => props.meta.meilisearch.size);
-const index_count = $computed(() => props.meta.meilisearch.indexed_count);
-const available = $computed(() => props.meta.meilisearch.health);
-</script>
-
-<style lang="scss" scoped>
-.ms_stats {
-	padding: 16px;
-
-	> div {
-		> p {
-			margin: 0;
-			font-size: 0.8em;
-
-			&:first-child {
-				font-weight: bold;
-				margin-bottom: 4px;
-
-				> i {
-					margin-right: 4px;
-				}
-			}
-		}
-	}
-}
-</style>
+<template>
+	<div class="ms_stats">
+		<div>
+			<p><i class="ph-file-search ph-bold ph-lg"></i>MeiliSearch</p>
+			<p>{{ i18n.ts._widgets.meiliStatus }}: {{ available }}</p>
+			<p>{{ i18n.ts._widgets.meiliSize }}: {{ bytes(total_size, 2) }}</p>
+			<p>{{ i18n.ts._widgets.meiliIndexCount }}: {{ index_count }}</p>
+		</div>
+	</div>
+	<br />
+</template>
+
+<script lang="ts" setup>
+import {} from "vue";
+import bytes from "@/filters/bytes";
+import {i18n} from "@/i18n";
+
+const props = defineProps<{
+	meta: any; // TODO
+}>();
+
+const total_size = $computed(() => props.meta.meilisearch.size);
+const index_count = $computed(() => props.meta.meilisearch.indexed_count);
+const available = $computed(() => props.meta.meilisearch.health);
+</script>
+
+<style lang="scss" scoped>
+.ms_stats {
+	padding: 16px;
+
+	> div {
+		> p {
+			margin: 0;
+			font-size: 0.8em;
+
+			&:first-child {
+				font-weight: bold;
+				margin-bottom: 4px;
+
+				> i {
+					margin-right: 4px;
+				}
+			}
+		}
+	}
+}
+</style>

From 40ad37b870d32f79aedd3605575e41b2c53d116f Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Fri, 26 May 2023 10:55:51 +0200
Subject: [PATCH 029/198] Default meilisearch data response + linting +
 prettyfier

---
 packages/backend/src/daemons/server-stats.ts               | 6 +++++-
 .../src/queue/processors/background/index-all-notes.ts     | 6 +++---
 packages/backend/src/server/api/endpoints/server-info.ts   | 7 +++++--
 3 files changed, 13 insertions(+), 6 deletions(-)

diff --git a/packages/backend/src/daemons/server-stats.ts b/packages/backend/src/daemons/server-stats.ts
index 4227ce6ee..2f1dd42ae 100644
--- a/packages/backend/src/daemons/server-stats.ts
+++ b/packages/backend/src/daemons/server-stats.ts
@@ -85,6 +85,10 @@ async function meilisearchStatus() {
 	if (meilisearch) {
 		return meilisearch.serverStats();
 	} else {
-		return null;
+		return {
+			health: "unconfigured",
+			size: 0,
+			indexed_count: 0,
+		};
 	}
 }
diff --git a/packages/backend/src/queue/processors/background/index-all-notes.ts b/packages/backend/src/queue/processors/background/index-all-notes.ts
index 9bc53d2d3..646984c93 100644
--- a/packages/backend/src/queue/processors/background/index-all-notes.ts
+++ b/packages/backend/src/queue/processors/background/index-all-notes.ts
@@ -1,8 +1,8 @@
 import type Bull from "bull";
 
-import { queueLogger } from "../../logger.js";
-import { Notes } from "@/models/index.js";
-import { MoreThan } from "typeorm";
+import {queueLogger} from "../../logger.js";
+import {Notes} from "@/models/index.js";
+import {MoreThan} from "typeorm";
 import {index} from "@/services/note/create.js";
 import {Note} from "@/models/entities/note.js";
 import meilisearch from "../../../db/meilisearch.js";
diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts
index 5ba920301..cc9aa91b2 100644
--- a/packages/backend/src/server/api/endpoints/server-info.ts
+++ b/packages/backend/src/server/api/endpoints/server-info.ts
@@ -34,7 +34,6 @@ export default define(meta, paramDef, async () => {
 			total: fsStats[0].size,
 			used: fsStats[0].used,
 		},
-		meilisearch: meilisearchStats,
 	};
 });
 
@@ -42,6 +41,10 @@ async function meilisearchStatus() {
 	if (meilisearch) {
 		return meilisearch.serverStats();
 	} else {
-		return null;
+		return {
+			health: "unconfigured",
+			size: 0,
+			indexed_count: 0,
+		};
 	}
 }

From 0a2c9a6c27c12df88b9c9c3e9a42898d88e03c29 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Fri, 26 May 2023 10:56:14 +0200
Subject: [PATCH 030/198] add semicolon after property

---
 packages/backend/src/config/types.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend/src/config/types.ts b/packages/backend/src/config/types.ts
index e61ef85ae..da0f5571e 100644
--- a/packages/backend/src/config/types.ts
+++ b/packages/backend/src/config/types.ts
@@ -43,7 +43,7 @@ export type Source = {
 		host: string;
 		port: number;
 		apiKey?: string;
-		ssl: boolean
+		ssl: boolean;
 	};
 
 	proxy?: string;

From 282fdf347a4a831c8305fc4dce9d2c28d76b55ad Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Sun, 28 May 2023 02:15:13 +0200
Subject: [PATCH 031/198] Implement follower and following searches

---
 packages/backend/src/db/meilisearch.ts        | 164 +++++++++++++-----
 .../src/server/api/endpoints/notes/search.ts  |   2 +-
 2 files changed, 123 insertions(+), 43 deletions(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index a58425c54..5d294b95d 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -4,8 +4,8 @@ import {dbLogger} from "./logger.js";
 import config from "@/config/index.js";
 import {Note} from "@/models/entities/note.js";
 import * as url from "url";
-import {User} from "@/models/entities/user.js";
-import {Users} from "@/models/index.js";
+import {ILocalUser, User} from "@/models/entities/user.js";
+import {Followings, Users} from "@/models/index.js";
 
 const logger = dbLogger.createSubLogger("meilisearch", "gray", false);
 
@@ -41,6 +41,7 @@ posts
 		"userHost",
 		"mediaAttachment",
 		"createdAt",
+		"userId",
 	])
 	.catch((e) =>
 		logger.error(
@@ -48,6 +49,14 @@ posts
 		),
 	);
 
+posts
+	.updateSortableAttributes(["createdAt"])
+	.catch((e) =>
+		logger.error(
+			`Setting sortable attr failed, placeholder searches won't sort properly: ${e}`,
+		),
+	);
+
 logger.info("Connected to MeiliSearch");
 
 export type MeilisearchNote = {
@@ -63,60 +72,130 @@ export type MeilisearchNote = {
 
 export default hasConfig
 	? {
-		search: (query: string, limit: number, offset: number) => {
+		search: async (
+			query: string,
+			limit: number,
+			offset: number,
+			userCtx: ILocalUser | null,
+		) => {
 			/// Advanced search syntax
 			/// from:user => filter by user + optional domain
 			/// has:image/video/audio/text/file => filter by attachment types
 			/// domain:domain.com => filter by domain
 			/// before:Date => show posts made before Date
 			/// after: Date => show posts made after Date
+			/// "text" => get posts with exact text between quotes
+			/// filter:following => show results only from users you follow
+			/// filter:followers => show results only from followers
 
 			let constructedFilters: string[] = [];
 
 			let splitSearch = query.split(" ");
 
 			// Detect search operators and remove them from the actual query
-			splitSearch = splitSearch.filter((term) => {
-				if (term.startsWith("has:")) {
-					let fileType = term.slice(4);
-					constructedFilters.push(`mediaAttachment = "${fileType}"`);
-					return false;
-				} else if (term.startsWith("from:")) {
-					let user = term.slice(5);
-					constructedFilters.push(`userName = ${user}`);
-					return false;
-				} else if (term.startsWith("domain:")) {
-					let domain = term.slice(7);
-					constructedFilters.push(`userHost = ${domain}`);
-					return false;
-				} else if (term.startsWith("after:")) {
-					let timestamp = term.slice(6);
-					// Try to parse the timestamp as JavaScript Date
-					let date = Date.parse(timestamp);
-					if (isNaN(date)) return false;
-					constructedFilters.push(`createdAt > ${date}`);
-					return false;
-				} else if (term.startsWith("before:")) {
-					let timestamp = term.slice(7);
-					// Try to parse the timestamp as JavaScript Date
-					let date = Date.parse(timestamp);
-					if (isNaN(date)) return false;
-					constructedFilters.push(`createdAt < ${date}`);
-					return false;
-				}
+			let filteredSearchTerms = (
+				await Promise.all(
+					splitSearch.map(async (term) => {
+						if (term.startsWith("has:")) {
+							let fileType = term.slice(4);
+							constructedFilters.push(`mediaAttachment = "${fileType}"`);
+							return null;
+						} else if (term.startsWith("from:")) {
+							let user = term.slice(5);
+							constructedFilters.push(`userName = ${user}`);
+							return null;
+						} else if (term.startsWith("domain:")) {
+							let domain = term.slice(7);
+							constructedFilters.push(`userHost = ${domain}`);
+							return null;
+						} else if (term.startsWith("after:")) {
+							let timestamp = term.slice(6);
+							// Try to parse the timestamp as JavaScript Date
+							let date = Date.parse(timestamp);
+							if (isNaN(date)) return null;
+							constructedFilters.push(`createdAt > ${date / 1000}`);
+							return null;
+						} else if (term.startsWith("before:")) {
+							let timestamp = term.slice(7);
+							// Try to parse the timestamp as JavaScript Date
+							let date = Date.parse(timestamp);
+							if (isNaN(date)) return null;
+							constructedFilters.push(`createdAt < ${date / 1000}`);
+							return null;
+						} else if (term.startsWith("filter:following")) {
+							// Check if we got a context user
+							if (userCtx) {
+								// Fetch user follows from DB
+								let followedUsers = await Followings.find({
+									where: {
+										followerId: userCtx.id,
+									},
+									select: {
+										followeeId: true,
+									},
+								});
+								let followIDs = followedUsers.map((user) => user.followeeId);
 
-				return true;
-			});
+								if (followIDs.length === 0) return null;
 
-			logger.info(`Searching for ${splitSearch.join(" ")}`);
+								constructedFilters.push(`userId IN [${followIDs.join(",")}]`);
+							} else {
+								logger.warn(
+									"search filtered to follows called without user context",
+								);
+							}
+
+							return null;
+						} else if (term.startsWith("filter:followers")) {
+							// Check if we got a context user
+							if (userCtx) {
+								// Fetch users follows from DB
+								let followedUsers = await Followings.find({
+									where: {
+										followeeId: userCtx.id,
+									},
+									select: {
+										followerId: true,
+									},
+								});
+								let followIDs = followedUsers.map((user) => user.followerId);
+
+								if (followIDs.length === 0) return null;
+
+								constructedFilters.push(`userId IN [${followIDs.join(",")}]`);
+							} else {
+								logger.warn(
+									"search filtered to followers called without user context",
+								);
+							}
+
+							return null;
+						}
+
+						return term;
+					}),
+				)
+			).filter((term) => term !== null);
+
+			let sortRules = [];
+
+			// An empty search term with defined filters means we have a placeholder search => https://www.meilisearch.com/docs/reference/api/search#placeholder-search
+			// These have to be ordered manually, otherwise the *oldest* posts are returned first, which we don't want
+			if (filteredSearchTerms.length === 0 && constructedFilters.length > 0) {
+				sortRules.push("createdAt:desc");
+			}
+
+			logger.info(`Searching for ${filteredSearchTerms.join(" ")}`);
 			logger.info(`Limit: ${limit}`);
 			logger.info(`Offset: ${offset}`);
 			logger.info(`Filters: ${constructedFilters}`);
+			logger.info(`Ordering: ${sortRules}`);
 
-			return posts.search(splitSearch.join(" "), {
+			return posts.search(filteredSearchTerms.join(" "), {
 				limit: limit,
 				offset: offset,
 				filter: constructedFilters,
+				sort: sortRules,
 			});
 		},
 		ingestNote: async (ingestNotes: Note | Note[]) => {
@@ -128,12 +207,11 @@ export default hasConfig
 
 			for (let note of ingestNotes) {
 				if (note.user === undefined) {
-					let user = await Users.findOne({
+					note.user = await Users.findOne({
 						where: {
 							id: note.userId,
 						},
 					});
-					note.user = user;
 				}
 
 				let attachmentType = "";
@@ -166,11 +244,13 @@ export default hasConfig
 				});
 			}
 
-			let indexingIDs = indexingBatch.map((note) => note.id);
-
-			return posts.addDocuments(indexingBatch, {
-				primaryKey: "id",
-			});
+			return posts
+				.addDocuments(indexingBatch, {
+					primaryKey: "id",
+				})
+				.then(() =>
+					console.log(`sent ${indexingBatch.length} posts for indexing`),
+				);
 		},
 		serverStats: async () => {
 			let health: Health = await client.health();
diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts
index 60f264708..346304470 100644
--- a/packages/backend/src/server/api/endpoints/notes/search.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search.ts
@@ -179,7 +179,7 @@ export default define(meta, paramDef, async (ps, me) => {
 		// Use meilisearch to fetch and step through all search results that could match the requirements
 		const ids = [];
 		while (true) {
-			const results = await meilisearch.search(ps.query, chunkSize, start);
+			const results = await meilisearch.search(ps.query, chunkSize, start, me);
 
 			start += chunkSize;
 

From 40b9f87bef02afaed6f5db7011bc966999f530be Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Sun, 28 May 2023 02:19:57 +0200
Subject: [PATCH 032/198] add advanced search parameters in search popup

---
 packages/client/src/scripts/search.ts | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/packages/client/src/scripts/search.ts b/packages/client/src/scripts/search.ts
index c7b8582ac..e405115bf 100644
--- a/packages/client/src/scripts/search.ts
+++ b/packages/client/src/scripts/search.ts
@@ -12,7 +12,10 @@ export async function search() {
 			"has:image/video/audio/text/file => filter by attachment types\n" +
 			"domain:domain.com => filter by domain\n" +
 			"before:Date => show posts made before Date\n" +
-			"after:Date => show posts made after Date",
+			"after:Date => show posts made after Date\n" +
+			'"text" => get posts with exact text between quotes\n' +
+			"filter:following => show results only from users you follow\n" +
+			"filter:followers => show results only from followers\n",
 	});
 	if (canceled || query == null || query === "") return;
 

From 01ce43e6ad5d28d754a933be97d8f3700ecb8e2f Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Sun, 28 May 2023 02:58:20 +0200
Subject: [PATCH 033/198] Fix Meilisearch widget reactivity

---
 .../src/widgets/server-metric/meilisearch.vue | 25 +++++++++++++++----
 1 file changed, 20 insertions(+), 5 deletions(-)

diff --git a/packages/client/src/widgets/server-metric/meilisearch.vue b/packages/client/src/widgets/server-metric/meilisearch.vue
index cb50c0301..48bf629b0 100644
--- a/packages/client/src/widgets/server-metric/meilisearch.vue
+++ b/packages/client/src/widgets/server-metric/meilisearch.vue
@@ -11,17 +11,32 @@
 </template>
 
 <script lang="ts" setup>
-import {} from "vue";
+import {onBeforeUnmount, onMounted} from "vue";
 import bytes from "@/filters/bytes";
 import {i18n} from "@/i18n";
 
 const props = defineProps<{
-	meta: any; // TODO
+	connection: any;
+	meta: any;
 }>();
 
-const total_size = $computed(() => props.meta.meilisearch.size);
-const index_count = $computed(() => props.meta.meilisearch.indexed_count);
-const available = $computed(() => props.meta.meilisearch.health);
+let total_size: number = $ref(0);
+let index_count: number = $ref(0);
+let available: string = $ref("unavailable");
+
+function onStats(stats) {
+	total_size = stats.meilisearch.size;
+	index_count = stats.meilisearch.indexed_count;
+	available = stats.meilisearch.available;
+}
+
+onMounted(() => {
+	props.connection.on("stats", onStats);
+});
+
+onBeforeUnmount(() => {
+	props.connection.off("stats", onStats);
+});
 </script>
 
 <style lang="scss" scoped>

From a1f1b74802fda690e176acc55166a25048d7d2cc Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Sun, 28 May 2023 03:05:32 +0200
Subject: [PATCH 034/198] use correct stats property

---
 packages/client/src/widgets/server-metric/meilisearch.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/widgets/server-metric/meilisearch.vue b/packages/client/src/widgets/server-metric/meilisearch.vue
index 48bf629b0..403e0154f 100644
--- a/packages/client/src/widgets/server-metric/meilisearch.vue
+++ b/packages/client/src/widgets/server-metric/meilisearch.vue
@@ -27,7 +27,7 @@ let available: string = $ref("unavailable");
 function onStats(stats) {
 	total_size = stats.meilisearch.size;
 	index_count = stats.meilisearch.indexed_count;
-	available = stats.meilisearch.available;
+	available = stats.meilisearch.health;
 }
 
 onMounted(() => {

From 0741df711aaefaacfe6385cd3470ca634c378db0 Mon Sep 17 00:00:00 2001
From: Freeplay <Freeplay@duck.com>
Date: Sun, 28 May 2023 20:20:53 -0400
Subject: [PATCH 035/198] fix: move isRenote check to note.vue

---
 .../client/src/components/MkNoteDetailed.vue  | 70 ++++++++-----------
 packages/client/src/pages/note.vue            | 46 +++++++-----
 2 files changed, 56 insertions(+), 60 deletions(-)

diff --git a/packages/client/src/components/MkNoteDetailed.vue b/packages/client/src/components/MkNoteDetailed.vue
index 55bae467c..8801e3955 100644
--- a/packages/client/src/components/MkNoteDetailed.vue
+++ b/packages/client/src/components/MkNoteDetailed.vue
@@ -17,10 +17,10 @@
 			:note="note"
 			:detailedView="true"
 		/>
-		<MkLoading v-else-if="appearNote.reply" mini />
+		<MkLoading v-else-if="note.reply" mini />
 		<MkNoteSub
-			v-if="appearNote.reply"
-			:note="appearNote.reply"
+			v-if="note.reply"
+			:note="note.reply"
 			class="reply-to"
 			:detailedView="true"
 		/>
@@ -29,21 +29,21 @@
 			ref="noteEl"
 			@contextmenu.stop="onContextmenu"
 			tabindex="-1"
-			:note="appearNote"
+			:note="note"
 			detailedView
 		></MkNote>
 
 		<MkTab v-model="tab" :style="'underline'" @update:modelValue="loadTab">
 			<option value="replies">
 				<!-- <i class="ph-arrow-u-up-left ph-bold ph-lg"></i> -->
-				<span v-if="appearNote.repliesCount > 0" class="count">{{
-					appearNote.repliesCount
+				<span v-if="note.repliesCount > 0" class="count">{{
+					note.repliesCount
 				}}</span>
 				{{ i18n.ts._notification._types.reply }}
 			</option>
-			<option value="renotes" v-if="appearNote.renoteCount > 0">
+			<option value="renotes" v-if="note.renoteCount > 0">
 				<!-- <i class="ph-repeat ph-bold ph-lg"></i> -->
-				<span class="count">{{ appearNote.renoteCount }}</span>
+				<span class="count">{{ note.renoteCount }}</span>
 				{{ i18n.ts._notification._types.renote }}
 			</option>
 			<option value="reactions" v-if="reactionsCount > 0">
@@ -71,10 +71,10 @@
 			class="reply"
 			:conversation="replies"
 			:detailedView="true"
-			:parentId="appearNote.id"
+			:parentId="note.id"
 		/>
 		<MkLoading
-			v-else-if="tab === 'replies' && appearNote.repliesCount > 0"
+			v-else-if="tab === 'replies' && note.repliesCount > 0"
 		/>
 
 		<MkNoteSub
@@ -85,7 +85,7 @@
 			class="reply"
 			:conversation="replies"
 			:detailedView="true"
-			:parentId="appearNote.id"
+			:parentId="note.id"
 		/>
 		<MkLoading v-else-if="tab === 'quotes' && directQuotes.length > 0" />
 
@@ -104,7 +104,7 @@
 		/>
 		<!-- </MkPagination> -->
 		<MkLoading
-			v-else-if="tab === 'renotes' && appearNote.renoteCount > 0"
+			v-else-if="tab === 'renotes' && note.renoteCount > 0"
 		/>
 
 		<div v-if="tab === 'clips' && clips.length > 0" class="_content clips">
@@ -132,7 +132,7 @@
 
 		<MkReactedUsers
 			v-if="tab === 'reactions' && reactionsCount > 0"
-			:note-id="appearNote.id"
+			:note-id="note.id"
 		></MkReactedUsers>
 	</div>
 	<div v-else class="_panel muted" @click="muted.muted = false">
@@ -217,23 +217,11 @@ if (noteViewInterruptors.length > 0) {
 	});
 }
 
-const isRenote =
-	note.renote != null &&
-	note.text == null &&
-	note.fileIds.length === 0 &&
-	note.poll == null;
-
 const el = ref<HTMLElement>();
 const noteEl = $ref();
 const menuButton = ref<HTMLElement>();
-const starButton = ref<InstanceType<typeof XStarButton>>();
 const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
-const renoteTime = ref<HTMLElement>();
 const reactButton = ref<HTMLElement>();
-let appearNote = $computed(() =>
-	isRenote ? (note.renote as misskey.entities.Note) : note
-);
-const isMyRenote = $i && $i.id === note.userId;
 const showContent = ref(false);
 const isDeleted = ref(false);
 const muted = ref(getWordSoftMute(note, $i, defaultStore.state.mutedWords));
@@ -263,14 +251,14 @@ const keymap = {
 
 useNoteCapture({
 	rootEl: el,
-	note: $$(appearNote),
+	note: $$(note),
 	isDeletedRef: isDeleted,
 });
 
 function reply(viaKeyboard = false): void {
 	pleaseLogin();
 	os.post({
-		reply: appearNote,
+		reply: note,
 		animation: !viaKeyboard,
 	}).then(() => {
 		focus();
@@ -284,7 +272,7 @@ function react(viaKeyboard = false): void {
 		reactButton.value,
 		(reaction) => {
 			os.api("notes/reactions/create", {
-				noteId: appearNote.id,
+				noteId: note.id,
 				reaction: reaction,
 			});
 		},
@@ -355,27 +343,27 @@ function blur() {
 
 directReplies = null;
 os.api("notes/children", {
-	noteId: appearNote.id,
+	noteId: note.id,
 	limit: 30,
 	depth: 12,
 }).then((res) => {
-	res = res.reduce((acc, note) => {
-		if (note.userId == appearNote.userId) {
-			return [...acc, note];
+	res = res.reduce((acc, resNote) => {
+		if (resNote.userId == note.userId) {
+			return [...acc, resNote];
 		}
-		return [note, ...acc];
+		return [resNote, ...acc];
 	}, []);
 	replies.value = res;
 	directReplies = res
-		.filter((note) => note.replyId === appearNote.id)
+		.filter((resNote) => resNote.replyId === note.id)
 		.reverse();
-	directQuotes = res.filter((note) => note.renoteId === appearNote.id);
+	directQuotes = res.filter((resNote) => resNote.renoteId === note.id);
 });
 
 conversation = null;
-if (appearNote.replyId) {
+if (note.replyId) {
 	os.api("notes/conversation", {
-		noteId: appearNote.replyId,
+		noteId: note.replyId,
 		limit: 30,
 	}).then((res) => {
 		conversation = res.reverse();
@@ -385,14 +373,14 @@ if (appearNote.replyId) {
 
 clips = null;
 os.api("notes/clips", {
-	noteId: appearNote.id,
+	noteId: note.id,
 }).then((res) => {
 	clips = res;
 });
 
 // const pagination = {
 // 	endpoint: "notes/renotes",
-// 	noteId: appearNote.id,
+// 	noteId: note.id,
 // 	limit: 10,
 // };
 
@@ -402,7 +390,7 @@ renotes = null;
 function loadTab() {
 	if (tab === "renotes" && !renotes) {
 		os.api("notes/renotes", {
-			noteId: appearNote.id,
+			noteId: note.id,
 			limit: 100,
 		}).then((res) => {
 			renotes = res;
@@ -414,7 +402,7 @@ async function onNoteUpdated(noteData: NoteUpdatedEvent): Promise<void> {
 	const { type, id, body } = noteData;
 
 	let found = -1;
-	if (id === appearNote.id) {
+	if (id === note.id) {
 		found = 0;
 	} else {
 		for (let i = 0; i < replies.value.length; i++) {
diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue
index e3c0bd7c2..fbb71bd16 100644
--- a/packages/client/src/pages/note.vue
+++ b/packages/client/src/pages/note.vue
@@ -13,7 +13,7 @@
 					:name="$store.state.animation ? 'fade' : ''"
 					mode="out-in"
 				>
-					<div v-if="note" class="note">
+					<div v-if="appearNote" class="note">
 						<div v-if="showNext" class="_gap">
 							<XNotes
 								class="_content"
@@ -33,12 +33,12 @@
 							</MkButton>
 							<div class="note _gap">
 								<MkRemoteCaution
-									v-if="note.user.host != null"
-									:href="note.url ?? note.uri"
+									v-if="appearNote.user.host != null"
+									:href="appearNote.url ?? appearNote.uri"
 								/>
 								<XNoteDetailed
-									:key="note.id"
-									v-model:note="note"
+									:key="appearNote.id"
+									v-model:note="appearNote"
 									class="note"
 								/>
 							</div>
@@ -71,7 +71,6 @@
 <script lang="ts" setup>
 import { computed, defineComponent, watch } from "vue";
 import * as misskey from "calckey-js";
-import XNote from "@/components/MkNote.vue";
 import XNoteDetailed from "@/components/MkNoteDetailed.vue";
 import XNotes from "@/components/MkNotes.vue";
 import MkRemoteCaution from "@/components/MkRemoteCaution.vue";
@@ -90,15 +89,17 @@ let hasNext = $ref(false);
 let showPrev = $ref(false);
 let showNext = $ref(false);
 let error = $ref();
+let isRenote = $ref(false);
+let appearNote = $ref<null | misskey.entities.Note>();
 
 const prevPagination = {
 	endpoint: "users/notes" as const,
 	limit: 10,
 	params: computed(() =>
-		note
+		appearNote
 			? {
-					userId: note.userId,
-					untilId: note.id,
+					userId: appearNote.userId,
+					untilId: appearNote.id,
 			  }
 			: null
 	),
@@ -109,10 +110,10 @@ const nextPagination = {
 	endpoint: "users/notes" as const,
 	limit: 10,
 	params: computed(() =>
-		note
+		appearNote
 			? {
-					userId: note.userId,
-					sinceId: note.id,
+					userId: appearNote.userId,
+					sinceId: appearNote.id,
 			  }
 			: null
 	),
@@ -129,6 +130,13 @@ function fetchNote() {
 	})
 		.then((res) => {
 			note = res;
+			isRenote =
+				note.renote != null &&
+				note.text == null &&
+				note.fileIds.length === 0 &&
+				note.poll == null;
+			appearNote = isRenote ? (note.renote as misskey.entities.Note) : note;
+			
 			Promise.all([
 				os.api("users/notes", {
 					userId: note.userId,
@@ -160,19 +168,19 @@ const headerTabs = $computed(() => []);
 
 definePageMetadata(
 	computed(() =>
-		note
+		appearNote
 			? {
 					title: i18n.t("noteOf", {
-						user: note.user.name || note.user.username,
+						user: appearNote.user.name || appearNote.user.username,
 					}),
-					subtitle: new Date(note.createdAt).toLocaleString(),
-					avatar: note.user,
-					path: `/notes/${note.id}`,
+					subtitle: new Date(appearNote.createdAt).toLocaleString(),
+					avatar: appearNote.user,
+					path: `/notes/${appearNote.id}`,
 					share: {
 						title: i18n.t("noteOf", {
-							user: note.user.name || note.user.username,
+							user: appearNote.user.name || appearNote.user.username,
 						}),
-						text: note.text,
+						text: appearNote.text,
 					},
 			  }
 			: null

From 02f247079137d1513cb7b7000bfb32d52225e95b Mon Sep 17 00:00:00 2001
From: Freeplay <Freeplay@duck.com>
Date: Sun, 28 May 2023 20:31:00 -0400
Subject: [PATCH 036/198] add channel federation warn

---
 locales/en-US.yml                      | 1 +
 packages/client/src/pages/channels.vue | 2 ++
 packages/client/src/style.scss         | 3 +++
 3 files changed, 6 insertions(+)

diff --git a/locales/en-US.yml b/locales/en-US.yml
index 84e68ea6b..89bf10c9f 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -685,6 +685,7 @@ logs: "Logs"
 delayed: "Delayed"
 database: "Database"
 channel: "Channels"
+channelFederationWarn: "Channels do not yet federate to other servers"
 create: "Create"
 notificationSetting: "Notification settings"
 notificationSettingDesc: "Select the types of notification to display."
diff --git a/packages/client/src/pages/channels.vue b/packages/client/src/pages/channels.vue
index d03fb61d5..c1e34ea44 100644
--- a/packages/client/src/pages/channels.vue
+++ b/packages/client/src/pages/channels.vue
@@ -7,6 +7,7 @@
 				:tabs="headerTabs"
 		/></template>
 		<MkSpacer :content-max="700">
+			<MkInfo class="_gap" :warn="true">{{ i18n.ts.channelFederationWarn }}</MkInfo>
 			<swiper
 				:round-lengths="true"
 				:touch-angle="25"
@@ -119,6 +120,7 @@ import MkInput from "@/components/form/input.vue";
 import MkRadios from "@/components/form/radios.vue";
 import MkButton from "@/components/MkButton.vue";
 import MkFolder from "@/components/MkFolder.vue";
+import MkInfo from "@/components/MkInfo.vue";
 import { useRouter } from "@/router";
 import { definePageMetadata } from "@/scripts/page-metadata";
 import { deviceKind } from "@/scripts/device-kind";
diff --git a/packages/client/src/style.scss b/packages/client/src/style.scss
index 4b914c408..ea786b164 100644
--- a/packages/client/src/style.scss
+++ b/packages/client/src/style.scss
@@ -298,6 +298,9 @@ hr {
 
 ._gap {
 	margin: var(--margin) 0;
+	&:first-child {
+		margin-top: 0;
+	}
 }
 
 // TODO: 廃止

From 399b0e96dc8f1a5f7a2f86b99a4c0227afe1744c Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Sun, 28 May 2023 20:24:48 -0400
Subject: [PATCH 037/198] use cuid2 with timestamp

---
 .config/example.yml                           | 22 ++++------
 .../native-utils/__test__/index.spec.mjs      |  8 ++--
 packages/backend/package.json                 |  1 +
 packages/backend/src/config/types.ts          |  5 ++-
 packages/backend/src/misc/gen-id.ts           | 42 +++++++++----------
 pnpm-lock.yaml                                | 13 ++++++
 6 files changed, 53 insertions(+), 38 deletions(-)

diff --git a/.config/example.yml b/.config/example.yml
index 7d8ba32be..a83e02eda 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -85,20 +85,16 @@ redis:
 #   ┌───────────────┐
 #───┘ ID generation └───────────────────────────────────────────
 
-# You can select the ID generation method.
-# You don't usually need to change this setting, but you can
-# change it according to your preferences.
+# No need to uncomment in most cases, but you may want to change
+# these settings if you plan to run a large and/or distributed server.
 
-# Available methods:
-# aid ... Short, Millisecond accuracy
-# meid ... Similar to ObjectID, Millisecond accuracy
-# ulid ... Millisecond accuracy
-# objectid ... This is left for backward compatibility
-
-# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
-# ID SETTINGS AFTER THAT!
-
-id: 'aid'
+# cuid: {
+#   # Min 16, Max 24
+#   length: 16,
+#   # Set this to a unique string across workers (e.g., machine's hostname)
+#   # if your workers are running in multiple hosts.
+#   fingerprint: "my-fingerperint",
+# }
 
 #   ┌─────────────────────┐
 #───┘ Other configuration └─────────────────────────────────────
diff --git a/packages/backend/native-utils/__test__/index.spec.mjs b/packages/backend/native-utils/__test__/index.spec.mjs
index 0d41e012d..7d68d6ac3 100644
--- a/packages/backend/native-utils/__test__/index.spec.mjs
+++ b/packages/backend/native-utils/__test__/index.spec.mjs
@@ -1,7 +1,9 @@
 import test from "ava";
 
-import { sum } from "../index.js";
+import { convertId, IdConvertType } from "../built/index.js";
 
-test("sum from native", (t) => {
-	t.is(sum(1, 2), 3);
+test("convert to mastodon id", (t) => {
+	t.is(convertId("9gf61ehcxv", IdConvertType.MastodonId), "960365976481219");
+	t.is(convertId("9fbr9z0wbrjqyd3u", IdConvertType.MastodonId), "3954607381600562394");
+	t.is(convertId("9fbs680oyviiqrol9md73p8g", IdConvertType.MastodonId), "3494513243013053824")
 });
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 96edb7f02..c0c5e24f9 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -32,6 +32,7 @@
 		"@koa/cors": "3.4.3",
 		"@koa/multer": "3.0.0",
 		"@koa/router": "9.0.1",
+		"@paralleldrive/cuid2": "2.2.0",
 		"@peertube/http-signature": "1.7.0",
 		"@redocly/openapi-core": "1.0.0-beta.120",
 		"@sinonjs/fake-timers": "9.1.2",
diff --git a/packages/backend/src/config/types.ts b/packages/backend/src/config/types.ts
index 01a98f9f0..e01c67ade 100644
--- a/packages/backend/src/config/types.ts
+++ b/packages/backend/src/config/types.ts
@@ -54,7 +54,10 @@ export type Source = {
 
 	onlyQueueProcessor?: boolean;
 
-	id: string;
+	cuid?: {
+		length?: number;
+		fingerprint?: string;
+	};
 
 	outgoingAddressFamily?: "ipv4" | "ipv6" | "dual";
 
diff --git a/packages/backend/src/misc/gen-id.ts b/packages/backend/src/misc/gen-id.ts
index b7cc0965a..fb92dd808 100644
--- a/packages/backend/src/misc/gen-id.ts
+++ b/packages/backend/src/misc/gen-id.ts
@@ -1,27 +1,27 @@
-import { ulid } from "ulid";
-import { genAid } from "./id/aid.js";
-import { genMeid } from "./id/meid.js";
-import { genMeidg } from "./id/meidg.js";
-import { genObjectId } from "./id/object-id.js";
+import { init, createId } from "@paralleldrive/cuid2";
 import config from "@/config/index.js";
 
-const metohd = config.id.toLowerCase();
+const TIME2000 = 946684800000;
+const TIMESTAMP_LENGTH = 8;
 
+const length =
+	Math.min(Math.max(config.cuid?.length ?? 16, 16), 24) - TIMESTAMP_LENGTH;
+const fingerprint = `${config.cuid?.fingerprint ?? ""}${createId()}`;
+
+const genCuid2 = init({ length, fingerprint });
+
+/**
+ * The generated ID results in the form of `[8 chars timestamp] + [cuid2]`.
+ * The minimum and maximum lengths are 16 and 24, respectively.
+ * With the length of 16, namely 8 for cuid2, roughly 1427399 IDs are needed
+ * in the same millisecond to reach 50% chance of collision.
+ *
+ * Ref: https://github.com/paralleldrive/cuid2#parameterized-length
+ */
 export function genId(date?: Date): string {
-	if (!date || date > new Date()) date = new Date();
+	const now = (date ?? new Date()).getTime();
+	const time = Math.max(now - TIME2000, 0);
+	const timestamp = time.toString(36).padStart(TIMESTAMP_LENGTH, "0");
 
-	switch (metohd) {
-		case "aid":
-			return genAid(date);
-		case "meid":
-			return genMeid(date);
-		case "meidg":
-			return genMeidg(date);
-		case "ulid":
-			return ulid(date.getTime());
-		case "objectid":
-			return genObjectId(date);
-		default:
-			throw new Error("unrecognized id generation method");
-	}
+	return `${timestamp}${genCuid2()}`;
 }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 493e9fc06..b1ec8dbd6 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -104,6 +104,9 @@ importers:
       '@koa/router':
         specifier: 9.0.1
         version: 9.0.1
+      '@paralleldrive/cuid2':
+        specifier: 2.2.0
+        version: 2.2.0
       '@peertube/http-signature':
         specifier: 1.7.0
         version: 1.7.0
@@ -2277,6 +2280,10 @@ packages:
     hasBin: true
     dev: false
 
+  /@noble/hashes@1.3.0:
+    resolution: {integrity: sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==}
+    dev: false
+
   /@nodelib/fs.scandir@2.1.5:
     resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
     engines: {node: '>= 8'}
@@ -2330,6 +2337,12 @@ packages:
       through: 2.3.4
     dev: false
 
+  /@paralleldrive/cuid2@2.2.0:
+    resolution: {integrity: sha512-CVQDpPIUHrUGGLdrMGz1NmqZvqmsB2j2rCIQEu1EvxWjlFh4fhvEGmgR409cY20/67/WlJsggenq0no3p3kYsw==}
+    dependencies:
+      '@noble/hashes': 1.3.0
+    dev: false
+
   /@peertube/http-signature@1.7.0:
     resolution: {integrity: sha512-aGQIwo6/sWtyyqhVK4e1MtxYz4N1X8CNt6SOtCc+Wnczs5S5ONaLHDDR8LYaGn0MgOwvGgXyuZ5sJIfd7iyoUw==}
     engines: {node: '>=0.10'}

From 5a8dfbb8fd01237290509dbdc2f8d05f430fbecd Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Sun, 28 May 2023 20:14:08 -0700
Subject: [PATCH 038/198] =?UTF-8?q?feat:=20=E2=9C=A8=20server=20info=20wid?=
 =?UTF-8?q?get?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Syuilo <syuilotan@yahoo.co.jp>
---
 locales/en-US.yml                             |  23 ++--
 .../backend/src/models/repositories/user.ts   |  23 ++--
 .../client/src/components/MkMediaImage.vue    |   2 +-
 .../client/src/components/MkMediaList.vue     |  13 +-
 .../client/src/components/MkMediaVideo.vue    |   4 +-
 .../client/src/components/MkNoteDetailed.vue  |   8 +-
 .../src/components/MkSubNoteContent.vue       |  29 +++--
 packages/client/src/pages/note.vue            |  10 +-
 packages/client/src/widgets/index.ts          |   5 +
 packages/client/src/widgets/server-info.vue   | 114 ++++++++++++++++++
 10 files changed, 181 insertions(+), 50 deletions(-)
 create mode 100644 packages/client/src/widgets/server-info.vue

diff --git a/locales/en-US.yml b/locales/en-US.yml
index 84e68ea6b..57afc1766 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1526,28 +1526,29 @@ _weekday:
   friday: "Friday"
   saturday: "Saturday"
 _widgets:
-  memo: "Sticky notes"
+  memo: "Sticky Notes"
   notifications: "Notifications"
   timeline: "Timeline"
   calendar: "Calendar"
   trends: "Trending"
   clock: "Clock"
-  rss: "RSS reader"
-  rssTicker: "RSS-Ticker"
+  rss: "RSS Reader"
+  rssTicker: "RSS Ticker"
   activity: "Activity"
   photos: "Photos"
-  digitalClock: "Digital clock"
-  unixClock: "UNIX clock"
+  digitalClock: "Digital Clock"
+  unixClock: "UNIX Clock"
   federation: "Federation"
-  instanceCloud: "Server cloud"
-  postForm: "Posting form"
+  instanceCloud: "Server Cloud"
+  postForm: "Posting Form"
   slideshow: "Slideshow"
   button: "Button"
-  onlineUsers: "Online users"
+  onlineUsers: "Online Users"
   jobQueue: "Job Queue"
-  serverMetric: "Server metrics"
-  aiscript: "AiScript console"
-  userList: "User list"
+  serverMetric: "Server Metrics"
+  aiscript: "AiScript Console"
+  userList: "User List"
+  serverInfo: "Server Info"
   _userList:
     chooseList: "Select a list"
 _cw:
diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts
index 5e0b83792..1ca9b3289 100644
--- a/packages/backend/src/models/repositories/user.ts
+++ b/packages/backend/src/models/repositories/user.ts
@@ -257,17 +257,22 @@ export const UserRepository = db.getRepository(User).extend({
 
 	async getHasUnreadAntenna(userId: User["id"]): Promise<boolean> {
 		try {
-  const myAntennas = (await getAntennas()).filter((a) => a.userId === userId);
+			const myAntennas = (await getAntennas()).filter(
+				(a) => a.userId === userId,
+			);
 
-		const unread =
-			myAntennas.length > 0
-				? await AntennaNotes.findOneBy({
-						antennaId: In(myAntennas.map((x) => x.id)),
-						read: false,
-				  })
-				: null;
+			const unread =
+				myAntennas.length > 0
+					? await AntennaNotes.findOneBy({
+							antennaId: In(myAntennas.map((x) => x.id)),
+							read: false,
+					  })
+					: null;
 
-		return unread != null; } catch(e) { return false; }
+			return unread != null;
+		} catch (e) {
+			return false;
+		}
 	},
 
 	async getHasUnreadChannel(userId: User["id"]): Promise<boolean> {
diff --git a/packages/client/src/components/MkMediaImage.vue b/packages/client/src/components/MkMediaImage.vue
index 74f45c69c..9097a4771 100644
--- a/packages/client/src/components/MkMediaImage.vue
+++ b/packages/client/src/components/MkMediaImage.vue
@@ -91,7 +91,7 @@ watch(
 		align-items: center;
 		padding: 30px;
 		box-sizing: border-box;
-		background: rgba(0,0,0,0.5);
+		background: rgba(0, 0, 0, 0.5);
 
 		> .wrapper {
 			display: table-cell;
diff --git a/packages/client/src/components/MkMediaList.vue b/packages/client/src/components/MkMediaList.vue
index 901aceeff..c01ccd5d8 100644
--- a/packages/client/src/components/MkMediaList.vue
+++ b/packages/client/src/components/MkMediaList.vue
@@ -11,10 +11,7 @@
 			:data-count="previewableCount < 5 ? previewableCount : null"
 			:class="{ dmWidth: inDm }"
 		>
-			<div
-				ref="gallery"
-				@click.stop
-			>
+			<div ref="gallery" @click.stop>
 				<template
 					v-for="media in mediaList.filter((media) =>
 						previewable(media)
@@ -189,7 +186,9 @@ const previewable = (file: misskey.entities.DriveFile): boolean => {
 		FILE_TYPE_BROWSERSAFE.includes(file.type)
 	);
 };
-const previewableCount = props.mediaList.filter((media) => previewable(media)).length;
+const previewableCount = props.mediaList.filter((media) =>
+	previewable(media)
+).length;
 </script>
 
 <style lang="scss" scoped>
@@ -251,14 +250,14 @@ const previewableCount = props.mediaList.filter((media) => previewable(media)).l
 			display: grid;
 			grid-gap: 8px;
 
-			> div, > button {
+			> div,
+			> button {
 				overflow: hidden;
 				border-radius: 6px;
 				pointer-events: all;
 				min-height: 50px;
 			}
 
-
 			> :nth-child(1) {
 				grid-column: 1 / 2;
 				grid-row: 1 / 2;
diff --git a/packages/client/src/components/MkMediaVideo.vue b/packages/client/src/components/MkMediaVideo.vue
index 29d85d9a3..21f8acedd 100644
--- a/packages/client/src/components/MkMediaVideo.vue
+++ b/packages/client/src/components/MkMediaVideo.vue
@@ -75,11 +75,11 @@ const hide = ref(
 onMounted(() => {
 	mini.value = plyr.value.player.media.scrollWidth < 300;
 	if (mini.value) {
-		plyr.value.player.on('play', () => {
+		plyr.value.player.on("play", () => {
 			plyr.value.player.fullscreen.enter();
 		});
 	}
-})
+});
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/client/src/components/MkNoteDetailed.vue b/packages/client/src/components/MkNoteDetailed.vue
index 8801e3955..68e33d701 100644
--- a/packages/client/src/components/MkNoteDetailed.vue
+++ b/packages/client/src/components/MkNoteDetailed.vue
@@ -73,9 +73,7 @@
 			:detailedView="true"
 			:parentId="note.id"
 		/>
-		<MkLoading
-			v-else-if="tab === 'replies' && note.repliesCount > 0"
-		/>
+		<MkLoading v-else-if="tab === 'replies' && note.repliesCount > 0" />
 
 		<MkNoteSub
 			v-if="directQuotes && tab === 'quotes'"
@@ -103,9 +101,7 @@
 			:with-chart="false"
 		/>
 		<!-- </MkPagination> -->
-		<MkLoading
-			v-else-if="tab === 'renotes' && note.renoteCount > 0"
-		/>
+		<MkLoading v-else-if="tab === 'renotes' && note.renoteCount > 0" />
 
 		<div v-if="tab === 'clips' && clips.length > 0" class="_content clips">
 			<MkA
diff --git a/packages/client/src/components/MkSubNoteContent.vue b/packages/client/src/components/MkSubNoteContent.vue
index ea0977054..d6263cfd2 100644
--- a/packages/client/src/components/MkSubNoteContent.vue
+++ b/packages/client/src/components/MkSubNoteContent.vue
@@ -97,7 +97,10 @@
 					:to="`/notes/${note.renoteId}`"
 					>{{ i18n.ts.quoteAttached }}: ...</MkA
 				>
-				<XMediaList v-if="note.files.length > 0" :media-list="note.files" />
+				<XMediaList
+					v-if="note.files.length > 0"
+					:media-list="note.files"
+				/>
 				<XPoll v-if="note.poll" :note="note" class="poll" />
 				<template v-if="detailed">
 					<MkUrlPreview
@@ -151,7 +154,10 @@
 				<i class="ph-stop ph-bold"></i> {{ i18n.ts._mfm.stop }}
 			</template>
 		</MkButton>
-		<div v-if="(isLong && !collapsed) || (props.note.cw && showContent)" class="fade"></div>
+		<div
+			v-if="(isLong && !collapsed) || (props.note.cw && showContent)"
+			class="fade"
+		></div>
 	</div>
 </template>
 
@@ -188,13 +194,13 @@ const emit = defineEmits<{
 
 const cwButton = ref<HTMLElement>();
 const showMoreButton = ref<HTMLElement>();
-const isLong = !props.detailedView
-	&& ( props.note.cw == null
-		&& (props.note.text != null
-			&& (props.note.text.split("\n").length > 9 || props.note.text.length > 500)
-		)
-		|| props.note.files.length > 4
-	);
+const isLong =
+	!props.detailedView &&
+	((props.note.cw == null &&
+		props.note.text != null &&
+		(props.note.text.split("\n").length > 9 ||
+			props.note.text.length > 500)) ||
+		props.note.files.length > 4);
 
 const collapsed = $ref(props.note.cw == null && isLong);
 
@@ -238,7 +244,8 @@ function focusFooter(ev) {
 </script>
 
 <style lang="scss" scoped>
-:deep(a), :deep(button) {
+:deep(a),
+:deep(button) {
 	position: relative;
 	z-index: 2;
 }
@@ -390,7 +397,7 @@ function focusFooter(ev) {
 			background: var(--panel);
 			mask: linear-gradient(to top, var(--gradient));
 			-webkit-mask: linear-gradient(to top, var(--gradient));
-			transition: background .2s;
+			transition: background 0.2s;
 		}
 	}
 }
diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue
index fbb71bd16..c127ad0df 100644
--- a/packages/client/src/pages/note.vue
+++ b/packages/client/src/pages/note.vue
@@ -135,8 +135,10 @@ function fetchNote() {
 				note.text == null &&
 				note.fileIds.length === 0 &&
 				note.poll == null;
-			appearNote = isRenote ? (note.renote as misskey.entities.Note) : note;
-			
+			appearNote = isRenote
+				? (note.renote as misskey.entities.Note)
+				: note;
+
 			Promise.all([
 				os.api("users/notes", {
 					userId: note.userId,
@@ -178,7 +180,9 @@ definePageMetadata(
 					path: `/notes/${appearNote.id}`,
 					share: {
 						title: i18n.t("noteOf", {
-							user: appearNote.user.name || appearNote.user.username,
+							user:
+								appearNote.user.name ||
+								appearNote.user.username,
 						}),
 						text: appearNote.text,
 					},
diff --git a/packages/client/src/widgets/index.ts b/packages/client/src/widgets/index.ts
index 216afa526..4c48811a9 100644
--- a/packages/client/src/widgets/index.ts
+++ b/packages/client/src/widgets/index.ts
@@ -89,6 +89,10 @@ export default function (app: App) {
 		"MkwUserList",
 		defineAsyncComponent(() => import("./user-list.vue")),
 	);
+	app.component(
+		"MkwServerInfo",
+		defineAsyncComponent(() => import("./server-info.vue")),
+	);
 }
 
 export const widgets = [
@@ -110,6 +114,7 @@ export const widgets = [
 	"postForm",
 	"slideshow",
 	"serverMetric",
+	"serverInfo",
 	"onlineUsers",
 	"jobQueue",
 	"button",
diff --git a/packages/client/src/widgets/server-info.vue b/packages/client/src/widgets/server-info.vue
new file mode 100644
index 000000000..852a9dcf2
--- /dev/null
+++ b/packages/client/src/widgets/server-info.vue
@@ -0,0 +1,114 @@
+<template>
+	<div class="_panel">
+		<div
+			:class="$style.container"
+			:style="{
+				backgroundImage: instance.bannerUrl
+					? `url(${instance.bannerUrl})`
+					: null,
+			}"
+		>
+			<div :class="$style.iconContainer">
+				<img
+					:src="
+						instance.iconUrl ??
+						instance.faviconUrl ??
+						'/favicon.ico'
+					"
+					alt=""
+					:class="$style.icon"
+				/>
+			</div>
+			<div :class="$style.bodyContainer">
+				<div :class="$style.body">
+					<MkA :class="$style.name" to="/about" behavior="window">{{
+						instance.name
+					}}</MkA>
+					<div :class="$style.host">{{ host }}</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script lang="ts" setup>
+import {
+	useWidgetPropsManager,
+	Widget,
+	WidgetComponentEmits,
+	WidgetComponentExpose,
+	WidgetComponentProps,
+} from "./widget";
+import { GetFormResultType } from "@/scripts/form";
+import { host } from "@/config";
+import { instance } from "@/instance";
+
+const name = "serverInfo";
+
+const widgetPropsDef = {};
+
+type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
+
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
+
+const { widgetProps, configure } = useWidgetPropsManager(
+	name,
+	widgetPropsDef,
+	props,
+	emit
+);
+
+defineExpose<WidgetComponentExpose>({
+	name,
+	configure,
+	id: props.widget ? props.widget.id : null,
+});
+</script>
+
+<style lang="scss" module>
+.container {
+	position: relative;
+	background-size: cover;
+	background-position: center;
+	display: flex;
+}
+
+.iconContainer {
+	display: inline-block;
+	text-align: center;
+	padding: 16px;
+}
+
+.icon {
+	display: inline-block;
+	width: 60px;
+	height: 60px;
+	border-radius: 8px;
+	box-sizing: border-box;
+	border: solid 3px var(--panelBorder);
+}
+
+.bodyContainer {
+	display: flex;
+	align-items: center;
+	min-width: 0;
+	padding: 0 16px 0 0;
+}
+
+.body {
+	text-overflow: ellipsis;
+	overflow: clip;
+}
+
+.name,
+.host {
+	color: var(--fg);
+	text-shadow: -1px -1px 0 var(--bg), 1px -1px 0 var(--bg),
+		-1px 1px 0 var(--bg), 1px 1px 0 var(--bg);
+}
+
+.host {
+	font-weight: bold;
+}
+</style>

From 62835aa4a3503d8cc8ae0c1150634db2cbfa29b6 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Sun, 28 May 2023 20:34:18 -0700
Subject: [PATCH 039/198] chore: formatting

---
 locales/en-US.yml                             |   2 +-
 packages/backend/src/db/meilisearch.ts        | 360 +++++++++---------
 .../processors/background/index-all-notes.ts  |  14 +-
 .../src/server/api/endpoints/notes/search.ts  |   2 +-
 packages/client/src/scripts/search.ts         |   2 +-
 .../src/widgets/server-metric/meilisearch.vue |   4 +-
 6 files changed, 192 insertions(+), 192 deletions(-)

diff --git a/locales/en-US.yml b/locales/en-US.yml
index e402b1c62..5184df4be 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -5,7 +5,7 @@ introMisskey: "Welcome! Calckey is an open source, decentralized social media pl
   \ that's free forever! \U0001F680"
 monthAndDay: "{month}/{day}"
 search: "Search"
-search_placeholder: "Enter search terms..."
+searchPlaceholder: "Search Calckey"
 notifications: "Notifications"
 username: "Username"
 password: "Password"
diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index 5d294b95d..7e176e058 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -1,11 +1,11 @@
-import {Health, MeiliSearch, Stats} from "meilisearch";
-import {dbLogger} from "./logger.js";
+import { Health, MeiliSearch, Stats } from "meilisearch";
+import { dbLogger } from "./logger.js";
 
 import config from "@/config/index.js";
-import {Note} from "@/models/entities/note.js";
+import { Note } from "@/models/entities/note.js";
 import * as url from "url";
-import {ILocalUser, User} from "@/models/entities/user.js";
-import {Followings, Users} from "@/models/index.js";
+import { ILocalUser, User } from "@/models/entities/user.js";
+import { Followings, Users } from "@/models/index.js";
 
 const logger = dbLogger.createSubLogger("meilisearch", "gray", false);
 
@@ -72,195 +72,195 @@ export type MeilisearchNote = {
 
 export default hasConfig
 	? {
-		search: async (
-			query: string,
-			limit: number,
-			offset: number,
-			userCtx: ILocalUser | null,
-		) => {
-			/// Advanced search syntax
-			/// from:user => filter by user + optional domain
-			/// has:image/video/audio/text/file => filter by attachment types
-			/// domain:domain.com => filter by domain
-			/// before:Date => show posts made before Date
-			/// after: Date => show posts made after Date
-			/// "text" => get posts with exact text between quotes
-			/// filter:following => show results only from users you follow
-			/// filter:followers => show results only from followers
+			search: async (
+				query: string,
+				limit: number,
+				offset: number,
+				userCtx: ILocalUser | null,
+			) => {
+				/// Advanced search syntax
+				/// from:user => filter by user + optional domain
+				/// has:image/video/audio/text/file => filter by attachment types
+				/// domain:domain.com => filter by domain
+				/// before:Date => show posts made before Date
+				/// after: Date => show posts made after Date
+				/// "text" => get posts with exact text between quotes
+				/// filter:following => show results only from users you follow
+				/// filter:followers => show results only from followers
 
-			let constructedFilters: string[] = [];
+				let constructedFilters: string[] = [];
 
-			let splitSearch = query.split(" ");
+				let splitSearch = query.split(" ");
 
-			// Detect search operators and remove them from the actual query
-			let filteredSearchTerms = (
-				await Promise.all(
-					splitSearch.map(async (term) => {
-						if (term.startsWith("has:")) {
-							let fileType = term.slice(4);
-							constructedFilters.push(`mediaAttachment = "${fileType}"`);
-							return null;
-						} else if (term.startsWith("from:")) {
-							let user = term.slice(5);
-							constructedFilters.push(`userName = ${user}`);
-							return null;
-						} else if (term.startsWith("domain:")) {
-							let domain = term.slice(7);
-							constructedFilters.push(`userHost = ${domain}`);
-							return null;
-						} else if (term.startsWith("after:")) {
-							let timestamp = term.slice(6);
-							// Try to parse the timestamp as JavaScript Date
-							let date = Date.parse(timestamp);
-							if (isNaN(date)) return null;
-							constructedFilters.push(`createdAt > ${date / 1000}`);
-							return null;
-						} else if (term.startsWith("before:")) {
-							let timestamp = term.slice(7);
-							// Try to parse the timestamp as JavaScript Date
-							let date = Date.parse(timestamp);
-							if (isNaN(date)) return null;
-							constructedFilters.push(`createdAt < ${date / 1000}`);
-							return null;
-						} else if (term.startsWith("filter:following")) {
-							// Check if we got a context user
-							if (userCtx) {
-								// Fetch user follows from DB
-								let followedUsers = await Followings.find({
-									where: {
-										followerId: userCtx.id,
-									},
-									select: {
-										followeeId: true,
-									},
-								});
-								let followIDs = followedUsers.map((user) => user.followeeId);
+				// Detect search operators and remove them from the actual query
+				let filteredSearchTerms = (
+					await Promise.all(
+						splitSearch.map(async (term) => {
+							if (term.startsWith("has:")) {
+								let fileType = term.slice(4);
+								constructedFilters.push(`mediaAttachment = "${fileType}"`);
+								return null;
+							} else if (term.startsWith("from:")) {
+								let user = term.slice(5);
+								constructedFilters.push(`userName = ${user}`);
+								return null;
+							} else if (term.startsWith("domain:")) {
+								let domain = term.slice(7);
+								constructedFilters.push(`userHost = ${domain}`);
+								return null;
+							} else if (term.startsWith("after:")) {
+								let timestamp = term.slice(6);
+								// Try to parse the timestamp as JavaScript Date
+								let date = Date.parse(timestamp);
+								if (isNaN(date)) return null;
+								constructedFilters.push(`createdAt > ${date / 1000}`);
+								return null;
+							} else if (term.startsWith("before:")) {
+								let timestamp = term.slice(7);
+								// Try to parse the timestamp as JavaScript Date
+								let date = Date.parse(timestamp);
+								if (isNaN(date)) return null;
+								constructedFilters.push(`createdAt < ${date / 1000}`);
+								return null;
+							} else if (term.startsWith("filter:following")) {
+								// Check if we got a context user
+								if (userCtx) {
+									// Fetch user follows from DB
+									let followedUsers = await Followings.find({
+										where: {
+											followerId: userCtx.id,
+										},
+										select: {
+											followeeId: true,
+										},
+									});
+									let followIDs = followedUsers.map((user) => user.followeeId);
 
-								if (followIDs.length === 0) return null;
+									if (followIDs.length === 0) return null;
 
-								constructedFilters.push(`userId IN [${followIDs.join(",")}]`);
-							} else {
-								logger.warn(
-									"search filtered to follows called without user context",
-								);
+									constructedFilters.push(`userId IN [${followIDs.join(",")}]`);
+								} else {
+									logger.warn(
+										"search filtered to follows called without user context",
+									);
+								}
+
+								return null;
+							} else if (term.startsWith("filter:followers")) {
+								// Check if we got a context user
+								if (userCtx) {
+									// Fetch users follows from DB
+									let followedUsers = await Followings.find({
+										where: {
+											followeeId: userCtx.id,
+										},
+										select: {
+											followerId: true,
+										},
+									});
+									let followIDs = followedUsers.map((user) => user.followerId);
+
+									if (followIDs.length === 0) return null;
+
+									constructedFilters.push(`userId IN [${followIDs.join(",")}]`);
+								} else {
+									logger.warn(
+										"search filtered to followers called without user context",
+									);
+								}
+
+								return null;
 							}
 
-							return null;
-						} else if (term.startsWith("filter:followers")) {
-							// Check if we got a context user
-							if (userCtx) {
-								// Fetch users follows from DB
-								let followedUsers = await Followings.find({
-									where: {
-										followeeId: userCtx.id,
-									},
-									select: {
-										followerId: true,
-									},
-								});
-								let followIDs = followedUsers.map((user) => user.followerId);
+							return term;
+						}),
+					)
+				).filter((term) => term !== null);
 
-								if (followIDs.length === 0) return null;
+				let sortRules = [];
 
-								constructedFilters.push(`userId IN [${followIDs.join(",")}]`);
-							} else {
-								logger.warn(
-									"search filtered to followers called without user context",
-								);
-							}
+				// An empty search term with defined filters means we have a placeholder search => https://www.meilisearch.com/docs/reference/api/search#placeholder-search
+				// These have to be ordered manually, otherwise the *oldest* posts are returned first, which we don't want
+				if (filteredSearchTerms.length === 0 && constructedFilters.length > 0) {
+					sortRules.push("createdAt:desc");
+				}
 
-							return null;
+				logger.info(`Searching for ${filteredSearchTerms.join(" ")}`);
+				logger.info(`Limit: ${limit}`);
+				logger.info(`Offset: ${offset}`);
+				logger.info(`Filters: ${constructedFilters}`);
+				logger.info(`Ordering: ${sortRules}`);
+
+				return posts.search(filteredSearchTerms.join(" "), {
+					limit: limit,
+					offset: offset,
+					filter: constructedFilters,
+					sort: sortRules,
+				});
+			},
+			ingestNote: async (ingestNotes: Note | Note[]) => {
+				if (ingestNotes instanceof Note) {
+					ingestNotes = [ingestNotes];
+				}
+
+				let indexingBatch: MeilisearchNote[] = [];
+
+				for (let note of ingestNotes) {
+					if (note.user === undefined) {
+						note.user = await Users.findOne({
+							where: {
+								id: note.userId,
+							},
+						});
+					}
+
+					let attachmentType = "";
+					if (note.attachedFileTypes.length > 0) {
+						attachmentType = note.attachedFileTypes[0].split("/")[0];
+						switch (attachmentType) {
+							case "image":
+							case "video":
+							case "audio":
+							case "text":
+								break;
+							default:
+								attachmentType = "file";
+								break;
 						}
+					}
 
-						return term;
-					}),
-				)
-			).filter((term) => term !== null);
-
-			let sortRules = [];
-
-			// An empty search term with defined filters means we have a placeholder search => https://www.meilisearch.com/docs/reference/api/search#placeholder-search
-			// These have to be ordered manually, otherwise the *oldest* posts are returned first, which we don't want
-			if (filteredSearchTerms.length === 0 && constructedFilters.length > 0) {
-				sortRules.push("createdAt:desc");
-			}
-
-			logger.info(`Searching for ${filteredSearchTerms.join(" ")}`);
-			logger.info(`Limit: ${limit}`);
-			logger.info(`Offset: ${offset}`);
-			logger.info(`Filters: ${constructedFilters}`);
-			logger.info(`Ordering: ${sortRules}`);
-
-			return posts.search(filteredSearchTerms.join(" "), {
-				limit: limit,
-				offset: offset,
-				filter: constructedFilters,
-				sort: sortRules,
-			});
-		},
-		ingestNote: async (ingestNotes: Note | Note[]) => {
-			if (ingestNotes instanceof Note) {
-				ingestNotes = [ingestNotes];
-			}
-
-			let indexingBatch: MeilisearchNote[] = [];
-
-			for (let note of ingestNotes) {
-				if (note.user === undefined) {
-					note.user = await Users.findOne({
-						where: {
-							id: note.userId,
-						},
+					indexingBatch.push(<MeilisearchNote>{
+						id: note.id.toString(),
+						text: note.text ? note.text : "",
+						userId: note.userId,
+						userHost:
+							note.userHost !== ""
+								? note.userHost
+								: url.parse(config.host).host,
+						channelId: note.channelId ? note.channelId : "",
+						mediaAttachment: attachmentType,
+						userName: note.user?.username ?? "UNKNOWN",
+						createdAt: note.createdAt.getTime() / 1000, // division by 1000 is necessary because Node returns in ms-accuracy
 					});
 				}
 
-				let attachmentType = "";
-				if (note.attachedFileTypes.length > 0) {
-					attachmentType = note.attachedFileTypes[0].split("/")[0];
-					switch (attachmentType) {
-						case "image":
-						case "video":
-						case "audio":
-						case "text":
-							break;
-						default:
-							attachmentType = "file";
-							break;
-					}
-				}
+				return posts
+					.addDocuments(indexingBatch, {
+						primaryKey: "id",
+					})
+					.then(() =>
+						console.log(`sent ${indexingBatch.length} posts for indexing`),
+					);
+			},
+			serverStats: async () => {
+				let health: Health = await client.health();
+				let stats: Stats = await client.getStats();
 
-				indexingBatch.push(<MeilisearchNote>{
-					id: note.id.toString(),
-					text: note.text ? note.text : "",
-					userId: note.userId,
-					userHost:
-						note.userHost !== ""
-							? note.userHost
-							: url.parse(config.host).host,
-					channelId: note.channelId ? note.channelId : "",
-					mediaAttachment: attachmentType,
-					userName: note.user?.username ?? "UNKNOWN",
-					createdAt: note.createdAt.getTime() / 1000, // division by 1000 is necessary because Node returns in ms-accuracy
-				});
-			}
-
-			return posts
-				.addDocuments(indexingBatch, {
-					primaryKey: "id",
-				})
-				.then(() =>
-					console.log(`sent ${indexingBatch.length} posts for indexing`),
-				);
-		},
-		serverStats: async () => {
-			let health: Health = await client.health();
-			let stats: Stats = await client.getStats();
-
-			return {
-				health: health.status,
-				size: stats.databaseSize,
-				indexed_count: stats.indexes["posts"].numberOfDocuments,
-			};
-		},
-	}
+				return {
+					health: health.status,
+					size: stats.databaseSize,
+					indexed_count: stats.indexes["posts"].numberOfDocuments,
+				};
+			},
+	  }
 	: null;
diff --git a/packages/backend/src/queue/processors/background/index-all-notes.ts b/packages/backend/src/queue/processors/background/index-all-notes.ts
index 646984c93..10c332aa3 100644
--- a/packages/backend/src/queue/processors/background/index-all-notes.ts
+++ b/packages/backend/src/queue/processors/background/index-all-notes.ts
@@ -1,10 +1,10 @@
 import type Bull from "bull";
 
-import {queueLogger} from "../../logger.js";
-import {Notes} from "@/models/index.js";
-import {MoreThan} from "typeorm";
-import {index} from "@/services/note/create.js";
-import {Note} from "@/models/entities/note.js";
+import { queueLogger } from "../../logger.js";
+import { Notes } from "@/models/index.js";
+import { MoreThan } from "typeorm";
+import { index } from "@/services/note/create.js";
+import { Note } from "@/models/entities/note.js";
 import meilisearch from "../../../db/meilisearch.js";
 
 const logger = queueLogger.createSubLogger("index-all-notes");
@@ -33,7 +33,7 @@ export default async function indexAllNotes(
 		try {
 			notes = await Notes.find({
 				where: {
-					...(cursor ? {id: MoreThan(cursor)} : {}),
+					...(cursor ? { id: MoreThan(cursor) } : {}),
 				},
 				take: take,
 				order: {
@@ -69,7 +69,7 @@ export default async function indexAllNotes(
 
 			indexedCount += chunk.length;
 			const pct = (indexedCount / total) * 100;
-			job.update({indexedCount, cursor, total});
+			job.update({ indexedCount, cursor, total });
 			job.progress(+pct.toFixed(1));
 			logger.info(`Indexed notes ${indexedCount}/${total ? total : "?"}`);
 		}
diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts
index 346304470..d0c2f8d77 100644
--- a/packages/backend/src/server/api/endpoints/notes/search.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search.ts
@@ -4,7 +4,7 @@ import { Note } from "@/models/entities/note.js";
 import config from "@/config/index.js";
 import es from "../../../../db/elasticsearch.js";
 import sonic from "../../../../db/sonic.js";
-import meilisearch, {MeilisearchNote} from "../../../../db/meilisearch.js";
+import meilisearch, { MeilisearchNote } from "../../../../db/meilisearch.js";
 import define from "../../define.js";
 import { makePaginationQuery } from "../../common/make-pagination-query.js";
 import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
diff --git a/packages/client/src/scripts/search.ts b/packages/client/src/scripts/search.ts
index e405115bf..c6eb497ea 100644
--- a/packages/client/src/scripts/search.ts
+++ b/packages/client/src/scripts/search.ts
@@ -5,7 +5,7 @@ import { mainRouter } from "@/router";
 export async function search() {
 	const { canceled, result: query } = await os.inputText({
 		title: i18n.ts.search,
-		placeholder: i18n.ts.search_placeholder,
+		placeholder: i18n.ts.searchPlaceholder,
 		text:
 			"Advanced search operators\n" +
 			"from:user => filter by user\n" +
diff --git a/packages/client/src/widgets/server-metric/meilisearch.vue b/packages/client/src/widgets/server-metric/meilisearch.vue
index 403e0154f..f9993cd09 100644
--- a/packages/client/src/widgets/server-metric/meilisearch.vue
+++ b/packages/client/src/widgets/server-metric/meilisearch.vue
@@ -11,9 +11,9 @@
 </template>
 
 <script lang="ts" setup>
-import {onBeforeUnmount, onMounted} from "vue";
+import { onBeforeUnmount, onMounted } from "vue";
 import bytes from "@/filters/bytes";
-import {i18n} from "@/i18n";
+import { i18n } from "@/i18n";
 
 const props = defineProps<{
 	connection: any;

From 8dc133ada205dd70f0a236c765fe7be6247be947 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Sun, 28 May 2023 20:37:53 -0700
Subject: [PATCH 040/198] add pie chart to meili stats

---
 .../client/src/widgets/server-metric/meilisearch.vue  | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/packages/client/src/widgets/server-metric/meilisearch.vue b/packages/client/src/widgets/server-metric/meilisearch.vue
index f9993cd09..4047793f5 100644
--- a/packages/client/src/widgets/server-metric/meilisearch.vue
+++ b/packages/client/src/widgets/server-metric/meilisearch.vue
@@ -1,5 +1,6 @@
 <template>
 	<div class="ms_stats">
+		<XPie class="pie" :value="progress" />
 		<div>
 			<p><i class="ph-file-search ph-bold ph-lg"></i>MeiliSearch</p>
 			<p>{{ i18n.ts._widgets.meiliStatus }}: {{ available }}</p>
@@ -13,6 +14,7 @@
 <script lang="ts" setup>
 import { onBeforeUnmount, onMounted } from "vue";
 import bytes from "@/filters/bytes";
+import XPie from "./pie.vue";
 import { i18n } from "@/i18n";
 
 const props = defineProps<{
@@ -20,6 +22,8 @@ const props = defineProps<{
 	meta: any;
 }>();
 
+let progress: number = $ref(0);
+
 let total_size: number = $ref(0);
 let index_count: number = $ref(0);
 let available: string = $ref("unavailable");
@@ -28,6 +32,7 @@ function onStats(stats) {
 	total_size = stats.meilisearch.size;
 	index_count = stats.meilisearch.indexed_count;
 	available = stats.meilisearch.health;
+	progress = Math.floor((index_count / total_size) * 100);
 }
 
 onMounted(() => {
@@ -43,6 +48,12 @@ onBeforeUnmount(() => {
 .ms_stats {
 	padding: 16px;
 
+	> .pie {
+		height: 82px;
+		flex-shrink: 0;
+		margin-right: 16px;
+	}
+
 	> div {
 		> p {
 			margin: 0;

From 3bca8b360bec5fa64066e1403bb28e353222b34e Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Sun, 28 May 2023 20:43:23 -0700
Subject: [PATCH 041/198] chore: :arrow_up: up pnpm

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 2899adf7e..593690d6d 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
 		"type": "git",
 		"url": "https://codeberg.org/calckey/calckey.git"
 	},
-	"packageManager": "pnpm@8.5.1",
+	"packageManager": "pnpm@8.6.0",
 	"private": true,
 	"scripts": {
 		"rebuild": "pnpm run clean && pnpm -r run build && pnpm run gulp",

From c4418590bcdb69cd64cbe56288711057e58c0dad Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Sun, 28 May 2023 20:47:22 -0700
Subject: [PATCH 042/198] fix

---
 packages/client/package.json                |   2 +-
 packages/client/src/widgets/server-info.vue |   4 +-
 pnpm-lock.yaml                              | 355 ++++++++++++++------
 3 files changed, 258 insertions(+), 103 deletions(-)

diff --git a/packages/client/package.json b/packages/client/package.json
index 1af31dd09..ccddda6b9 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -79,7 +79,7 @@
 		"typescript": "4.9.4",
 		"uuid": "9.0.0",
 		"vanilla-tilt": "1.8.0",
-		"vite": "^4.1.1",
+		"vite": "4.3.9",
 		"vite-plugin-compression": "^0.5.1",
 		"vue": "3.2.45",
 		"vue-isyourpasswordsafe": "^2.0.0",
diff --git a/packages/client/src/widgets/server-info.vue b/packages/client/src/widgets/server-info.vue
index 852a9dcf2..35dd53c45 100644
--- a/packages/client/src/widgets/server-info.vue
+++ b/packages/client/src/widgets/server-info.vue
@@ -49,8 +49,8 @@ const widgetPropsDef = {};
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-const props = defineProps<WidgetComponentProps<WidgetProps>>();
-const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
+	const props = defineProps<{ widget?: Widget<WidgetProps> }>();
+const emit = defineEmits<{ (ev: "updateProps", props: WidgetProps) }>();
 
 const { widgetProps, configure } = useWidgetPropsManager(
 	name,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 48a0e498b..abf12b62e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,4 +1,8 @@
-lockfileVersion: '6.0'
+lockfileVersion: '6.1'
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
 
 overrides:
   chokidar: ^3.3.1
@@ -24,7 +28,7 @@ importers:
         version: 7.2.0
       focus-trap-vue:
         specifier: ^4.0.1
-        version: 4.0.1(focus-trap@7.2.0)(vue@3.2.45)
+        version: 4.0.1(focus-trap@7.2.0)(vue@3.3.4)
       js-yaml:
         specifier: 4.1.0
         version: 4.1.0
@@ -264,7 +268,7 @@ importers:
         specifier: 7.0.2
         version: 7.0.2(@types/koa@2.13.5)(ejs@3.1.8)(pug@3.0.2)
       meilisearch:
-        specifier: ^0.32.4
+        specifier: 0.32.4
         version: 0.32.4
       mfm-js:
         specifier: 0.23.3
@@ -718,7 +722,7 @@ importers:
         version: 8.3.4
       '@vitejs/plugin-vue':
         specifier: 4.0.0
-        version: 4.0.0(vite@4.1.1)(vue@3.2.45)
+        version: 4.0.0(vite@4.3.9)(vue@3.2.45)
       '@vue/compiler-sfc':
         specifier: 3.2.45
         version: 3.2.45
@@ -879,11 +883,11 @@ importers:
         specifier: 1.8.0
         version: 1.8.0
       vite:
-        specifier: ^4.1.1
-        version: 4.1.1(sass@1.57.1)
+        specifier: 4.3.9
+        version: 4.3.9(sass@1.57.1)
       vite-plugin-compression:
         specifier: ^0.5.1
-        version: 0.5.1(vite@4.1.1)
+        version: 0.5.1(vite@4.3.9)
       vue:
         specifier: 3.2.45
         version: 3.2.45
@@ -1080,6 +1084,11 @@ packages:
     resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==}
     engines: {node: '>=6.9.0'}
 
+  /@babel/helper-string-parser@7.21.5:
+    resolution: {integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==}
+    engines: {node: '>=6.9.0'}
+    dev: false
+
   /@babel/helper-validator-identifier@7.19.1:
     resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==}
     engines: {node: '>=6.9.0'}
@@ -1123,6 +1132,14 @@ packages:
       '@babel/types': 7.21.4
     dev: true
 
+  /@babel/parser@7.22.3:
+    resolution: {integrity: sha512-vrukxyW/ep8UD1UDzOYpTKQ6abgjFoeG6L+4ar9+c5TN9QnlqiOi6QK7LSR5ewm/ERyGkT/Ai6VboNrxhbr9Uw==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+    dependencies:
+      '@babel/types': 7.22.3
+    dev: false
+
   /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.21.4):
     resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
     peerDependencies:
@@ -1309,6 +1326,15 @@ packages:
       to-fast-properties: 2.0.0
     dev: true
 
+  /@babel/types@7.22.3:
+    resolution: {integrity: sha512-P3na3xIQHTKY4L0YOG7pM8M8uoUIB910WQaSiiMCZUC2Cy8XFEQONGABFnHWBa2gpGKODTAJcNhi5Zk0sLRrzg==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/helper-string-parser': 7.21.5
+      '@babel/helper-validator-identifier': 7.19.1
+      to-fast-properties: 2.0.0
+    dev: false
+
   /@bcoe/v8-coverage@0.2.3:
     resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
     dev: true
@@ -1599,8 +1625,8 @@ packages:
       - supports-color
     dev: false
 
-  /@esbuild/android-arm64@0.16.17:
-    resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==}
+  /@esbuild/android-arm64@0.17.19:
+    resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [android]
@@ -1608,8 +1634,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/android-arm@0.16.17:
-    resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==}
+  /@esbuild/android-arm@0.17.19:
+    resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==}
     engines: {node: '>=12'}
     cpu: [arm]
     os: [android]
@@ -1617,8 +1643,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/android-x64@0.16.17:
-    resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==}
+  /@esbuild/android-x64@0.17.19:
+    resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [android]
@@ -1626,8 +1652,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/darwin-arm64@0.16.17:
-    resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==}
+  /@esbuild/darwin-arm64@0.17.19:
+    resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [darwin]
@@ -1635,8 +1661,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/darwin-x64@0.16.17:
-    resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==}
+  /@esbuild/darwin-x64@0.17.19:
+    resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [darwin]
@@ -1644,8 +1670,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/freebsd-arm64@0.16.17:
-    resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==}
+  /@esbuild/freebsd-arm64@0.17.19:
+    resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [freebsd]
@@ -1653,8 +1679,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/freebsd-x64@0.16.17:
-    resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==}
+  /@esbuild/freebsd-x64@0.17.19:
+    resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [freebsd]
@@ -1662,8 +1688,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-arm64@0.16.17:
-    resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==}
+  /@esbuild/linux-arm64@0.17.19:
+    resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [linux]
@@ -1671,8 +1697,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-arm@0.16.17:
-    resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==}
+  /@esbuild/linux-arm@0.17.19:
+    resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==}
     engines: {node: '>=12'}
     cpu: [arm]
     os: [linux]
@@ -1680,8 +1706,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-ia32@0.16.17:
-    resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==}
+  /@esbuild/linux-ia32@0.17.19:
+    resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==}
     engines: {node: '>=12'}
     cpu: [ia32]
     os: [linux]
@@ -1689,8 +1715,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-loong64@0.16.17:
-    resolution: {integrity: sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==}
+  /@esbuild/linux-loong64@0.17.19:
+    resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==}
     engines: {node: '>=12'}
     cpu: [loong64]
     os: [linux]
@@ -1698,8 +1724,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-mips64el@0.16.17:
-    resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==}
+  /@esbuild/linux-mips64el@0.17.19:
+    resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==}
     engines: {node: '>=12'}
     cpu: [mips64el]
     os: [linux]
@@ -1707,8 +1733,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-ppc64@0.16.17:
-    resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==}
+  /@esbuild/linux-ppc64@0.17.19:
+    resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==}
     engines: {node: '>=12'}
     cpu: [ppc64]
     os: [linux]
@@ -1716,8 +1742,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-riscv64@0.16.17:
-    resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==}
+  /@esbuild/linux-riscv64@0.17.19:
+    resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==}
     engines: {node: '>=12'}
     cpu: [riscv64]
     os: [linux]
@@ -1725,8 +1751,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-s390x@0.16.17:
-    resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==}
+  /@esbuild/linux-s390x@0.17.19:
+    resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==}
     engines: {node: '>=12'}
     cpu: [s390x]
     os: [linux]
@@ -1734,8 +1760,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-x64@0.16.17:
-    resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==}
+  /@esbuild/linux-x64@0.17.19:
+    resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [linux]
@@ -1743,8 +1769,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/netbsd-x64@0.16.17:
-    resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==}
+  /@esbuild/netbsd-x64@0.17.19:
+    resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [netbsd]
@@ -1752,8 +1778,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/openbsd-x64@0.16.17:
-    resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==}
+  /@esbuild/openbsd-x64@0.17.19:
+    resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [openbsd]
@@ -1761,8 +1787,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/sunos-x64@0.16.17:
-    resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==}
+  /@esbuild/sunos-x64@0.17.19:
+    resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [sunos]
@@ -1770,8 +1796,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/win32-arm64@0.16.17:
-    resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==}
+  /@esbuild/win32-arm64@0.17.19:
+    resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [win32]
@@ -1779,8 +1805,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/win32-ia32@0.16.17:
-    resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==}
+  /@esbuild/win32-ia32@0.17.19:
+    resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==}
     engines: {node: '>=12'}
     cpu: [ia32]
     os: [win32]
@@ -1788,8 +1814,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/win32-x64@0.16.17:
-    resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==}
+  /@esbuild/win32-x64@0.17.19:
+    resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [win32]
@@ -2091,6 +2117,10 @@ packages:
   /@jridgewell/sourcemap-codec@1.4.14:
     resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
 
+  /@jridgewell/sourcemap-codec@1.4.15:
+    resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
+    dev: false
+
   /@jridgewell/trace-mapping@0.3.17:
     resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==}
     dependencies:
@@ -2143,7 +2173,7 @@ packages:
       detect-libc: 2.0.1
       https-proxy-agent: 5.0.1
       make-dir: 3.1.0
-      node-fetch: 2.6.8
+      node-fetch: 2.6.11
       nopt: 5.0.0
       npmlog: 5.0.1
       rimraf: 3.0.2
@@ -2161,7 +2191,7 @@ packages:
       detect-libc: 2.0.1
       https-proxy-agent: 5.0.1
       make-dir: 3.1.0
-      node-fetch: 2.6.8
+      node-fetch: 2.6.11
       nopt: 5.0.0
       npmlog: 5.0.1
       rimraf: 3.0.2
@@ -3791,14 +3821,14 @@ packages:
       eslint-visitor-keys: 3.3.0
     dev: true
 
-  /@vitejs/plugin-vue@4.0.0(vite@4.1.1)(vue@3.2.45):
+  /@vitejs/plugin-vue@4.0.0(vite@4.3.9)(vue@3.2.45):
     resolution: {integrity: sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
       vite: ^4.0.0
       vue: ^3.2.25
     dependencies:
-      vite: 4.1.1(sass@1.57.1)
+      vite: 4.3.9(sass@1.57.1)
       vue: 3.2.45
     dev: true
 
@@ -3809,18 +3839,36 @@ packages:
       '@vue/shared': 3.2.45
       estree-walker: 2.0.2
       source-map: 0.6.1
+    dev: true
+
+  /@vue/compiler-core@3.3.4:
+    resolution: {integrity: sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==}
+    dependencies:
+      '@babel/parser': 7.22.3
+      '@vue/shared': 3.3.4
+      estree-walker: 2.0.2
+      source-map-js: 1.0.2
+    dev: false
 
   /@vue/compiler-dom@3.2.45:
     resolution: {integrity: sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==}
     dependencies:
       '@vue/compiler-core': 3.2.45
       '@vue/shared': 3.2.45
+    dev: true
+
+  /@vue/compiler-dom@3.3.4:
+    resolution: {integrity: sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==}
+    dependencies:
+      '@vue/compiler-core': 3.3.4
+      '@vue/shared': 3.3.4
+    dev: false
 
   /@vue/compiler-sfc@2.7.14:
     resolution: {integrity: sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==}
     dependencies:
       '@babel/parser': 7.20.7
-      postcss: 8.4.21
+      postcss: 8.4.24
       source-map: 0.6.1
     dev: true
 
@@ -3837,12 +3885,36 @@ packages:
       magic-string: 0.25.9
       postcss: 8.4.21
       source-map: 0.6.1
+    dev: true
+
+  /@vue/compiler-sfc@3.3.4:
+    resolution: {integrity: sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==}
+    dependencies:
+      '@babel/parser': 7.22.3
+      '@vue/compiler-core': 3.3.4
+      '@vue/compiler-dom': 3.3.4
+      '@vue/compiler-ssr': 3.3.4
+      '@vue/reactivity-transform': 3.3.4
+      '@vue/shared': 3.3.4
+      estree-walker: 2.0.2
+      magic-string: 0.30.0
+      postcss: 8.4.24
+      source-map-js: 1.0.2
+    dev: false
 
   /@vue/compiler-ssr@3.2.45:
     resolution: {integrity: sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==}
     dependencies:
       '@vue/compiler-dom': 3.2.45
       '@vue/shared': 3.2.45
+    dev: true
+
+  /@vue/compiler-ssr@3.3.4:
+    resolution: {integrity: sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==}
+    dependencies:
+      '@vue/compiler-dom': 3.3.4
+      '@vue/shared': 3.3.4
+    dev: false
 
   /@vue/reactivity-transform@3.2.45:
     resolution: {integrity: sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==}
@@ -3852,17 +3924,43 @@ packages:
       '@vue/shared': 3.2.45
       estree-walker: 2.0.2
       magic-string: 0.25.9
+    dev: true
+
+  /@vue/reactivity-transform@3.3.4:
+    resolution: {integrity: sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==}
+    dependencies:
+      '@babel/parser': 7.22.3
+      '@vue/compiler-core': 3.3.4
+      '@vue/shared': 3.3.4
+      estree-walker: 2.0.2
+      magic-string: 0.30.0
+    dev: false
 
   /@vue/reactivity@3.2.45:
     resolution: {integrity: sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==}
     dependencies:
       '@vue/shared': 3.2.45
+    dev: true
+
+  /@vue/reactivity@3.3.4:
+    resolution: {integrity: sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==}
+    dependencies:
+      '@vue/shared': 3.3.4
+    dev: false
 
   /@vue/runtime-core@3.2.45:
     resolution: {integrity: sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==}
     dependencies:
       '@vue/reactivity': 3.2.45
       '@vue/shared': 3.2.45
+    dev: true
+
+  /@vue/runtime-core@3.3.4:
+    resolution: {integrity: sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==}
+    dependencies:
+      '@vue/reactivity': 3.3.4
+      '@vue/shared': 3.3.4
+    dev: false
 
   /@vue/runtime-dom@3.2.45:
     resolution: {integrity: sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==}
@@ -3870,6 +3968,15 @@ packages:
       '@vue/runtime-core': 3.2.45
       '@vue/shared': 3.2.45
       csstype: 2.6.21
+    dev: true
+
+  /@vue/runtime-dom@3.3.4:
+    resolution: {integrity: sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==}
+    dependencies:
+      '@vue/runtime-core': 3.3.4
+      '@vue/shared': 3.3.4
+      csstype: 3.1.2
+    dev: false
 
   /@vue/server-renderer@3.2.45(vue@3.2.45):
     resolution: {integrity: sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==}
@@ -3879,9 +3986,25 @@ packages:
       '@vue/compiler-ssr': 3.2.45
       '@vue/shared': 3.2.45
       vue: 3.2.45
+    dev: true
+
+  /@vue/server-renderer@3.3.4(vue@3.3.4):
+    resolution: {integrity: sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==}
+    peerDependencies:
+      vue: 3.3.4
+    dependencies:
+      '@vue/compiler-ssr': 3.3.4
+      '@vue/shared': 3.3.4
+      vue: 3.3.4
+    dev: false
 
   /@vue/shared@3.2.45:
     resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==}
+    dev: true
+
+  /@vue/shared@3.3.4:
+    resolution: {integrity: sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==}
+    dev: false
 
   /@webassemblyjs/ast@1.11.1:
     resolution: {integrity: sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==}
@@ -5883,8 +6006,8 @@ packages:
     requiresBuild: true
     dev: false
 
-  /core-js@3.30.1:
-    resolution: {integrity: sha512-ZNS5nbiSwDTq4hFosEDqm65izl2CWmLz0hARJMyNQBgkUZMIF51cQiMvIQKA6hvuaeWxQDP3hEedM1JZIgTldQ==}
+  /core-js@3.30.2:
+    resolution: {integrity: sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg==}
     requiresBuild: true
     dev: true
 
@@ -6051,11 +6174,16 @@ packages:
 
   /csstype@2.6.21:
     resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==}
+    dev: true
 
   /csstype@3.1.1:
     resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
     dev: true
 
+  /csstype@3.1.2:
+    resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
+    dev: false
+
   /custom-event-polyfill@1.0.7:
     resolution: {integrity: sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==}
     dev: true
@@ -6667,34 +6795,34 @@ packages:
       es6-symbol: 3.1.3
     dev: true
 
-  /esbuild@0.16.17:
-    resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==}
+  /esbuild@0.17.19:
+    resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==}
     engines: {node: '>=12'}
     hasBin: true
     requiresBuild: true
     optionalDependencies:
-      '@esbuild/android-arm': 0.16.17
-      '@esbuild/android-arm64': 0.16.17
-      '@esbuild/android-x64': 0.16.17
-      '@esbuild/darwin-arm64': 0.16.17
-      '@esbuild/darwin-x64': 0.16.17
-      '@esbuild/freebsd-arm64': 0.16.17
-      '@esbuild/freebsd-x64': 0.16.17
-      '@esbuild/linux-arm': 0.16.17
-      '@esbuild/linux-arm64': 0.16.17
-      '@esbuild/linux-ia32': 0.16.17
-      '@esbuild/linux-loong64': 0.16.17
-      '@esbuild/linux-mips64el': 0.16.17
-      '@esbuild/linux-ppc64': 0.16.17
-      '@esbuild/linux-riscv64': 0.16.17
-      '@esbuild/linux-s390x': 0.16.17
-      '@esbuild/linux-x64': 0.16.17
-      '@esbuild/netbsd-x64': 0.16.17
-      '@esbuild/openbsd-x64': 0.16.17
-      '@esbuild/sunos-x64': 0.16.17
-      '@esbuild/win32-arm64': 0.16.17
-      '@esbuild/win32-ia32': 0.16.17
-      '@esbuild/win32-x64': 0.16.17
+      '@esbuild/android-arm': 0.17.19
+      '@esbuild/android-arm64': 0.17.19
+      '@esbuild/android-x64': 0.17.19
+      '@esbuild/darwin-arm64': 0.17.19
+      '@esbuild/darwin-x64': 0.17.19
+      '@esbuild/freebsd-arm64': 0.17.19
+      '@esbuild/freebsd-x64': 0.17.19
+      '@esbuild/linux-arm': 0.17.19
+      '@esbuild/linux-arm64': 0.17.19
+      '@esbuild/linux-ia32': 0.17.19
+      '@esbuild/linux-loong64': 0.17.19
+      '@esbuild/linux-mips64el': 0.17.19
+      '@esbuild/linux-ppc64': 0.17.19
+      '@esbuild/linux-riscv64': 0.17.19
+      '@esbuild/linux-s390x': 0.17.19
+      '@esbuild/linux-x64': 0.17.19
+      '@esbuild/netbsd-x64': 0.17.19
+      '@esbuild/openbsd-x64': 0.17.19
+      '@esbuild/sunos-x64': 0.17.19
+      '@esbuild/win32-arm64': 0.17.19
+      '@esbuild/win32-ia32': 0.17.19
+      '@esbuild/win32-x64': 0.17.19
     dev: true
 
   /escalade@3.1.1:
@@ -7408,14 +7536,14 @@ packages:
       readable-stream: 2.3.7
     dev: true
 
-  /focus-trap-vue@4.0.1(focus-trap@7.2.0)(vue@3.2.45):
+  /focus-trap-vue@4.0.1(focus-trap@7.2.0)(vue@3.3.4):
     resolution: {integrity: sha512-2iqOeoSvgq7Um6aL+255a/wXPskj6waLq2oKCa4gOnMORPo15JX7wN6J5bl1SMhMlTlkHXGSrQ9uJPJLPZDl5w==}
     peerDependencies:
       focus-trap: ^7.0.0
       vue: ^3.0.0
     dependencies:
       focus-trap: 7.2.0
-      vue: 3.2.45
+      vue: 3.3.4
     dev: false
 
   /focus-trap@7.2.0:
@@ -9654,7 +9782,7 @@ packages:
   /jsonfile@4.0.0:
     resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
     optionalDependencies:
-      graceful-fs: 4.2.11
+      graceful-fs: 4.2.10
 
   /jsonfile@5.0.0:
     resolution: {integrity: sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==}
@@ -9668,7 +9796,7 @@ packages:
     dependencies:
       universalify: 2.0.0
     optionalDependencies:
-      graceful-fs: 4.2.11
+      graceful-fs: 4.2.10
     dev: true
 
   /jsonld@6.0.0:
@@ -10294,6 +10422,14 @@ packages:
     resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
     dependencies:
       sourcemap-codec: 1.4.8
+    dev: true
+
+  /magic-string@0.30.0:
+    resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==}
+    engines: {node: '>=12'}
+    dependencies:
+      '@jridgewell/sourcemap-codec': 1.4.15
+    dev: false
 
   /mailcheck@1.1.1:
     resolution: {integrity: sha512-3WjL8+ZDouZwKlyJBMp/4LeziLFXgleOdsYu87piGcMLqhBzCsy2QFdbtAwv757TFC/rtqd738fgJw1tFQCSgA==}
@@ -10762,8 +10898,8 @@ packages:
     hasBin: true
     dev: true
 
-  /nanoid@3.3.4:
-    resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
+  /nanoid@3.3.6:
+    resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
     hasBin: true
 
@@ -11896,7 +12032,15 @@ packages:
     resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==}
     engines: {node: ^10 || ^12 || >=14}
     dependencies:
-      nanoid: 3.3.4
+      nanoid: 3.3.6
+      picocolors: 1.0.0
+      source-map-js: 1.0.2
+
+  /postcss@8.4.24:
+    resolution: {integrity: sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==}
+    engines: {node: ^10 || ^12 || >=14}
+    dependencies:
+      nanoid: 3.3.6
       picocolors: 1.0.0
       source-map-js: 1.0.2
 
@@ -12789,8 +12933,8 @@ packages:
       rangestr: 0.0.1
       seedrandom: 2.4.2
 
-  /rollup@3.12.1:
-    resolution: {integrity: sha512-t9elERrz2i4UU9z7AwISj3CQcXP39cWxgRWLdf4Tm6aKm1eYrqHIgjzXBgb67GNY1sZckTFFi0oMozh3/S++Ig==}
+  /rollup@3.23.0:
+    resolution: {integrity: sha512-h31UlwEi7FHihLe1zbk+3Q7z1k/84rb9BSwmBSr/XjOCEaBJ2YyedQDuM0t/kfOS0IxM+vk1/zI9XxYj9V+NJQ==}
     engines: {node: '>=14.18.0', npm: '>=8.0.0'}
     hasBin: true
     optionalDependencies:
@@ -13224,6 +13368,7 @@ packages:
   /sourcemap-codec@1.4.8:
     resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
     deprecated: Please use @jridgewell/sourcemap-codec instead
+    dev: true
 
   /sparkles@1.0.1:
     resolution: {integrity: sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==}
@@ -14627,7 +14772,7 @@ packages:
       replace-ext: 1.0.1
     dev: true
 
-  /vite-plugin-compression@0.5.1(vite@4.1.1):
+  /vite-plugin-compression@0.5.1(vite@4.3.9):
     resolution: {integrity: sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==}
     peerDependencies:
       vite: '>=2.0.0'
@@ -14635,13 +14780,13 @@ packages:
       chalk: 4.1.2
       debug: 4.3.4(supports-color@8.1.1)
       fs-extra: 10.1.0
-      vite: 4.1.1(sass@1.57.1)
+      vite: 4.3.9(sass@1.57.1)
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /vite@4.1.1(sass@1.57.1):
-    resolution: {integrity: sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==}
+  /vite@4.3.9(sass@1.57.1):
+    resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==}
     engines: {node: ^14.18.0 || >=16.0.0}
     hasBin: true
     peerDependencies:
@@ -14665,10 +14810,9 @@ packages:
       terser:
         optional: true
     dependencies:
-      esbuild: 0.16.17
-      postcss: 8.4.21
-      resolve: 1.22.1
-      rollup: 3.12.1
+      esbuild: 0.17.19
+      postcss: 8.4.24
+      rollup: 3.23.0
       sass: 1.57.1
     optionalDependencies:
       fsevents: 2.3.2
@@ -14715,6 +14859,17 @@ packages:
       '@vue/runtime-dom': 3.2.45
       '@vue/server-renderer': 3.2.45(vue@3.2.45)
       '@vue/shared': 3.2.45
+    dev: true
+
+  /vue@3.3.4:
+    resolution: {integrity: sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==}
+    dependencies:
+      '@vue/compiler-dom': 3.3.4
+      '@vue/compiler-sfc': 3.3.4
+      '@vue/runtime-dom': 3.3.4
+      '@vue/server-renderer': 3.3.4(vue@3.3.4)
+      '@vue/shared': 3.3.4
+    dev: false
 
   /vuedraggable@4.1.0(vue@3.2.45):
     resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}
@@ -15340,7 +15495,7 @@ packages:
     name: plyr
     version: 3.7.0
     dependencies:
-      core-js: 3.30.1
+      core-js: 3.30.2
       custom-event-polyfill: 1.0.7
       loadjs: 4.2.0
       rangetouch: 2.0.1

From 53cdd62533f2cd838b94f47f6e94da590fdf8e9c Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Sun, 28 May 2023 23:49:55 -0400
Subject: [PATCH 043/198] fix example config format

---
 .config/example.yml | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/.config/example.yml b/.config/example.yml
index a83e02eda..f5248fb9a 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -88,13 +88,14 @@ redis:
 # No need to uncomment in most cases, but you may want to change
 # these settings if you plan to run a large and/or distributed server.
 
-# cuid: {
+# cuid:
 #   # Min 16, Max 24
-#   length: 16,
+#   length: 16
+#
 #   # Set this to a unique string across workers (e.g., machine's hostname)
 #   # if your workers are running in multiple hosts.
-#   fingerprint: "my-fingerperint",
-# }
+#   fingerprint: my-fingerprint
+
 
 #   ┌─────────────────────┐
 #───┘ Other configuration └─────────────────────────────────────

From 24f4348868fda1809bb4c627ac185382dc2d8923 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Sun, 28 May 2023 20:59:36 -0700
Subject: [PATCH 044/198] fix

---
 packages/client/src/widgets/server-info.vue | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/packages/client/src/widgets/server-info.vue b/packages/client/src/widgets/server-info.vue
index 35dd53c45..32cd35883 100644
--- a/packages/client/src/widgets/server-info.vue
+++ b/packages/client/src/widgets/server-info.vue
@@ -3,16 +3,16 @@
 		<div
 			:class="$style.container"
 			:style="{
-				backgroundImage: instance.bannerUrl
-					? `url(${instance.bannerUrl})`
+				backgroundImage: $instance.bannerUrl
+					? `url(${$instance.bannerUrl})`
 					: null,
 			}"
 		>
 			<div :class="$style.iconContainer">
 				<img
 					:src="
-						instance.iconUrl ??
-						instance.faviconUrl ??
+						$instance.iconUrl ??
+						$instance.faviconUrl ??
 						'/favicon.ico'
 					"
 					alt=""
@@ -22,7 +22,7 @@
 			<div :class="$style.bodyContainer">
 				<div :class="$style.body">
 					<MkA :class="$style.name" to="/about" behavior="window">{{
-						instance.name
+						$instance.name
 					}}</MkA>
 					<div :class="$style.host">{{ host }}</div>
 				</div>
@@ -41,7 +41,6 @@ import {
 } from "./widget";
 import { GetFormResultType } from "@/scripts/form";
 import { host } from "@/config";
-import { instance } from "@/instance";
 
 const name = "serverInfo";
 

From fab56443ef5f44018a2c1cb49e81257a6f824352 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Sun, 28 May 2023 21:04:54 -0700
Subject: [PATCH 045/198] chore: :bulb: meili

---
 packages/backend/src/daemons/server-stats.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/packages/backend/src/daemons/server-stats.ts b/packages/backend/src/daemons/server-stats.ts
index 2f1dd42ae..c936d619a 100644
--- a/packages/backend/src/daemons/server-stats.ts
+++ b/packages/backend/src/daemons/server-stats.ts
@@ -81,6 +81,7 @@ async function fs() {
 	return data || { rIO_sec: 0, wIO_sec: 0 };
 }
 
+// MEILI STAT
 async function meilisearchStatus() {
 	if (meilisearch) {
 		return meilisearch.serverStats();

From 916716b5891b062dc6e649e11c543f03b9eaa2f3 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Sun, 28 May 2023 21:10:21 -0700
Subject: [PATCH 046/198] properly calculate percent

---
 packages/client/src/widgets/server-metric/meilisearch.vue | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/packages/client/src/widgets/server-metric/meilisearch.vue b/packages/client/src/widgets/server-metric/meilisearch.vue
index 4047793f5..80a3f4146 100644
--- a/packages/client/src/widgets/server-metric/meilisearch.vue
+++ b/packages/client/src/widgets/server-metric/meilisearch.vue
@@ -16,6 +16,7 @@ import { onBeforeUnmount, onMounted } from "vue";
 import bytes from "@/filters/bytes";
 import XPie from "./pie.vue";
 import { i18n } from "@/i18n";
+import * as os from "@/os";
 
 const props = defineProps<{
 	connection: any;
@@ -23,7 +24,7 @@ const props = defineProps<{
 }>();
 
 let progress: number = $ref(0);
-
+let serverStats = $ref(null);
 let total_size: number = $ref(0);
 let index_count: number = $ref(0);
 let available: string = $ref("unavailable");
@@ -32,10 +33,13 @@ function onStats(stats) {
 	total_size = stats.meilisearch.size;
 	index_count = stats.meilisearch.indexed_count;
 	available = stats.meilisearch.health;
-	progress = Math.floor((index_count / total_size) * 100);
+	progress = Math.floor((index_count / serverStats.notesCount) * 100);
 }
 
 onMounted(() => {
+	os.api("stats", {}).then((res) => {
+		serverStats = res;
+	});
 	props.connection.on("stats", onStats);
 });
 

From ca8d63c2b03e33d5f82e0cfbc499ce0f07daee6c Mon Sep 17 00:00:00 2001
From: Freeplay <Freeplay@duck.com>
Date: Mon, 29 May 2023 09:11:42 -0400
Subject: [PATCH 047/198] Add clips, antennas hint

---
 locales/en-US.yml                             |  2 +
 packages/client/src/components/MkInfo.vue     | 30 ++++++-
 .../client/src/components/MkPagination.vue    |  6 +-
 .../client/src/pages/my-antennas/create.vue   |  8 +-
 .../client/src/pages/my-antennas/index.vue    | 80 +++++++++++--------
 packages/client/src/pages/my-clips/index.vue  | 43 +++++-----
 6 files changed, 108 insertions(+), 61 deletions(-)

diff --git a/locales/en-US.yml b/locales/en-US.yml
index 89bf10c9f..98d4d7703 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -404,6 +404,7 @@ avoidMultiCaptchaConfirm: "Using multiple Captcha systems may cause interference
   \ them. Would you like to disable the other Captcha systems currently active? If\
   \ you would like them to stay enabled, press cancel."
 antennas: "Antennas"
+antennasDesc: "Antennas display new posts matching the criteria you set!\n They can be accessed from the timelines page."
 manageAntennas: "Manage Antennas"
 name: "Name"
 antennaSource: "Antenna source"
@@ -771,6 +772,7 @@ pageLikedCount: "Number of received Page likes"
 contact: "Contact"
 useSystemFont: "Use the system's default font"
 clips: "Clips"
+clipsDesc: "Clips are like share-able categorized bookmarks. You can create clips from the menu of individual posts."
 experimentalFeatures: "Experimental features"
 developer: "Developer"
 makeExplorable: "Make account visible in \"Explore\""
diff --git a/packages/client/src/components/MkInfo.vue b/packages/client/src/components/MkInfo.vue
index 43f2de29e..e59b5d56e 100644
--- a/packages/client/src/components/MkInfo.vue
+++ b/packages/client/src/components/MkInfo.vue
@@ -1,7 +1,7 @@
 <template>
-	<div class="fpezltsf" :class="{ warn }">
+	<div class="info" :class="{ warn, card }">
 		<i v-if="warn" class="ph-warning ph-bold ph-lg"></i>
-		<i v-else class="ph-info ph-bold ph-lg"></i>
+		<i v-else class="ph-bold ph-lg" :class="icon ? `ph-${icon}` : 'ph-info'"></i>
 		<slot></slot>
 	</div>
 </template>
@@ -10,12 +10,14 @@
 import {} from "vue";
 
 defineProps<{
+	icon?: string;
 	warn?: boolean;
+	card?: boolean;
 }>();
 </script>
 
 <style lang="scss" scoped>
-.fpezltsf {
+.info {
 	padding: 16px;
 	font-size: 90%;
 	background: var(--infoBg);
@@ -27,6 +29,28 @@ defineProps<{
 		color: var(--infoWarnFg);
 	}
 
+	&.card {
+		background: var(--panel);
+		color: var(--fg);
+		padding: 48px;
+		font-size: 1em;
+		text-align: center;
+		> i {
+			display: block;
+			font-size: 4em;
+			margin: 0;
+		}
+		> :deep(*) {
+			margin-inline: auto;
+		}
+		> :deep(:not(:last-child)) {
+			margin-bottom: 20px;
+		}
+		> :deep(p) {
+			max-width: 30em;
+		}
+	}
+
 	> i {
 		margin-right: 4px;
 	}
diff --git a/packages/client/src/components/MkPagination.vue b/packages/client/src/components/MkPagination.vue
index 24018e526..564b214e0 100644
--- a/packages/client/src/components/MkPagination.vue
+++ b/packages/client/src/components/MkPagination.vue
@@ -17,7 +17,7 @@
 			</slot>
 		</div>
 
-		<div v-else ref="rootEl">
+		<div v-else ref="rootEl" class="list">
 			<div
 				v-show="pagination.reversed && more"
 				key="_more_"
@@ -487,4 +487,8 @@ defineExpose({
 		margin-right: auto;
 	}
 }
+.list > :deep(._button) {
+	margin-inline: auto;
+	margin-bottom: 16px;
+}
 </style>
diff --git a/packages/client/src/pages/my-antennas/create.vue b/packages/client/src/pages/my-antennas/create.vue
index c0d838629..040a6b052 100644
--- a/packages/client/src/pages/my-antennas/create.vue
+++ b/packages/client/src/pages/my-antennas/create.vue
@@ -1,7 +1,9 @@
 <template>
-	<div class="geegznzt">
-		<XAntenna :antenna="draft" @created="onAntennaCreated" />
-	</div>
+	<MkSpacer :content-max="700">
+		<div class="geegznzt">
+			<XAntenna :antenna="draft" @created="onAntennaCreated" />
+		</div>
+	</MkSpacer>
 </template>
 
 <script lang="ts" setup>
diff --git a/packages/client/src/pages/my-antennas/index.vue b/packages/client/src/pages/my-antennas/index.vue
index cc3262a79..a4d15433a 100644
--- a/packages/client/src/pages/my-antennas/index.vue
+++ b/packages/client/src/pages/my-antennas/index.vue
@@ -8,43 +8,60 @@
 		/></template>
 		<MkSpacer :content-max="700">
 			<div class="ieepwinx">
-				<MkButton
-					:link="true"
-					to="/my/antennas/create"
-					primary
-					class="add"
-					><i class="ph-plus ph-bold ph-lg"></i>
-					{{ i18n.ts.add }}</MkButton
-				>
 
 				<div class="">
 					<MkPagination
-						v-slot="{ items }"
 						ref="list"
 						:pagination="pagination"
 					>
-						<div v-for="antenna in items" :key="antenna.id">
-							<MkA
-								class="uopelskx"
+						<template #empty>
+							<MkInfo
+								:icon="'flying-saucer'"
+								:card="true"
+							>
+								<p>{{ i18n.ts.antennasDesc }}</p>
+								<MkButton
+									:link="true"
+									to="/my/antennas/create"
+									primary
+									class="add"
+									><i class="ph-plus ph-bold ph-lg"></i>
+									{{ i18n.ts.add }}</MkButton
+								>
+							</MkInfo>
+						</template>
+						<template #default="{ items }">
+							<MkButton
 								:link="true"
-								:to="`/timeline/antenna/${antenna.id}`"
+								to="/my/antennas/create"
+								primary
+								class="add"
+								><i class="ph-plus ph-bold ph-lg"></i>
+								{{ i18n.ts.add }}</MkButton
 							>
-								<i class="ph-flying-saucer ph-bold ph-lg"></i
-								><i
-									:class="`${
-										antenna.hasUnreadNote
-											? 'ph-circle ph-fill'
-											: 'ph-check'
-									} ph-xs notify-icon`"
-								></i>
-							</MkA>
-							<MkA
-								class="ljoevbzj"
-								:to="`/my/antennas/${antenna.id}`"
-							>
-								<div class="name">{{ antenna.name }}</div>
-							</MkA>
-						</div>
+							<div v-for="antenna in items" :key="antenna.id">
+								<MkA
+									class="uopelskx"
+									:link="true"
+									:to="`/timeline/antenna/${antenna.id}`"
+								>
+									<i class="ph-flying-saucer ph-bold ph-lg"></i
+									><i
+										:class="`${
+											antenna.hasUnreadNote
+												? 'ph-circle ph-fill'
+												: 'ph-check'
+										} ph-xs notify-icon`"
+									></i>
+								</MkA>
+								<MkA
+									class="ljoevbzj"
+									:to="`/my/antennas/${antenna.id}`"
+								>
+									<div class="name">{{ antenna.name }}</div>
+								</MkA>
+							</div>
+						</template>
 					</MkPagination>
 				</div>
 			</div>
@@ -56,6 +73,7 @@
 import { onActivated, onDeactivated, ref } from "vue";
 import MkPagination from "@/components/MkPagination.vue";
 import MkButton from "@/components/MkButton.vue";
+import MkInfo from "@/components/MkInfo.vue";
 import { i18n } from "@/i18n";
 import { definePageMetadata } from "@/scripts/page-metadata";
 
@@ -101,10 +119,6 @@ definePageMetadata({
 
 <style lang="scss" scoped>
 .ieepwinx {
-	> .add {
-		margin: 0 auto 16px auto;
-	}
-
 	.uopelskx {
 		float: left;
 		min-width: 25px;
diff --git a/packages/client/src/pages/my-clips/index.vue b/packages/client/src/pages/my-clips/index.vue
index 1c950a3f1..2d9ce3db6 100644
--- a/packages/client/src/pages/my-clips/index.vue
+++ b/packages/client/src/pages/my-clips/index.vue
@@ -8,28 +8,32 @@
 		/></template>
 		<MkSpacer :content-max="700">
 			<div class="qtcaoidl">
-				<MkButton primary class="add" @click="create"
-					><i class="ph-plus ph-bold ph-lg"></i>
-					{{ i18n.ts.add }}</MkButton
-				>
-
 				<MkPagination
-					v-slot="{ items }"
 					ref="pagingComponent"
 					:pagination="pagination"
 					class="list"
 				>
-					<MkA
-						v-for="item in items"
-						:key="item.id"
-						:to="`/clips/${item.id}`"
-						class="item _panel _gap"
-					>
-						<b>{{ item.name }}</b>
-						<div v-if="item.description" class="description">
-							{{ item.description }}
-						</div>
-					</MkA>
+					<template #empty>
+						<MkInfo
+							:icon="'paperclip'"
+							:card="true"
+						>
+							<p>{{ i18n.ts.clipsDesc }}</p>
+						</MkInfo>
+					</template>
+					<template #default="{ items }">
+						<MkA
+							v-for="item in items"
+							:key="item.id"
+							:to="`/clips/${item.id}`"
+							class="item _panel _gap"
+						>
+							<b>{{ item.name }}</b>
+							<div v-if="item.description" class="description">
+								{{ item.description }}
+							</div>
+						</MkA>
+					</template>
 				</MkPagination>
 			</div>
 		</MkSpacer>
@@ -40,6 +44,7 @@
 import {} from "vue";
 import MkPagination from "@/components/MkPagination.vue";
 import MkButton from "@/components/MkButton.vue";
+import MkInfo from "@/components/MkInfo.vue";
 import * as os from "@/os";
 import { i18n } from "@/i18n";
 import { definePageMetadata } from "@/scripts/page-metadata";
@@ -100,10 +105,6 @@ definePageMetadata({
 
 <style lang="scss" scoped>
 .qtcaoidl {
-	> .add {
-		margin: 0 auto 16px auto;
-	}
-
 	> .list {
 		> .item {
 			display: block;

From 66c4e8ed764b3f9b3e693127796b328a9a1be4e8 Mon Sep 17 00:00:00 2001
From: Freeplay <Freeplay@duck.com>
Date: Mon, 29 May 2023 09:40:00 -0400
Subject: [PATCH 048/198] Move follow button + always show label

---
 packages/client/src/pages/user/home.vue | 68 ++++++++++---------------
 packages/client/src/style.scss          |  5 ++
 2 files changed, 33 insertions(+), 40 deletions(-)

diff --git a/packages/client/src/pages/user/home.vue b/packages/client/src/pages/user/home.vue
index 5de9b28d6..277d5f5ef 100644
--- a/packages/client/src/pages/user/home.vue
+++ b/packages/client/src/pages/user/home.vue
@@ -134,20 +134,19 @@
 						</div>
 						<div class="follow-container">
 							<div class="actions">
-								<MkFollowButton
-									:user="user"
-									@refresh="emit('refresh')"
-									:inline="true"
-									:transparent="false"
-									:full="!narrow"
-									class="koudoku"
-								/>
 								<button class="menu _button" @click="menu">
 									<i
 										class="ph-dots-three-outline ph-bold ph-lg"
 									></i>
 								</button>
-								<!-- <MkFollowButton v-else-if="$i == null" :user="user" :remote="true" :inline="true" :transparent="false" :full="true" class="koudoku"/> -->
+								<MkFollowButton
+									:user="user"
+									@refresh="emit('refresh')"
+									:inline="true"
+									:transparent="false"
+									:full="true"
+									class="koudoku"
+								/>
 							</div>
 						</div>
 						<div class="description">
@@ -490,29 +489,6 @@ onUnmounted(() => {
 						border-radius: 6px;
 					}
 
-					> .actions {
-						position: absolute;
-						top: 12px;
-						right: 12px;
-						padding: 8px;
-						border-radius: 24px;
-
-						> .menu {
-							vertical-align: bottom;
-							height: 31px;
-							width: 31px;
-							color: #fff;
-							text-shadow: 0 0 8px var(--shadow);
-							font-size: 16px;
-						}
-
-						> .koudoku {
-							margin-left: 4px;
-							width: 31px;
-							vertical-align: bottom;
-						}
-					}
-
 					> .title {
 						position: absolute;
 						bottom: 0;
@@ -581,22 +557,23 @@ onUnmounted(() => {
 
 					> .actions {
 						position: absolute;
-						top: 12px;
+						top: 6px;
 						right: 12px;
 						padding: 8px;
 						border-radius: 24px;
+						display: flex;
+						justify-content: center;
+						align-items: center;
 
 						> .menu {
-							vertical-align: bottom;
 							height: 31px;
 							width: 31px;
 							color: --fg;
 							font-size: 16px;
 						}
 
-						> .koudoku {
-							margin-left: 4px;
-							vertical-align: bottom;
+						> :deep(.follow-button) {
+							margin-left: 8px;
 						}
 					}
 
@@ -797,6 +774,13 @@ onUnmounted(() => {
 
 				> .title {
 					display: block;
+					border-bottom: 0;
+					padding-bottom: 0;
+					> .bottom {
+						> .username {
+							margin-right: 0;
+						}
+					}
 				}
 
 				> .avatar {
@@ -822,15 +806,19 @@ onUnmounted(() => {
 				}
 
 				> .description {
-					top: -55px;
+					top: 0;
 					position: relative;
 				}
 
 				> .follow-container {
 					overflow: visible !important;
+					display: flex;
+					justify-content: center;
+					height: auto;
+					border-bottom: 1px solid var(--divider);
+					padding-bottom: 5px;
 					> .actions {
-						top: -110px;
-						right: 0px;
+						position: static;
 					}
 				}
 			}
diff --git a/packages/client/src/style.scss b/packages/client/src/style.scss
index 4b914c408..10b591489 100644
--- a/packages/client/src/style.scss
+++ b/packages/client/src/style.scss
@@ -212,6 +212,11 @@ hr {
 		opacity: 0.5;
 		cursor: default;
 	}
+
+	> i:only-child {
+		display: block;
+		margin: auto;
+	}
 }
 
 ._buttonPrimary {

From b8bd07e3c4073234a416b5151e1812b8e8253b36 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Mon, 29 May 2023 17:06:39 +0200
Subject: [PATCH 049/198] Only connect to Meili with valid config

---
 packages/backend/src/db/meilisearch.ts | 76 +++++++++++++-------------
 1 file changed, 39 insertions(+), 37 deletions(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index 7e176e058..9a64da850 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -9,55 +9,57 @@ import { Followings, Users } from "@/models/index.js";
 
 const logger = dbLogger.createSubLogger("meilisearch", "gray", false);
 
-logger.info("Connecting to MeiliSearch");
-
 const hasConfig =
 	config.meilisearch &&
 	(config.meilisearch.host ||
 		config.meilisearch.port ||
 		config.meilisearch.apiKey);
 
-const host = hasConfig ? config.meilisearch.host ?? "localhost" : "";
-const port = hasConfig ? config.meilisearch.port ?? 7700 : 0;
-const auth = hasConfig ? config.meilisearch.apiKey ?? "" : "";
-const ssl = hasConfig ? config.meilisearch.ssl ?? false : false;
+if (hasConfig) {
+	const host = hasConfig ? config.meilisearch.host ?? "localhost" : "";
+	const port = hasConfig ? config.meilisearch.port ?? 7700 : 0;
+	const auth = hasConfig ? config.meilisearch.apiKey ?? "" : "";
+	const ssl = hasConfig ? config.meilisearch.ssl ?? false : false;
 
-const client: MeiliSearch = new MeiliSearch({
-	host: `${ssl ? "https" : "http"}://${host}:${port}`,
-	apiKey: auth,
-});
+	logger.info("Connecting to MeiliSearch");
 
-const posts = client.index("posts");
+	const client: MeiliSearch = new MeiliSearch({
+		host: `${ssl ? "https" : "http"}://${host}:${port}`,
+		apiKey: auth,
+	});
 
-posts
-	.updateSearchableAttributes(["text"])
-	.catch((e) =>
-		logger.error(`Setting searchable attr failed, searches won't work: ${e}`),
-	);
+	const posts = client.index("posts");
 
-posts
-	.updateFilterableAttributes([
-		"userName",
-		"userHost",
-		"mediaAttachment",
-		"createdAt",
-		"userId",
-	])
-	.catch((e) =>
-		logger.error(
-			`Setting filterable attr failed, advanced searches won't work: ${e}`,
-		),
-	);
+	posts
+		.updateSearchableAttributes(["text"])
+		.catch((e) =>
+			logger.error(`Setting searchable attr failed, searches won't work: ${e}`),
+		);
 
-posts
-	.updateSortableAttributes(["createdAt"])
-	.catch((e) =>
-		logger.error(
-			`Setting sortable attr failed, placeholder searches won't sort properly: ${e}`,
-		),
-	);
+	posts
+		.updateFilterableAttributes([
+			"userName",
+			"userHost",
+			"mediaAttachment",
+			"createdAt",
+			"userId",
+		])
+		.catch((e) =>
+			logger.error(
+				`Setting filterable attr failed, advanced searches won't work: ${e}`,
+			),
+		);
 
-logger.info("Connected to MeiliSearch");
+	posts
+		.updateSortableAttributes(["createdAt"])
+		.catch((e) =>
+			logger.error(
+				`Setting sortable attr failed, placeholder searches won't sort properly: ${e}`,
+			),
+		);
+
+	logger.info("Connected to MeiliSearch");
+}
 
 export type MeilisearchNote = {
 	id: string;

From 35b2432102dc862e73ea21e9e00ab2c2070c22cf Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Fri, 19 May 2023 20:41:19 -0700
Subject: [PATCH 050/198] docs: searc providers

---
 README.md | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index a72a171bb..9a0279a65 100644
--- a/README.md
+++ b/README.md
@@ -148,7 +148,11 @@ psql postgres -c "create database calckey with encoding = 'UTF8';"
 
 In Calckey's directory, fill out the `db` section of `.config/default.yml` with the correct information, where the `db` key is `calckey`.
 
-## 🦔 Set up search
+## 🔎 Set up search
+
+### 🦔 Sonic
+
+Sonic is better suited for self hosters with smaller deployments. It's easier to use, uses almost no resources, and takes barely any any disk space.
 
 Follow sonic's [installation guide](https://github.com/valeriansaliou/sonic#installation)
 
@@ -157,6 +161,17 @@ Follow sonic's [installation guide](https://github.com/valeriansaliou/sonic#inst
 
 In Calckey's directory, fill out the `sonic` section of `.config/default.yml` with the correct information.
 
+### Meilisearch
+
+Meilisearch is better suited for larger deployments. It's faster but uses far more resources and disk space.
+
+Follow Meilisearch's [quick start guide](https://www.meilisearch.com/docs/learn/getting_started/quick_start)
+
+In Calckey's directory, fill out the `meilisearch` section of `.config/default.yml` with the correct information.
+
+### ElasticSearch
+
+Please don't use ElasticSearch unless you already have an ElasticSearch setup and want to continue using it for Calckey. ElasticSearch is slow, heavy, and offers very few benefits over Sonic/Meilisearch.
 
 ## 💅 Customize
 

From 5d58ed6858543ccbeca538853cc622cee07e7fc5 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 09:22:24 -0700
Subject: [PATCH 051/198] docs: deps

---
 README.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 9a0279a65..5b3eb98fd 100644
--- a/README.md
+++ b/README.md
@@ -89,7 +89,8 @@ If you have access to a server that supports one of the sources below, I recomme
 
 - [FFmpeg](https://ffmpeg.org/) for video transcoding
 - Full text search (one of the following)
-  - 🦔 [Sonic](https://crates.io/crates/sonic-server) (recommended)
+  - 🦔 [Sonic](https://crates.io/crates/sonic-server)
+  - [MeiliSearch](https://www.meilisearch.com/)
   - [ElasticSearch](https://www.elastic.co/elasticsearch/)
 
 ### 🏗️ Build dependencies

From aaa2041d38a8d9e34361341663609ecdc5e52db5 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 09:31:02 -0700
Subject: [PATCH 052/198] chore: format

---
 packages/backend/jsconfig.json                |   7 +-
 .../native-utils/__test__/index.spec.mjs      |  10 +-
 .../src/models/entities/abuse-user-report.ts  |  38 +--
 .../src/models/entities/access-token.ts       |  35 +--
 packages/backend/src/models/entities/ad.ts    |  38 +--
 .../src/models/entities/announcement-read.ts  |  14 +-
 .../src/models/entities/announcement.ts       |  23 +-
 .../src/models/entities/antenna-note.ts       |  16 +-
 .../backend/src/models/entities/antenna.ts    |  52 ++--
 packages/backend/src/models/entities/app.ts   |  36 +--
 .../models/entities/attestation-challenge.ts  |  16 +-
 .../src/models/entities/auth-session.ts       |  14 +-
 .../backend/src/models/entities/blocking.ts   |  18 +-
 .../src/models/entities/channel-following.ts  |  18 +-
 .../models/entities/channel-note-pining.ts    |  14 +-
 .../backend/src/models/entities/channel.ts    |  37 +--
 .../backend/src/models/entities/clip-note.ts  |  14 +-
 packages/backend/src/models/entities/clip.ts  |  23 +-
 .../backend/src/models/entities/drive-file.ts | 132 +++++----
 .../src/models/entities/drive-folder.ts       |  21 +-
 packages/backend/src/models/entities/emoji.ts |  53 ++--
 .../src/models/entities/follow-request.ts     |  67 ++---
 .../backend/src/models/entities/following.ts  |  60 +++--
 .../src/models/entities/gallery-like.ts       |  12 +-
 .../src/models/entities/gallery-post.ts       |  36 +--
 .../backend/src/models/entities/hashtag.ts    |  14 +-
 .../backend/src/models/entities/instance.ts   |  83 +++---
 .../src/models/entities/messaging-message.ts  |  47 ++--
 packages/backend/src/models/entities/meta.ts  | 255 ++++++++++--------
 .../src/models/entities/moderation-log.ts     |  12 +-
 .../backend/src/models/entities/muted-note.ts |  18 +-
 .../backend/src/models/entities/muting.ts     |  20 +-
 .../backend/src/models/entities/note-edit.ts  |  20 +-
 .../src/models/entities/note-favorite.ts      |  14 +-
 .../src/models/entities/note-reaction.ts      |  16 +-
 .../src/models/entities/note-thread-muting.ts |  11 +-
 .../src/models/entities/note-unread.ts        |  18 +-
 .../src/models/entities/note-watching.ts      |  20 +-
 packages/backend/src/models/entities/note.ts  | 143 +++++-----
 .../src/models/entities/notification.ts       |  62 +++--
 .../backend/src/models/entities/page-like.ts  |  12 +-
 packages/backend/src/models/entities/page.ts  |  52 ++--
 .../models/entities/password-reset-request.ts |   8 +-
 .../backend/src/models/entities/poll-vote.ts  |  16 +-
 packages/backend/src/models/entities/poll.ts  |  29 +-
 .../backend/src/models/entities/promo-note.ts |   8 +-
 .../backend/src/models/entities/promo-read.ts |  14 +-
 .../models/entities/registration-tickets.ts   |   4 +-
 .../src/models/entities/registry-item.ts      |  36 +--
 packages/backend/src/models/entities/relay.ts |   9 +-
 .../src/models/entities/renote-muting.ts      |   4 +-
 .../backend/src/models/entities/signin.ts     |  14 +-
 .../src/models/entities/sw-subscription.ts    |  14 +-
 .../src/models/entities/used-username.ts      |   4 +-
 .../models/entities/user-group-invitation.ts  |  18 +-
 .../src/models/entities/user-group-joining.ts |  18 +-
 .../backend/src/models/entities/user-group.ts |  14 +-
 .../backend/src/models/entities/user-ip.ts    |   7 +-
 .../src/models/entities/user-keypair.ts       |   8 +-
 .../src/models/entities/user-list-joining.ts  |  18 +-
 .../backend/src/models/entities/user-list.ts  |  14 +-
 .../src/models/entities/user-note-pining.ts   |  14 +-
 .../src/models/entities/user-pending.ts       |  10 +-
 .../src/models/entities/user-profile.ts       | 144 +++++-----
 .../src/models/entities/user-publickey.ts     |   8 +-
 .../src/models/entities/user-security-key.ts  |  20 +-
 packages/backend/src/models/entities/user.ts  | 183 +++++++------
 .../backend/src/models/entities/webhook.ts    |  30 ++-
 packages/backend/src/server/web/manifest.json |   8 +-
 packages/calckey-js/package.json              |   4 +-
 packages/calckey-js/tsconfig.json             |   9 +-
 packages/client/src/widgets/server-info.vue   |   2 +-
 packages/sw/package.json                      |   4 +-
 rome.json                                     |  17 +-
 74 files changed, 1252 insertions(+), 1079 deletions(-)

diff --git a/packages/backend/jsconfig.json b/packages/backend/jsconfig.json
index 1230aadd1..f3f4f9c77 100644
--- a/packages/backend/jsconfig.json
+++ b/packages/backend/jsconfig.json
@@ -4,10 +4,5 @@
 		"module": "commonjs",
 		"allowSyntheticDefaultImports": true
 	},
-	"exclude": [
-		"node_modules",
-		"jspm_packages",
-		"tmp",
-		"temp"
-	]
+	"exclude": ["node_modules", "jspm_packages", "tmp", "temp"]
 }
diff --git a/packages/backend/native-utils/__test__/index.spec.mjs b/packages/backend/native-utils/__test__/index.spec.mjs
index 7d68d6ac3..1788dbb06 100644
--- a/packages/backend/native-utils/__test__/index.spec.mjs
+++ b/packages/backend/native-utils/__test__/index.spec.mjs
@@ -4,6 +4,12 @@ import { convertId, IdConvertType } from "../built/index.js";
 
 test("convert to mastodon id", (t) => {
 	t.is(convertId("9gf61ehcxv", IdConvertType.MastodonId), "960365976481219");
-	t.is(convertId("9fbr9z0wbrjqyd3u", IdConvertType.MastodonId), "3954607381600562394");
-	t.is(convertId("9fbs680oyviiqrol9md73p8g", IdConvertType.MastodonId), "3494513243013053824")
+	t.is(
+		convertId("9fbr9z0wbrjqyd3u", IdConvertType.MastodonId),
+		"3954607381600562394",
+	);
+	t.is(
+		convertId("9fbs680oyviiqrol9md73p8g", IdConvertType.MastodonId),
+		"3494513243013053824",
+	);
 });
diff --git a/packages/backend/src/models/entities/abuse-user-report.ts b/packages/backend/src/models/entities/abuse-user-report.ts
index 655fdd3ca..be183548d 100644
--- a/packages/backend/src/models/entities/abuse-user-report.ts
+++ b/packages/backend/src/models/entities/abuse-user-report.ts
@@ -15,8 +15,8 @@ export class AbuseUserReport {
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the AbuseUserReport.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the AbuseUserReport.",
 	})
 	public createdAt: Date;
 
@@ -24,8 +24,8 @@ export class AbuseUserReport {
 	@Column(id())
 	public targetUserId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public targetUser: User | null;
@@ -34,8 +34,8 @@ export class AbuseUserReport {
 	@Column(id())
 	public reporterId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public reporter: User | null;
@@ -46,40 +46,42 @@ export class AbuseUserReport {
 	})
 	public assigneeId: User["id"] | null;
 
-	@ManyToOne(type => User, {
-		onDelete: 'SET NULL',
+	@ManyToOne((type) => User, {
+		onDelete: "SET NULL",
 	})
 	@JoinColumn()
 	public assignee: User | null;
 
 	@Index()
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public resolved: boolean;
 
-	@Column('boolean', {
-		default: false
+	@Column("boolean", {
+		default: false,
 	})
 	public forwarded: boolean;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 2048,
 	})
 	public comment: string;
 
 	//#region Denormalized fields
 	@Index()
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public targetUserHost: string | null;
 
 	@Index()
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public reporterHost: string | null;
 	//#endregion
diff --git a/packages/backend/src/models/entities/access-token.ts b/packages/backend/src/models/entities/access-token.ts
index 83d7bbda8..8b950b171 100644
--- a/packages/backend/src/models/entities/access-token.ts
+++ b/packages/backend/src/models/entities/access-token.ts
@@ -15,31 +15,31 @@ export class AccessToken {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the AccessToken.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the AccessToken.",
 	})
 	public createdAt: Date;
 
-	@Column('timestamp with time zone', {
+	@Column("timestamp with time zone", {
 		nullable: true,
 	})
 	public lastUsedAt: Date | null;
 
 	@Index()
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 	})
 	public token: string;
 
 	@Index()
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 		nullable: true,
 	})
 	public session: string | null;
 
 	@Index()
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 	})
 	public hash: string;
@@ -48,8 +48,8 @@ export class AccessToken {
 	@Column(id())
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
@@ -60,37 +60,38 @@ export class AccessToken {
 	})
 	public appId: App["id"] | null;
 
-	@ManyToOne(type => App, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => App, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public app: App | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 		nullable: true,
 	})
 	public name: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
 	})
 	public description: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
 	})
 	public iconUrl: string | null;
 
-	@Column('varchar', {
-		length: 64, array: true,
-		default: '{}',
+	@Column("varchar", {
+		length: 64,
+		array: true,
+		default: "{}",
 	})
 	public permission: string[];
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public fetched: boolean;
diff --git a/packages/backend/src/models/entities/ad.ts b/packages/backend/src/models/entities/ad.ts
index fa4297365..80d54ddd5 100644
--- a/packages/backend/src/models/entities/ad.ts
+++ b/packages/backend/src/models/entities/ad.ts
@@ -7,45 +7,51 @@ export class Ad {
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the Ad.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the Ad.",
 	})
 	public createdAt: Date;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The expired date of the Ad.',
+	@Column("timestamp with time zone", {
+		comment: "The expired date of the Ad.",
 	})
 	public expiresAt: Date;
 
-	@Column('varchar', {
-		length: 32, nullable: false,
+	@Column("varchar", {
+		length: 32,
+		nullable: false,
 	})
 	public place: string;
 
 	// 今は使われていないが将来的に活用される可能性はある
-	@Column('varchar', {
-		length: 32, nullable: false,
+	@Column("varchar", {
+		length: 32,
+		nullable: false,
 	})
 	public priority: string;
 
-	@Column('integer', {
-		default: 1, nullable: false,
+	@Column("integer", {
+		default: 1,
+		nullable: false,
 	})
 	public ratio: number;
 
-	@Column('varchar', {
-		length: 1024, nullable: false,
+	@Column("varchar", {
+		length: 1024,
+		nullable: false,
 	})
 	public url: string;
 
-	@Column('varchar', {
-		length: 1024, nullable: false,
+	@Column("varchar", {
+		length: 1024,
+		nullable: false,
 	})
 	public imageUrl: string;
 
-	@Column('varchar', {
-		length: 8192, nullable: false,
+	@Column("varchar", {
+		length: 8192,
+		nullable: false,
 	})
 	public memo: string;
 
diff --git a/packages/backend/src/models/entities/announcement-read.ts b/packages/backend/src/models/entities/announcement-read.ts
index 87d0f0e9e..79af9e48e 100644
--- a/packages/backend/src/models/entities/announcement-read.ts
+++ b/packages/backend/src/models/entities/announcement-read.ts
@@ -11,13 +11,13 @@ import { Announcement } from "./announcement.js";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['userId', 'announcementId'], { unique: true })
+@Index(["userId", "announcementId"], { unique: true })
 export class AnnouncementRead {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the AnnouncementRead.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the AnnouncementRead.",
 	})
 	public createdAt: Date;
 
@@ -25,8 +25,8 @@ export class AnnouncementRead {
 	@Column(id())
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
@@ -35,8 +35,8 @@ export class AnnouncementRead {
 	@Column(id())
 	public announcementId: Announcement["id"];
 
-	@ManyToOne(type => Announcement, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Announcement, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public announcement: Announcement | null;
diff --git a/packages/backend/src/models/entities/announcement.ts b/packages/backend/src/models/entities/announcement.ts
index 9d45af014..1939604b9 100644
--- a/packages/backend/src/models/entities/announcement.ts
+++ b/packages/backend/src/models/entities/announcement.ts
@@ -7,29 +7,32 @@ export class Announcement {
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the Announcement.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the Announcement.",
 	})
 	public createdAt: Date;
 
-	@Column('timestamp with time zone', {
-		comment: 'The updated date of the Announcement.',
+	@Column("timestamp with time zone", {
+		comment: "The updated date of the Announcement.",
 		nullable: true,
 	})
 	public updatedAt: Date | null;
 
-	@Column('varchar', {
-		length: 8192, nullable: false,
+	@Column("varchar", {
+		length: 8192,
+		nullable: false,
 	})
 	public text: string;
 
-	@Column('varchar', {
-		length: 256, nullable: false,
+	@Column("varchar", {
+		length: 256,
+		nullable: false,
 	})
 	public title: string;
 
-	@Column('varchar', {
-		length: 1024, nullable: true,
+	@Column("varchar", {
+		length: 1024,
+		nullable: true,
 	})
 	public imageUrl: string | null;
 
diff --git a/packages/backend/src/models/entities/antenna-note.ts b/packages/backend/src/models/entities/antenna-note.ts
index c47c796bb..fe982c19e 100644
--- a/packages/backend/src/models/entities/antenna-note.ts
+++ b/packages/backend/src/models/entities/antenna-note.ts
@@ -11,7 +11,7 @@ import { Antenna } from "./antenna.js";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['noteId', 'antennaId'], { unique: true })
+@Index(["noteId", "antennaId"], { unique: true })
 export class AntennaNote {
 	@PrimaryColumn(id())
 	public id: string;
@@ -19,12 +19,12 @@ export class AntennaNote {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The note ID.',
+		comment: "The note ID.",
 	})
 	public noteId: Note["id"];
 
-	@ManyToOne(type => Note, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Note, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public note: Note | null;
@@ -32,18 +32,18 @@ export class AntennaNote {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The antenna ID.',
+		comment: "The antenna ID.",
 	})
 	public antennaId: Antenna["id"];
 
-	@ManyToOne(type => Antenna, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Antenna, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public antenna: Antenna | null;
 
 	@Index()
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public read: boolean;
diff --git a/packages/backend/src/models/entities/antenna.ts b/packages/backend/src/models/entities/antenna.ts
index c653b2a05..633dcc1d2 100644
--- a/packages/backend/src/models/entities/antenna.ts
+++ b/packages/backend/src/models/entities/antenna.ts
@@ -16,31 +16,33 @@ export class Antenna {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the Antenna.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the Antenna.",
 	})
 	public createdAt: Date;
 
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The owner ID.',
+		comment: "The owner ID.",
 	})
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
-		comment: 'The name of the Antenna.',
+		comment: "The name of the Antenna.",
 	})
 	public name: string;
 
-	@Column('enum', { enum: ['home', 'all', 'users', 'list', 'group', 'instances'] })
+	@Column("enum", {
+		enum: ["home", "all", "users", "list", "group", "instances"],
+	})
 	public src: "home" | "all" | "users" | "list" | "group" | "instances";
 
 	@Column({
@@ -49,8 +51,8 @@ export class Antenna {
 	})
 	public userListId: UserList["id"] | null;
 
-	@ManyToOne(type => UserList, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => UserList, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public userList: UserList | null;
@@ -61,51 +63,53 @@ export class Antenna {
 	})
 	public userGroupJoiningId: UserGroupJoining["id"] | null;
 
-	@ManyToOne(type => UserGroupJoining, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => UserGroupJoining, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public userGroupJoining: UserGroupJoining | null;
 
-	@Column('varchar', {
-		length: 1024, array: true,
-		default: '{}',
+	@Column("varchar", {
+		length: 1024,
+		array: true,
+		default: "{}",
 	})
 	public users: string[];
 
-	@Column('jsonb', {
+	@Column("jsonb", {
 		default: [],
 	})
 	public instances: string[];
 
-	@Column('jsonb', {
+	@Column("jsonb", {
 		default: [],
 	})
 	public keywords: string[][];
 
-	@Column('jsonb', {
+	@Column("jsonb", {
 		default: [],
 	})
 	public excludeKeywords: string[][];
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public caseSensitive: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public withReplies: boolean;
 
-	@Column('boolean')
+	@Column("boolean")
 	public withFile: boolean;
 
-	@Column('varchar', {
-		length: 2048, nullable: true,
+	@Column("varchar", {
+		length: 2048,
+		nullable: true,
 	})
 	public expression: string | null;
 
-	@Column('boolean')
+	@Column("boolean")
 	public notify: boolean;
 }
diff --git a/packages/backend/src/models/entities/app.ts b/packages/backend/src/models/entities/app.ts
index bb33eede4..a41e35aa9 100644
--- a/packages/backend/src/models/entities/app.ts
+++ b/packages/backend/src/models/entities/app.ts
@@ -8,8 +8,8 @@ export class App {
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the App.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the App.",
 	})
 	public createdAt: Date;
 
@@ -17,44 +17,46 @@ export class App {
 	@Column({
 		...id(),
 		nullable: true,
-		comment: 'The owner ID.',
+		comment: "The owner ID.",
 	})
 	public userId: User["id"] | null;
 
-	@ManyToOne(type => User, {
-		onDelete: 'SET NULL',
+	@ManyToOne((type) => User, {
+		onDelete: "SET NULL",
 		nullable: true,
 	})
 	public user: User | null;
 
 	@Index()
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 64,
-		comment: 'The secret key of the App.',
+		comment: "The secret key of the App.",
 	})
 	public secret: string;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
-		comment: 'The name of the App.',
+		comment: "The name of the App.",
 	})
 	public name: string;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
-		comment: 'The description of the App.',
+		comment: "The description of the App.",
 	})
 	public description: string;
 
-	@Column('varchar', {
-		length: 64, array: true,
-		comment: 'The permission of the App.',
+	@Column("varchar", {
+		length: 64,
+		array: true,
+		comment: "The permission of the App.",
 	})
 	public permission: string[];
 
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: 'The callbackUrl of the App.',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment: "The callbackUrl of the App.",
 	})
 	public callbackUrl: string | null;
 }
diff --git a/packages/backend/src/models/entities/attestation-challenge.ts b/packages/backend/src/models/entities/attestation-challenge.ts
index 7a87d42be..6a3a9c8ed 100644
--- a/packages/backend/src/models/entities/attestation-challenge.ts
+++ b/packages/backend/src/models/entities/attestation-challenge.ts
@@ -18,27 +18,27 @@ export class AttestationChallenge {
 	@PrimaryColumn(id())
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
 
 	@Index()
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 64,
-		comment: 'Hex-encoded sha256 hash of the challenge.',
+		comment: "Hex-encoded sha256 hash of the challenge.",
 	})
 	public challenge: string;
 
-	@Column('timestamp with time zone', {
-		comment: 'The date challenge was created for expiry purposes.',
+	@Column("timestamp with time zone", {
+		comment: "The date challenge was created for expiry purposes.",
 	})
 	public createdAt: Date;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		comment:
-			'Indicates that the challenge is only for registration purposes if true to prevent the challenge for being used as authentication.',
+			"Indicates that the challenge is only for registration purposes if true to prevent the challenge for being used as authentication.",
 		default: false,
 	})
 	public registrationChallenge: boolean;
diff --git a/packages/backend/src/models/entities/auth-session.ts b/packages/backend/src/models/entities/auth-session.ts
index b762f8462..b31dca56c 100644
--- a/packages/backend/src/models/entities/auth-session.ts
+++ b/packages/backend/src/models/entities/auth-session.ts
@@ -15,13 +15,13 @@ export class AuthSession {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the AuthSession.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the AuthSession.",
 	})
 	public createdAt: Date;
 
 	@Index()
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 	})
 	public token: string;
@@ -32,8 +32,8 @@ export class AuthSession {
 	})
 	public userId: User["id"] | null;
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 		nullable: true,
 	})
 	@JoinColumn()
@@ -42,8 +42,8 @@ export class AuthSession {
 	@Column(id())
 	public appId: App["id"];
 
-	@ManyToOne(type => App, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => App, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public app: App | null;
diff --git a/packages/backend/src/models/entities/blocking.ts b/packages/backend/src/models/entities/blocking.ts
index 3a44a4d65..55f677a98 100644
--- a/packages/backend/src/models/entities/blocking.ts
+++ b/packages/backend/src/models/entities/blocking.ts
@@ -10,26 +10,26 @@ import { User } from "./user.js";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['blockerId', 'blockeeId'], { unique: true })
+@Index(["blockerId", "blockeeId"], { unique: true })
 export class Blocking {
 	@PrimaryColumn(id())
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the Blocking.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the Blocking.",
 	})
 	public createdAt: Date;
 
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The blockee user ID.',
+		comment: "The blockee user ID.",
 	})
 	public blockeeId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public blockee: User | null;
@@ -37,12 +37,12 @@ export class Blocking {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The blocker user ID.',
+		comment: "The blocker user ID.",
 	})
 	public blockerId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public blocker: User | null;
diff --git a/packages/backend/src/models/entities/channel-following.ts b/packages/backend/src/models/entities/channel-following.ts
index 04ec193e1..ee329fa50 100644
--- a/packages/backend/src/models/entities/channel-following.ts
+++ b/packages/backend/src/models/entities/channel-following.ts
@@ -11,26 +11,26 @@ import { id } from "../id.js";
 import { Channel } from "./channel.js";
 
 @Entity()
-@Index(['followerId', 'followeeId'], { unique: true })
+@Index(["followerId", "followeeId"], { unique: true })
 export class ChannelFollowing {
 	@PrimaryColumn(id())
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the ChannelFollowing.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the ChannelFollowing.",
 	})
 	public createdAt: Date;
 
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The followee channel ID.',
+		comment: "The followee channel ID.",
 	})
 	public followeeId: Channel["id"];
 
-	@ManyToOne(type => Channel, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Channel, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public followee: Channel | null;
@@ -38,12 +38,12 @@ export class ChannelFollowing {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The follower user ID.',
+		comment: "The follower user ID.",
 	})
 	public followerId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public follower: User | null;
diff --git a/packages/backend/src/models/entities/channel-note-pining.ts b/packages/backend/src/models/entities/channel-note-pining.ts
index bd13f4ca3..67d1d48cc 100644
--- a/packages/backend/src/models/entities/channel-note-pining.ts
+++ b/packages/backend/src/models/entities/channel-note-pining.ts
@@ -11,13 +11,13 @@ import { Channel } from "./channel.js";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['channelId', 'noteId'], { unique: true })
+@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.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the ChannelNotePining.",
 	})
 	public createdAt: Date;
 
@@ -25,8 +25,8 @@ export class ChannelNotePining {
 	@Column(id())
 	public channelId: Channel["id"];
 
-	@ManyToOne(type => Channel, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Channel, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public channel: Channel | null;
@@ -34,8 +34,8 @@ export class ChannelNotePining {
 	@Column(id())
 	public noteId: Note["id"];
 
-	@ManyToOne(type => Note, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Note, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public note: Note | null;
diff --git a/packages/backend/src/models/entities/channel.ts b/packages/backend/src/models/entities/channel.ts
index 7f9851dbf..ea22fed50 100644
--- a/packages/backend/src/models/entities/channel.ts
+++ b/packages/backend/src/models/entities/channel.ts
@@ -16,13 +16,13 @@ export class Channel {
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the Channel.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the Channel.",
 	})
 	public createdAt: Date;
 
 	@Index()
-	@Column('timestamp with time zone', {
+	@Column("timestamp with time zone", {
 		nullable: true,
 	})
 	public lastNotedAt: Date | null;
@@ -31,52 +31,53 @@ export class Channel {
 	@Column({
 		...id(),
 		nullable: true,
-		comment: 'The owner ID.',
+		comment: "The owner ID.",
 	})
 	public userId: User["id"] | null;
 
-	@ManyToOne(type => User, {
-		onDelete: 'SET NULL',
+	@ManyToOne((type) => User, {
+		onDelete: "SET NULL",
 	})
 	@JoinColumn()
 	public user: User | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
-		comment: 'The name of the Channel.',
+		comment: "The name of the Channel.",
 	})
 	public name: string;
 
-	@Column('varchar', {
-		length: 2048, nullable: true,
-		comment: 'The description of the Channel.',
+	@Column("varchar", {
+		length: 2048,
+		nullable: true,
+		comment: "The description of the Channel.",
 	})
 	public description: string | null;
 
 	@Column({
 		...id(),
 		nullable: true,
-		comment: 'The ID of banner Channel.',
+		comment: "The ID of banner Channel.",
 	})
 	public bannerId: DriveFile["id"] | null;
 
-	@ManyToOne(type => DriveFile, {
-		onDelete: 'SET NULL',
+	@ManyToOne((type) => DriveFile, {
+		onDelete: "SET NULL",
 	})
 	@JoinColumn()
 	public banner: DriveFile | null;
 
 	@Index()
-	@Column('integer', {
+	@Column("integer", {
 		default: 0,
-		comment: 'The count of notes.',
+		comment: "The count of notes.",
 	})
 	public notesCount: number;
 
 	@Index()
-	@Column('integer', {
+	@Column("integer", {
 		default: 0,
-		comment: 'The count of users.',
+		comment: "The count of users.",
 	})
 	public usersCount: number;
 }
diff --git a/packages/backend/src/models/entities/clip-note.ts b/packages/backend/src/models/entities/clip-note.ts
index bc51daaf4..1697474a8 100644
--- a/packages/backend/src/models/entities/clip-note.ts
+++ b/packages/backend/src/models/entities/clip-note.ts
@@ -11,7 +11,7 @@ import { Clip } from "./clip.js";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['noteId', 'clipId'], { unique: true })
+@Index(["noteId", "clipId"], { unique: true })
 export class ClipNote {
 	@PrimaryColumn(id())
 	public id: string;
@@ -19,12 +19,12 @@ export class ClipNote {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The note ID.',
+		comment: "The note ID.",
 	})
 	public noteId: Note["id"];
 
-	@ManyToOne(type => Note, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Note, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public note: Note | null;
@@ -32,12 +32,12 @@ export class ClipNote {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The clip ID.',
+		comment: "The clip ID.",
 	})
 	public clipId: Clip["id"];
 
-	@ManyToOne(type => Clip, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Clip, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public clip: Clip | null;
diff --git a/packages/backend/src/models/entities/clip.ts b/packages/backend/src/models/entities/clip.ts
index 10591cbee..9554703a4 100644
--- a/packages/backend/src/models/entities/clip.ts
+++ b/packages/backend/src/models/entities/clip.ts
@@ -14,38 +14,39 @@ export class Clip {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the Clip.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the Clip.",
 	})
 	public createdAt: Date;
 
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The owner ID.',
+		comment: "The owner ID.",
 	})
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
-		comment: 'The name of the Clip.',
+		comment: "The name of the Clip.",
 	})
 	public name: string;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public isPublic: boolean;
 
-	@Column('varchar', {
-		length: 2048, nullable: true,
-		comment: 'The description of the Clip.',
+	@Column("varchar", {
+		length: 2048,
+		nullable: true,
+		comment: "The description of the Clip.",
 	})
 	public description: string | null;
 }
diff --git a/packages/backend/src/models/entities/drive-file.ts b/packages/backend/src/models/entities/drive-file.ts
index 32e19bc6e..d8b54fa19 100644
--- a/packages/backend/src/models/entities/drive-file.ts
+++ b/packages/backend/src/models/entities/drive-file.ts
@@ -12,14 +12,14 @@ import { DriveFolder } from "./drive-folder.js";
 import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js";
 
 @Entity()
-@Index(['userId', 'folderId', 'id'])
+@Index(["userId", "folderId", "id"])
 export class DriveFile {
 	@PrimaryColumn(id())
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the DriveFile.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the DriveFile.",
 	})
 	public createdAt: Date;
 
@@ -27,64 +27,67 @@ export class DriveFile {
 	@Column({
 		...id(),
 		nullable: true,
-		comment: 'The owner ID.',
+		comment: "The owner ID.",
 	})
 	public userId: User["id"] | null;
 
-	@ManyToOne(type => User, {
-		onDelete: 'SET NULL',
+	@ManyToOne((type) => User, {
+		onDelete: "SET NULL",
 	})
 	@JoinColumn()
 	public user: User | null;
 
 	@Index()
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: 'The host of owner. It will be null if the user in local.',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment: "The host of owner. It will be null if the user in local.",
 	})
 	public userHost: string | null;
 
 	@Index()
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 32,
-		comment: 'The MD5 hash of the DriveFile.',
+		comment: "The MD5 hash of the DriveFile.",
 	})
 	public md5: string;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 256,
-		comment: 'The file name of the DriveFile.',
+		comment: "The file name of the DriveFile.",
 	})
 	public name: string;
 
 	@Index()
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
-		comment: 'The content type (MIME) of the DriveFile.',
+		comment: "The content type (MIME) of the DriveFile.",
 	})
 	public type: string;
 
-	@Column('integer', {
-		comment: 'The file size (bytes) of the DriveFile.',
+	@Column("integer", {
+		comment: "The file size (bytes) of the DriveFile.",
 	})
 	public size: number;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: DB_MAX_IMAGE_COMMENT_LENGTH,
 		nullable: true,
-		comment: 'The comment of the DriveFile.',
+		comment: "The comment of the DriveFile.",
 	})
 	public comment: string | null;
 
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: 'The BlurHash string.',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment: "The BlurHash string.",
 	})
 	public blurhash: string | null;
 
-	@Column('jsonb', {
+	@Column("jsonb", {
 		default: {},
-		comment: 'The any properties of the DriveFile. For example, it includes image width/height.',
+		comment:
+			"The any properties of the DriveFile. For example, it includes image width/height.",
 	})
 	public properties: {
 		width?: number;
@@ -93,59 +96,68 @@ export class DriveFile {
 		avgColor?: string;
 	};
 
-	@Column('boolean')
+	@Column("boolean")
 	public storedInternal: boolean;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
-		comment: 'The URL of the DriveFile.',
+		comment: "The URL of the DriveFile.",
 	})
 	public url: string;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: 'The URL of the thumbnail of the DriveFile.',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment: "The URL of the thumbnail of the DriveFile.",
 	})
 	public thumbnailUrl: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: 'The URL of the webpublic of the DriveFile.',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment: "The URL of the webpublic of the DriveFile.",
 	})
 	public webpublicUrl: string | null;
 
-	@Column('varchar', {
-		length: 128, nullable: true,
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
 	})
 	public webpublicType: string | null;
 
 	@Index({ unique: true })
-	@Column('varchar', {
-		length: 256, nullable: true,
+	@Column("varchar", {
+		length: 256,
+		nullable: true,
 	})
 	public accessKey: string | null;
 
 	@Index({ unique: true })
-	@Column('varchar', {
-		length: 256, nullable: true,
+	@Column("varchar", {
+		length: 256,
+		nullable: true,
 	})
 	public thumbnailAccessKey: string | null;
 
 	@Index({ unique: true })
-	@Column('varchar', {
-		length: 256, nullable: true,
+	@Column("varchar", {
+		length: 256,
+		nullable: true,
 	})
 	public webpublicAccessKey: string | null;
 
 	@Index()
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: 'The URI of the DriveFile. it will be null when the DriveFile is local.',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment:
+			"The URI of the DriveFile. it will be null when the DriveFile is local.",
 	})
 	public uri: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
 	})
 	public src: string | null;
 
@@ -153,32 +165,33 @@ export class DriveFile {
 	@Column({
 		...id(),
 		nullable: true,
-		comment: 'The parent folder ID. If null, it means the DriveFile is located in root.',
+		comment:
+			"The parent folder ID. If null, it means the DriveFile is located in root.",
 	})
 	public folderId: DriveFolder["id"] | null;
 
-	@ManyToOne(type => DriveFolder, {
-		onDelete: 'SET NULL',
+	@ManyToOne((type) => DriveFolder, {
+		onDelete: "SET NULL",
 	})
 	@JoinColumn()
 	public folder: DriveFolder | null;
 
 	@Index()
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
-		comment: 'Whether the DriveFile is NSFW.',
+		comment: "Whether the DriveFile is NSFW.",
 	})
 	public isSensitive: boolean;
 
 	@Index()
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
-		comment: 'Whether the DriveFile is NSFW. (predict)',
+		comment: "Whether the DriveFile is NSFW. (predict)",
 	})
 	public maybeSensitive: boolean;
 
 	@Index()
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public maybePorn: boolean;
@@ -187,20 +200,21 @@ export class DriveFile {
 	 * 外部の(信頼されていない)URLへの直リンクか否か
 	 */
 	@Index()
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
-		comment: 'Whether the DriveFile is direct link to remote server.',
+		comment: "Whether the DriveFile is direct link to remote server.",
 	})
 	public isLink: boolean;
 
-	@Column('jsonb', {
+	@Column("jsonb", {
 		default: {},
 		nullable: true,
 	})
 	public requestHeaders: Record<string, string> | null;
 
-	@Column('varchar', {
-		length: 128, nullable: true,
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
 	})
 	public requestIp: string | null;
 }
diff --git a/packages/backend/src/models/entities/drive-folder.ts b/packages/backend/src/models/entities/drive-folder.ts
index 77031ce4e..0bb2c7a3d 100644
--- a/packages/backend/src/models/entities/drive-folder.ts
+++ b/packages/backend/src/models/entities/drive-folder.ts
@@ -15,14 +15,14 @@ export class DriveFolder {
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the DriveFolder.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the DriveFolder.",
 	})
 	public createdAt: Date;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
-		comment: 'The name of the DriveFolder.',
+		comment: "The name of the DriveFolder.",
 	})
 	public name: string;
 
@@ -30,12 +30,12 @@ export class DriveFolder {
 	@Column({
 		...id(),
 		nullable: true,
-		comment: 'The owner ID.',
+		comment: "The owner ID.",
 	})
 	public userId: User["id"] | null;
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
@@ -44,12 +44,13 @@ export class DriveFolder {
 	@Column({
 		...id(),
 		nullable: true,
-		comment: 'The parent folder ID. If null, it means the DriveFolder is located in root.',
+		comment:
+			"The parent folder ID. If null, it means the DriveFolder is located in root.",
 	})
 	public parentId: DriveFolder["id"] | null;
 
-	@ManyToOne(type => DriveFolder, {
-		onDelete: 'SET NULL',
+	@ManyToOne((type) => DriveFolder, {
+		onDelete: "SET NULL",
 	})
 	@JoinColumn()
 	public parent: DriveFolder | null;
diff --git a/packages/backend/src/models/entities/emoji.ts b/packages/backend/src/models/entities/emoji.ts
index 773265d91..727ba2f10 100644
--- a/packages/backend/src/models/entities/emoji.ts
+++ b/packages/backend/src/models/entities/emoji.ts
@@ -2,73 +2,82 @@ import { PrimaryColumn, Entity, Index, Column } from "typeorm";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['name', 'host'], { unique: true })
+@Index(["name", "host"], { unique: true })
 export class Emoji {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
+	@Column("timestamp with time zone", {
 		nullable: true,
 	})
 	public updatedAt: Date | null;
 
 	@Index()
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 	})
 	public name: string;
 
 	@Index()
-	@Column('varchar', {
-		length: 128, nullable: true,
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
 	})
 	public host: string | null;
 
-	@Column('varchar', {
-		length: 128, nullable: true,
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
 	})
 	public category: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 	})
 	public originalUrl: string;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
-		default: '',
+		default: "",
 	})
 	public publicUrl: string;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
 	})
 	public uri: string | null;
 
 	// publicUrlの方のtypeが入る
 	// (mime)
-	@Column('varchar', {
-		length: 64, nullable: true,
+	@Column("varchar", {
+		length: 64,
+		nullable: true,
 	})
 	public type: string | null;
 
-	@Column('varchar', {
-		array: true, length: 128, default: '{}',
+	@Column("varchar", {
+		array: true,
+		length: 128,
+		default: "{}",
 	})
 	public aliases: string[];
 
-	@Column('varchar', {
-		length: 1024, nullable: true,
+	@Column("varchar", {
+		length: 1024,
+		nullable: true,
 	})
 	public license: string | null;
 
-	@Column('integer', {
-		nullable: true, comment: 'Image width',
+	@Column("integer", {
+		nullable: true,
+		comment: "Image width",
 	})
 	public width: number | null;
 
-	@Column('integer', {
-		nullable: true, comment: "Image height",
+	@Column("integer", {
+		nullable: true,
+		comment: "Image height",
 	})
 	public height: number | null;
 }
diff --git a/packages/backend/src/models/entities/follow-request.ts b/packages/backend/src/models/entities/follow-request.ts
index 658fed5a5..281eab917 100644
--- a/packages/backend/src/models/entities/follow-request.ts
+++ b/packages/backend/src/models/entities/follow-request.ts
@@ -10,25 +10,25 @@ import { User } from "./user.js";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['followerId', 'followeeId'], { unique: true })
+@Index(["followerId", "followeeId"], { unique: true })
 export class FollowRequest {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the FollowRequest.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the FollowRequest.",
 	})
 	public createdAt: Date;
 
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The followee user ID.',
+		comment: "The followee user ID.",
 	})
 	public followeeId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public followee: User | null;
@@ -36,56 +36,63 @@ export class FollowRequest {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The follower user ID.',
+		comment: "The follower user ID.",
 	})
 	public followerId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public follower: User | null;
 
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: 'id of Follow Activity.',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment: "id of Follow Activity.",
 	})
 	public requestId: string | null;
 
 	//#region Denormalized fields
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public followerHost: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public followerInbox: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public followerSharedInbox: string | null;
 
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public followeeHost: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public followeeInbox: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public followeeSharedInbox: string | null;
 	//#endregion
diff --git a/packages/backend/src/models/entities/following.ts b/packages/backend/src/models/entities/following.ts
index 11f633fcd..fafcf8885 100644
--- a/packages/backend/src/models/entities/following.ts
+++ b/packages/backend/src/models/entities/following.ts
@@ -10,26 +10,26 @@ import { User } from "./user.js";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['followerId', 'followeeId'], { unique: true })
+@Index(["followerId", "followeeId"], { unique: true })
 export class Following {
 	@PrimaryColumn(id())
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the Following.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the Following.",
 	})
 	public createdAt: Date;
 
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The followee user ID.',
+		comment: "The followee user ID.",
 	})
 	public followeeId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public followee: User | null;
@@ -37,52 +37,58 @@ export class Following {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The follower user ID.',
+		comment: "The follower user ID.",
 	})
 	public followerId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public follower: User | null;
 
 	//#region Denormalized fields
 	@Index()
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public followerHost: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public followerInbox: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public followerSharedInbox: string | null;
 
 	@Index()
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public followeeHost: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public followeeInbox: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public followeeSharedInbox: string | null;
 	//#endregion
diff --git a/packages/backend/src/models/entities/gallery-like.ts b/packages/backend/src/models/entities/gallery-like.ts
index e74e3c3ce..259feb8bb 100644
--- a/packages/backend/src/models/entities/gallery-like.ts
+++ b/packages/backend/src/models/entities/gallery-like.ts
@@ -11,20 +11,20 @@ import { id } from "../id.js";
 import { GalleryPost } from "./gallery-post.js";
 
 @Entity()
-@Index(['userId', 'postId'], { unique: true })
+@Index(["userId", "postId"], { unique: true })
 export class GalleryLike {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone')
+	@Column("timestamp with time zone")
 	public createdAt: Date;
 
 	@Index()
 	@Column(id())
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
@@ -32,8 +32,8 @@ export class GalleryLike {
 	@Column(id())
 	public postId: GalleryPost["id"];
 
-	@ManyToOne(type => GalleryPost, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => GalleryPost, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public post: GalleryPost | null;
diff --git a/packages/backend/src/models/entities/gallery-post.ts b/packages/backend/src/models/entities/gallery-post.ts
index a79bb8835..938348659 100644
--- a/packages/backend/src/models/entities/gallery-post.ts
+++ b/packages/backend/src/models/entities/gallery-post.ts
@@ -16,36 +16,37 @@ export class GalleryPost {
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the GalleryPost.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the GalleryPost.",
 	})
 	public createdAt: Date;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The updated date of the GalleryPost.',
+	@Column("timestamp with time zone", {
+		comment: "The updated date of the GalleryPost.",
 	})
 	public updatedAt: Date;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 256,
 	})
 	public title: string;
 
-	@Column('varchar', {
-		length: 2048, nullable: true,
+	@Column("varchar", {
+		length: 2048,
+		nullable: true,
 	})
 	public description: string | null;
 
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The ID of author.',
+		comment: "The ID of author.",
 	})
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
@@ -53,26 +54,29 @@ export class GalleryPost {
 	@Index()
 	@Column({
 		...id(),
-		array: true, default: '{}',
+		array: true,
+		default: "{}",
 	})
 	public fileIds: DriveFile["id"][];
 
 	@Index()
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
-		comment: 'Whether the post is sensitive.',
+		comment: "Whether the post is sensitive.",
 	})
 	public isSensitive: boolean;
 
 	@Index()
-	@Column('integer', {
+	@Column("integer", {
 		default: 0,
 	})
 	public likedCount: number;
 
 	@Index()
-	@Column('varchar', {
-		length: 128, array: true, default: '{}',
+	@Column("varchar", {
+		length: 128,
+		array: true,
+		default: "{}",
 	})
 	public tags: string[];
 
diff --git a/packages/backend/src/models/entities/hashtag.ts b/packages/backend/src/models/entities/hashtag.ts
index 06fa004be..7b3df1cc2 100644
--- a/packages/backend/src/models/entities/hashtag.ts
+++ b/packages/backend/src/models/entities/hashtag.ts
@@ -8,7 +8,7 @@ export class Hashtag {
 	public id: string;
 
 	@Index({ unique: true })
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 	})
 	public name: string;
@@ -20,7 +20,7 @@ export class Hashtag {
 	public mentionedUserIds: User["id"][];
 
 	@Index()
-	@Column('integer', {
+	@Column("integer", {
 		default: 0,
 	})
 	public mentionedUsersCount: number;
@@ -32,7 +32,7 @@ export class Hashtag {
 	public mentionedLocalUserIds: User["id"][];
 
 	@Index()
-	@Column('integer', {
+	@Column("integer", {
 		default: 0,
 	})
 	public mentionedLocalUsersCount: number;
@@ -44,7 +44,7 @@ export class Hashtag {
 	public mentionedRemoteUserIds: User["id"][];
 
 	@Index()
-	@Column('integer', {
+	@Column("integer", {
 		default: 0,
 	})
 	public mentionedRemoteUsersCount: number;
@@ -56,7 +56,7 @@ export class Hashtag {
 	public attachedUserIds: User["id"][];
 
 	@Index()
-	@Column('integer', {
+	@Column("integer", {
 		default: 0,
 	})
 	public attachedUsersCount: number;
@@ -68,7 +68,7 @@ export class Hashtag {
 	public attachedLocalUserIds: User["id"][];
 
 	@Index()
-	@Column('integer', {
+	@Column("integer", {
 		default: 0,
 	})
 	public attachedLocalUsersCount: number;
@@ -80,7 +80,7 @@ export class Hashtag {
 	public attachedRemoteUserIds: User["id"][];
 
 	@Index()
-	@Column('integer', {
+	@Column("integer", {
 		default: 0,
 	})
 	public attachedRemoteUsersCount: number;
diff --git a/packages/backend/src/models/entities/instance.ts b/packages/backend/src/models/entities/instance.ts
index 2b118455d..7e0b08583 100644
--- a/packages/backend/src/models/entities/instance.ts
+++ b/packages/backend/src/models/entities/instance.ts
@@ -10,8 +10,8 @@ export class Instance {
 	 * このインスタンスを捕捉した日時
 	 */
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The caught date of the Instance.',
+	@Column("timestamp with time zone", {
+		comment: "The caught date of the Instance.",
 	})
 	public caughtAt: Date;
 
@@ -19,34 +19,34 @@ export class Instance {
 	 * ホスト
 	 */
 	@Index({ unique: true })
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
-		comment: 'The host of the Instance.',
+		comment: "The host of the Instance.",
 	})
 	public host: string;
 
 	/**
 	 * インスタンスのユーザー数
 	 */
-	@Column('integer', {
+	@Column("integer", {
 		default: 0,
-		comment: 'The count of the users of the Instance.',
+		comment: "The count of the users of the Instance.",
 	})
 	public usersCount: number;
 
 	/**
 	 * インスタンスの投稿数
 	 */
-	@Column('integer', {
+	@Column("integer", {
 		default: 0,
-		comment: 'The count of the notes of the Instance.',
+		comment: "The count of the notes of the Instance.",
 	})
 	public notesCount: number;
 
 	/**
 	 * このインスタンスのユーザーからフォローされている、自インスタンスのユーザーの数
 	 */
-	@Column('integer', {
+	@Column("integer", {
 		default: 0,
 	})
 	public followingCount: number;
@@ -54,7 +54,7 @@ export class Instance {
 	/**
 	 * このインスタンスのユーザーをフォローしている、自インスタンスのユーザーの数
 	 */
-	@Column('integer', {
+	@Column("integer", {
 		default: 0,
 	})
 	public followersCount: number;
@@ -62,7 +62,7 @@ export class Instance {
 	/**
 	 * 直近のリクエスト送信日時
 	 */
-	@Column('timestamp with time zone', {
+	@Column("timestamp with time zone", {
 		nullable: true,
 	})
 	public latestRequestSentAt: Date | null;
@@ -70,7 +70,7 @@ export class Instance {
 	/**
 	 * 直近のリクエスト送信時のHTTPステータスコード
 	 */
-	@Column('integer', {
+	@Column("integer", {
 		nullable: true,
 	})
 	public latestStatus: number | null;
@@ -78,7 +78,7 @@ export class Instance {
 	/**
 	 * 直近のリクエスト受信日時
 	 */
-	@Column('timestamp with time zone', {
+	@Column("timestamp with time zone", {
 		nullable: true,
 	})
 	public latestRequestReceivedAt: Date | null;
@@ -86,13 +86,13 @@ export class Instance {
 	/**
 	 * このインスタンスと最後にやり取りした日時
 	 */
-	@Column('timestamp with time zone')
+	@Column("timestamp with time zone")
 	public lastCommunicatedAt: Date;
 
 	/**
 	 * このインスタンスと不通かどうか
 	 */
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public isNotResponding: boolean;
@@ -101,63 +101,72 @@ export class Instance {
 	 * このインスタンスへの配信を停止するか
 	 */
 	@Index()
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public isSuspended: boolean;
 
-	@Column('varchar', {
-		length: 64, nullable: true,
-		comment: 'The software of the Instance.',
+	@Column("varchar", {
+		length: 64,
+		nullable: true,
+		comment: "The software of the Instance.",
 	})
 	public softwareName: string | null;
 
-	@Column('varchar', {
-		length: 64, nullable: true,
+	@Column("varchar", {
+		length: 64,
+		nullable: true,
 	})
 	public softwareVersion: string | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		nullable: true,
 	})
 	public openRegistrations: boolean | null;
 
-	@Column('varchar', {
-		length: 256, nullable: true,
+	@Column("varchar", {
+		length: 256,
+		nullable: true,
 	})
 	public name: string | null;
 
-	@Column('varchar', {
-		length: 4096, nullable: true,
+	@Column("varchar", {
+		length: 4096,
+		nullable: true,
 	})
 	public description: string | null;
 
-	@Column('varchar', {
-		length: 128, nullable: true,
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
 	})
 	public maintainerName: string | null;
 
-	@Column('varchar', {
-		length: 256, nullable: true,
+	@Column("varchar", {
+		length: 256,
+		nullable: true,
 	})
 	public maintainerEmail: string | null;
 
-	@Column('varchar', {
-		length: 256, nullable: true,
+	@Column("varchar", {
+		length: 256,
+		nullable: true,
 	})
 	public iconUrl: string | null;
 
-	@Column('varchar', {
-		length: 256, nullable: true,
+	@Column("varchar", {
+		length: 256,
+		nullable: true,
 	})
 	public faviconUrl: string | null;
 
-	@Column('varchar', {
-		length: 64, nullable: true,
+	@Column("varchar", {
+		length: 64,
+		nullable: true,
 	})
 	public themeColor: string | null;
 
-	@Column('timestamp with time zone', {
+	@Column("timestamp with time zone", {
 		nullable: true,
 	})
 	public infoUpdatedAt: Date | null;
diff --git a/packages/backend/src/models/entities/messaging-message.ts b/packages/backend/src/models/entities/messaging-message.ts
index 9cf197fa3..d1da00eae 100644
--- a/packages/backend/src/models/entities/messaging-message.ts
+++ b/packages/backend/src/models/entities/messaging-message.ts
@@ -17,68 +17,73 @@ export class MessagingMessage {
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the MessagingMessage.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the MessagingMessage.",
 	})
 	public createdAt: Date;
 
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The sender user ID.',
+		comment: "The sender user ID.",
 	})
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
 
 	@Index()
 	@Column({
-		...id(), nullable: true,
-		comment: 'The recipient user ID.',
+		...id(),
+		nullable: true,
+		comment: "The recipient user ID.",
 	})
 	public recipientId: User["id"] | null;
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public recipient: User | null;
 
 	@Index()
 	@Column({
-		...id(), nullable: true,
-		comment: 'The recipient group ID.',
+		...id(),
+		nullable: true,
+		comment: "The recipient group ID.",
 	})
 	public groupId: UserGroup["id"] | null;
 
-	@ManyToOne(type => UserGroup, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => UserGroup, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public group: UserGroup | null;
 
-	@Column('varchar', {
-		length: 4096, nullable: true,
+	@Column("varchar", {
+		length: 4096,
+		nullable: true,
 	})
 	public text: string | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public isRead: boolean;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
 	})
 	public uri: string | null;
 
 	@Column({
 		...id(),
-		array: true, default: '{}',
+		array: true,
+		default: "{}",
 	})
 	public reads: User["id"][];
 
@@ -88,8 +93,8 @@ export class MessagingMessage {
 	})
 	public fileId: DriveFile["id"] | null;
 
-	@ManyToOne(type => DriveFile, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => DriveFile, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public file: DriveFile | null;
diff --git a/packages/backend/src/models/entities/meta.ts b/packages/backend/src/models/entities/meta.ts
index 3a3c50d4a..433ad0db5 100644
--- a/packages/backend/src/models/entities/meta.ts
+++ b/packages/backend/src/models/entities/meta.ts
@@ -6,119 +6,144 @@ import type { Clip } from "./clip.js";
 @Entity()
 export class Meta {
 	@PrimaryColumn({
-		type: 'varchar',
+		type: "varchar",
 		length: 32,
 	})
 	public id: string;
 
-	@Column('varchar', {
-		length: 128, nullable: true,
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
 	})
 	public name: string | null;
 
-	@Column('varchar', {
-		length: 1024, nullable: true,
+	@Column("varchar", {
+		length: 1024,
+		nullable: true,
 	})
 	public description: string | null;
 
 	/**
 	 * メンテナの名前
 	 */
-	@Column('varchar', {
-		length: 128, nullable: true,
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
 	})
 	public maintainerName: string | null;
 
 	/**
 	 * メンテナの連絡先
 	 */
-	@Column('varchar', {
-		length: 128, nullable: true,
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
 	})
 	public maintainerEmail: string | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public disableRegistration: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public disableLocalTimeline: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: true,
 	})
 	public disableRecommendedTimeline: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public disableGlobalTimeline: boolean;
 
-	@Column('varchar', {
-		length: 256, default: '⭐',
+	@Column("varchar", {
+		length: 256,
+		default: "⭐",
 	})
 	public defaultReaction: string;
 
-	@Column('varchar', {
-		length: 64, array: true, default: '{}',
+	@Column("varchar", {
+		length: 64,
+		array: true,
+		default: "{}",
 	})
 	public langs: string[];
 
-	@Column('varchar', {
-		length: 256, array: true, default: '{}',
+	@Column("varchar", {
+		length: 256,
+		array: true,
+		default: "{}",
 	})
 	public pinnedUsers: string[];
 
-	@Column('varchar', {
-		length: 256, array: true, default: '{}',
+	@Column("varchar", {
+		length: 256,
+		array: true,
+		default: "{}",
 	})
 	public recommendedInstances: string[];
 
-	@Column('varchar', {
-		length: 256, array: true, default: '{}',
+	@Column("varchar", {
+		length: 256,
+		array: true,
+		default: "{}",
 	})
 	public customMOTD: string[];
 
-	@Column('varchar', {
-		length: 256, array: true, default: '{}',
+	@Column("varchar", {
+		length: 256,
+		array: true,
+		default: "{}",
 	})
 	public customSplashIcons: string[];
 
-	@Column('varchar', {
-		length: 256, array: true, default: '{}',
+	@Column("varchar", {
+		length: 256,
+		array: true,
+		default: "{}",
 	})
 	public hiddenTags: string[];
 
-	@Column('varchar', {
-		length: 256, array: true, default: '{}',
+	@Column("varchar", {
+		length: 256,
+		array: true,
+		default: "{}",
 	})
 	public blockedHosts: string[];
 
-	@Column('varchar', {
-		length: 256, array: true, default: '{}',
+	@Column("varchar", {
+		length: 256,
+		array: true,
+		default: "{}",
 	})
 	public silencedHosts: string[];
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public secureMode: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public privateMode: boolean;
 
-	@Column('varchar', {
-		length: 256, array: true, default: '{}',
+	@Column("varchar", {
+		length: 256,
+		array: true,
+		default: "{}",
 	})
 	public allowedHosts: string[];
 
-	@Column('varchar', {
-		length: 512, array: true, default: '{/featured,/channels,/explore,/pages,/about-calckey}',
+	@Column("varchar", {
+		length: 512,
+		array: true,
+		default: "{/featured,/channels,/explore,/pages,/about-calckey}",
 	})
 	public pinnedPages: string[];
 
@@ -128,51 +153,51 @@ export class Meta {
 	})
 	public pinnedClipId: Clip["id"] | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
 	})
 	public themeColor: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
-		default: '/assets/ai.png',
+		default: "/assets/ai.png",
 	})
 	public mascotImageUrl: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
 	})
 	public bannerUrl: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
 	})
 	public backgroundImageUrl: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
 	})
 	public logoImageUrl: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
-		default: 'https://xn--931a.moe/aiart/yubitun.png',
+		default: "https://xn--931a.moe/aiart/yubitun.png",
 	})
 	public errorImageUrl: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
 	})
 	public iconUrl: string | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: true,
 	})
 	public cacheRemoteFiles: boolean;
@@ -183,60 +208,60 @@ export class Meta {
 	})
 	public proxyAccountId: User["id"] | null;
 
-	@ManyToOne(type => User, {
-		onDelete: 'SET NULL',
+	@ManyToOne((type) => User, {
+		onDelete: "SET NULL",
 	})
 	@JoinColumn()
 	public proxyAccount: User | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public emailRequiredForSignup: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public enableHcaptcha: boolean;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 64,
 		nullable: true,
 	})
 	public hcaptchaSiteKey: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 64,
 		nullable: true,
 	})
 	public hcaptchaSecretKey: string | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public enableRecaptcha: boolean;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 64,
 		nullable: true,
 	})
 	public recaptchaSiteKey: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 64,
 		nullable: true,
 	})
 	public recaptchaSecretKey: string | null;
 
-	@Column('enum', {
-		enum: ['none', 'all', 'local', 'remote'],
-		default: 'none',
+	@Column("enum", {
+		enum: ["none", "all", "local", "remote"],
+		default: "none",
 	})
 	public sensitiveMediaDetection: "none" | "all" | "local" | "remote";
 
-	@Column('enum', {
-		enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'],
-		default: 'medium',
+	@Column("enum", {
+		enum: ["medium", "low", "high", "veryLow", "veryHigh"],
+		default: "medium",
 	})
 	public sensitiveMediaDetectionSensitivity:
 		| "medium"
@@ -245,279 +270,279 @@ export class Meta {
 		| "veryLow"
 		| "veryHigh";
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public setSensitiveFlagAutomatically: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public enableSensitiveMediaDetectionForVideos: boolean;
 
-	@Column('integer', {
+	@Column("integer", {
 		default: 1024,
-		comment: 'Drive capacity of a local user (MB)',
+		comment: "Drive capacity of a local user (MB)",
 	})
 	public localDriveCapacityMb: number;
 
-	@Column('integer', {
+	@Column("integer", {
 		default: 32,
-		comment: 'Drive capacity of a remote user (MB)',
+		comment: "Drive capacity of a remote user (MB)",
 	})
 	public remoteDriveCapacityMb: number;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 		nullable: true,
 	})
 	public summalyProxy: string | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public enableEmail: boolean;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 		nullable: true,
 	})
 	public email: string | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public smtpSecure: boolean;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 		nullable: true,
 	})
 	public smtpHost: string | null;
 
-	@Column('integer', {
+	@Column("integer", {
 		nullable: true,
 	})
 	public smtpPort: number | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 		nullable: true,
 	})
 	public smtpUser: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 		nullable: true,
 	})
 	public smtpPass: string | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public enableServiceWorker: boolean;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 		nullable: true,
 	})
 	public swPublicKey: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 		nullable: true,
 	})
 	public swPrivateKey: string | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public enableTwitterIntegration: boolean;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 		nullable: true,
 	})
 	public twitterConsumerKey: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 		nullable: true,
 	})
 	public twitterConsumerSecret: string | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public enableGithubIntegration: boolean;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 		nullable: true,
 	})
 	public githubClientId: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 		nullable: true,
 	})
 	public githubClientSecret: string | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public enableDiscordIntegration: boolean;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 		nullable: true,
 	})
 	public discordClientId: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 		nullable: true,
 	})
 	public discordClientSecret: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 		nullable: true,
 	})
 	public deeplAuthKey: string | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public deeplIsPro: boolean;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
 	})
 	public libreTranslateApiUrl: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 		nullable: true,
 	})
 	public libreTranslateApiKey: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
 	})
 	public ToSUrl: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
-		default: 'https://codeberg.org/calckey/calckey',
+		default: "https://codeberg.org/calckey/calckey",
 		nullable: false,
 	})
 	public repositoryUrl: string;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
-		default: 'https://codeberg.org/calckey/calckey/issues/new',
+		default: "https://codeberg.org/calckey/calckey/issues/new",
 		nullable: true,
 	})
 	public feedbackUrl: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 8192,
 		nullable: true,
 	})
 	public defaultLightTheme: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 8192,
 		nullable: true,
 	})
 	public defaultDarkTheme: string | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public useObjectStorage: boolean;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
 	})
 	public objectStorageBucket: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
 	})
 	public objectStoragePrefix: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
 	})
 	public objectStorageBaseUrl: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
 	})
 	public objectStorageEndpoint: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
 	})
 	public objectStorageRegion: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
 	})
 	public objectStorageAccessKey: string | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
 	})
 	public objectStorageSecretKey: string | null;
 
-	@Column('integer', {
+	@Column("integer", {
 		nullable: true,
 	})
 	public objectStoragePort: number | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: true,
 	})
 	public objectStorageUseSSL: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: true,
 	})
 	public objectStorageUseProxy: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public objectStorageSetPublicRead: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: true,
 	})
 	public objectStorageS3ForcePathStyle: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public enableIpLogging: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: true,
 	})
 	public enableActiveEmailValidation: boolean;
 
-	@Column('jsonb', {
+	@Column("jsonb", {
 		default: {},
 	})
 	public experimentalFeatures: Record<string, unknown>;
diff --git a/packages/backend/src/models/entities/moderation-log.ts b/packages/backend/src/models/entities/moderation-log.ts
index cc745e0d2..26bf1cdfa 100644
--- a/packages/backend/src/models/entities/moderation-log.ts
+++ b/packages/backend/src/models/entities/moderation-log.ts
@@ -14,8 +14,8 @@ export class ModerationLog {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the ModerationLog.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the ModerationLog.",
 	})
 	public createdAt: Date;
 
@@ -23,17 +23,17 @@ export class ModerationLog {
 	@Column(id())
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 	})
 	public type: string;
 
-	@Column('jsonb')
+	@Column("jsonb")
 	public info: Record<string, any>;
 }
diff --git a/packages/backend/src/models/entities/muted-note.ts b/packages/backend/src/models/entities/muted-note.ts
index 11a6ae95d..0ee245aea 100644
--- a/packages/backend/src/models/entities/muted-note.ts
+++ b/packages/backend/src/models/entities/muted-note.ts
@@ -12,7 +12,7 @@ import { id } from "../id.js";
 import { mutedNoteReasons } from "../../types.js";
 
 @Entity()
-@Index(['noteId', 'userId'], { unique: true })
+@Index(["noteId", "userId"], { unique: true })
 export class MutedNote {
 	@PrimaryColumn(id())
 	public id: string;
@@ -20,12 +20,12 @@ export class MutedNote {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The note ID.',
+		comment: "The note ID.",
 	})
 	public noteId: Note["id"];
 
-	@ManyToOne(type => Note, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Note, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public note: Note | null;
@@ -33,12 +33,12 @@ export class MutedNote {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The user ID.',
+		comment: "The user ID.",
 	})
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
@@ -47,9 +47,9 @@ export class MutedNote {
 	 * ミュートされた理由。
 	 */
 	@Index()
-	@Column('enum', {
+	@Column("enum", {
 		enum: mutedNoteReasons,
-		comment: 'The reason of the MutedNote.',
+		comment: "The reason of the MutedNote.",
 	})
 	public reason: typeof mutedNoteReasons[number];
 }
diff --git a/packages/backend/src/models/entities/muting.ts b/packages/backend/src/models/entities/muting.ts
index 561bcfb95..603619b46 100644
--- a/packages/backend/src/models/entities/muting.ts
+++ b/packages/backend/src/models/entities/muting.ts
@@ -10,19 +10,19 @@ import { User } from "./user.js";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['muterId', 'muteeId'], { unique: true })
+@Index(["muterId", "muteeId"], { unique: true })
 export class Muting {
 	@PrimaryColumn(id())
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the Muting.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the Muting.",
 	})
 	public createdAt: Date;
 
 	@Index()
-	@Column('timestamp with time zone', {
+	@Column("timestamp with time zone", {
 		nullable: true,
 	})
 	public expiresAt: Date | null;
@@ -30,12 +30,12 @@ export class Muting {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The mutee user ID.',
+		comment: "The mutee user ID.",
 	})
 	public muteeId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public mutee: User | null;
@@ -43,12 +43,12 @@ export class Muting {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The muter user ID.',
+		comment: "The muter user ID.",
 	})
 	public muterId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public muter: User | null;
diff --git a/packages/backend/src/models/entities/note-edit.ts b/packages/backend/src/models/entities/note-edit.ts
index a65375efb..8761e2b15 100644
--- a/packages/backend/src/models/entities/note-edit.ts
+++ b/packages/backend/src/models/entities/note-edit.ts
@@ -18,34 +18,36 @@ export class NoteEdit {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The ID of note.',
+		comment: "The ID of note.",
 	})
 	public noteId: Note["id"];
 
-	@ManyToOne(type => Note, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Note, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public note: Note | null;
 
-	@Column('text', {
+	@Column("text", {
 		nullable: true,
 	})
 	public text: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
 	})
 	public cw: string | null;
 
 	@Column({
 		...id(),
-		array: true, default: '{}',
+		array: true,
+		default: "{}",
 	})
 	public fileIds: DriveFile["id"][];
 
-	@Column('timestamp with time zone', {
-		comment: 'The updated date of the Note.',
+	@Column("timestamp with time zone", {
+		comment: "The updated date of the Note.",
 	})
 	public updatedAt: Date;
 }
diff --git a/packages/backend/src/models/entities/note-favorite.ts b/packages/backend/src/models/entities/note-favorite.ts
index ab12d8b1b..19641ecf4 100644
--- a/packages/backend/src/models/entities/note-favorite.ts
+++ b/packages/backend/src/models/entities/note-favorite.ts
@@ -11,13 +11,13 @@ import { User } from "./user.js";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['userId', 'noteId'], { unique: true })
+@Index(["userId", "noteId"], { unique: true })
 export class NoteFavorite {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the NoteFavorite.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the NoteFavorite.",
 	})
 	public createdAt: Date;
 
@@ -25,8 +25,8 @@ export class NoteFavorite {
 	@Column(id())
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
@@ -34,8 +34,8 @@ export class NoteFavorite {
 	@Column(id())
 	public noteId: Note["id"];
 
-	@ManyToOne(type => Note, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Note, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public note: Note | null;
diff --git a/packages/backend/src/models/entities/note-reaction.ts b/packages/backend/src/models/entities/note-reaction.ts
index 0e51c33b1..5e2a8d3e8 100644
--- a/packages/backend/src/models/entities/note-reaction.ts
+++ b/packages/backend/src/models/entities/note-reaction.ts
@@ -11,14 +11,14 @@ import { Note } from "./note.js";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['userId', 'noteId'], { unique: true })
+@Index(["userId", "noteId"], { unique: true })
 export class NoteReaction {
 	@PrimaryColumn(id())
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the NoteReaction.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the NoteReaction.",
 	})
 	public createdAt: Date;
 
@@ -26,8 +26,8 @@ export class NoteReaction {
 	@Column(id())
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user?: User | null;
@@ -36,15 +36,15 @@ export class NoteReaction {
 	@Column(id())
 	public noteId: Note["id"];
 
-	@ManyToOne(type => Note, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Note, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public note?: Note | null;
 
 	// TODO: 対象noteのuserIdを非正規化したい(「受け取ったリアクション一覧」のようなものを(JOIN無しで)実装したいため)
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 260,
 	})
 	public reaction: string;
diff --git a/packages/backend/src/models/entities/note-thread-muting.ts b/packages/backend/src/models/entities/note-thread-muting.ts
index 2985b195f..704b32850 100644
--- a/packages/backend/src/models/entities/note-thread-muting.ts
+++ b/packages/backend/src/models/entities/note-thread-muting.ts
@@ -11,13 +11,12 @@ import { Note } from "./note.js";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['userId', 'threadId'], { unique: true })
+@Index(["userId", "threadId"], { unique: true })
 export class NoteThreadMuting {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
-	})
+	@Column("timestamp with time zone", {})
 	public createdAt: Date;
 
 	@Index()
@@ -26,14 +25,14 @@ export class NoteThreadMuting {
 	})
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
 
 	@Index()
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 256,
 	})
 	public threadId: string;
diff --git a/packages/backend/src/models/entities/note-unread.ts b/packages/backend/src/models/entities/note-unread.ts
index d5bba7221..95695cbc8 100644
--- a/packages/backend/src/models/entities/note-unread.ts
+++ b/packages/backend/src/models/entities/note-unread.ts
@@ -12,7 +12,7 @@ import { id } from "../id.js";
 import type { Channel } from "./channel.js";
 
 @Entity()
-@Index(['userId', 'noteId'], { unique: true })
+@Index(["userId", "noteId"], { unique: true })
 export class NoteUnread {
 	@PrimaryColumn(id())
 	public id: string;
@@ -21,8 +21,8 @@ export class NoteUnread {
 	@Column(id())
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
@@ -31,8 +31,8 @@ export class NoteUnread {
 	@Column(id())
 	public noteId: Note["id"];
 
-	@ManyToOne(type => Note, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Note, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public note: Note | null;
@@ -41,21 +41,21 @@ export class NoteUnread {
 	 * メンションか否か
 	 */
 	@Index()
-	@Column('boolean')
+	@Column("boolean")
 	public isMentioned: boolean;
 
 	/**
 	 * ダイレクト投稿か否か
 	 */
 	@Index()
-	@Column('boolean')
+	@Column("boolean")
 	public isSpecified: boolean;
 
 	//#region Denormalized fields
 	@Index()
 	@Column({
 		...id(),
-		comment: '[Denormalized]',
+		comment: "[Denormalized]",
 	})
 	public noteUserId: User["id"];
 
@@ -63,7 +63,7 @@ export class NoteUnread {
 	@Column({
 		...id(),
 		nullable: true,
-		comment: '[Denormalized]',
+		comment: "[Denormalized]",
 	})
 	public noteChannelId: Channel["id"] | null;
 	//#endregion
diff --git a/packages/backend/src/models/entities/note-watching.ts b/packages/backend/src/models/entities/note-watching.ts
index 7ac3e8e29..724b084af 100644
--- a/packages/backend/src/models/entities/note-watching.ts
+++ b/packages/backend/src/models/entities/note-watching.ts
@@ -11,26 +11,26 @@ import { Note } from "./note.js";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['userId', 'noteId'], { unique: true })
+@Index(["userId", "noteId"], { unique: true })
 export class NoteWatching {
 	@PrimaryColumn(id())
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the NoteWatching.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the NoteWatching.",
 	})
 	public createdAt: Date;
 
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The watcher ID.',
+		comment: "The watcher ID.",
 	})
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
@@ -38,12 +38,12 @@ export class NoteWatching {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The target Note ID.',
+		comment: "The target Note ID.",
 	})
 	public noteId: Note["id"];
 
-	@ManyToOne(type => Note, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Note, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public note: Note | null;
@@ -52,7 +52,7 @@ export class NoteWatching {
 	@Index()
 	@Column({
 		...id(),
-		comment: '[Denormalized]',
+		comment: "[Denormalized]",
 	})
 	public noteUserId: Note["userId"];
 	//#endregion
diff --git a/packages/backend/src/models/entities/note.ts b/packages/backend/src/models/entities/note.ts
index f4e76c1db..edcfdb635 100644
--- a/packages/backend/src/models/entities/note.ts
+++ b/packages/backend/src/models/entities/note.ts
@@ -13,16 +13,16 @@ import { noteVisibilities } from "../../types.js";
 import { Channel } from "./channel.js";
 
 @Entity()
-@Index('IDX_NOTE_TAGS', { synchronize: false })
-@Index('IDX_NOTE_MENTIONS', { synchronize: false })
-@Index('IDX_NOTE_VISIBLE_USER_IDS', { synchronize: false })
+@Index("IDX_NOTE_TAGS", { synchronize: false })
+@Index("IDX_NOTE_MENTIONS", { synchronize: false })
+@Index("IDX_NOTE_VISIBLE_USER_IDS", { synchronize: false })
 export class Note {
 	@PrimaryColumn(id())
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the Note.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the Note.",
 	})
 	public createdAt: Date;
 
@@ -30,12 +30,12 @@ export class Note {
 	@Column({
 		...id(),
 		nullable: true,
-		comment: 'The ID of reply target.',
+		comment: "The ID of reply target.",
 	})
 	public replyId: Note["id"] | null;
 
-	@ManyToOne(type => Note, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Note, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public reply: Note | null;
@@ -44,66 +44,69 @@ export class Note {
 	@Column({
 		...id(),
 		nullable: true,
-		comment: 'The ID of renote target.',
+		comment: "The ID of renote target.",
 	})
 	public renoteId: Note["id"] | null;
 
-	@ManyToOne(type => Note, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Note, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public renote: Note | null;
 
 	@Index()
-	@Column('varchar', {
-		length: 256, nullable: true,
+	@Column("varchar", {
+		length: 256,
+		nullable: true,
 	})
 	public threadId: string | null;
 
-	@Column('text', {
+	@Column("text", {
 		nullable: true,
 	})
 	public text: string | null;
 
-	@Column('varchar', {
-		length: 256, nullable: true,
+	@Column("varchar", {
+		length: 256,
+		nullable: true,
 	})
 	public name: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
 	})
 	public cw: string | null;
 
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The ID of author.',
+		comment: "The ID of author.",
 	})
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public localOnly: boolean;
 
-	@Column('smallint', {
+	@Column("smallint", {
 		default: 0,
 	})
 	public renoteCount: number;
 
-	@Column('smallint', {
+	@Column("smallint", {
 		default: 0,
 	})
 	public repliesCount: number;
 
-	@Column('jsonb', {
+	@Column("jsonb", {
 		default: {},
 	})
 	public reactions: Record<string, number>;
@@ -115,71 +118,84 @@ export class Note {
 	 * followers ... フォロワーのみ
 	 * specified ... visibleUserIds で指定したユーザーのみ
 	 */
-	@Column('enum', { enum: noteVisibilities })
+	@Column("enum", { enum: noteVisibilities })
 	public visibility: typeof noteVisibilities[number];
 
 	@Index({ unique: true })
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: 'The URI of a note. it will be null when the note is local.',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment: "The URI of a note. it will be null when the note is local.",
 	})
 	public uri: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: 'The human readable url of a note. it will be null when the note is local.',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment:
+			"The human readable url of a note. it will be null when the note is local.",
 	})
 	public url: string | null;
 
-	@Column('integer', {
-		default: 0, select: false,
+	@Column("integer", {
+		default: 0,
+		select: false,
 	})
 	public score: number;
 
 	@Index()
 	@Column({
 		...id(),
-		array: true, default: '{}',
+		array: true,
+		default: "{}",
 	})
 	public fileIds: DriveFile["id"][];
 
 	@Index()
-	@Column('varchar', {
-		length: 256, array: true, default: '{}',
+	@Column("varchar", {
+		length: 256,
+		array: true,
+		default: "{}",
 	})
 	public attachedFileTypes: string[];
 
 	@Index()
 	@Column({
 		...id(),
-		array: true, default: '{}',
+		array: true,
+		default: "{}",
 	})
 	public visibleUserIds: User["id"][];
 
 	@Index()
 	@Column({
 		...id(),
-		array: true, default: '{}',
+		array: true,
+		default: "{}",
 	})
 	public mentions: User["id"][];
 
-	@Column('text', {
-		default: '[]',
+	@Column("text", {
+		default: "[]",
 	})
 	public mentionedRemoteUsers: string;
 
-	@Column('varchar', {
-		length: 128, array: true, default: '{}',
+	@Column("varchar", {
+		length: 128,
+		array: true,
+		default: "{}",
 	})
 	public emojis: string[];
 
 	@Index()
-	@Column('varchar', {
-		length: 128, array: true, default: '{}',
+	@Column("varchar", {
+		length: 128,
+		array: true,
+		default: "{}",
 	})
 	public tags: string[];
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public hasPoll: boolean;
@@ -188,53 +204,56 @@ export class Note {
 	@Column({
 		...id(),
 		nullable: true,
-		comment: 'The ID of source channel.',
+		comment: "The ID of source channel.",
 	})
 	public channelId: Channel["id"] | null;
 
-	@ManyToOne(type => Channel, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Channel, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public channel: Channel | null;
 
 	//#region Denormalized fields
 	@Index()
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public userHost: string | null;
 
 	@Column({
 		...id(),
 		nullable: true,
-		comment: '[Denormalized]',
+		comment: "[Denormalized]",
 	})
 	public replyUserId: User["id"] | null;
 
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public replyUserHost: string | null;
 
 	@Column({
 		...id(),
 		nullable: true,
-		comment: '[Denormalized]',
+		comment: "[Denormalized]",
 	})
 	public renoteUserId: User["id"] | null;
 
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public renoteUserHost: string | null;
 
-	@Column('timestamp with time zone', {
+	@Column("timestamp with time zone", {
 		nullable: true,
-		comment: 'The updated date of the Note.',
+		comment: "The updated date of the Note.",
 	})
 	public updatedAt: Date;
 	//#endregion
diff --git a/packages/backend/src/models/entities/notification.ts b/packages/backend/src/models/entities/notification.ts
index 2c55e988f..da23f7d3e 100644
--- a/packages/backend/src/models/entities/notification.ts
+++ b/packages/backend/src/models/entities/notification.ts
@@ -20,8 +20,8 @@ export class Notification {
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the Notification.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the Notification.",
 	})
 	public createdAt: Date;
 
@@ -31,12 +31,12 @@ export class Notification {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The ID of recipient user of the Notification.',
+		comment: "The ID of recipient user of the Notification.",
 	})
 	public notifieeId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public notifiee: User | null;
@@ -48,12 +48,12 @@ export class Notification {
 	@Column({
 		...id(),
 		nullable: true,
-		comment: 'The ID of sender user of the Notification.',
+		comment: "The ID of sender user of the Notification.",
 	})
 	public notifierId: User["id"] | null;
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public notifier: User | null;
@@ -74,9 +74,9 @@ export class Notification {
 	 * app - App notifications.
 	 */
 	@Index()
-	@Column('enum', {
+	@Column("enum", {
 		enum: notificationTypes,
-		comment: 'The type of the Notification.',
+		comment: "The type of the Notification.",
 	})
 	public type: typeof notificationTypes[number];
 
@@ -84,9 +84,9 @@ export class Notification {
 	 * Whether the notification was read.
 	 */
 	@Index()
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
-		comment: 'Whether the notification was read.',
+		comment: "Whether the notification was read.",
 	})
 	public isRead: boolean;
 
@@ -96,8 +96,8 @@ export class Notification {
 	})
 	public noteId: Note["id"] | null;
 
-	@ManyToOne(type => Note, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Note, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public note: Note | null;
@@ -108,8 +108,8 @@ export class Notification {
 	})
 	public followRequestId: FollowRequest["id"] | null;
 
-	@ManyToOne(type => FollowRequest, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => FollowRequest, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public followRequest: FollowRequest | null;
@@ -120,18 +120,19 @@ export class Notification {
 	})
 	public userGroupInvitationId: UserGroupInvitation["id"] | null;
 
-	@ManyToOne(type => UserGroupInvitation, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => UserGroupInvitation, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public userGroupInvitation: UserGroupInvitation | null;
 
-	@Column('varchar', {
-		length: 128, nullable: true,
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
 	})
 	public reaction: string | null;
 
-	@Column('integer', {
+	@Column("integer", {
 		nullable: true,
 	})
 	public choice: number | null;
@@ -139,8 +140,9 @@ export class Notification {
 	/**
 	 * App notification body
 	 */
-	@Column('varchar', {
-		length: 2048, nullable: true,
+	@Column("varchar", {
+		length: 2048,
+		nullable: true,
 	})
 	public customBody: string | null;
 
@@ -148,8 +150,9 @@ export class Notification {
 	 * App notification header
 	 * (If omitted, it is expected to be displayed with the app name)
 	 */
-	@Column('varchar', {
-		length: 256, nullable: true,
+	@Column("varchar", {
+		length: 256,
+		nullable: true,
 	})
 	public customHeader: string | null;
 
@@ -157,8 +160,9 @@ export class Notification {
 	 * App notification icon (URL)
 	 * (If omitted, it is expected to be displayed as an app icon)
 	 */
-	@Column('varchar', {
-		length: 1024, nullable: true,
+	@Column("varchar", {
+		length: 1024,
+		nullable: true,
 	})
 	public customIcon: string | null;
 
@@ -172,8 +176,8 @@ export class Notification {
 	})
 	public appAccessTokenId: AccessToken["id"] | null;
 
-	@ManyToOne(type => AccessToken, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => AccessToken, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public appAccessToken: AccessToken | null;
diff --git a/packages/backend/src/models/entities/page-like.ts b/packages/backend/src/models/entities/page-like.ts
index 75f4dc49b..6304e0b24 100644
--- a/packages/backend/src/models/entities/page-like.ts
+++ b/packages/backend/src/models/entities/page-like.ts
@@ -11,20 +11,20 @@ import { id } from "../id.js";
 import { Page } from "./page.js";
 
 @Entity()
-@Index(['userId', 'pageId'], { unique: true })
+@Index(["userId", "pageId"], { unique: true })
 export class PageLike {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone')
+	@Column("timestamp with time zone")
 	public createdAt: Date;
 
 	@Index()
 	@Column(id())
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
@@ -32,8 +32,8 @@ export class PageLike {
 	@Column(id())
 	public pageId: Page["id"];
 
-	@ManyToOne(type => Page, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Page, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public page: Page | null;
diff --git a/packages/backend/src/models/entities/page.ts b/packages/backend/src/models/entities/page.ts
index 5fe9f5208..d0733c8ce 100644
--- a/packages/backend/src/models/entities/page.ts
+++ b/packages/backend/src/models/entities/page.ts
@@ -11,51 +11,52 @@ import { id } from "../id.js";
 import { DriveFile } from "./drive-file.js";
 
 @Entity()
-@Index(['userId', 'name'], { unique: true })
+@Index(["userId", "name"], { unique: true })
 export class Page {
 	@PrimaryColumn(id())
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the Page.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the Page.",
 	})
 	public createdAt: Date;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The updated date of the Page.',
+	@Column("timestamp with time zone", {
+		comment: "The updated date of the Page.",
 	})
 	public updatedAt: Date;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 256,
 	})
 	public title: string;
 
 	@Index()
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 256,
 	})
 	public name: string;
 
-	@Column('varchar', {
-		length: 256, nullable: true,
+	@Column("varchar", {
+		length: 256,
+		nullable: true,
 	})
 	public summary: string | null;
 
-	@Column('boolean')
+	@Column("boolean")
 	public alignCenter: boolean;
 
-	@Column('boolean')
+	@Column("boolean")
 	public isPublic: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public hideTitleWhenPinned: boolean;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 32,
 	})
 	public font: string;
@@ -63,12 +64,12 @@ export class Page {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The ID of author.',
+		comment: "The ID of author.",
 	})
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
@@ -79,25 +80,25 @@ export class Page {
 	})
 	public eyeCatchingImageId: DriveFile["id"] | null;
 
-	@ManyToOne(type => DriveFile, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => DriveFile, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public eyeCatchingImage: DriveFile | null;
 
-	@Column('jsonb', {
+	@Column("jsonb", {
 		default: [],
 	})
 	public content: Record<string, any>[];
 
-	@Column('jsonb', {
+	@Column("jsonb", {
 		default: [],
 	})
 	public variables: Record<string, any>[];
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 16384,
-		default: '',
+		default: "",
 	})
 	public script: string;
 
@@ -106,17 +107,18 @@ export class Page {
 	 * followers ... フォロワーのみ
 	 * specified ... visibleUserIds で指定したユーザーのみ
 	 */
-	@Column('enum', { enum: ['public', 'followers', 'specified'] })
+	@Column("enum", { enum: ["public", "followers", "specified"] })
 	public visibility: "public" | "followers" | "specified";
 
 	@Index()
 	@Column({
 		...id(),
-		array: true, default: '{}',
+		array: true,
+		default: "{}",
 	})
 	public visibleUserIds: User["id"][];
 
-	@Column('integer', {
+	@Column("integer", {
 		default: 0,
 	})
 	public likedCount: number;
diff --git a/packages/backend/src/models/entities/password-reset-request.ts b/packages/backend/src/models/entities/password-reset-request.ts
index 4c681d4f7..ab0bccbbe 100644
--- a/packages/backend/src/models/entities/password-reset-request.ts
+++ b/packages/backend/src/models/entities/password-reset-request.ts
@@ -14,11 +14,11 @@ export class PasswordResetRequest {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone')
+	@Column("timestamp with time zone")
 	public createdAt: Date;
 
 	@Index({ unique: true })
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 256,
 	})
 	public token: string;
@@ -29,8 +29,8 @@ export class PasswordResetRequest {
 	})
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
diff --git a/packages/backend/src/models/entities/poll-vote.ts b/packages/backend/src/models/entities/poll-vote.ts
index 0649951cf..d59a720c3 100644
--- a/packages/backend/src/models/entities/poll-vote.ts
+++ b/packages/backend/src/models/entities/poll-vote.ts
@@ -11,14 +11,14 @@ import { Note } from "./note.js";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['userId', 'noteId', 'choice'], { unique: true })
+@Index(["userId", "noteId", "choice"], { unique: true })
 export class PollVote {
 	@PrimaryColumn(id())
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the PollVote.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the PollVote.",
 	})
 	public createdAt: Date;
 
@@ -26,8 +26,8 @@ export class PollVote {
 	@Column(id())
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
@@ -36,12 +36,12 @@ export class PollVote {
 	@Column(id())
 	public noteId: Note["id"];
 
-	@ManyToOne(type => Note, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Note, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public note: Note | null;
 
-	@Column('integer')
+	@Column("integer")
 	public choice: number;
 }
diff --git a/packages/backend/src/models/entities/poll.ts b/packages/backend/src/models/entities/poll.ts
index 28a70b3c7..405cca222 100644
--- a/packages/backend/src/models/entities/poll.ts
+++ b/packages/backend/src/models/entities/poll.ts
@@ -16,48 +16,51 @@ export class Poll {
 	@PrimaryColumn(id())
 	public noteId: Note["id"];
 
-	@OneToOne(type => Note, {
-		onDelete: 'CASCADE',
+	@OneToOne((type) => Note, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public note: Note | null;
 
-	@Column('timestamp with time zone', {
+	@Column("timestamp with time zone", {
 		nullable: true,
 	})
 	public expiresAt: Date | null;
 
-	@Column('boolean')
+	@Column("boolean")
 	public multiple: boolean;
 
-	@Column('varchar', {
-		length: 256, array: true, default: '{}',
+	@Column("varchar", {
+		length: 256,
+		array: true,
+		default: "{}",
 	})
 	public choices: string[];
 
-	@Column('integer', {
+	@Column("integer", {
 		array: true,
 	})
 	public votes: number[];
 
 	//#region Denormalized fields
-	@Column('enum', {
+	@Column("enum", {
 		enum: noteVisibilities,
-		comment: '[Denormalized]',
+		comment: "[Denormalized]",
 	})
 	public noteVisibility: typeof noteVisibilities[number];
 
 	@Index()
 	@Column({
 		...id(),
-		comment: '[Denormalized]',
+		comment: "[Denormalized]",
 	})
 	public userId: User["id"];
 
 	@Index()
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public userHost: string | null;
 	//#endregion
diff --git a/packages/backend/src/models/entities/promo-note.ts b/packages/backend/src/models/entities/promo-note.ts
index 4daacd246..caa64927e 100644
--- a/packages/backend/src/models/entities/promo-note.ts
+++ b/packages/backend/src/models/entities/promo-note.ts
@@ -15,20 +15,20 @@ export class PromoNote {
 	@PrimaryColumn(id())
 	public noteId: Note["id"];
 
-	@OneToOne(type => Note, {
-		onDelete: 'CASCADE',
+	@OneToOne((type) => Note, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public note: Note | null;
 
-	@Column('timestamp with time zone')
+	@Column("timestamp with time zone")
 	public expiresAt: Date;
 
 	//#region Denormalized fields
 	@Index()
 	@Column({
 		...id(),
-		comment: '[Denormalized]',
+		comment: "[Denormalized]",
 	})
 	public userId: User["id"];
 	//#endregion
diff --git a/packages/backend/src/models/entities/promo-read.ts b/packages/backend/src/models/entities/promo-read.ts
index 5938bfde9..b31877dc3 100644
--- a/packages/backend/src/models/entities/promo-read.ts
+++ b/packages/backend/src/models/entities/promo-read.ts
@@ -11,13 +11,13 @@ import { User } from "./user.js";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['userId', 'noteId'], { unique: true })
+@Index(["userId", "noteId"], { unique: true })
 export class PromoRead {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the PromoRead.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the PromoRead.",
 	})
 	public createdAt: Date;
 
@@ -25,8 +25,8 @@ export class PromoRead {
 	@Column(id())
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
@@ -34,8 +34,8 @@ export class PromoRead {
 	@Column(id())
 	public noteId: Note["id"];
 
-	@ManyToOne(type => Note, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Note, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public note: Note | null;
diff --git a/packages/backend/src/models/entities/registration-tickets.ts b/packages/backend/src/models/entities/registration-tickets.ts
index af785fbc0..549f05d07 100644
--- a/packages/backend/src/models/entities/registration-tickets.ts
+++ b/packages/backend/src/models/entities/registration-tickets.ts
@@ -6,11 +6,11 @@ export class RegistrationTicket {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone')
+	@Column("timestamp with time zone")
 	public createdAt: Date;
 
 	@Index({ unique: true })
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 64,
 	})
 	public code: string;
diff --git a/packages/backend/src/models/entities/registry-item.ts b/packages/backend/src/models/entities/registry-item.ts
index 655573883..d044222e6 100644
--- a/packages/backend/src/models/entities/registry-item.ts
+++ b/packages/backend/src/models/entities/registry-item.ts
@@ -15,51 +15,55 @@ export class RegistryItem {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the RegistryItem.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the RegistryItem.",
 	})
 	public createdAt: Date;
 
-	@Column('timestamp with time zone', {
-		comment: 'The updated date of the RegistryItem.',
+	@Column("timestamp with time zone", {
+		comment: "The updated date of the RegistryItem.",
 	})
 	public updatedAt: Date;
 
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The owner ID.',
+		comment: "The owner ID.",
 	})
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 1024,
-		comment: 'The key of the RegistryItem.',
+		comment: "The key of the RegistryItem.",
 	})
 	public key: string;
 
-	@Column('jsonb', {
-		default: {}, nullable: true,
-		comment: 'The value of the RegistryItem.',
+	@Column("jsonb", {
+		default: {},
+		nullable: true,
+		comment: "The value of the RegistryItem.",
 	})
 	public value: any | null;
 
 	@Index()
-	@Column('varchar', {
-		length: 1024, array: true, default: '{}',
+	@Column("varchar", {
+		length: 1024,
+		array: true,
+		default: "{}",
 	})
 	public scope: string[];
 
 	// サードパーティアプリに開放するときのためのカラム
 	@Index()
-	@Column('varchar', {
-		length: 512, nullable: true,
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
 	})
 	public domain: string | null;
 }
diff --git a/packages/backend/src/models/entities/relay.ts b/packages/backend/src/models/entities/relay.ts
index 82c0779ff..c7509dcf4 100644
--- a/packages/backend/src/models/entities/relay.ts
+++ b/packages/backend/src/models/entities/relay.ts
@@ -7,13 +7,14 @@ export class Relay {
 	public id: string;
 
 	@Index({ unique: true })
-	@Column('varchar', {
-		length: 512, nullable: false,
+	@Column("varchar", {
+		length: 512,
+		nullable: false,
 	})
 	public inbox: string;
 
-	@Column('enum', {
-		enum: ['requesting', 'accepted', 'rejected'],
+	@Column("enum", {
+		enum: ["requesting", "accepted", "rejected"],
 	})
 	public status: "requesting" | "accepted" | "rejected";
 }
diff --git a/packages/backend/src/models/entities/renote-muting.ts b/packages/backend/src/models/entities/renote-muting.ts
index 64ec7f583..e8856492f 100644
--- a/packages/backend/src/models/entities/renote-muting.ts
+++ b/packages/backend/src/models/entities/renote-muting.ts
@@ -28,7 +28,7 @@ export class RenoteMuting {
 	})
 	public muteeId: User["id"];
 
-	@ManyToOne(type => User, {
+	@ManyToOne((type) => User, {
 		onDelete: "CASCADE",
 	})
 	@JoinColumn()
@@ -41,7 +41,7 @@ export class RenoteMuting {
 	})
 	public muterId: User["id"];
 
-	@ManyToOne(type => User, {
+	@ManyToOne((type) => User, {
 		onDelete: "CASCADE",
 	})
 	@JoinColumn()
diff --git a/packages/backend/src/models/entities/signin.ts b/packages/backend/src/models/entities/signin.ts
index 785991823..517e71c8f 100644
--- a/packages/backend/src/models/entities/signin.ts
+++ b/packages/backend/src/models/entities/signin.ts
@@ -14,8 +14,8 @@ export class Signin {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the Signin.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the Signin.",
 	})
 	public createdAt: Date;
 
@@ -23,20 +23,20 @@ export class Signin {
 	@Column(id())
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 	})
 	public ip: string;
 
-	@Column('jsonb')
+	@Column("jsonb")
 	public headers: Record<string, any>;
 
-	@Column('boolean')
+	@Column("boolean")
 	public success: boolean;
 }
diff --git a/packages/backend/src/models/entities/sw-subscription.ts b/packages/backend/src/models/entities/sw-subscription.ts
index 8f18688ea..f7823fbaa 100644
--- a/packages/backend/src/models/entities/sw-subscription.ts
+++ b/packages/backend/src/models/entities/sw-subscription.ts
@@ -14,35 +14,35 @@ export class SwSubscription {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone')
+	@Column("timestamp with time zone")
 	public createdAt: Date;
 
 	@Index()
 	@Column(id())
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 	})
 	public endpoint: string;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 256,
 	})
 	public auth: string;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 	})
 	public publickey: string;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public sendReadMessage: boolean;
diff --git a/packages/backend/src/models/entities/used-username.ts b/packages/backend/src/models/entities/used-username.ts
index a069205a5..d00a25991 100644
--- a/packages/backend/src/models/entities/used-username.ts
+++ b/packages/backend/src/models/entities/used-username.ts
@@ -2,12 +2,12 @@ import { PrimaryColumn, Entity, Column } from "typeorm";
 
 @Entity()
 export class UsedUsername {
-	@PrimaryColumn('varchar', {
+	@PrimaryColumn("varchar", {
 		length: 128,
 	})
 	public username: string;
 
-	@Column('timestamp with time zone')
+	@Column("timestamp with time zone")
 	public createdAt: Date;
 
 	constructor(data: Partial<UsedUsername>) {
diff --git a/packages/backend/src/models/entities/user-group-invitation.ts b/packages/backend/src/models/entities/user-group-invitation.ts
index 8037b30e1..fa2655ab6 100644
--- a/packages/backend/src/models/entities/user-group-invitation.ts
+++ b/packages/backend/src/models/entities/user-group-invitation.ts
@@ -11,25 +11,25 @@ import { UserGroup } from "./user-group.js";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['userId', 'userGroupId'], { unique: true })
+@Index(["userId", "userGroupId"], { unique: true })
 export class UserGroupInvitation {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the UserGroupInvitation.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the UserGroupInvitation.",
 	})
 	public createdAt: Date;
 
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The user ID.',
+		comment: "The user ID.",
 	})
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
@@ -37,12 +37,12 @@ export class UserGroupInvitation {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The group ID.',
+		comment: "The group ID.",
 	})
 	public userGroupId: UserGroup["id"];
 
-	@ManyToOne(type => UserGroup, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => UserGroup, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public userGroup: UserGroup | null;
diff --git a/packages/backend/src/models/entities/user-group-joining.ts b/packages/backend/src/models/entities/user-group-joining.ts
index 6d503b274..78f820d0e 100644
--- a/packages/backend/src/models/entities/user-group-joining.ts
+++ b/packages/backend/src/models/entities/user-group-joining.ts
@@ -11,25 +11,25 @@ import { UserGroup } from "./user-group.js";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['userId', 'userGroupId'], { unique: true })
+@Index(["userId", "userGroupId"], { unique: true })
 export class UserGroupJoining {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the UserGroupJoining.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the UserGroupJoining.",
 	})
 	public createdAt: Date;
 
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The user ID.',
+		comment: "The user ID.",
 	})
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
@@ -37,12 +37,12 @@ export class UserGroupJoining {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The group ID.',
+		comment: "The group ID.",
 	})
 	public userGroupId: UserGroup["id"];
 
-	@ManyToOne(type => UserGroup, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => UserGroup, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public userGroup: UserGroup | null;
diff --git a/packages/backend/src/models/entities/user-group.ts b/packages/backend/src/models/entities/user-group.ts
index 38e5af334..23876ec8b 100644
--- a/packages/backend/src/models/entities/user-group.ts
+++ b/packages/backend/src/models/entities/user-group.ts
@@ -15,12 +15,12 @@ export class UserGroup {
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the UserGroup.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the UserGroup.",
 	})
 	public createdAt: Date;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 256,
 	})
 	public name: string;
@@ -28,17 +28,17 @@ export class UserGroup {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The ID of owner.',
+		comment: "The ID of owner.",
 	})
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public isPrivate: boolean;
diff --git a/packages/backend/src/models/entities/user-ip.ts b/packages/backend/src/models/entities/user-ip.ts
index 6b88d5221..c30e56b66 100644
--- a/packages/backend/src/models/entities/user-ip.ts
+++ b/packages/backend/src/models/entities/user-ip.ts
@@ -12,20 +12,19 @@ import { Note } from "./note.js";
 import type { User } from "./user.js";
 
 @Entity()
-@Index(['userId', 'ip'], { unique: true })
+@Index(["userId", "ip"], { unique: true })
 export class UserIp {
 	@PrimaryGeneratedColumn()
 	public id: string;
 
-	@Column('timestamp with time zone', {
-	})
+	@Column("timestamp with time zone", {})
 	public createdAt: Date;
 
 	@Index()
 	@Column(id())
 	public userId: User["id"];
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 	})
 	public ip: string;
diff --git a/packages/backend/src/models/entities/user-keypair.ts b/packages/backend/src/models/entities/user-keypair.ts
index 212e742b9..f98384f53 100644
--- a/packages/backend/src/models/entities/user-keypair.ts
+++ b/packages/backend/src/models/entities/user-keypair.ts
@@ -7,18 +7,18 @@ export class UserKeypair {
 	@PrimaryColumn(id())
 	public userId: User["id"];
 
-	@OneToOne(type => User, {
-		onDelete: 'CASCADE',
+	@OneToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 4096,
 	})
 	public publicKey: string;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 4096,
 	})
 	public privateKey: string;
diff --git a/packages/backend/src/models/entities/user-list-joining.ts b/packages/backend/src/models/entities/user-list-joining.ts
index e52fa7b39..4caa71ad3 100644
--- a/packages/backend/src/models/entities/user-list-joining.ts
+++ b/packages/backend/src/models/entities/user-list-joining.ts
@@ -11,25 +11,25 @@ import { UserList } from "./user-list.js";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['userId', 'userListId'], { unique: true })
+@Index(["userId", "userListId"], { unique: true })
 export class UserListJoining {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the UserListJoining.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the UserListJoining.",
 	})
 	public createdAt: Date;
 
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The user ID.',
+		comment: "The user ID.",
 	})
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
@@ -37,12 +37,12 @@ export class UserListJoining {
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The list ID.',
+		comment: "The list ID.",
 	})
 	public userListId: UserList["id"];
 
-	@ManyToOne(type => UserList, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => UserList, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public userList: UserList | null;
diff --git a/packages/backend/src/models/entities/user-list.ts b/packages/backend/src/models/entities/user-list.ts
index 7c4345230..3c95d44d6 100644
--- a/packages/backend/src/models/entities/user-list.ts
+++ b/packages/backend/src/models/entities/user-list.ts
@@ -14,27 +14,27 @@ export class UserList {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the UserList.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the UserList.",
 	})
 	public createdAt: Date;
 
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The owner ID.',
+		comment: "The owner ID.",
 	})
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
-		comment: 'The name of the UserList.',
+		comment: "The name of the UserList.",
 	})
 	public name: string;
 }
diff --git a/packages/backend/src/models/entities/user-note-pining.ts b/packages/backend/src/models/entities/user-note-pining.ts
index dc6d61f7e..c30fe1e02 100644
--- a/packages/backend/src/models/entities/user-note-pining.ts
+++ b/packages/backend/src/models/entities/user-note-pining.ts
@@ -11,13 +11,13 @@ import { User } from "./user.js";
 import { id } from "../id.js";
 
 @Entity()
-@Index(['userId', 'noteId'], { unique: true })
+@Index(["userId", "noteId"], { unique: true })
 export class UserNotePining {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the UserNotePinings.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the UserNotePinings.",
 	})
 	public createdAt: Date;
 
@@ -25,8 +25,8 @@ export class UserNotePining {
 	@Column(id())
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
@@ -34,8 +34,8 @@ export class UserNotePining {
 	@Column(id())
 	public noteId: Note["id"];
 
-	@ManyToOne(type => Note, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => Note, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public note: Note | null;
diff --git a/packages/backend/src/models/entities/user-pending.ts b/packages/backend/src/models/entities/user-pending.ts
index cac85d1c0..18ae5ad99 100644
--- a/packages/backend/src/models/entities/user-pending.ts
+++ b/packages/backend/src/models/entities/user-pending.ts
@@ -6,26 +6,26 @@ export class UserPending {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone')
+	@Column("timestamp with time zone")
 	public createdAt: Date;
 
 	@Index({ unique: true })
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 	})
 	public code: string;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 	})
 	public username: string;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 	})
 	public email: string;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
 	})
 	public password: string;
diff --git a/packages/backend/src/models/entities/user-profile.ts b/packages/backend/src/models/entities/user-profile.ts
index a5eca6f48..119eecdc7 100644
--- a/packages/backend/src/models/entities/user-profile.ts
+++ b/packages/backend/src/models/entities/user-profile.ts
@@ -18,31 +18,34 @@ export class UserProfile {
 	@PrimaryColumn(id())
 	public userId: User["id"];
 
-	@OneToOne(type => User, {
-		onDelete: 'CASCADE',
+	@OneToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
 
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: 'The location of the User.',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment: "The location of the User.",
 	})
 	public location: string | null;
 
-	@Column('char', {
-		length: 10, nullable: true,
-		comment: 'The birthday (YYYY-MM-DD) of the User.',
+	@Column("char", {
+		length: 10,
+		nullable: true,
+		comment: "The birthday (YYYY-MM-DD) of the User.",
 	})
 	public birthday: string | null;
 
-	@Column('varchar', {
-		length: 2048, nullable: true,
-		comment: 'The description (bio) of the User.',
+	@Column("varchar", {
+		length: 2048,
+		nullable: true,
+		comment: "The description (bio) of the User.",
 	})
 	public description: string | null;
 
-	@Column('jsonb', {
+	@Column("jsonb", {
 		default: [],
 	})
 	public fields: {
@@ -50,136 +53,145 @@ export class UserProfile {
 		value: string;
 	}[];
 
-	@Column('varchar', {
-		length: 32, nullable: true,
+	@Column("varchar", {
+		length: 32,
+		nullable: true,
 	})
 	public lang: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: 'Remote URL of the user.',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment: "Remote URL of the user.",
 	})
 	public url: string | null;
 
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: 'The email address of the User.',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment: "The email address of the User.",
 	})
 	public email: string | null;
 
-	@Column('varchar', {
-		length: 128, nullable: true,
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
 	})
 	public emailVerifyCode: string | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public emailVerified: boolean;
 
-	@Column('jsonb', {
-		default: ['follow', 'receiveFollowRequest', 'groupInvited'],
+	@Column("jsonb", {
+		default: ["follow", "receiveFollowRequest", "groupInvited"],
 	})
 	public emailNotificationTypes: string[];
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public publicReactions: boolean;
 
-	@Column('enum', {
+	@Column("enum", {
 		enum: ffVisibility,
-		default: 'public',
+		default: "public",
 	})
 	public ffVisibility: typeof ffVisibility[number];
 
-	@Column('varchar', {
-		length: 128, nullable: true,
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
 	})
 	public twoFactorTempSecret: string | null;
 
-	@Column('varchar', {
-		length: 128, nullable: true,
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
 	})
 	public twoFactorSecret: string | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public twoFactorEnabled: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public securityKeysAvailable: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public usePasswordLessLogin: boolean;
 
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: 'The password hash of the User. It will be null if the origin of the user is local.',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment:
+			"The password hash of the User. It will be null if the origin of the user is local.",
 	})
 	public password: string | null;
 
-	@Column('varchar', {
-		length: 8192, default: '',
+	@Column("varchar", {
+		length: 8192,
+		default: "",
 	})
 	public moderationNote: string | null;
 
 	// TODO: そのうち消す
-	@Column('jsonb', {
+	@Column("jsonb", {
 		default: {},
-		comment: 'The client-specific data of the User.',
+		comment: "The client-specific data of the User.",
 	})
 	public clientData: Record<string, any>;
 
 	// TODO: そのうち消す
-	@Column('jsonb', {
+	@Column("jsonb", {
 		default: {},
-		comment: 'The room data of the User.',
+		comment: "The room data of the User.",
 	})
 	public room: Record<string, any>;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public autoAcceptFollowed: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
-		comment: 'Whether reject index by crawler.',
+		comment: "Whether reject index by crawler.",
 	})
 	public noCrawle: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: true,
 	})
 	public preventAiLearning: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public alwaysMarkNsfw: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public autoSensitive: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public carefulBot: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: true,
 	})
 	public injectFeaturedNote: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: true,
 	})
 	public receiveAnnouncementEmail: boolean;
@@ -190,35 +202,36 @@ export class UserProfile {
 	})
 	public pinnedPageId: Page["id"] | null;
 
-	@OneToOne(type => Page, {
-		onDelete: 'SET NULL',
+	@OneToOne((type) => Page, {
+		onDelete: "SET NULL",
 	})
 	@JoinColumn()
 	public pinnedPage: Page | null;
 
-	@Column('jsonb', {
+	@Column("jsonb", {
 		default: {},
 	})
 	public integrations: Record<string, any>;
 
 	@Index()
-	@Column('boolean', {
-		default: false, select: false,
+	@Column("boolean", {
+		default: false,
+		select: false,
 	})
 	public enableWordMute: boolean;
 
-	@Column('jsonb', {
+	@Column("jsonb", {
 		default: [],
 	})
 	public mutedWords: string[][];
 
-	@Column('jsonb', {
+	@Column("jsonb", {
 		default: [],
-		comment: 'List of instances muted by the user.',
+		comment: "List of instances muted by the user.",
 	})
 	public mutedInstances: string[];
 
-	@Column('enum', {
+	@Column("enum", {
 		enum: notificationTypes,
 		array: true,
 		default: [],
@@ -227,9 +240,10 @@ export class UserProfile {
 
 	//#region Denormalized fields
 	@Index()
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: '[Denormalized]',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment: "[Denormalized]",
 	})
 	public userHost: string | null;
 	//#endregion
diff --git a/packages/backend/src/models/entities/user-publickey.ts b/packages/backend/src/models/entities/user-publickey.ts
index d1a9239d1..83a86b8a3 100644
--- a/packages/backend/src/models/entities/user-publickey.ts
+++ b/packages/backend/src/models/entities/user-publickey.ts
@@ -14,19 +14,19 @@ export class UserPublickey {
 	@PrimaryColumn(id())
 	public userId: User["id"];
 
-	@OneToOne(type => User, {
-		onDelete: 'CASCADE',
+	@OneToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
 
 	@Index({ unique: true })
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 256,
 	})
 	public keyId: string;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 4096,
 	})
 	public keyPem: string;
diff --git a/packages/backend/src/models/entities/user-security-key.ts b/packages/backend/src/models/entities/user-security-key.ts
index 3b9d925d9..511cab4ae 100644
--- a/packages/backend/src/models/entities/user-security-key.ts
+++ b/packages/backend/src/models/entities/user-security-key.ts
@@ -11,8 +11,8 @@ import { id } from "../id.js";
 
 @Entity()
 export class UserSecurityKey {
-	@PrimaryColumn('varchar', {
-		comment: 'Variable-length id given to navigator.credentials.get()',
+	@PrimaryColumn("varchar", {
+		comment: "Variable-length id given to navigator.credentials.get()",
 	})
 	public id: string;
 
@@ -20,27 +20,27 @@ export class UserSecurityKey {
 	@Column(id())
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
 
 	@Index()
-	@Column('varchar', {
+	@Column("varchar", {
 		comment:
-			'Variable-length public key used to verify attestations (hex-encoded).',
+			"Variable-length public key used to verify attestations (hex-encoded).",
 	})
 	public publicKey: string;
 
-	@Column('timestamp with time zone', {
+	@Column("timestamp with time zone", {
 		comment:
-			'The date of the last time the UserSecurityKey was successfully validated.',
+			"The date of the last time the UserSecurityKey was successfully validated.",
 	})
 	public lastUsed: Date;
 
-	@Column('varchar', {
-		comment: 'User-defined name for this key',
+	@Column("varchar", {
+		comment: "User-defined name for this key",
 		length: 30,
 	})
 	public name: string;
diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts
index c23f4f28d..53dc7e60b 100644
--- a/packages/backend/src/models/entities/user.ts
+++ b/packages/backend/src/models/entities/user.ts
@@ -10,99 +10,101 @@ import { id } from "../id.js";
 import { DriveFile } from "./drive-file.js";
 
 @Entity()
-@Index(['usernameLower', 'host'], { unique: true })
+@Index(["usernameLower", "host"], { unique: true })
 export class User {
 	@PrimaryColumn(id())
 	public id: string;
 
 	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the User.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the User.",
 	})
 	public createdAt: Date;
 
 	@Index()
-	@Column('timestamp with time zone', {
+	@Column("timestamp with time zone", {
 		nullable: true,
-		comment: 'The updated date of the User.',
+		comment: "The updated date of the User.",
 	})
 	public updatedAt: Date | null;
 
-	@Column('timestamp with time zone', {
+	@Column("timestamp with time zone", {
 		nullable: true,
 	})
 	public lastFetchedAt: Date | null;
 
 	@Index()
-	@Column('timestamp with time zone', {
+	@Column("timestamp with time zone", {
 		nullable: true,
 	})
 	public lastActiveDate: Date | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
 	})
 	public hideOnlineStatus: boolean;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
-		comment: 'The username of the User.',
+		comment: "The username of the User.",
 	})
 	public username: string;
 
 	@Index()
-	@Column('varchar', {
-		length: 128, select: false,
-		comment: 'The username (lowercased) of the User.',
+	@Column("varchar", {
+		length: 128,
+		select: false,
+		comment: "The username (lowercased) of the User.",
 	})
 	public usernameLower: string;
 
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: 'The name of the User.',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment: "The name of the User.",
 	})
 	public name: string | null;
 
-	@Column('integer', {
+	@Column("integer", {
 		default: 0,
-		comment: 'The count of followers.',
+		comment: "The count of followers.",
 	})
 	public followersCount: number;
 
-	@Column('integer', {
+	@Column("integer", {
 		default: 0,
-		comment: 'The count of following.',
+		comment: "The count of following.",
 	})
 	public followingCount: number;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 512,
 		nullable: true,
-		comment: 'The URI of the new account of the User',
+		comment: "The URI of the new account of the User",
 	})
 	public movedToUri: string | null;
 
-	@Column('simple-array', {
+	@Column("simple-array", {
 		nullable: true,
-		comment: 'URIs the user is known as too',
+		comment: "URIs the user is known as too",
 	})
 	public alsoKnownAs: string[] | null;
 
-	@Column('integer', {
+	@Column("integer", {
 		default: 0,
-		comment: 'The count of notes.',
+		comment: "The count of notes.",
 	})
 	public notesCount: number;
 
 	@Column({
 		...id(),
 		nullable: true,
-		comment: 'The ID of avatar DriveFile.',
+		comment: "The ID of avatar DriveFile.",
 	})
 	public avatarId: DriveFile["id"] | null;
 
-	@OneToOne(type => DriveFile, {
-		onDelete: 'SET NULL',
+	@OneToOne((type) => DriveFile, {
+		onDelete: "SET NULL",
 	})
 	@JoinColumn()
 	public avatar: DriveFile | null;
@@ -110,143 +112,162 @@ export class User {
 	@Column({
 		...id(),
 		nullable: true,
-		comment: 'The ID of banner DriveFile.',
+		comment: "The ID of banner DriveFile.",
 	})
 	public bannerId: DriveFile["id"] | null;
 
-	@OneToOne(type => DriveFile, {
-		onDelete: 'SET NULL',
+	@OneToOne((type) => DriveFile, {
+		onDelete: "SET NULL",
 	})
 	@JoinColumn()
 	public banner: DriveFile | null;
 
 	@Index()
-	@Column('varchar', {
-		length: 128, array: true, default: '{}',
+	@Column("varchar", {
+		length: 128,
+		array: true,
+		default: "{}",
 	})
 	public tags: string[];
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
-		comment: 'Whether the User is suspended.',
+		comment: "Whether the User is suspended.",
 	})
 	public isSuspended: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
-		comment: 'Whether the User is silenced.',
+		comment: "Whether the User is silenced.",
 	})
 	public isSilenced: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
-		comment: 'Whether the User is locked.',
+		comment: "Whether the User is locked.",
 	})
 	public isLocked: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
-		comment: 'Whether the User is a bot.',
+		comment: "Whether the User is a bot.",
 	})
 	public isBot: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
-		comment: 'Whether the User is a cat.',
+		comment: "Whether the User is a cat.",
 	})
 	public isCat: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: true,
-		comment: 'Whether to speak as a cat if isCat.',
+		comment: "Whether to speak as a cat if isCat.",
 	})
 	public speakAsCat: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
-		comment: 'Whether the User is the admin.',
+		comment: "Whether the User is the admin.",
 	})
 	public isAdmin: boolean;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
-		comment: 'Whether the User is a moderator.',
+		comment: "Whether the User is a moderator.",
 	})
 	public isModerator: boolean;
 
 	@Index()
-	@Column('boolean', {
+	@Column("boolean", {
 		default: true,
-		comment: 'Whether the User is explorable.',
+		comment: "Whether the User is explorable.",
 	})
 	public isExplorable: boolean;
 
 	// アカウントが削除されたかどうかのフラグだが、完全に削除される際は物理削除なので実質削除されるまでの「削除が進行しているかどうか」のフラグ
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
-		comment: 'Whether the User is deleted.',
+		comment: "Whether the User is deleted.",
 	})
 	public isDeleted: boolean;
 
-	@Column('varchar', {
-		length: 128, array: true, default: '{}',
+	@Column("varchar", {
+		length: 128,
+		array: true,
+		default: "{}",
 	})
 	public emojis: string[];
 
 	@Index()
-	@Column('varchar', {
-		length: 128, nullable: true,
-		comment: 'The host of the User. It will be null if the origin of the user is local.',
+	@Column("varchar", {
+		length: 128,
+		nullable: true,
+		comment:
+			"The host of the User. It will be null if the origin of the user is local.",
 	})
 	public host: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: 'The inbox URL of the User. It will be null if the origin of the user is local.',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment:
+			"The inbox URL of the User. It will be null if the origin of the user is local.",
 	})
 	public inbox: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: 'The sharedInbox URL of the User. It will be null if the origin of the user is local.',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment:
+			"The sharedInbox URL of the User. It will be null if the origin of the user is local.",
 	})
 	public sharedInbox: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: 'The featured URL of the User. It will be null if the origin of the user is local.',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment:
+			"The featured URL of the User. It will be null if the origin of the user is local.",
 	})
 	public featured: string | null;
 
 	@Index()
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: 'The URI of the User. It will be null if the origin of the user is local.',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment:
+			"The URI of the User. It will be null if the origin of the user is local.",
 	})
 	public uri: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
-		comment: 'The URI of the user Follower Collection. It will be null if the origin of the user is local.',
+	@Column("varchar", {
+		length: 512,
+		nullable: true,
+		comment:
+			"The URI of the user Follower Collection. It will be null if the origin of the user is local.",
 	})
 	public followersUri: string | null;
 
-	@Column('boolean', {
+	@Column("boolean", {
 		default: false,
-		comment: 'Whether to show users replying to other users in the timeline.',
+		comment: "Whether to show users replying to other users in the timeline.",
 	})
 	public showTimelineReplies: boolean;
 
 	@Index({ unique: true })
-	@Column('char', {
-		length: 16, nullable: true, unique: true,
-		comment: 'The native access token of the User. It will be null if the origin of the user is local.',
+	@Column("char", {
+		length: 16,
+		nullable: true,
+		unique: true,
+		comment:
+			"The native access token of the User. It will be null if the origin of the user is local.",
 	})
 	public token: string | null;
 
-	@Column('integer', {
+	@Column("integer", {
 		nullable: true,
-		comment: 'Overrides user drive capacity limit',
+		comment: "Overrides user drive capacity limit",
 	})
 	public driveCapacityOverrideMb: number | null;
 
diff --git a/packages/backend/src/models/entities/webhook.ts b/packages/backend/src/models/entities/webhook.ts
index 5db51c3a3..47fd79966 100644
--- a/packages/backend/src/models/entities/webhook.ts
+++ b/packages/backend/src/models/entities/webhook.ts
@@ -25,48 +25,50 @@ export class Webhook {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the Antenna.',
+	@Column("timestamp with time zone", {
+		comment: "The created date of the Antenna.",
 	})
 	public createdAt: Date;
 
 	@Index()
 	@Column({
 		...id(),
-		comment: 'The owner ID.',
+		comment: "The owner ID.",
 	})
 	public userId: User["id"];
 
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
+	@ManyToOne((type) => User, {
+		onDelete: "CASCADE",
 	})
 	@JoinColumn()
 	public user: User | null;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 128,
-		comment: 'The name of the Antenna.',
+		comment: "The name of the Antenna.",
 	})
 	public name: string;
 
 	@Index()
-	@Column('varchar', {
-		length: 128, array: true, default: '{}',
+	@Column("varchar", {
+		length: 128,
+		array: true,
+		default: "{}",
 	})
 	public on: typeof webhookEventTypes[number][];
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 1024,
 	})
 	public url: string;
 
-	@Column('varchar', {
+	@Column("varchar", {
 		length: 1024,
 	})
 	public secret: string;
 
 	@Index()
-	@Column('boolean', {
+	@Column("boolean", {
 		default: true,
 	})
 	public active: boolean;
@@ -74,7 +76,7 @@ export class Webhook {
 	/**
 	 * 直近のリクエスト送信日時
 	 */
-	@Column('timestamp with time zone', {
+	@Column("timestamp with time zone", {
 		nullable: true,
 	})
 	public latestSentAt: Date | null;
@@ -82,7 +84,7 @@ export class Webhook {
 	/**
 	 * 直近のリクエスト送信時のHTTPステータスコード
 	 */
-	@Column('integer', {
+	@Column("integer", {
 		nullable: true,
 	})
 	public latestStatus: number | null;
diff --git a/packages/backend/src/server/web/manifest.json b/packages/backend/src/server/web/manifest.json
index 1e662fb20..647a5d437 100644
--- a/packages/backend/src/server/web/manifest.json
+++ b/packages/backend/src/server/web/manifest.json
@@ -41,7 +41,7 @@
 			"url": "url"
 		}
 	},
-	"screenshots" : [
+	"screenshots": [
 		{
 			"src": "/static-assets/screenshots/1.webp",
 			"sizes": "1195x579",
@@ -57,7 +57,7 @@
 			"label": "Posts"
 		}
 	],
-	"shortcuts" : [
+	"shortcuts": [
 		{
 			"name": "Notifications",
 			"short_name": "Notifs",
@@ -68,7 +68,5 @@
 			"url": "/my/messaging"
 		}
 	],
-	"categories": [
-    "social"
-  ]
+	"categories": ["social"]
 }
diff --git a/packages/calckey-js/package.json b/packages/calckey-js/package.json
index 598dd1cdb..6f724fc21 100644
--- a/packages/calckey-js/package.json
+++ b/packages/calckey-js/package.json
@@ -34,9 +34,7 @@
 		"tsd": "^0.19.1",
 		"typescript": "4.5.4"
 	},
-	"files": [
-		"built"
-	],
+	"files": ["built"],
 	"dependencies": {
 		"autobind-decorator": "^2.4.0",
 		"eventemitter3": "^4.0.7",
diff --git a/packages/calckey-js/tsconfig.json b/packages/calckey-js/tsconfig.json
index a03a24262..642bcc45b 100644
--- a/packages/calckey-js/tsconfig.json
+++ b/packages/calckey-js/tsconfig.json
@@ -15,11 +15,6 @@
 		"noImplicitReturns": true,
 		"esModuleInterop": true
 	},
-	"include": [
-		"src/**/*"
-	],
-	"exclude": [
-		"node_modules",
-		"test/**/*"
-	]
+	"include": ["src/**/*"],
+	"exclude": ["node_modules", "test/**/*"]
 }
diff --git a/packages/client/src/widgets/server-info.vue b/packages/client/src/widgets/server-info.vue
index 32cd35883..d24dd8a98 100644
--- a/packages/client/src/widgets/server-info.vue
+++ b/packages/client/src/widgets/server-info.vue
@@ -48,7 +48,7 @@ const widgetPropsDef = {};
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-	const props = defineProps<{ widget?: Widget<WidgetProps> }>();
+const props = defineProps<{ widget?: Widget<WidgetProps> }>();
 const emit = defineEmits<{ (ev: "updateProps", props: WidgetProps) }>();
 
 const { widgetProps, configure } = useWidgetPropsManager(
diff --git a/packages/sw/package.json b/packages/sw/package.json
index 112005ba7..0d0ac52df 100644
--- a/packages/sw/package.json
+++ b/packages/sw/package.json
@@ -6,9 +6,7 @@
 		"watch": "pnpm swc src -d built -D -w",
 		"lint": "pnpm rome check \"src/**/*.ts\""
 	},
-	"dependencies": {
-
-	},
+	"dependencies": {},
 	"devDependencies": {
 		"@swc/cli": "^0.1.62",
 		"@swc/core": "^1.3.50",
diff --git a/rome.json b/rome.json
index 6b74808ed..7a51b60e6 100644
--- a/rome.json
+++ b/rome.json
@@ -5,5 +5,18 @@
     "rules": {
       "recommended": true
     }
-  }
-}
\ No newline at end of file
+  },
+	"formatter": {
+		"ignore": [
+			"packages/calckey-js/api-extractor.json",
+			"packages/*/tsconfig.json",
+			"packages/*/package-lock.json",
+			"packages/backend/src/server/web/manifest.ts",
+			"packages/backend/built/",
+			"packages/backend/nsfw-model/",
+			"packages/client/src/emojilist.json",
+			"*.md",
+			"*/.yml"
+		]
+	}
+}

From 31a593d45b9e6d2bd94a73ae588fe7194d773222 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 09:32:55 -0700
Subject: [PATCH 053/198] dev31

---
 package.json                                             | 2 +-
 .../client/src/widgets/server-metric/meilisearch.vue     | 9 ++++++---
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/package.json b/package.json
index 593690d6d..b793290eb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "calckey",
-	"version": "14.0.0-dev26",
+	"version": "14.0.0-dev31",
 	"codename": "aqua",
 	"repository": {
 		"type": "git",
diff --git a/packages/client/src/widgets/server-metric/meilisearch.vue b/packages/client/src/widgets/server-metric/meilisearch.vue
index 80a3f4146..a4a899200 100644
--- a/packages/client/src/widgets/server-metric/meilisearch.vue
+++ b/packages/client/src/widgets/server-metric/meilisearch.vue
@@ -1,5 +1,5 @@
 <template>
-	<div class="ms_stats">
+	<div class="verusivbr">
 		<XPie class="pie" :value="progress" />
 		<div>
 			<p><i class="ph-file-search ph-bold ph-lg"></i>MeiliSearch</p>
@@ -33,7 +33,7 @@ function onStats(stats) {
 	total_size = stats.meilisearch.size;
 	index_count = stats.meilisearch.indexed_count;
 	available = stats.meilisearch.health;
-	progress = Math.floor((index_count / serverStats.notesCount) * 100);
+	progress = (index_count / serverStats.notesCount);
 }
 
 onMounted(() => {
@@ -49,7 +49,8 @@ onBeforeUnmount(() => {
 </script>
 
 <style lang="scss" scoped>
-.ms_stats {
+.verusivbr {
+	display: flex;
 	padding: 16px;
 
 	> .pie {
@@ -59,6 +60,8 @@ onBeforeUnmount(() => {
 	}
 
 	> div {
+		flex: 1;
+
 		> p {
 			margin: 0;
 			font-size: 0.8em;

From 51160a6a77d7e9dfe9eef24764cb2b3202d80130 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Mon, 29 May 2023 18:33:57 +0200
Subject: [PATCH 054/198] Delete deleted posts from Meilisearch

---
 packages/backend/src/db/meilisearch.ts        | 24 +++++++++++++++++++
 .../src/queue/processors/db/delete-account.ts |  4 ++++
 packages/backend/src/services/note/delete.ts  |  5 ++++
 3 files changed, 33 insertions(+)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index 7e176e058..206e0dc62 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -262,5 +262,29 @@ export default hasConfig
 					indexed_count: stats.indexes["posts"].numberOfDocuments,
 				};
 			},
+			deleteNotes: async (note: Note | Note[] | string | string[]) => {
+				if (note instanceof Note) {
+					note = [note];
+				}
+				if (typeof note === "string") {
+					note = [note];
+				}
+
+				let deletionBatch = note.map((n) => {
+					if(n instanceof Note) {
+						return n.id;
+					}
+
+					if(n.length > 0) return n;
+
+					logger.error(`Failed to delete note from Meilisearch, invalid post ID: ${JSON.stringify(n)}`)
+
+					throw new Error(`Invalid note ID passed to meilisearch deleteNote: ${JSON.stringify(n)}`)
+				}).filter((el) => el !== null);
+
+				await posts.deleteDocuments(deletionBatch as string[]).then(() => {
+					logger.info(`submitted ${deletionBatch.length} large batch for deletion`)
+				});
+			},
 	  }
 	: null;
diff --git a/packages/backend/src/queue/processors/db/delete-account.ts b/packages/backend/src/queue/processors/db/delete-account.ts
index 764b83db2..a356ca7ab 100644
--- a/packages/backend/src/queue/processors/db/delete-account.ts
+++ b/packages/backend/src/queue/processors/db/delete-account.ts
@@ -7,6 +7,7 @@ import type { DriveFile } from "@/models/entities/drive-file.js";
 import { MoreThan } from "typeorm";
 import { deleteFileSync } from "@/services/drive/delete-file.js";
 import { sendEmail } from "@/services/send-email.js";
+import meilisearch from "@/db/meilisearch.js";
 
 const logger = queueLogger.createSubLogger("delete-account");
 
@@ -43,6 +44,9 @@ export async function deleteAccount(
 			cursor = notes[notes.length - 1].id;
 
 			await Notes.delete(notes.map((note) => note.id));
+			if (meilisearch) {
+				await meilisearch.deleteNotes(notes);
+			}
 		}
 
 		logger.succ("All of notes deleted");
diff --git a/packages/backend/src/services/note/delete.ts b/packages/backend/src/services/note/delete.ts
index 392578e2f..285a08075 100644
--- a/packages/backend/src/services/note/delete.ts
+++ b/packages/backend/src/services/note/delete.ts
@@ -21,6 +21,7 @@ import {
 import { countSameRenotes } from "@/misc/count-same-renotes.js";
 import { registerOrFetchInstanceDoc } from "../register-or-fetch-instance-doc.js";
 import { deliverToRelays } from "../relay.js";
+import meilisearch from "@/db/meilisearch.js";
 
 /**
  * 投稿を削除します。
@@ -119,6 +120,10 @@ export default async function (
 		id: note.id,
 		userId: user.id,
 	});
+
+	if(meilisearch) {
+		await meilisearch.deleteNotes(note.id);
+	}
 }
 
 async function findCascadingNotes(note: Note) {

From 8d363898d49c6d03be464752ba39f7fac2cc845e Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 10:00:16 -0700
Subject: [PATCH 055/198] revert #10222

---
 packages/backend/src/db/meilisearch.ts | 112 ++++++++++++-------------
 1 file changed, 55 insertions(+), 57 deletions(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index 9a64da850..6740cf08b 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -15,51 +15,49 @@ const hasConfig =
 		config.meilisearch.port ||
 		config.meilisearch.apiKey);
 
-if (hasConfig) {
-	const host = hasConfig ? config.meilisearch.host ?? "localhost" : "";
-	const port = hasConfig ? config.meilisearch.port ?? 7700 : 0;
-	const auth = hasConfig ? config.meilisearch.apiKey ?? "" : "";
-	const ssl = hasConfig ? config.meilisearch.ssl ?? false : false;
+const host = hasConfig ? config.meilisearch.host ?? "localhost" : "";
+const port = hasConfig ? config.meilisearch.port ?? 7700 : 0;
+const auth = hasConfig ? config.meilisearch.apiKey ?? "" : "";
+const ssl = hasConfig ? config.meilisearch.ssl ?? false : false;
 
-	logger.info("Connecting to MeiliSearch");
+logger.info("Connecting to MeiliSearch");
 
-	const client: MeiliSearch = new MeiliSearch({
-		host: `${ssl ? "https" : "http"}://${host}:${port}`,
-		apiKey: auth,
-	});
+const client: MeiliSearch = new MeiliSearch({
+	host: `${ssl ? "https" : "http"}://${host}:${port}`,
+	apiKey: auth,
+});
 
-	const posts = client.index("posts");
+const posts = client.index("posts");
 
-	posts
-		.updateSearchableAttributes(["text"])
-		.catch((e) =>
-			logger.error(`Setting searchable attr failed, searches won't work: ${e}`),
-		);
+posts
+	.updateSearchableAttributes(["text"])
+	.catch((e) =>
+		logger.error(`Setting searchable attr failed, searches won't work: ${e}`),
+	);
 
-	posts
-		.updateFilterableAttributes([
-			"userName",
-			"userHost",
-			"mediaAttachment",
-			"createdAt",
-			"userId",
-		])
-		.catch((e) =>
-			logger.error(
-				`Setting filterable attr failed, advanced searches won't work: ${e}`,
-			),
-		);
+posts
+	.updateFilterableAttributes([
+		"userName",
+		"userHost",
+		"mediaAttachment",
+		"createdAt",
+		"userId",
+	])
+	.catch((e) =>
+		logger.error(
+			`Setting filterable attr failed, advanced searches won't work: ${e}`,
+		),
+	);
 
-	posts
-		.updateSortableAttributes(["createdAt"])
-		.catch((e) =>
-			logger.error(
-				`Setting sortable attr failed, placeholder searches won't sort properly: ${e}`,
-			),
-		);
+posts
+	.updateSortableAttributes(["createdAt"])
+	.catch((e) =>
+		logger.error(
+			`Setting sortable attr failed, placeholder searches won't sort properly: ${e}`,
+		),
+	);
 
-	logger.info("Connected to MeiliSearch");
-}
+logger.info("Connected to MeiliSearch");
 
 export type MeilisearchNote = {
 	id: string;
@@ -90,37 +88,37 @@ export default hasConfig
 				/// filter:following => show results only from users you follow
 				/// filter:followers => show results only from followers
 
-				let constructedFilters: string[] = [];
+				const constructedFilters: string[] = [];
 
-				let splitSearch = query.split(" ");
+				const splitSearch = query.split(" ");
 
 				// Detect search operators and remove them from the actual query
-				let filteredSearchTerms = (
+				const filteredSearchTerms = (
 					await Promise.all(
 						splitSearch.map(async (term) => {
 							if (term.startsWith("has:")) {
-								let fileType = term.slice(4);
+								const fileType = term.slice(4);
 								constructedFilters.push(`mediaAttachment = "${fileType}"`);
 								return null;
 							} else if (term.startsWith("from:")) {
-								let user = term.slice(5);
+								const user = term.slice(5);
 								constructedFilters.push(`userName = ${user}`);
 								return null;
 							} else if (term.startsWith("domain:")) {
-								let domain = term.slice(7);
+								const domain = term.slice(7);
 								constructedFilters.push(`userHost = ${domain}`);
 								return null;
 							} else if (term.startsWith("after:")) {
-								let timestamp = term.slice(6);
+								const timestamp = term.slice(6);
 								// Try to parse the timestamp as JavaScript Date
-								let date = Date.parse(timestamp);
+								const date = Date.parse(timestamp);
 								if (isNaN(date)) return null;
 								constructedFilters.push(`createdAt > ${date / 1000}`);
 								return null;
 							} else if (term.startsWith("before:")) {
-								let timestamp = term.slice(7);
+								const timestamp = term.slice(7);
 								// Try to parse the timestamp as JavaScript Date
-								let date = Date.parse(timestamp);
+								const date = Date.parse(timestamp);
 								if (isNaN(date)) return null;
 								constructedFilters.push(`createdAt < ${date / 1000}`);
 								return null;
@@ -128,7 +126,7 @@ export default hasConfig
 								// Check if we got a context user
 								if (userCtx) {
 									// Fetch user follows from DB
-									let followedUsers = await Followings.find({
+									const followedUsers = await Followings.find({
 										where: {
 											followerId: userCtx.id,
 										},
@@ -136,7 +134,7 @@ export default hasConfig
 											followeeId: true,
 										},
 									});
-									let followIDs = followedUsers.map((user) => user.followeeId);
+									const followIDs = followedUsers.map((user) => user.followeeId);
 
 									if (followIDs.length === 0) return null;
 
@@ -152,7 +150,7 @@ export default hasConfig
 								// Check if we got a context user
 								if (userCtx) {
 									// Fetch users follows from DB
-									let followedUsers = await Followings.find({
+									const followedUsers = await Followings.find({
 										where: {
 											followeeId: userCtx.id,
 										},
@@ -160,7 +158,7 @@ export default hasConfig
 											followerId: true,
 										},
 									});
-									let followIDs = followedUsers.map((user) => user.followerId);
+									const followIDs = followedUsers.map((user) => user.followerId);
 
 									if (followIDs.length === 0) return null;
 
@@ -179,7 +177,7 @@ export default hasConfig
 					)
 				).filter((term) => term !== null);
 
-				let sortRules = [];
+				const sortRules = [];
 
 				// An empty search term with defined filters means we have a placeholder search => https://www.meilisearch.com/docs/reference/api/search#placeholder-search
 				// These have to be ordered manually, otherwise the *oldest* posts are returned first, which we don't want
@@ -205,9 +203,9 @@ export default hasConfig
 					ingestNotes = [ingestNotes];
 				}
 
-				let indexingBatch: MeilisearchNote[] = [];
+				const indexingBatch: MeilisearchNote[] = [];
 
-				for (let note of ingestNotes) {
+				for (const note of ingestNotes) {
 					if (note.user === undefined) {
 						note.user = await Users.findOne({
 							where: {
@@ -255,8 +253,8 @@ export default hasConfig
 					);
 			},
 			serverStats: async () => {
-				let health: Health = await client.health();
-				let stats: Stats = await client.getStats();
+				const health: Health = await client.health();
+				const stats: Stats = await client.getStats();
 
 				return {
 					health: health.status,

From 0c9e12c340704e431f05761399161aa2d1399dcf Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 10:01:34 -0700
Subject: [PATCH 056/198] chore: format

---
 packages/backend/src/db/meilisearch.ts        | 38 +++++++++++++------
 packages/backend/src/services/note/delete.ts  |  2 +-
 .../src/widgets/server-metric/meilisearch.vue |  2 +-
 3 files changed, 29 insertions(+), 13 deletions(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index 120450ac4..95f57a42d 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -134,7 +134,9 @@ export default hasConfig
 											followeeId: true,
 										},
 									});
-									const followIDs = followedUsers.map((user) => user.followeeId);
+									const followIDs = followedUsers.map(
+										(user) => user.followeeId,
+									);
 
 									if (followIDs.length === 0) return null;
 
@@ -158,7 +160,9 @@ export default hasConfig
 											followerId: true,
 										},
 									});
-									const followIDs = followedUsers.map((user) => user.followerId);
+									const followIDs = followedUsers.map(
+										(user) => user.followerId,
+									);
 
 									if (followIDs.length === 0) return null;
 
@@ -270,20 +274,32 @@ export default hasConfig
 					note = [note];
 				}
 
-				let deletionBatch = note.map((n) => {
-					if(n instanceof Note) {
-						return n.id;
-					}
+				let deletionBatch = note
+					.map((n) => {
+						if (n instanceof Note) {
+							return n.id;
+						}
 
-					if(n.length > 0) return n;
+						if (n.length > 0) return n;
 
-					logger.error(`Failed to delete note from Meilisearch, invalid post ID: ${JSON.stringify(n)}`)
+						logger.error(
+							`Failed to delete note from Meilisearch, invalid post ID: ${JSON.stringify(
+								n,
+							)}`,
+						);
 
-					throw new Error(`Invalid note ID passed to meilisearch deleteNote: ${JSON.stringify(n)}`)
-				}).filter((el) => el !== null);
+						throw new Error(
+							`Invalid note ID passed to meilisearch deleteNote: ${JSON.stringify(
+								n,
+							)}`,
+						);
+					})
+					.filter((el) => el !== null);
 
 				await posts.deleteDocuments(deletionBatch as string[]).then(() => {
-					logger.info(`submitted ${deletionBatch.length} large batch for deletion`)
+					logger.info(
+						`submitted ${deletionBatch.length} large batch for deletion`,
+					);
 				});
 			},
 	  }
diff --git a/packages/backend/src/services/note/delete.ts b/packages/backend/src/services/note/delete.ts
index 285a08075..90175ccdc 100644
--- a/packages/backend/src/services/note/delete.ts
+++ b/packages/backend/src/services/note/delete.ts
@@ -121,7 +121,7 @@ export default async function (
 		userId: user.id,
 	});
 
-	if(meilisearch) {
+	if (meilisearch) {
 		await meilisearch.deleteNotes(note.id);
 	}
 }
diff --git a/packages/client/src/widgets/server-metric/meilisearch.vue b/packages/client/src/widgets/server-metric/meilisearch.vue
index a4a899200..8119ff537 100644
--- a/packages/client/src/widgets/server-metric/meilisearch.vue
+++ b/packages/client/src/widgets/server-metric/meilisearch.vue
@@ -33,7 +33,7 @@ function onStats(stats) {
 	total_size = stats.meilisearch.size;
 	index_count = stats.meilisearch.indexed_count;
 	available = stats.meilisearch.health;
-	progress = (index_count / serverStats.notesCount);
+	progress = index_count / serverStats.notesCount;
 }
 
 onMounted(() => {

From 7a2b2b638a2c52f3bd7647761175d3190f23fb4e Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 10:01:41 -0700
Subject: [PATCH 057/198] dev32

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index b793290eb..de993cde0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "calckey",
-	"version": "14.0.0-dev31",
+	"version": "14.0.0-dev32",
 	"codename": "aqua",
 	"repository": {
 		"type": "git",

From 6c8dd6d7f0d092720ceca44205b796ad61318802 Mon Sep 17 00:00:00 2001
From: Freeplay <Freeplay@duck.com>
Date: Mon, 29 May 2023 13:10:35 -0400
Subject: [PATCH 058/198] always show antennas hint at top

---
 .../client/src/pages/my-antennas/index.vue    | 40 +++++++------------
 1 file changed, 15 insertions(+), 25 deletions(-)

diff --git a/packages/client/src/pages/my-antennas/index.vue b/packages/client/src/pages/my-antennas/index.vue
index a4d15433a..d8c9617ec 100644
--- a/packages/client/src/pages/my-antennas/index.vue
+++ b/packages/client/src/pages/my-antennas/index.vue
@@ -8,37 +8,27 @@
 		/></template>
 		<MkSpacer :content-max="700">
 			<div class="ieepwinx">
-
+				<MkInfo
+					class="_gap"
+					:icon="'flying-saucer'"
+					:card="true"
+				>
+					<p>{{ i18n.ts.antennasDesc }}</p>
+					<MkButton
+						:link="true"
+						to="/my/antennas/create"
+						primary
+						class="add"
+						><i class="ph-plus ph-bold ph-lg"></i>
+						{{ i18n.ts.add }}</MkButton
+					>
+				</MkInfo>
 				<div class="">
 					<MkPagination
 						ref="list"
 						:pagination="pagination"
 					>
-						<template #empty>
-							<MkInfo
-								:icon="'flying-saucer'"
-								:card="true"
-							>
-								<p>{{ i18n.ts.antennasDesc }}</p>
-								<MkButton
-									:link="true"
-									to="/my/antennas/create"
-									primary
-									class="add"
-									><i class="ph-plus ph-bold ph-lg"></i>
-									{{ i18n.ts.add }}</MkButton
-								>
-							</MkInfo>
-						</template>
 						<template #default="{ items }">
-							<MkButton
-								:link="true"
-								to="/my/antennas/create"
-								primary
-								class="add"
-								><i class="ph-plus ph-bold ph-lg"></i>
-								{{ i18n.ts.add }}</MkButton
-							>
 							<div v-for="antenna in items" :key="antenna.id">
 								<MkA
 									class="uopelskx"

From e761bd6b2317e20e1d74271699ac05edd7aa5fca Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 10:18:12 -0700
Subject: [PATCH 059/198] fixes

---
 packages/backend/src/db/meilisearch.ts |  2 +-
 packages/client/src/scripts/search.ts  | 35 ++++++++++++++++----------
 2 files changed, 23 insertions(+), 14 deletions(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index 95f57a42d..7f3178955 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -274,7 +274,7 @@ export default hasConfig
 					note = [note];
 				}
 
-				let deletionBatch = note
+				const deletionBatch = note
 					.map((n) => {
 						if (n instanceof Note) {
 							return n.id;
diff --git a/packages/client/src/scripts/search.ts b/packages/client/src/scripts/search.ts
index c6eb497ea..8f1f0a7df 100644
--- a/packages/client/src/scripts/search.ts
+++ b/packages/client/src/scripts/search.ts
@@ -3,19 +3,30 @@ import { i18n } from "@/i18n";
 import { mainRouter } from "@/router";
 
 export async function search() {
+	let searchOptions = "";
+
+	let meta = null;
+
+	os.api("server-info", {}).then((res) => {
+		meta = res;
+	});
+
+	if (meta.meilisearch.health === "available") {
+		searchOptions =
+		"Advanced search operators\n" +
+		"from:user => filter by user\n" +
+		"has:image/video/audio/text/file => filter by attachment types\n" +
+		"domain:domain.com => filter by domain\n" +
+		"before:Date => show posts made before Date\n" +
+		"after:Date => show posts made after Date\n" +
+		'"text" => get posts with exact text between quotes\n' +
+		"filter:following => show results only from users you follow\n" +
+		"filter:followers => show results only from followers\n";
+	}
 	const { canceled, result: query } = await os.inputText({
 		title: i18n.ts.search,
 		placeholder: i18n.ts.searchPlaceholder,
-		text:
-			"Advanced search operators\n" +
-			"from:user => filter by user\n" +
-			"has:image/video/audio/text/file => filter by attachment types\n" +
-			"domain:domain.com => filter by domain\n" +
-			"before:Date => show posts made before Date\n" +
-			"after:Date => show posts made after Date\n" +
-			'"text" => get posts with exact text between quotes\n' +
-			"filter:following => show results only from users you follow\n" +
-			"filter:followers => show results only from followers\n",
+		text: searchOptions,
 	});
 	if (canceled || query == null || query === "") return;
 
@@ -46,9 +57,7 @@ export async function search() {
 		// TODO
 		//v.$root.$emit('warp', date);
 		os.alert({
-			icon: "ph-clock-counter-clockwise ph-bold ph-lg",
-			iconOnly: true,
-			autoClose: true,
+			type: "waiting",
 		});
 		return;
 	}

From cef16a12504b90ce07e9beb053f4056f145f37c9 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 10:19:13 -0700
Subject: [PATCH 060/198] formatting

---
 packages/client/src/scripts/search.ts | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/packages/client/src/scripts/search.ts b/packages/client/src/scripts/search.ts
index 8f1f0a7df..379f09c08 100644
--- a/packages/client/src/scripts/search.ts
+++ b/packages/client/src/scripts/search.ts
@@ -13,15 +13,15 @@ export async function search() {
 
 	if (meta.meilisearch.health === "available") {
 		searchOptions =
-		"Advanced search operators\n" +
-		"from:user => filter by user\n" +
-		"has:image/video/audio/text/file => filter by attachment types\n" +
-		"domain:domain.com => filter by domain\n" +
-		"before:Date => show posts made before Date\n" +
-		"after:Date => show posts made after Date\n" +
-		'"text" => get posts with exact text between quotes\n' +
-		"filter:following => show results only from users you follow\n" +
-		"filter:followers => show results only from followers\n";
+			"Advanced search operators\n" +
+			"from:user => filter by user\n" +
+			"has:image/video/audio/text/file => filter by attachment types\n" +
+			"domain:domain.com => filter by domain\n" +
+			"before:Date => show posts made before Date\n" +
+			"after:Date => show posts made after Date\n" +
+			'"text" => get posts with exact text between quotes\n' +
+			"filter:following => show results only from users you follow\n" +
+			"filter:followers => show results only from followers\n";
 	}
 	const { canceled, result: query } = await os.inputText({
 		title: i18n.ts.search,

From 7ef78fcd1283e5a17eaf48aff64a356fbd330047 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Mon, 29 May 2023 19:45:55 +0200
Subject: [PATCH 061/198] Make Meilisearch optional and don't connect if
 unconfigured

---
 packages/backend/src/db/meilisearch.ts | 81 ++++++++++++++------------
 1 file changed, 43 insertions(+), 38 deletions(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index 7f3178955..60491f882 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -1,63 +1,68 @@
-import { Health, MeiliSearch, Stats } from "meilisearch";
+import { Health, Index, MeiliSearch, Stats } from "meilisearch";
 import { dbLogger } from "./logger.js";
 
 import config from "@/config/index.js";
 import { Note } from "@/models/entities/note.js";
 import * as url from "url";
-import { ILocalUser, User } from "@/models/entities/user.js";
+import { ILocalUser } from "@/models/entities/user.js";
 import { Followings, Users } from "@/models/index.js";
 
 const logger = dbLogger.createSubLogger("meilisearch", "gray", false);
 
+let posts: Index;
+let client: MeiliSearch;
+
 const hasConfig =
 	config.meilisearch &&
 	(config.meilisearch.host ||
 		config.meilisearch.port ||
 		config.meilisearch.apiKey);
 
-const host = hasConfig ? config.meilisearch.host ?? "localhost" : "";
-const port = hasConfig ? config.meilisearch.port ?? 7700 : 0;
-const auth = hasConfig ? config.meilisearch.apiKey ?? "" : "";
-const ssl = hasConfig ? config.meilisearch.ssl ?? false : false;
+if(hasConfig) {
+	const host = hasConfig ? config.meilisearch.host ?? "localhost" : "";
+    const port = hasConfig ? config.meilisearch.port ?? 7700 : 0;
+    const auth = hasConfig ? config.meilisearch.apiKey ?? "" : "";
+    const ssl = hasConfig ? config.meilisearch.ssl ?? false : false;
 
-logger.info("Connecting to MeiliSearch");
+    logger.info("Connecting to MeiliSearch");
 
-const client: MeiliSearch = new MeiliSearch({
-	host: `${ssl ? "https" : "http"}://${host}:${port}`,
-	apiKey: auth,
-});
+	client = new MeiliSearch({
+		host: `${ssl ? "https" : "http"}://${host}:${port}`,
+    	apiKey: auth,
+    });
 
-const posts = client.index("posts");
+	posts = client.index("posts");
 
-posts
-	.updateSearchableAttributes(["text"])
-	.catch((e) =>
-		logger.error(`Setting searchable attr failed, searches won't work: ${e}`),
-	);
+	posts
+    	.updateSearchableAttributes(["text"])
+    	.catch((e) =>
+    		logger.error(`Setting searchable attr failed, searches won't work: ${e}`),
+    	);
 
-posts
-	.updateFilterableAttributes([
-		"userName",
-		"userHost",
-		"mediaAttachment",
-		"createdAt",
-		"userId",
-	])
-	.catch((e) =>
-		logger.error(
-			`Setting filterable attr failed, advanced searches won't work: ${e}`,
-		),
-	);
+    posts
+    	.updateFilterableAttributes([
+    		"userName",
+    		"userHost",
+    		"mediaAttachment",
+    		"createdAt",
+    		"userId",
+    	])
+    	.catch((e) =>
+    		logger.error(
+    			`Setting filterable attr failed, advanced searches won't work: ${e}`,
+    		),
+    	);
 
-posts
-	.updateSortableAttributes(["createdAt"])
-	.catch((e) =>
-		logger.error(
-			`Setting sortable attr failed, placeholder searches won't sort properly: ${e}`,
-		),
-	);
+    posts
+    	.updateSortableAttributes(["createdAt"])
+    	.catch((e) =>
+    		logger.error(
+    			`Setting sortable attr failed, placeholder searches won't sort properly: ${e}`,
+    		),
+    	);
 
-logger.info("Connected to MeiliSearch");
+    logger.info("Connected to MeiliSearch");
+}
 
 export type MeilisearchNote = {
 	id: string;

From 91970a2fc66dba8c89ee6885cba51be9f1696d60 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Mon, 29 May 2023 20:09:52 +0200
Subject: [PATCH 062/198] formatter

---
 packages/backend/src/db/meilisearch.ts | 64 +++++++++++++-------------
 1 file changed, 32 insertions(+), 32 deletions(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index 60491f882..01efaa0ba 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -18,50 +18,50 @@ const hasConfig =
 		config.meilisearch.port ||
 		config.meilisearch.apiKey);
 
-if(hasConfig) {
+if (hasConfig) {
 	const host = hasConfig ? config.meilisearch.host ?? "localhost" : "";
-    const port = hasConfig ? config.meilisearch.port ?? 7700 : 0;
-    const auth = hasConfig ? config.meilisearch.apiKey ?? "" : "";
-    const ssl = hasConfig ? config.meilisearch.ssl ?? false : false;
+	const port = hasConfig ? config.meilisearch.port ?? 7700 : 0;
+	const auth = hasConfig ? config.meilisearch.apiKey ?? "" : "";
+	const ssl = hasConfig ? config.meilisearch.ssl ?? false : false;
 
-    logger.info("Connecting to MeiliSearch");
+	logger.info("Connecting to MeiliSearch");
 
 	client = new MeiliSearch({
 		host: `${ssl ? "https" : "http"}://${host}:${port}`,
-    	apiKey: auth,
-    });
+		apiKey: auth,
+	});
 
 	posts = client.index("posts");
 
 	posts
-    	.updateSearchableAttributes(["text"])
-    	.catch((e) =>
-    		logger.error(`Setting searchable attr failed, searches won't work: ${e}`),
-    	);
+		.updateSearchableAttributes(["text"])
+		.catch((e) =>
+			logger.error(`Setting searchable attr failed, searches won't work: ${e}`),
+		);
 
-    posts
-    	.updateFilterableAttributes([
-    		"userName",
-    		"userHost",
-    		"mediaAttachment",
-    		"createdAt",
-    		"userId",
-    	])
-    	.catch((e) =>
-    		logger.error(
-    			`Setting filterable attr failed, advanced searches won't work: ${e}`,
-    		),
-    	);
+	posts
+		.updateFilterableAttributes([
+			"userName",
+			"userHost",
+			"mediaAttachment",
+			"createdAt",
+			"userId",
+		])
+		.catch((e) =>
+			logger.error(
+				`Setting filterable attr failed, advanced searches won't work: ${e}`,
+			),
+		);
 
-    posts
-    	.updateSortableAttributes(["createdAt"])
-    	.catch((e) =>
-    		logger.error(
-    			`Setting sortable attr failed, placeholder searches won't sort properly: ${e}`,
-    		),
-    	);
+	posts
+		.updateSortableAttributes(["createdAt"])
+		.catch((e) =>
+			logger.error(
+				`Setting sortable attr failed, placeholder searches won't sort properly: ${e}`,
+			),
+		);
 
-    logger.info("Connected to MeiliSearch");
+	logger.info("Connected to MeiliSearch");
 }
 
 export type MeilisearchNote = {

From a15c8d782a3ec32e74d53ec48efea3abb163d892 Mon Sep 17 00:00:00 2001
From: Freeplay <Freeplay@duck.com>
Date: Mon, 29 May 2023 14:51:02 -0400
Subject: [PATCH 063/198] Add timeline hints

---
 packages/client/src/components/MkInfo.vue     | 23 ++++++++-
 packages/client/src/components/MkTimeline.vue | 48 +++++++++++++++++++
 packages/client/src/store.ts                  | 20 ++++++++
 3 files changed, 89 insertions(+), 2 deletions(-)

diff --git a/packages/client/src/components/MkInfo.vue b/packages/client/src/components/MkInfo.vue
index e59b5d56e..848a4e37a 100644
--- a/packages/client/src/components/MkInfo.vue
+++ b/packages/client/src/components/MkInfo.vue
@@ -1,19 +1,34 @@
 <template>
-	<div class="info" :class="{ warn, card }">
+	<div v-if="visible" class="info" :class="{ warn, card }">
 		<i v-if="warn" class="ph-warning ph-bold ph-lg"></i>
 		<i v-else class="ph-bold ph-lg" :class="icon ? `ph-${icon}` : 'ph-info'"></i>
 		<slot></slot>
+		<button class="_button close" @click.stop="close">
+			<i class="ph-x ph-bold ph-lg"></i>
+		</button>
 	</div>
 </template>
 
 <script lang="ts" setup>
-import {} from "vue";
+import { ref } from "vue";
+
+const visible = ref(true);
 
 defineProps<{
 	icon?: string;
 	warn?: boolean;
 	card?: boolean;
 }>();
+
+const emit = defineEmits<{
+	(ev: "close"): void;
+}>();
+
+function close() {
+	visible.value = false;
+	emit("close");
+}
+
 </script>
 
 <style lang="scss" scoped>
@@ -54,5 +69,9 @@ defineProps<{
 	> i {
 		margin-right: 4px;
 	}
+	> .close {
+		margin-left: auto;
+		float: right;
+	}
 }
 </style>
diff --git a/packages/client/src/components/MkTimeline.vue b/packages/client/src/components/MkTimeline.vue
index 317e556cf..01885beec 100644
--- a/packages/client/src/components/MkTimeline.vue
+++ b/packages/client/src/components/MkTimeline.vue
@@ -1,4 +1,11 @@
 <template>
+	<MkInfo v-if="tlHint && !tlHintClosed" class="_gap" @close="closeHint"> 
+		<I18n
+			:src="tlHint"
+		>
+			<template #icon></template>
+		</I18n>
+	</MkInfo>
 	<XNotes
 		ref="tlComponent"
 		:no-gap="!$store.state.showGapBetweenNotesInTimeline"
@@ -10,10 +17,13 @@
 <script lang="ts" setup>
 import { ref, computed, provide, onUnmounted } from "vue";
 import XNotes from "@/components/MkNotes.vue";
+import MkInfo from "@/components/MkInfo.vue";
 import * as os from "@/os";
 import { stream } from "@/stream";
 import * as sound from "@/scripts/sound";
 import { $i } from "@/account";
+import { i18n } from "@/i18n";
+import { defaultStore } from "@/store";
 
 const props = defineProps<{
 	src: string;
@@ -64,6 +74,9 @@ let query;
 let connection;
 let connection2;
 
+let tlHint;
+let tlHintClosed;
+
 if (props.src === "antenna") {
 	endpoint = "antennas/notes";
 	query = {
@@ -81,22 +94,37 @@ if (props.src === "antenna") {
 	connection2 = stream.useChannel("main");
 	connection2.on("follow", onChangeFollowing);
 	connection2.on("unfollow", onChangeFollowing);
+
+	tlHint = i18n.ts._tutorial.step5_3;
+	tlHintClosed = defaultStore.state.tlHomeHintClosed;
 } else if (props.src === "local") {
 	endpoint = "notes/local-timeline";
 	connection = stream.useChannel("localTimeline");
 	connection.on("note", prepend);
+
+	tlHint = i18n.ts._tutorial.step5_4;
+	tlHintClosed = defaultStore.state.tlLocalHintClosed;
 } else if (props.src === "recommended") {
 	endpoint = "notes/recommended-timeline";
 	connection = stream.useChannel("recommendedTimeline");
 	connection.on("note", prepend);
+
+	tlHint = i18n.ts._tutorial.step5_6;
+	tlHintClosed = defaultStore.state.tlRecommendedHintClosed;
 } else if (props.src === "social") {
 	endpoint = "notes/hybrid-timeline";
 	connection = stream.useChannel("hybridTimeline");
 	connection.on("note", prepend);
+
+	tlHint = i18n.ts._tutorial.step5_5;
+	tlHintClosed = defaultStore.state.tlSocialHintClosed;
 } else if (props.src === "global") {
 	endpoint = "notes/global-timeline";
 	connection = stream.useChannel("globalTimeline");
 	connection.on("note", prepend);
+
+	tlHint = i18n.ts._tutorial.step5_7;
+	tlHintClosed = defaultStore.state.tlGlobalHintClosed;
 } else if (props.src === "mentions") {
 	endpoint = "notes/mentions";
 	connection = stream.useChannel("main");
@@ -135,6 +163,26 @@ if (props.src === "antenna") {
 	connection.on("note", prepend);
 }
 
+function closeHint() {
+	switch (props.src) {
+		case 'home':
+			defaultStore.set("tlHomeHintClosed", true);
+			break;
+		case 'local':
+			defaultStore.set("tlLocalHintClosed", true);
+			break;
+		case 'recommended':
+			defaultStore.set("tlRecommendedHintClosed", true);
+			break;
+		case 'social':
+			defaultStore.set("tlSocialHintClosed", true);
+			break;
+		case 'global':
+			defaultStore.set("tlGlobalHintClosed", true);
+			break;
+	}
+}
+
 const pagination = {
 	endpoint: endpoint,
 	limit: 10,
diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts
index 7043c88aa..b6897a65c 100644
--- a/packages/client/src/store.ts
+++ b/packages/client/src/store.ts
@@ -26,6 +26,26 @@ export const defaultStore = markRaw(
 			where: "account",
 			default: 0,
 		},
+		tlHomeHintClosed: {
+			where: "device",
+			default: false,
+		},
+		tlLocalHintClosed: {
+			where: "device",
+			default: false,
+		},
+		tlRecommendedHintClosed: {
+			where: "device",
+			default: false,
+		},
+		tlSocialHintClosed: {
+			where: "device",
+			default: false,
+		},
+		tlGlobalHintClosed: {
+			where: "device",
+			default: false,
+		},
 		keepCw: {
 			where: "account",
 			default: true,

From db6f0686b9eafd8a68b4afeee6008d20ca4eeb40 Mon Sep 17 00:00:00 2001
From: Freeplay <Freeplay@duck.com>
Date: Mon, 29 May 2023 14:55:00 -0400
Subject: [PATCH 064/198] closeable prop

---
 packages/client/src/components/MkInfo.vue     | 3 ++-
 packages/client/src/components/MkTimeline.vue | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/packages/client/src/components/MkInfo.vue b/packages/client/src/components/MkInfo.vue
index 848a4e37a..4ab4c1eba 100644
--- a/packages/client/src/components/MkInfo.vue
+++ b/packages/client/src/components/MkInfo.vue
@@ -3,7 +3,7 @@
 		<i v-if="warn" class="ph-warning ph-bold ph-lg"></i>
 		<i v-else class="ph-bold ph-lg" :class="icon ? `ph-${icon}` : 'ph-info'"></i>
 		<slot></slot>
-		<button class="_button close" @click.stop="close">
+		<button v-if="closeable" class="_button close" @click.stop="close">
 			<i class="ph-x ph-bold ph-lg"></i>
 		</button>
 	</div>
@@ -18,6 +18,7 @@ defineProps<{
 	icon?: string;
 	warn?: boolean;
 	card?: boolean;
+	closeable?: boolean;
 }>();
 
 const emit = defineEmits<{
diff --git a/packages/client/src/components/MkTimeline.vue b/packages/client/src/components/MkTimeline.vue
index 01885beec..9712fb023 100644
--- a/packages/client/src/components/MkTimeline.vue
+++ b/packages/client/src/components/MkTimeline.vue
@@ -1,5 +1,5 @@
 <template>
-	<MkInfo v-if="tlHint && !tlHintClosed" class="_gap" @close="closeHint"> 
+	<MkInfo v-if="tlHint && !tlHintClosed" :closeable="true" class="_gap" @close="closeHint"> 
 		<I18n
 			:src="tlHint"
 		>

From 7def5ce16ca9f1f053bc4fc4f503ea52c85a88ca Mon Sep 17 00:00:00 2001
From: Freeplay <Freeplay@duck.com>
Date: Mon, 29 May 2023 15:08:33 -0400
Subject: [PATCH 065/198] Add lists hint

---
 locales/en-US.yml                             |  1 +
 .../client/src/components/MkPagination.vue    |  3 +++
 packages/client/src/pages/my-lists/index.vue  | 27 +++++++++----------
 3 files changed, 16 insertions(+), 15 deletions(-)

diff --git a/locales/en-US.yml b/locales/en-US.yml
index 437a9cd35..0bc53783f 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -83,6 +83,7 @@ exportRequested: "You've requested an export. This may take a while. It will be
   \ to your Drive once completed."
 importRequested: "You've requested an import. This may take a while."
 lists: "Lists"
+listsDesc: "Lists let you create timelines with specified users. They can be accessed from the timelines page."
 noLists: "You don't have any lists"
 note: "Post"
 notes: "Posts"
diff --git a/packages/client/src/components/MkPagination.vue b/packages/client/src/components/MkPagination.vue
index 564b214e0..4caea737e 100644
--- a/packages/client/src/components/MkPagination.vue
+++ b/packages/client/src/components/MkPagination.vue
@@ -490,5 +490,8 @@ defineExpose({
 .list > :deep(._button) {
 	margin-inline: auto;
 	margin-bottom: 16px;
+	&:last-of-type:not(:first-child) {
+		margin-top: 16px;
+	}
 }
 </style>
diff --git a/packages/client/src/pages/my-lists/index.vue b/packages/client/src/pages/my-lists/index.vue
index 6ecfd12f4..e3c4da3cc 100644
--- a/packages/client/src/pages/my-lists/index.vue
+++ b/packages/client/src/pages/my-lists/index.vue
@@ -8,16 +8,17 @@
 		/></template>
 		<MkSpacer :content-max="700">
 			<div class="qkcjvfiv">
-				<div class="buttonWrapper">
+				<MkInfo
+					class="_gap"
+					:icon="'list-bullets'"
+					:card="true"
+				>
+					<p>{{ i18n.ts.listsDesc }}</p>
 					<MkButton primary class="add" @click="create"
 						><i class="ph-plus ph-bold ph-lg"></i>
 						{{ i18n.ts.createList }}</MkButton
 					>
-					<MkButton @click="deleteAll"
-						><i class="ph-trash ph-bold ph-lg"></i>
-						{{ i18n.ts.deleteAll }}</MkButton
-					>
-				</div>
+				</MkInfo>
 
 				<MkPagination
 					v-slot="{ items }"
@@ -34,6 +35,10 @@
 						<div class="name">{{ list.name }}</div>
 						<MkAvatars :user-ids="list.userIds" />
 					</MkA>
+					<MkButton @click="deleteAll"
+						><i class="ph-trash ph-bold ph-lg"></i>
+						{{ i18n.ts.deleteAll }}</MkButton
+					>
 				</MkPagination>
 			</div>
 		</MkSpacer>
@@ -45,6 +50,7 @@ import {} from "vue";
 import MkPagination from "@/components/MkPagination.vue";
 import MkButton from "@/components/MkButton.vue";
 import MkAvatars from "@/components/MkAvatars.vue";
+import MkInfo from "@/components/MkInfo.vue";
 import * as os from "@/os";
 import { i18n } from "@/i18n";
 import { definePageMetadata } from "@/scripts/page-metadata";
@@ -92,15 +98,6 @@ definePageMetadata({
 
 <style lang="scss" scoped>
 .qkcjvfiv {
-	> .buttonWrapper {
-		display: grid;
-		justify-content: center;
-
-		> .add {
-			margin: 0 auto var(--margin) auto;
-		}
-	}
-
 	> .lists {
 		> .list {
 			display: block;

From cc77c6c175aea7f900e09b02539dd8d6a856886c Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 12:16:35 -0700
Subject: [PATCH 066/198] locales

---
 locales/ca-ES.yml                          |  4 ++--
 locales/en-US.yml                          |  3 ++-
 locales/ja-JP.yml                          |  3 ++-
 locales/pl-PL.yml                          |  3 ++-
 locales/ru-RU.yml                          |  4 ++--
 packages/client/src/scripts/index-posts.ts |  1 +
 packages/client/src/scripts/search.ts      | 13 ++-----------
 7 files changed, 13 insertions(+), 18 deletions(-)

diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index f64f70583..f438b4f9a 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -1802,8 +1802,8 @@ pushNotificationNotSupported: El vostre navegador o servidor no admet notificaci
   push
 license: Llicència
 indexPosts: Índex de notes
-indexFrom: Índex a partir de l'ID de Publicacions (deixeu en blanc per indexar cada
-  publicació)
+indexFrom: Índex a partir de l'ID de Publicacions
+indexFromDescription: Deixeu en blanc per indexar cada publicació
 indexNotice: Ara indexant. Això probablement trigarà una estona, si us plau, no reinicieu
   el servidor durant almenys una hora.
 _instanceTicker:
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 5184df4be..5b6af9bed 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1065,7 +1065,8 @@ migrationConfirm: "Are you absolutely sure you want to migrate your account to {
 defaultReaction: "Default emoji reaction for outgoing and incoming posts"
 license: "License"
 indexPosts: "Index Posts"
-indexFrom: "Index from Post ID onwards (leave blank to index every post)"
+indexFrom: "Index from Post ID onwards"
+indexFromDescription: "Leave blank to index every post"
 indexNotice: "Now indexing. This will probably take a while, please don't restart\
   \ your server for at least an hour."
 customKaTeXMacro: "Custom KaTeX macros"
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 84c894c7a..94454f3f9 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -966,7 +966,8 @@ migrationConfirm: "本当にこのアカウントを {account} に引っ越し
 defaultReaction: "リモートとローカルの投稿に対するデフォルトの絵文字リアクション"
 license: "ライセンス"
 indexPosts: "投稿をインデックス"
-indexFrom: "この投稿ID以降をインデックスする(空白で全ての投稿を指定します)"
+indexFrom: "この投稿ID以降をインデックスする"
+indexFromDescription: "空白で全ての投稿を指定します"
 indexNotice: "インデックスを開始しました。完了まで時間がかかる場合があるため、少なくとも1時間はサーバーを再起動しないでください。"
 customKaTeXMacro: "カスタムKaTeXマクロ"
 customKaTeXMacroDescription: "数式入力を楽にするためのマクロを設定しましょう!記法はLaTeXにおけるコマンドの定義と同様に \\newcommand{\\\
diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml
index c127c5a52..659e204af 100644
--- a/locales/pl-PL.yml
+++ b/locales/pl-PL.yml
@@ -1898,7 +1898,8 @@ sendPushNotificationReadMessageCaption: Powiadomienie zawierające tekst "{empty
 defaultReaction: Domyślna reakcja emoji dla wychodzących i przychodzących wpisów
 license: Licencja
 indexPosts: Indeksuj wpisy
-indexFrom: Indeksuj wpisy od ID (zostaw puste dla indeksowania wszystkich wpisów)
+indexFrom: Indeksuj wpisy od ID
+indexFromDescription: Zostaw puste dla indeksowania wszystkich wpisów
 indexNotice: Indeksuję. Zapewne zajmie to chwilę, nie restartuj serwera przez co najmniej
   godzinę.
 customKaTeXMacro: Niestandardowe makra KaTeX
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index 4d94ab287..ad47d7d37 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -1909,8 +1909,8 @@ recommendedInstances: Рекомендованные инстансы
 defaultReaction: Эмодзи реакция по умолчанию для выходящих и исходящих постов
 license: Лицензия
 indexPosts: Индексировать посты
-indexFrom: Индексировать начиная с идентификатора поста и далее (оставьте пустым для
-  индексации каждого поста)
+indexFrom: Индексировать начиная с идентификатора поста и далее
+indexFromDescription: оставьте пустым для индексации каждого поста
 indexNotice: Теперь индексирование. Вероятно, это займет некоторое время, пожалуйста,
   не перезагружайте свой сервер по крайней мере в течение часа.
 customKaTeXMacro: Кастомные KaTex макросы
diff --git a/packages/client/src/scripts/index-posts.ts b/packages/client/src/scripts/index-posts.ts
index 9817a8328..94b545e90 100644
--- a/packages/client/src/scripts/index-posts.ts
+++ b/packages/client/src/scripts/index-posts.ts
@@ -4,6 +4,7 @@ import * as os from "@/os";
 export async function indexPosts() {
 	const { canceled, result: index } = await os.inputText({
 		title: i18n.ts.indexFrom,
+		text: i18n.ts.indexFromDescription,
 	});
 	if (canceled) return;
 
diff --git a/packages/client/src/scripts/search.ts b/packages/client/src/scripts/search.ts
index 379f09c08..2b52dc668 100644
--- a/packages/client/src/scripts/search.ts
+++ b/packages/client/src/scripts/search.ts
@@ -3,16 +3,7 @@ import { i18n } from "@/i18n";
 import { mainRouter } from "@/router";
 
 export async function search() {
-	let searchOptions = "";
-
-	let meta = null;
-
-	os.api("server-info", {}).then((res) => {
-		meta = res;
-	});
-
-	if (meta.meilisearch.health === "available") {
-		searchOptions =
+	const searchOptions  =
 			"Advanced search operators\n" +
 			"from:user => filter by user\n" +
 			"has:image/video/audio/text/file => filter by attachment types\n" +
@@ -22,7 +13,7 @@ export async function search() {
 			'"text" => get posts with exact text between quotes\n' +
 			"filter:following => show results only from users you follow\n" +
 			"filter:followers => show results only from followers\n";
-	}
+
 	const { canceled, result: query } = await os.inputText({
 		title: i18n.ts.search,
 		placeholder: i18n.ts.searchPlaceholder,

From 6843ee409ffb58d2b4d56682d999b8c47f585a57 Mon Sep 17 00:00:00 2001
From: Freeplay <Freeplay@duck.com>
Date: Mon, 29 May 2023 15:19:53 -0400
Subject: [PATCH 067/198] forgot to give aria to close button o o p s

---
 packages/client/src/components/MkInfo.vue | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/packages/client/src/components/MkInfo.vue b/packages/client/src/components/MkInfo.vue
index 4ab4c1eba..2e535739e 100644
--- a/packages/client/src/components/MkInfo.vue
+++ b/packages/client/src/components/MkInfo.vue
@@ -3,7 +3,7 @@
 		<i v-if="warn" class="ph-warning ph-bold ph-lg"></i>
 		<i v-else class="ph-bold ph-lg" :class="icon ? `ph-${icon}` : 'ph-info'"></i>
 		<slot></slot>
-		<button v-if="closeable" class="_button close" @click.stop="close">
+		<button v-if="closeable" v-tooltip="i18n.ts.close" class="_button close" @click.stop="close">
 			<i class="ph-x ph-bold ph-lg"></i>
 		</button>
 	</div>
@@ -11,6 +11,7 @@
 
 <script lang="ts" setup>
 import { ref } from "vue";
+import { i18n } from "@/i18n";
 
 const visible = ref(true);
 

From 6119b18a1dafcfb78d65a44ed551a03f21e6c72a Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 12:22:03 -0700
Subject: [PATCH 068/198] chore: :technologist: pull request template

---
 pull_request_template.yml | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)
 create mode 100644 pull_request_template.yml

diff --git a/pull_request_template.yml b/pull_request_template.yml
new file mode 100644
index 000000000..f45c199aa
--- /dev/null
+++ b/pull_request_template.yml
@@ -0,0 +1,28 @@
+name: Pull Request
+about: Create a pull request
+title: "[PR]: "
+body:
+  - type: markdown
+    attributes:
+      value: |
+                Thanks for taking the time to fill out this bug report!
+  - type: textarea
+    id: about
+    attributes:
+      label: What does this PR do?
+      description: Please give us a brief description of what this PR does.
+      placeholder: Makes Calckey so amazing by...
+    validations:
+      required: true
+  - type: checkboxes
+    id: terms
+    attributes:
+      label: Contribution Guidelines
+      description: By submitting this issue, you agree to follow our [Contribution Guidelines](https://codeberg.org/calckey/calckey/src/branch/develop/CONTRIBUTING.md)
+      options:
+        - label: I agree to follow this project's Contribution Guidelines
+          required: true
+        - label: I have made sure to test this pull request
+          required: true
+        - label: I have made sure to run `pnpm run format` before submitting this pull request
+          required: true

From 03cf3c336dd0adf23d944aaa8eaee6c15a8e8db0 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 12:23:19 -0700
Subject: [PATCH 069/198] typo

---
 pull_request_template.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pull_request_template.yml b/pull_request_template.yml
index f45c199aa..41d04f59f 100644
--- a/pull_request_template.yml
+++ b/pull_request_template.yml
@@ -5,7 +5,7 @@ body:
   - type: markdown
     attributes:
       value: |
-                Thanks for taking the time to fill out this bug report!
+                Thanks for taking the time to make Calckey better!
   - type: textarea
     id: about
     attributes:

From b00d1afe9db2a878f0a8fc63992807a708657a74 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 12:28:58 -0700
Subject: [PATCH 070/198] dev33

---
 package.json                          | 2 +-
 packages/client/src/scripts/search.ts | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/package.json b/package.json
index de993cde0..47b379b8a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "calckey",
-	"version": "14.0.0-dev32",
+	"version": "14.0.0-dev33",
 	"codename": "aqua",
 	"repository": {
 		"type": "git",
diff --git a/packages/client/src/scripts/search.ts b/packages/client/src/scripts/search.ts
index 2b52dc668..40f09e4bc 100644
--- a/packages/client/src/scripts/search.ts
+++ b/packages/client/src/scripts/search.ts
@@ -17,7 +17,7 @@ export async function search() {
 	const { canceled, result: query } = await os.inputText({
 		title: i18n.ts.search,
 		placeholder: i18n.ts.searchPlaceholder,
-		text: searchOptions,
+		// text: searchOptions,
 	});
 	if (canceled || query == null || query === "") return;
 

From 1dc2a34537f473a21d45996d713d45ad8f99b226 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 12:52:43 -0700
Subject: [PATCH 071/198] remove repetitive errors in groups

---
 packages/client/src/pages/my-groups/index.vue | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/packages/client/src/pages/my-groups/index.vue b/packages/client/src/pages/my-groups/index.vue
index 4c411e71a..ce04f9f60 100644
--- a/packages/client/src/pages/my-groups/index.vue
+++ b/packages/client/src/pages/my-groups/index.vue
@@ -111,6 +111,10 @@ async function leave(group) {
 </script>
 
 <style lang="scss" scoped>
+._fullinfo {
+	display: none !important;
+}
+
 ._card {
 	margin-bottom: 1rem;
 	._title {

From 7303857ee829451d0e4806374de30a08ad64e085 Mon Sep 17 00:00:00 2001
From: Freeplay <Freeplay@duck.com>
Date: Mon, 29 May 2023 15:59:04 -0400
Subject: [PATCH 072/198] typo

---
 locales/en-US.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/locales/en-US.yml b/locales/en-US.yml
index d1f416d32..ece9a9201 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1451,7 +1451,7 @@ _tutorial:
     \ you follow."
   step5_4: "The Local {icon} timeline is where you can see posts from everyone else on this server."
   step5_5: "The Social {icon} timeline is a combination of the Home and Local timelines."
-  step5_6: "The Recommended {icon} timeline is where you can see posts from server\
+  step5_6: "The Recommended {icon} timeline is where you can see posts from servers\
     \ the admins recommend."
   step5_7: "The Global {icon} timeline is where you can see posts from every other\
     \ connected server."

From 5d9ffe86ecf4f30b21ca6472d0f16add674b220b Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 13:03:47 -0700
Subject: [PATCH 073/198] add tooltip to meili pie chart

---
 packages/client/src/widgets/server-metric/meilisearch.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/widgets/server-metric/meilisearch.vue b/packages/client/src/widgets/server-metric/meilisearch.vue
index 8119ff537..b640131e2 100644
--- a/packages/client/src/widgets/server-metric/meilisearch.vue
+++ b/packages/client/src/widgets/server-metric/meilisearch.vue
@@ -1,6 +1,6 @@
 <template>
 	<div class="verusivbr">
-		<XPie class="pie" :value="progress" />
+		<XPie v-tooltip="i18n.ts.meiliIndexCount" class="pie" :value="progress" />
 		<div>
 			<p><i class="ph-file-search ph-bold ph-lg"></i>MeiliSearch</p>
 			<p>{{ i18n.ts._widgets.meiliStatus }}: {{ available }}</p>

From 08f31cce4ca6b424d4aaa40672442582bb0d98bf Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 13:16:41 -0700
Subject: [PATCH 074/198] fix: server info widget images

---
 packages/client/src/widgets/server-info.vue | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)

diff --git a/packages/client/src/widgets/server-info.vue b/packages/client/src/widgets/server-info.vue
index d24dd8a98..19cfbb06a 100644
--- a/packages/client/src/widgets/server-info.vue
+++ b/packages/client/src/widgets/server-info.vue
@@ -3,19 +3,17 @@
 		<div
 			:class="$style.container"
 			:style="{
-				backgroundImage: $instance.bannerUrl
-					? `url(${$instance.bannerUrl})`
-					: null,
+				backgroundImage: `url(${$instance.bannerUrl})`,
 			}"
 		>
 			<div :class="$style.iconContainer">
 				<img
 					:src="
-						$instance.iconUrl ??
-						$instance.faviconUrl ??
-						'/favicon.ico'
-					"
-					alt=""
+							$instance.iconUrl ||
+							$instance.faviconUrl ||
+							'/favicon.ico'
+						"
+					alt="Instance logo"
 					:class="$style.icon"
 				/>
 			</div>
@@ -107,7 +105,7 @@ defineExpose<WidgetComponentExpose>({
 		-1px 1px 0 var(--bg), 1px 1px 0 var(--bg);
 }
 
-.host {
+.name {
 	font-weight: bold;
 }
 </style>

From 8dda62f953a8fc32c4e70240207f66f40788edf2 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 13:17:42 -0700
Subject: [PATCH 075/198] chore: formatting

---
 package.json                                  |  2 +-
 packages/client/src/components/MkInfo.vue     | 14 ++++++++++---
 packages/client/src/components/MkTimeline.vue | 21 +++++++++++--------
 packages/client/src/pages/channels.vue        |  4 +++-
 .../client/src/pages/my-antennas/index.vue    | 15 +++++--------
 packages/client/src/pages/my-clips/index.vue  |  5 +----
 packages/client/src/pages/my-lists/index.vue  |  6 +-----
 packages/client/src/scripts/search.ts         | 20 +++++++++---------
 packages/client/src/widgets/server-info.vue   |  8 +++----
 .../src/widgets/server-metric/meilisearch.vue |  6 +++++-
 10 files changed, 53 insertions(+), 48 deletions(-)

diff --git a/package.json b/package.json
index 47b379b8a..06433d6eb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "calckey",
-	"version": "14.0.0-dev33",
+	"version": "14.0.0-dev34",
 	"codename": "aqua",
 	"repository": {
 		"type": "git",
diff --git a/packages/client/src/components/MkInfo.vue b/packages/client/src/components/MkInfo.vue
index 2e535739e..d8bf68fc1 100644
--- a/packages/client/src/components/MkInfo.vue
+++ b/packages/client/src/components/MkInfo.vue
@@ -1,9 +1,18 @@
 <template>
 	<div v-if="visible" class="info" :class="{ warn, card }">
 		<i v-if="warn" class="ph-warning ph-bold ph-lg"></i>
-		<i v-else class="ph-bold ph-lg" :class="icon ? `ph-${icon}` : 'ph-info'"></i>
+		<i
+			v-else
+			class="ph-bold ph-lg"
+			:class="icon ? `ph-${icon}` : 'ph-info'"
+		></i>
 		<slot></slot>
-		<button v-if="closeable" v-tooltip="i18n.ts.close" class="_button close" @click.stop="close">
+		<button
+			v-if="closeable"
+			v-tooltip="i18n.ts.close"
+			class="_button close"
+			@click.stop="close"
+		>
 			<i class="ph-x ph-bold ph-lg"></i>
 		</button>
 	</div>
@@ -30,7 +39,6 @@ function close() {
 	visible.value = false;
 	emit("close");
 }
-
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/client/src/components/MkTimeline.vue b/packages/client/src/components/MkTimeline.vue
index 9712fb023..11fe175d9 100644
--- a/packages/client/src/components/MkTimeline.vue
+++ b/packages/client/src/components/MkTimeline.vue
@@ -1,8 +1,11 @@
 <template>
-	<MkInfo v-if="tlHint && !tlHintClosed" :closeable="true" class="_gap" @close="closeHint"> 
-		<I18n
-			:src="tlHint"
-		>
+	<MkInfo
+		v-if="tlHint && !tlHintClosed"
+		:closeable="true"
+		class="_gap"
+		@close="closeHint"
+	>
+		<I18n :src="tlHint">
 			<template #icon></template>
 		</I18n>
 	</MkInfo>
@@ -165,19 +168,19 @@ if (props.src === "antenna") {
 
 function closeHint() {
 	switch (props.src) {
-		case 'home':
+		case "home":
 			defaultStore.set("tlHomeHintClosed", true);
 			break;
-		case 'local':
+		case "local":
 			defaultStore.set("tlLocalHintClosed", true);
 			break;
-		case 'recommended':
+		case "recommended":
 			defaultStore.set("tlRecommendedHintClosed", true);
 			break;
-		case 'social':
+		case "social":
 			defaultStore.set("tlSocialHintClosed", true);
 			break;
-		case 'global':
+		case "global":
 			defaultStore.set("tlGlobalHintClosed", true);
 			break;
 	}
diff --git a/packages/client/src/pages/channels.vue b/packages/client/src/pages/channels.vue
index c1e34ea44..b79985baf 100644
--- a/packages/client/src/pages/channels.vue
+++ b/packages/client/src/pages/channels.vue
@@ -7,7 +7,9 @@
 				:tabs="headerTabs"
 		/></template>
 		<MkSpacer :content-max="700">
-			<MkInfo class="_gap" :warn="true">{{ i18n.ts.channelFederationWarn }}</MkInfo>
+			<MkInfo class="_gap" :warn="true">{{
+				i18n.ts.channelFederationWarn
+			}}</MkInfo>
 			<swiper
 				:round-lengths="true"
 				:touch-angle="25"
diff --git a/packages/client/src/pages/my-antennas/index.vue b/packages/client/src/pages/my-antennas/index.vue
index d8c9617ec..259c6db69 100644
--- a/packages/client/src/pages/my-antennas/index.vue
+++ b/packages/client/src/pages/my-antennas/index.vue
@@ -8,11 +8,7 @@
 		/></template>
 		<MkSpacer :content-max="700">
 			<div class="ieepwinx">
-				<MkInfo
-					class="_gap"
-					:icon="'flying-saucer'"
-					:card="true"
-				>
+				<MkInfo class="_gap" :icon="'flying-saucer'" :card="true">
 					<p>{{ i18n.ts.antennasDesc }}</p>
 					<MkButton
 						:link="true"
@@ -24,10 +20,7 @@
 					>
 				</MkInfo>
 				<div class="">
-					<MkPagination
-						ref="list"
-						:pagination="pagination"
-					>
+					<MkPagination ref="list" :pagination="pagination">
 						<template #default="{ items }">
 							<div v-for="antenna in items" :key="antenna.id">
 								<MkA
@@ -35,7 +28,9 @@
 									:link="true"
 									:to="`/timeline/antenna/${antenna.id}`"
 								>
-									<i class="ph-flying-saucer ph-bold ph-lg"></i
+									<i
+										class="ph-flying-saucer ph-bold ph-lg"
+									></i
 									><i
 										:class="`${
 											antenna.hasUnreadNote
diff --git a/packages/client/src/pages/my-clips/index.vue b/packages/client/src/pages/my-clips/index.vue
index 2d9ce3db6..f88ee5a9b 100644
--- a/packages/client/src/pages/my-clips/index.vue
+++ b/packages/client/src/pages/my-clips/index.vue
@@ -14,10 +14,7 @@
 					class="list"
 				>
 					<template #empty>
-						<MkInfo
-							:icon="'paperclip'"
-							:card="true"
-						>
+						<MkInfo :icon="'paperclip'" :card="true">
 							<p>{{ i18n.ts.clipsDesc }}</p>
 						</MkInfo>
 					</template>
diff --git a/packages/client/src/pages/my-lists/index.vue b/packages/client/src/pages/my-lists/index.vue
index e3c4da3cc..cb3a74f57 100644
--- a/packages/client/src/pages/my-lists/index.vue
+++ b/packages/client/src/pages/my-lists/index.vue
@@ -8,11 +8,7 @@
 		/></template>
 		<MkSpacer :content-max="700">
 			<div class="qkcjvfiv">
-				<MkInfo
-					class="_gap"
-					:icon="'list-bullets'"
-					:card="true"
-				>
+				<MkInfo class="_gap" :icon="'list-bullets'" :card="true">
 					<p>{{ i18n.ts.listsDesc }}</p>
 					<MkButton primary class="add" @click="create"
 						><i class="ph-plus ph-bold ph-lg"></i>
diff --git a/packages/client/src/scripts/search.ts b/packages/client/src/scripts/search.ts
index 40f09e4bc..03f61f1f0 100644
--- a/packages/client/src/scripts/search.ts
+++ b/packages/client/src/scripts/search.ts
@@ -3,16 +3,16 @@ import { i18n } from "@/i18n";
 import { mainRouter } from "@/router";
 
 export async function search() {
-	const searchOptions  =
-			"Advanced search operators\n" +
-			"from:user => filter by user\n" +
-			"has:image/video/audio/text/file => filter by attachment types\n" +
-			"domain:domain.com => filter by domain\n" +
-			"before:Date => show posts made before Date\n" +
-			"after:Date => show posts made after Date\n" +
-			'"text" => get posts with exact text between quotes\n' +
-			"filter:following => show results only from users you follow\n" +
-			"filter:followers => show results only from followers\n";
+	// const searchOptions =
+	// 	"Advanced search operators\n" +
+	// 	"from:user => filter by user\n" +
+	// 	"has:image/video/audio/text/file => filter by attachment types\n" +
+	// 	"domain:domain.com => filter by domain\n" +
+	// 	"before:Date => show posts made before Date\n" +
+	// 	"after:Date => show posts made after Date\n" +
+	// 	'"text" => get posts with exact text between quotes\n' +
+	// 	"filter:following => show results only from users you follow\n" +
+	// 	"filter:followers => show results only from followers\n";
 
 	const { canceled, result: query } = await os.inputText({
 		title: i18n.ts.search,
diff --git a/packages/client/src/widgets/server-info.vue b/packages/client/src/widgets/server-info.vue
index 19cfbb06a..a608c26ce 100644
--- a/packages/client/src/widgets/server-info.vue
+++ b/packages/client/src/widgets/server-info.vue
@@ -9,10 +9,10 @@
 			<div :class="$style.iconContainer">
 				<img
 					:src="
-							$instance.iconUrl ||
-							$instance.faviconUrl ||
-							'/favicon.ico'
-						"
+						$instance.iconUrl ||
+						$instance.faviconUrl ||
+						'/favicon.ico'
+					"
 					alt="Instance logo"
 					:class="$style.icon"
 				/>
diff --git a/packages/client/src/widgets/server-metric/meilisearch.vue b/packages/client/src/widgets/server-metric/meilisearch.vue
index b640131e2..f8668c61b 100644
--- a/packages/client/src/widgets/server-metric/meilisearch.vue
+++ b/packages/client/src/widgets/server-metric/meilisearch.vue
@@ -1,6 +1,10 @@
 <template>
 	<div class="verusivbr">
-		<XPie v-tooltip="i18n.ts.meiliIndexCount" class="pie" :value="progress" />
+		<XPie
+			v-tooltip="i18n.ts.meiliIndexCount"
+			class="pie"
+			:value="progress"
+		/>
 		<div>
 			<p><i class="ph-file-search ph-bold ph-lg"></i>MeiliSearch</p>
 			<p>{{ i18n.ts._widgets.meiliStatus }}: {{ available }}</p>

From 71a78a9305c59c799ec38200a965e291b2ac3a9d Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 13:49:38 -0700
Subject: [PATCH 076/198] refactor: :coffin: remove old metrics view

---
 packages/client/src/pages/admin/metrics.vue | 554 --------------------
 1 file changed, 554 deletions(-)
 delete mode 100644 packages/client/src/pages/admin/metrics.vue

diff --git a/packages/client/src/pages/admin/metrics.vue b/packages/client/src/pages/admin/metrics.vue
deleted file mode 100644
index d0b9de74b..000000000
--- a/packages/client/src/pages/admin/metrics.vue
+++ /dev/null
@@ -1,554 +0,0 @@
-<template>
-	<div class="_debobigegoItem">
-		<div class="_debobigegoLabel">
-			<i class="ph-microchip ph-bold ph-lg"></i>
-			{{ i18n.ts.cpuAndMemory }}
-		</div>
-		<div class="_debobigegoPanel xhexznfu">
-			<div>
-				<canvas :ref="cpumem"></canvas>
-			</div>
-			<div v-if="serverInfo">
-				<div class="_table">
-					<div class="_row">
-						<div class="_cell">
-							<div class="_label">MEM total</div>
-							{{ bytes(serverInfo.mem.total) }}
-						</div>
-						<div class="_cell">
-							<div class="_label">MEM used</div>
-							{{ bytes(memUsage) }} ({{
-								(
-									(memUsage / serverInfo.mem.total) *
-									100
-								).toFixed(0)
-							}}%)
-						</div>
-						<div class="_cell">
-							<div class="_label">MEM free</div>
-							{{ bytes(serverInfo.mem.total - memUsage) }} ({{
-								(
-									((serverInfo.mem.total - memUsage) /
-										serverInfo.mem.total) *
-									100
-								).toFixed(0)
-							}}%)
-						</div>
-					</div>
-				</div>
-			</div>
-		</div>
-	</div>
-	<div class="_debobigegoItem">
-		<div class="_debobigegoLabel">
-			<i class="ph-hard-drives ph-bold ph-lg"></i> {{ i18n.ts.disk }}
-		</div>
-		<div class="_debobigegoPanel xhexznfu">
-			<div>
-				<canvas :ref="disk"></canvas>
-			</div>
-			<div v-if="serverInfo">
-				<div class="_table">
-					<div class="_row">
-						<div class="_cell">
-							<div class="_label">Disk total</div>
-							{{ bytes(serverInfo.fs.total) }}
-						</div>
-						<div class="_cell">
-							<div class="_label">Disk used</div>
-							{{ bytes(serverInfo.fs.used) }} ({{
-								(
-									(serverInfo.fs.used / serverInfo.fs.total) *
-									100
-								).toFixed(0)
-							}}%)
-						</div>
-						<div class="_cell">
-							<div class="_label">Disk free</div>
-							{{
-								bytes(serverInfo.fs.total - serverInfo.fs.used)
-							}}
-							({{
-								(
-									((serverInfo.fs.total -
-										serverInfo.fs.used) /
-										serverInfo.fs.total) *
-									100
-								).toFixed(0)
-							}}%)
-						</div>
-					</div>
-				</div>
-			</div>
-		</div>
-	</div>
-	<div class="_debobigegoItem">
-		<div class="_debobigegoLabel">
-			<i class="ph-swap ph-bold ph-lg"></i> {{ i18n.ts.network }}
-		</div>
-		<div class="_debobigegoPanel xhexznfu">
-			<div>
-				<canvas :ref="net"></canvas>
-			</div>
-			<div v-if="serverInfo">
-				<div class="_table">
-					<div class="_row">
-						<div class="_cell">
-							<div class="_label">Interface</div>
-							{{ serverInfo.net.interface }}
-						</div>
-					</div>
-				</div>
-			</div>
-		</div>
-	</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, markRaw } from "vue";
-import {
-	Chart,
-	ArcElement,
-	LineElement,
-	BarElement,
-	PointElement,
-	BarController,
-	LineController,
-	CategoryScale,
-	LinearScale,
-	Legend,
-	Title,
-	Tooltip,
-	SubTitle,
-} from "chart.js";
-import MkwFederation from "../../widgets/federation.vue";
-import MkButton from "@/components/MkButton.vue";
-import MkSelect from "@/components/form/select.vue";
-import MkInput from "@/components/form/input.vue";
-import MkContainer from "@/components/MkContainer.vue";
-import MkFolder from "@/components/MkFolder.vue";
-import { version, url } from "@/config";
-import bytes from "@/filters/bytes";
-import number from "@/filters/number";
-import { i18n } from "@/i18n";
-
-Chart.register(
-	ArcElement,
-	LineElement,
-	BarElement,
-	PointElement,
-	BarController,
-	LineController,
-	CategoryScale,
-	LinearScale,
-	Legend,
-	Title,
-	Tooltip,
-	SubTitle
-);
-
-const alpha = (hex, a) => {
-	const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!;
-	const r = parseInt(result[1], 16);
-	const g = parseInt(result[2], 16);
-	const b = parseInt(result[3], 16);
-	return `rgba(${r}, ${g}, ${b}, ${a})`;
-};
-import * as os from "@/os";
-import { stream } from "@/stream";
-
-export default defineComponent({
-	components: {
-		MkButton,
-		MkSelect,
-		MkInput,
-		MkContainer,
-		MkFolder,
-		MkwFederation,
-	},
-
-	data() {
-		return {
-			version,
-			url,
-			stats: null,
-			serverInfo: null,
-			connection: null,
-			queueConnection: markRaw(stream.useChannel("queueStats")),
-			memUsage: 0,
-			chartCpuMem: null,
-			chartNet: null,
-			jobs: [],
-			logs: [],
-			logLevel: "all",
-			logDomain: "",
-			modLogs: [],
-			dbInfo: null,
-			overviewHeight: "1fr",
-			queueHeight: "1fr",
-			paused: false,
-			i18n,
-		};
-	},
-
-	computed: {
-		gridColor() {
-			// TODO: var(--panel)の色が暗いか明るいかで判定する
-			return this.$store.state.darkMode
-				? "rgba(255, 255, 255, 0.1)"
-				: "rgba(0, 0, 0, 0.1)";
-		},
-	},
-
-	mounted() {
-		this.fetchJobs();
-
-		Chart.defaults.color = getComputedStyle(
-			document.documentElement
-		).getPropertyValue("--fg");
-
-		os.api("admin/server-info", {}).then((res) => {
-			this.serverInfo = res;
-
-			this.connection = markRaw(stream.useChannel("serverStats"));
-			this.connection.on("stats", this.onStats);
-			this.connection.on("statsLog", this.onStatsLog);
-			this.connection.send("requestLog", {
-				id: Math.random().toString().substr(2, 8),
-				length: 150,
-			});
-
-			this.$nextTick(() => {
-				this.queueConnection.send("requestLog", {
-					id: Math.random().toString().substr(2, 8),
-					length: 200,
-				});
-			});
-		});
-	},
-
-	beforeUnmount() {
-		if (this.connection) {
-			this.connection.off("stats", this.onStats);
-			this.connection.off("statsLog", this.onStatsLog);
-			this.connection.dispose();
-		}
-		this.queueConnection.dispose();
-	},
-
-	methods: {
-		cpumem(el) {
-			if (this.chartCpuMem != null) return;
-			this.chartCpuMem = markRaw(
-				new Chart(el, {
-					type: "line",
-					data: {
-						labels: [],
-						datasets: [
-							{
-								label: "CPU",
-								pointRadius: 0,
-								tension: 0,
-								borderWidth: 2,
-								borderColor: "#31748f",
-								backgroundColor: alpha("#31748f", 0.1),
-								data: [],
-							},
-							{
-								label: "MEM (active)",
-								pointRadius: 0,
-								tension: 0,
-								borderWidth: 2,
-								borderColor: "#c4a7e7",
-								backgroundColor: alpha("#c4a7e7", 0.02),
-								data: [],
-							},
-							{
-								label: "MEM (used)",
-								pointRadius: 0,
-								tension: 0,
-								borderWidth: 2,
-								borderColor: "#ebbcba",
-								borderDash: [5, 5],
-								fill: false,
-								data: [],
-							},
-						],
-					},
-					options: {
-						aspectRatio: 3,
-						layout: {
-							padding: {
-								left: 16,
-								right: 16,
-								top: 16,
-								bottom: 0,
-							},
-						},
-						legend: {
-							position: "bottom",
-							labels: {
-								boxWidth: 16,
-							},
-						},
-						scales: {
-							x: {
-								gridLines: {
-									display: false,
-									color: this.gridColor,
-									zeroLineColor: this.gridColor,
-								},
-								ticks: {
-									display: false,
-								},
-							},
-							y: {
-								position: "right",
-								gridLines: {
-									display: true,
-									color: this.gridColor,
-									zeroLineColor: this.gridColor,
-								},
-								ticks: {
-									display: false,
-									max: 100,
-								},
-							},
-						},
-						tooltips: {
-							intersect: false,
-							mode: "index",
-						},
-					},
-				})
-			);
-		},
-
-		net(el) {
-			if (this.chartNet != null) return;
-			this.chartNet = markRaw(
-				new Chart(el, {
-					type: "line",
-					data: {
-						labels: [],
-						datasets: [
-							{
-								label: "In",
-								pointRadius: 0,
-								tension: 0,
-								borderWidth: 2,
-								borderColor: "#94a029",
-								backgroundColor: alpha("#94a029", 0.1),
-								data: [],
-							},
-							{
-								label: "Out",
-								pointRadius: 0,
-								tension: 0,
-								borderWidth: 2,
-								borderColor: "#ff9156",
-								backgroundColor: alpha("#ff9156", 0.1),
-								data: [],
-							},
-						],
-					},
-					options: {
-						aspectRatio: 3,
-						layout: {
-							padding: {
-								left: 16,
-								right: 16,
-								top: 16,
-								bottom: 0,
-							},
-						},
-						legend: {
-							position: "bottom",
-							labels: {
-								boxWidth: 16,
-							},
-						},
-						scales: {
-							x: {
-								gridLines: {
-									display: false,
-									color: this.gridColor,
-									zeroLineColor: this.gridColor,
-								},
-								ticks: {
-									display: false,
-								},
-							},
-							y: {
-								position: "right",
-								gridLines: {
-									display: true,
-									color: this.gridColor,
-									zeroLineColor: this.gridColor,
-								},
-								ticks: {
-									display: false,
-								},
-							},
-						},
-						tooltips: {
-							intersect: false,
-							mode: "index",
-						},
-					},
-				})
-			);
-		},
-
-		disk(el) {
-			if (this.chartDisk != null) return;
-			this.chartDisk = markRaw(
-				new Chart(el, {
-					type: "line",
-					data: {
-						labels: [],
-						datasets: [
-							{
-								label: "Read",
-								pointRadius: 0,
-								tension: 0,
-								borderWidth: 2,
-								borderColor: "#94a029",
-								backgroundColor: alpha("#94a029", 0.1),
-								data: [],
-							},
-							{
-								label: "Write",
-								pointRadius: 0,
-								tension: 0,
-								borderWidth: 2,
-								borderColor: "#ff9156",
-								backgroundColor: alpha("#ff9156", 0.1),
-								data: [],
-							},
-						],
-					},
-					options: {
-						aspectRatio: 3,
-						layout: {
-							padding: {
-								left: 16,
-								right: 16,
-								top: 16,
-								bottom: 0,
-							},
-						},
-						legend: {
-							position: "bottom",
-							labels: {
-								boxWidth: 16,
-							},
-						},
-						scales: {
-							x: {
-								gridLines: {
-									display: false,
-									color: this.gridColor,
-									zeroLineColor: this.gridColor,
-								},
-								ticks: {
-									display: false,
-								},
-							},
-							y: {
-								position: "right",
-								gridLines: {
-									display: true,
-									color: this.gridColor,
-									zeroLineColor: this.gridColor,
-								},
-								ticks: {
-									display: false,
-								},
-							},
-						},
-						tooltips: {
-							intersect: false,
-							mode: "index",
-						},
-					},
-				})
-			);
-		},
-
-		fetchJobs() {
-			os.api("admin/queue/deliver-delayed", {}).then((jobs) => {
-				this.jobs = jobs;
-			});
-		},
-
-		onStats(stats) {
-			if (this.paused) return;
-
-			const cpu = (stats.cpu * 100).toFixed(0);
-			const memActive = (
-				(stats.mem.active / this.serverInfo.mem.total) *
-				100
-			).toFixed(0);
-			const memUsed = (
-				(stats.mem.used / this.serverInfo.mem.total) *
-				100
-			).toFixed(0);
-			this.memUsage = stats.mem.active;
-
-			this.chartCpuMem.data.labels.push("");
-			this.chartCpuMem.data.datasets[0].data.push(cpu);
-			this.chartCpuMem.data.datasets[1].data.push(memActive);
-			this.chartCpuMem.data.datasets[2].data.push(memUsed);
-			this.chartNet.data.labels.push("");
-			this.chartNet.data.datasets[0].data.push(stats.net.rx);
-			this.chartNet.data.datasets[1].data.push(stats.net.tx);
-			this.chartDisk.data.labels.push("");
-			this.chartDisk.data.datasets[0].data.push(stats.fs.r);
-			this.chartDisk.data.datasets[1].data.push(stats.fs.w);
-			if (this.chartCpuMem.data.datasets[0].data.length > 150) {
-				this.chartCpuMem.data.labels.shift();
-				this.chartCpuMem.data.datasets[0].data.shift();
-				this.chartCpuMem.data.datasets[1].data.shift();
-				this.chartCpuMem.data.datasets[2].data.shift();
-				this.chartNet.data.labels.shift();
-				this.chartNet.data.datasets[0].data.shift();
-				this.chartNet.data.datasets[1].data.shift();
-				this.chartDisk.data.labels.shift();
-				this.chartDisk.data.datasets[0].data.shift();
-				this.chartDisk.data.datasets[1].data.shift();
-			}
-			this.chartCpuMem.update();
-			this.chartNet.update();
-			this.chartDisk.update();
-		},
-
-		onStatsLog(statsLog) {
-			for (const stats of [...statsLog].reverse()) {
-				this.onStats(stats);
-			}
-		},
-
-		bytes,
-
-		number,
-
-		pause() {
-			this.paused = true;
-		},
-
-		resume() {
-			this.paused = false;
-		},
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.xhexznfu {
-	> div:nth-child(2) {
-		padding: 16px;
-		border-top: solid 0.5px var(--divider);
-	}
-}
-</style>

From 0f2aecdf597a8ecf3b2df36e1b9eac1b8f218ee7 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 15:10:14 -0700
Subject: [PATCH 077/198] =?UTF-8?q?style:=20=F0=9F=92=84=20server=20metric?=
 =?UTF-8?q?s=20widgets?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 packages/client/src/widgets/server-metric/cpu.vue  |  2 +-
 .../src/widgets/server-metric/meilisearch.vue      | 14 +++++++-------
 packages/client/src/widgets/server-metric/mem.vue  |  2 +-
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/packages/client/src/widgets/server-metric/cpu.vue b/packages/client/src/widgets/server-metric/cpu.vue
index 5aa4d2698..563fe3b0f 100644
--- a/packages/client/src/widgets/server-metric/cpu.vue
+++ b/packages/client/src/widgets/server-metric/cpu.vue
@@ -2,7 +2,7 @@
 	<div class="vrvdvrys">
 		<XPie class="pie" :value="usage" />
 		<div>
-			<p><i class="ph-microchip ph-bold ph-lg"></i>CPU</p>
+			<p><i class="ph-cpu ph-bold ph-lg"></i>CPU</p>
 			<p>{{ meta.cpu.cores }} Logical cores</p>
 			<p>{{ meta.cpu.model }}</p>
 		</div>
diff --git a/packages/client/src/widgets/server-metric/meilisearch.vue b/packages/client/src/widgets/server-metric/meilisearch.vue
index f8668c61b..78032cb6e 100644
--- a/packages/client/src/widgets/server-metric/meilisearch.vue
+++ b/packages/client/src/widgets/server-metric/meilisearch.vue
@@ -8,8 +8,8 @@
 		<div>
 			<p><i class="ph-file-search ph-bold ph-lg"></i>MeiliSearch</p>
 			<p>{{ i18n.ts._widgets.meiliStatus }}: {{ available }}</p>
-			<p>{{ i18n.ts._widgets.meiliSize }}: {{ bytes(total_size, 2) }}</p>
-			<p>{{ i18n.ts._widgets.meiliIndexCount }}: {{ index_count }}</p>
+			<p>{{ i18n.ts._widgets.meiliSize }}: {{ bytes(totalSize, 1) }}</p>
+			<p>{{ i18n.ts._widgets.meiliIndexCount }}: {{ indexCount }}</p>
 		</div>
 	</div>
 	<br />
@@ -29,15 +29,15 @@ const props = defineProps<{
 
 let progress: number = $ref(0);
 let serverStats = $ref(null);
-let total_size: number = $ref(0);
-let index_count: number = $ref(0);
+let totalSize: number = $ref(0);
+let indexCount: number = $ref(0);
 let available: string = $ref("unavailable");
 
 function onStats(stats) {
-	total_size = stats.meilisearch.size;
-	index_count = stats.meilisearch.indexed_count;
+	totalSize = stats.meilisearch.size;
+	indexCount = stats.meilisearch.indexed_count;
 	available = stats.meilisearch.health;
-	progress = index_count / serverStats.notesCount;
+	progress = indexCount / serverStats.notesCount;
 }
 
 onMounted(() => {
diff --git a/packages/client/src/widgets/server-metric/mem.vue b/packages/client/src/widgets/server-metric/mem.vue
index e3ef00d5e..80cad4a82 100644
--- a/packages/client/src/widgets/server-metric/mem.vue
+++ b/packages/client/src/widgets/server-metric/mem.vue
@@ -2,7 +2,7 @@
 	<div class="zlxnikvl">
 		<XPie class="pie" :value="usage" />
 		<div>
-			<p><i class="ph-cpu ph-bold ph-lg"></i>RAM</p>
+			<p><i class="ph-microchip ph-bold ph-lg"></i>RAM</p>
 			<p>Total: {{ bytes(total, 1) }}</p>
 			<p>Used: {{ bytes(used, 1) }}</p>
 			<p>Free: {{ bytes(free, 1) }}</p>

From bf48923be7b80b813bcd89d044778419c9b4491a Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 15:10:25 -0700
Subject: [PATCH 078/198] feat: :sparkles: server metrics in admin overview

---
 .../src/pages/admin/overview.metrics.vue      | 145 ++++++++++++++++++
 packages/client/src/pages/admin/overview.vue  |   6 +
 2 files changed, 151 insertions(+)
 create mode 100644 packages/client/src/pages/admin/overview.metrics.vue

diff --git a/packages/client/src/pages/admin/overview.metrics.vue b/packages/client/src/pages/admin/overview.metrics.vue
new file mode 100644
index 000000000..afd6552a6
--- /dev/null
+++ b/packages/client/src/pages/admin/overview.metrics.vue
@@ -0,0 +1,145 @@
+<template>
+	<div class="_panel" :class="$style.root">
+		<div class="ntjkdlsfk">
+			<div class="_panel">
+				<XPie class="pie" :value="cpuUsage" />
+				<div>
+					<p><i class="ph-cpu ph-bold ph-lg"></i>CPU</p>
+					<p>{{ meta.cpu.cores }} Logical cores</p>
+					<p>{{ meta.cpu.model }}</p>
+				</div>
+			</div>
+
+			<div class="_panel">
+				<XPie class="pie" :value="memUsage" />
+				<div>
+					<p><i class="ph-microchip ph-bold ph-lg"></i>RAM</p>
+					<p>Total: {{ bytes(memTotal, 1) }}</p>
+					<p>Used: {{ bytes(memUsed, 1) }}</p>
+					<p>Free: {{ bytes(memFree, 1) }}</p>
+				</div>
+			</div>
+
+			<div class="_panel">
+				<XPie class="pie" :value="diskUsage" />
+				<div>
+					<p><i class="ph-hard-drives ph-bold ph-lg"></i>Disk</p>
+					<p>Total: {{ bytes(diskTotal, 1) }}</p>
+					<p>Free: {{ bytes(diskAvailable, 1) }}</p>
+					<p>Used: {{ bytes(diskUsed, 1) }}</p>
+				</div>
+			</div>
+
+			<div class="_panel">
+				<XPie class="pie" :value="meiliProgress" />
+				<div>
+					<p>
+						<i class="ph-file-search ph-bold ph-lg"></i>MeiliSearch
+					</p>
+					<p>
+						{{ i18n.ts._widgets.meiliStatus }}: {{ meiliAvailable }}
+					</p>
+					<p>
+						{{ i18n.ts._widgets.meiliSize }}:
+						{{ bytes(meiliTotalSize, 1) }}
+					</p>
+					<p>
+						{{ i18n.ts._widgets.meiliIndexCount }}:
+						{{ meiliIndexCount }}
+					</p>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, onUnmounted } from "vue";
+import XPie from "../../widgets/server-metric/pie.vue";
+import bytes from "@/filters/bytes";
+import { stream } from "@/stream";
+import * as os from "@/os";
+import { i18n } from "@/i18n";
+
+const meta = await os.api("server-info", {});
+const serverStats = await os.api("stats", {});
+
+let cpuUsage: number = $ref(0);
+
+let memUsage: number = $ref(0);
+let memTotal: number = $ref(0);
+let memUsed: number = $ref(0);
+let memFree: number = $ref(0);
+
+let meiliProgress: number = $ref(0);
+let meiliTotalSize: number = $ref(0);
+let meiliIndexCount: number = $ref(0);
+let meiliAvailable: string = $ref("unavailable");
+
+const diskUsage = $computed(() => meta.fs.used / meta.fs.total);
+const diskTotal = $computed(() => meta.fs.total);
+const diskUsed = $computed(() => meta.fs.used);
+const diskAvailable = $computed(() => meta.fs.total - meta.fs.used);
+
+function onStats(stats) {
+	cpuUsage = stats.cpu;
+
+	memUsage = stats.mem.active / meta.mem.total;
+	memTotal = meta.mem.total;
+	memUsed = stats.mem.active;
+	memFree = memTotal - memUsed;
+
+	meiliTotalSize = stats.meilisearch.size;
+	meiliIndexCount = stats.meilisearch.indexed_count;
+	meiliAvailable = stats.meilisearch.health;
+	meiliProgress = meiliIndexCount / serverStats.notesCount;
+}
+
+const connection = stream.useChannel("serverStats");
+onMounted(() => {
+	connection.on("stats", onStats);
+	connection.dispose();
+});
+
+onUnmounted(() => {
+	connection.off("stats", onStats);
+	connection.dispose();
+});
+</script>
+
+<style lang="scss" module>
+.root {
+	padding: 20px;
+}
+
+.ntjkdlsfk {
+	display: flex;
+	padding: 16px;
+
+	> ._panel {
+		> .pie {
+			height: 82px;
+			flex-shrink: 0;
+			margin-right: 16px;
+		}
+
+		> div {
+			flex: 1;
+
+			> p {
+				margin: 0;
+				font-size: 0.8em;
+
+				&:first-child {
+					font-weight: bold;
+					margin-bottom: 4px;
+
+					> i {
+						margin-right: 4px;
+					}
+				}
+			}
+		}
+	}
+}
+</style>
diff --git a/packages/client/src/pages/admin/overview.vue b/packages/client/src/pages/admin/overview.vue
index 4c3391ce4..67da86746 100644
--- a/packages/client/src/pages/admin/overview.vue
+++ b/packages/client/src/pages/admin/overview.vue
@@ -50,6 +50,11 @@
 				<template #header>Inbox queue</template>
 				<XQueue domain="inbox" />
 			</MkFolder>
+
+			<MkFolder class="item">
+				<template #header>Server metrics</template>
+				<XMetrics domain="inbox" />
+			</MkFolder>
 		</div>
 	</MkSpacer>
 </template>
@@ -71,6 +76,7 @@ import XActiveUsers from "./overview.active-users.vue";
 import XStats from "./overview.stats.vue";
 import XModerators from "./overview.moderators.vue";
 import XHeatmap from "./overview.heatmap.vue";
+import XMetrics from "./overview.metrics.vue";
 import MkTagCloud from "@/components/MkTagCloud.vue";
 import { version, url } from "@/config";
 import * as os from "@/os";

From 5c5db40cd4306de1080474f39ac9d0539a4c923c Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 15:10:33 -0700
Subject: [PATCH 079/198] dev35

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 06433d6eb..311e522e7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "calckey",
-	"version": "14.0.0-dev34",
+	"version": "14.0.0-dev35",
 	"codename": "aqua",
 	"repository": {
 		"type": "git",

From 8a0e3ecfa1b3b0bbd56b4ed4f1038013e14b6bff Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 15:21:29 -0700
Subject: [PATCH 080/198] fix query

---
 packages/client/src/pages/admin/overview.metrics.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/pages/admin/overview.metrics.vue b/packages/client/src/pages/admin/overview.metrics.vue
index afd6552a6..cfd497439 100644
--- a/packages/client/src/pages/admin/overview.metrics.vue
+++ b/packages/client/src/pages/admin/overview.metrics.vue
@@ -62,7 +62,7 @@ import * as os from "@/os";
 import { i18n } from "@/i18n";
 
 const meta = await os.api("server-info", {});
-const serverStats = await os.api("stats", {});
+const serverStats = await os.api("stats");
 
 let cpuUsage: number = $ref(0);
 

From 8932099c45b576462cdb52b4ab02b70a7aa997b2 Mon Sep 17 00:00:00 2001
From: Freeplay <Freeplay@duck.com>
Date: Mon, 29 May 2023 18:29:42 -0400
Subject: [PATCH 081/198] fix close button pos in compose box

---
 packages/client/src/components/MkPostForm.vue | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/packages/client/src/components/MkPostForm.vue b/packages/client/src/components/MkPostForm.vue
index e0d2a8f4c..697e1c86f 100644
--- a/packages/client/src/components/MkPostForm.vue
+++ b/packages/client/src/components/MkPostForm.vue
@@ -1030,6 +1030,8 @@ onMounted(() => {
 	}
 
 	> header {
+		display: flex;
+		flex-wrap: wrap;
 		z-index: 1000;
 		height: 66px;
 

From 161927d0e8f6adfa8b57faa95cecc0b8478a8922 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 15:40:02 -0700
Subject: [PATCH 082/198] margin

---
 packages/client/src/components/MkUrlPreview.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/components/MkUrlPreview.vue b/packages/client/src/components/MkUrlPreview.vue
index 8421fab5a..3706f7885 100644
--- a/packages/client/src/components/MkUrlPreview.vue
+++ b/packages/client/src/components/MkUrlPreview.vue
@@ -227,7 +227,7 @@ onUnmounted(() => {
 
 .mk-url-preview {
 	> .expand-tweet {
-		margin-top: 1rem;
+		margin-top: 0.5rem;
 	}
 
 	&.max-width_400px {

From b62fc511213e7008b0c60ad87807e5103028eff1 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 16:13:36 -0700
Subject: [PATCH 083/198] refactor: add back old info display from mkv12

---
 .../src/pages/admin/overview.metrics.vue      | 161 +++++++++++-------
 1 file changed, 101 insertions(+), 60 deletions(-)

diff --git a/packages/client/src/pages/admin/overview.metrics.vue b/packages/client/src/pages/admin/overview.metrics.vue
index cfd497439..fbef55c3d 100644
--- a/packages/client/src/pages/admin/overview.metrics.vue
+++ b/packages/client/src/pages/admin/overview.metrics.vue
@@ -1,52 +1,72 @@
 <template>
-	<div class="_panel" :class="$style.root">
-		<div class="ntjkdlsfk">
-			<div class="_panel">
-				<XPie class="pie" :value="cpuUsage" />
-				<div>
-					<p><i class="ph-cpu ph-bold ph-lg"></i>CPU</p>
-					<p>{{ meta.cpu.cores }} Logical cores</p>
-					<p>{{ meta.cpu.model }}</p>
+	<div class="_panel ntjkdlsfk" :class="$style.root">
+		<div class="container env">
+			<div class="body">
+				<div class="number _panel">
+					<div class="label">Calckey</div>
+					<div class="value _monospace">{{ version }}</div>
 				</div>
-			</div>
-
-			<div class="_panel">
-				<XPie class="pie" :value="memUsage" />
-				<div>
-					<p><i class="ph-microchip ph-bold ph-lg"></i>RAM</p>
-					<p>Total: {{ bytes(memTotal, 1) }}</p>
-					<p>Used: {{ bytes(memUsed, 1) }}</p>
-					<p>Free: {{ bytes(memFree, 1) }}</p>
+				<div v-if="serverInfo" class="number _panel">
+					<div class="label">Node.js</div>
+					<div class="value _monospace">{{ serverInfo.node }}</div>
 				</div>
-			</div>
-
-			<div class="_panel">
-				<XPie class="pie" :value="diskUsage" />
-				<div>
-					<p><i class="ph-hard-drives ph-bold ph-lg"></i>Disk</p>
-					<p>Total: {{ bytes(diskTotal, 1) }}</p>
-					<p>Free: {{ bytes(diskAvailable, 1) }}</p>
-					<p>Used: {{ bytes(diskUsed, 1) }}</p>
+				<div v-if="serverInfo" class="number _panel">
+					<div class="label">PostgreSQL</div>
+					<div class="value _monospace">{{ serverInfo.psql }}</div>
+				</div>
+				<div v-if="serverInfo" class="number _panel">
+					<div class="label">Redis</div>
+					<div class="value _monospace">{{ serverInfo.redis }}</div>
+				</div>
+				<div class="_panel">
+					<XPie class="pie" :value="cpuUsage" />
+					<div>
+						<p><i class="ph-cpu ph-bold ph-lg"></i>CPU</p>
+						<p>{{ meta.cpu.cores }} Logical cores</p>
+						<p>{{ meta.cpu.model }}</p>
+					</div>
 				</div>
-			</div>
 
-			<div class="_panel">
-				<XPie class="pie" :value="meiliProgress" />
-				<div>
-					<p>
-						<i class="ph-file-search ph-bold ph-lg"></i>MeiliSearch
-					</p>
-					<p>
-						{{ i18n.ts._widgets.meiliStatus }}: {{ meiliAvailable }}
-					</p>
-					<p>
-						{{ i18n.ts._widgets.meiliSize }}:
-						{{ bytes(meiliTotalSize, 1) }}
-					</p>
-					<p>
-						{{ i18n.ts._widgets.meiliIndexCount }}:
-						{{ meiliIndexCount }}
-					</p>
+				<div class="_panel">
+					<XPie class="pie" :value="memUsage" />
+					<div>
+						<p><i class="ph-microchip ph-bold ph-lg"></i>RAM</p>
+						<p>Total: {{ bytes(memTotal, 1) }}</p>
+						<p>Used: {{ bytes(memUsed, 1) }}</p>
+						<p>Free: {{ bytes(memFree, 1) }}</p>
+					</div>
+				</div>
+
+				<div class="_panel">
+					<XPie class="pie" :value="diskUsage" />
+					<div>
+						<p><i class="ph-hard-drives ph-bold ph-lg"></i>Disk</p>
+						<p>Total: {{ bytes(diskTotal, 1) }}</p>
+						<p>Free: {{ bytes(diskAvailable, 1) }}</p>
+						<p>Used: {{ bytes(diskUsed, 1) }}</p>
+					</div>
+				</div>
+
+				<div class="_panel">
+					<XPie class="pie" :value="meiliProgress" />
+					<div>
+						<p>
+							<i class="ph-file-search ph-bold ph-lg"></i
+							>MeiliSearch
+						</p>
+						<p>
+							{{ i18n.ts._widgets.meiliStatus }}:
+							{{ meiliAvailable }}
+						</p>
+						<p>
+							{{ i18n.ts._widgets.meiliSize }}:
+							{{ bytes(meiliTotalSize, 1) }}
+						</p>
+						<p>
+							{{ i18n.ts._widgets.meiliIndexCount }}:
+							{{ meiliIndexCount }}
+						</p>
+					</div>
 				</div>
 			</div>
 		</div>
@@ -60,9 +80,11 @@ import bytes from "@/filters/bytes";
 import { stream } from "@/stream";
 import * as os from "@/os";
 import { i18n } from "@/i18n";
+import { version } from "@/config";
 
 const meta = await os.api("server-info", {});
 const serverStats = await os.api("stats");
+const serverInfo = await os.api("admin/server-info");
 
 let cpuUsage: number = $ref(0);
 
@@ -116,26 +138,45 @@ onUnmounted(() => {
 	display: flex;
 	padding: 16px;
 
-	> ._panel {
-		> .pie {
-			height: 82px;
-			flex-shrink: 0;
-			margin-right: 16px;
-		}
+	&.env {
+		> .body {
+			display: grid;
+			grid-gap: 16px;
+			grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
 
-		> div {
-			flex: 1;
+			> .number {
+				padding: 14px 20px;
 
-			> p {
-				margin: 0;
-				font-size: 0.8em;
+				> .label {
+					opacity: 0.7;
+					font-size: 0.8em;
+				}
 
-				&:first-child {
-					font-weight: bold;
-					margin-bottom: 4px;
+				> .value {
+					font-size: 1.1em;
+				}
+			}
 
-					> i {
-						margin-right: 4px;
+			> .pie {
+				height: 82px;
+				flex-shrink: 0;
+				margin-right: 16px;
+			}
+
+			> div {
+				flex: 1;
+
+				> p {
+					margin: 0;
+					font-size: 0.8em;
+
+					&:first-child {
+						font-weight: bold;
+						margin-bottom: 4px;
+
+						> i {
+							margin-right: 4px;
+						}
 					}
 				}
 			}

From 578d023d3207f6b01ec068ccf2b0346f81a69909 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 19:30:20 -0700
Subject: [PATCH 084/198] chore: up bull-board deps

---
 package.json                  |   4 +-
 packages/backend/package.json |   6 +-
 pnpm-lock.yaml                | 200 +++++-----------------------------
 3 files changed, 35 insertions(+), 175 deletions(-)

diff --git a/package.json b/package.json
index 311e522e7..5c57c79cf 100644
--- a/package.json
+++ b/package.json
@@ -36,8 +36,8 @@
 		"chokidar": "^3.3.1"
 	},
 	"dependencies": {
-		"@bull-board/api": "^4.10.2",
-		"@bull-board/ui": "^4.10.2",
+		"@bull-board/api": "5.2.0",
+		"@bull-board/ui": "5.2.0",
 		"@napi-rs/cli": "^2.15.0",
 		"@tensorflow/tfjs": "^3.21.0",
 		"focus-trap": "^7.2.0",
diff --git a/packages/backend/package.json b/packages/backend/package.json
index bffa42c49..3d483e003 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -23,9 +23,9 @@
 		"@tensorflow/tfjs-node": "3.21.1"
 	},
 	"dependencies": {
-		"@bull-board/api": "^4.6.4",
-		"@bull-board/koa": "^4.6.4",
-		"@bull-board/ui": "^4.6.4",
+		"@bull-board/api": "5.2.0",
+		"@bull-board/koa": "5.2.0",
+		"@bull-board/ui": "5.2.0",
 		"@calckey/megalodon": "5.2.0",
 		"@discordapp/twemoji": "14.0.2",
 		"@elastic/elasticsearch": "7.17.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4c9b78673..d69f3142a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -12,11 +12,11 @@ importers:
   .:
     dependencies:
       '@bull-board/api':
-        specifier: ^4.10.2
-        version: 4.10.2
+        specifier: 5.2.0
+        version: 5.2.0(@bull-board/ui@5.2.0)
       '@bull-board/ui':
-        specifier: ^4.10.2
-        version: 4.10.2
+        specifier: 5.2.0
+        version: 5.2.0
       '@napi-rs/cli':
         specifier: ^2.15.0
         version: 2.15.0
@@ -28,7 +28,7 @@ importers:
         version: 7.2.0
       focus-trap-vue:
         specifier: ^4.0.1
-        version: 4.0.1(focus-trap@7.2.0)(vue@3.3.4)
+        version: 4.0.1(focus-trap@7.2.0)(vue@3.2.45)
       js-yaml:
         specifier: 4.1.0
         version: 4.1.0
@@ -82,14 +82,14 @@ importers:
   packages/backend:
     dependencies:
       '@bull-board/api':
-        specifier: ^4.6.4
-        version: 4.10.2
+        specifier: 5.2.0
+        version: 5.2.0(@bull-board/ui@5.2.0)
       '@bull-board/koa':
-        specifier: ^4.6.4
-        version: 4.10.2(@types/koa@2.13.5)(pug@3.0.2)
+        specifier: 5.2.0
+        version: 5.2.0(@types/koa@2.13.5)(pug@3.0.2)
       '@bull-board/ui':
-        specifier: ^4.6.4
-        version: 4.10.2
+        specifier: 5.2.0
+        version: 5.2.0
       '@calckey/megalodon':
         specifier: 5.2.0
         version: 5.2.0
@@ -1087,11 +1087,6 @@ packages:
     resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==}
     engines: {node: '>=6.9.0'}
 
-  /@babel/helper-string-parser@7.21.5:
-    resolution: {integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==}
-    engines: {node: '>=6.9.0'}
-    dev: false
-
   /@babel/helper-validator-identifier@7.19.1:
     resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==}
     engines: {node: '>=6.9.0'}
@@ -1135,14 +1130,6 @@ packages:
       '@babel/types': 7.21.4
     dev: true
 
-  /@babel/parser@7.22.3:
-    resolution: {integrity: sha512-vrukxyW/ep8UD1UDzOYpTKQ6abgjFoeG6L+4ar9+c5TN9QnlqiOi6QK7LSR5ewm/ERyGkT/Ai6VboNrxhbr9Uw==}
-    engines: {node: '>=6.0.0'}
-    hasBin: true
-    dependencies:
-      '@babel/types': 7.22.3
-    dev: false
-
   /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.21.4):
     resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
     peerDependencies:
@@ -1329,30 +1316,24 @@ packages:
       to-fast-properties: 2.0.0
     dev: true
 
-  /@babel/types@7.22.3:
-    resolution: {integrity: sha512-P3na3xIQHTKY4L0YOG7pM8M8uoUIB910WQaSiiMCZUC2Cy8XFEQONGABFnHWBa2gpGKODTAJcNhi5Zk0sLRrzg==}
-    engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/helper-string-parser': 7.21.5
-      '@babel/helper-validator-identifier': 7.19.1
-      to-fast-properties: 2.0.0
-    dev: false
-
   /@bcoe/v8-coverage@0.2.3:
     resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
     dev: true
 
-  /@bull-board/api@4.10.2:
-    resolution: {integrity: sha512-lRHo0A7hsz71aOx1ZN0SmLLWfSuvKdL6EZ4imlgo5SuXGozybvlRc5KPIJU2/E1w5meoUGi+nFezBwp1gT/SMw==}
+  /@bull-board/api@5.2.0(@bull-board/ui@5.2.0):
+    resolution: {integrity: sha512-1HGF2EF/4zI3+Cj414nQzwFprLXOJTlVdqXUf5UEBS4HtYafWv93mGIwkrD8S4Bpz4VSvM87adF6tQPJ7Ewt+w==}
+    peerDependencies:
+      '@bull-board/ui': 5.2.0
     dependencies:
+      '@bull-board/ui': 5.2.0
       redis-info: 3.1.0
     dev: false
 
-  /@bull-board/koa@4.10.2(@types/koa@2.13.5)(pug@3.0.2):
-    resolution: {integrity: sha512-gabPtsMOt2SQHkS5VcY1q/FCpbBRFiFrbWbcouZ7zWKg413J8nG+yErz3pc0rbmp23kbKX6wTG/diWKhE7EWbA==}
+  /@bull-board/koa@5.2.0(@types/koa@2.13.5)(pug@3.0.2):
+    resolution: {integrity: sha512-jntDAl/POouD0PS/iiKXBNl26SuUf7Y5uL3EgpDN7isvwFcpKhvdk0VdBypjrkRHN6rPaEJJPkEtK30qv01XYw==}
     dependencies:
-      '@bull-board/api': 4.10.2
-      '@bull-board/ui': 4.10.2
+      '@bull-board/api': 5.2.0(@bull-board/ui@5.2.0)
+      '@bull-board/ui': 5.2.0
       ejs: 3.1.8
       koa: 2.13.4
       koa-mount: 4.0.0
@@ -1416,10 +1397,10 @@ packages:
       - whiskers
     dev: false
 
-  /@bull-board/ui@4.10.2:
-    resolution: {integrity: sha512-vaHGojG5D3xjnaed3nwOaLy4Y06RgDJdYRaFR5E06SjZ0vOvjVYGD6s4cykK512Aw/ElFhKDPwzhf8BvpwAtDQ==}
+  /@bull-board/ui@5.2.0:
+    resolution: {integrity: sha512-f2sgs7AjOVch7tFhbmlVCkhZjJWboxwNxWEfAsIUd1WidUC+Ef5J02tpQvu7apzRtu5zcn8IiJtI5HFO6oKaCA==}
     dependencies:
-      '@bull-board/api': 4.10.2
+      '@bull-board/api': 5.2.0(@bull-board/ui@5.2.0)
     dev: false
 
   /@calckey/megalodon@5.2.0:
@@ -2120,10 +2101,6 @@ packages:
   /@jridgewell/sourcemap-codec@1.4.14:
     resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
 
-  /@jridgewell/sourcemap-codec@1.4.15:
-    resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
-    dev: false
-
   /@jridgewell/trace-mapping@0.3.17:
     resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==}
     dependencies:
@@ -3852,30 +3829,12 @@ packages:
       '@vue/shared': 3.2.45
       estree-walker: 2.0.2
       source-map: 0.6.1
-    dev: true
-
-  /@vue/compiler-core@3.3.4:
-    resolution: {integrity: sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==}
-    dependencies:
-      '@babel/parser': 7.22.3
-      '@vue/shared': 3.3.4
-      estree-walker: 2.0.2
-      source-map-js: 1.0.2
-    dev: false
 
   /@vue/compiler-dom@3.2.45:
     resolution: {integrity: sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==}
     dependencies:
       '@vue/compiler-core': 3.2.45
       '@vue/shared': 3.2.45
-    dev: true
-
-  /@vue/compiler-dom@3.3.4:
-    resolution: {integrity: sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==}
-    dependencies:
-      '@vue/compiler-core': 3.3.4
-      '@vue/shared': 3.3.4
-    dev: false
 
   /@vue/compiler-sfc@2.7.14:
     resolution: {integrity: sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==}
@@ -3898,36 +3857,12 @@ packages:
       magic-string: 0.25.9
       postcss: 8.4.21
       source-map: 0.6.1
-    dev: true
-
-  /@vue/compiler-sfc@3.3.4:
-    resolution: {integrity: sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==}
-    dependencies:
-      '@babel/parser': 7.22.3
-      '@vue/compiler-core': 3.3.4
-      '@vue/compiler-dom': 3.3.4
-      '@vue/compiler-ssr': 3.3.4
-      '@vue/reactivity-transform': 3.3.4
-      '@vue/shared': 3.3.4
-      estree-walker: 2.0.2
-      magic-string: 0.30.0
-      postcss: 8.4.24
-      source-map-js: 1.0.2
-    dev: false
 
   /@vue/compiler-ssr@3.2.45:
     resolution: {integrity: sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==}
     dependencies:
       '@vue/compiler-dom': 3.2.45
       '@vue/shared': 3.2.45
-    dev: true
-
-  /@vue/compiler-ssr@3.3.4:
-    resolution: {integrity: sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==}
-    dependencies:
-      '@vue/compiler-dom': 3.3.4
-      '@vue/shared': 3.3.4
-    dev: false
 
   /@vue/reactivity-transform@3.2.45:
     resolution: {integrity: sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==}
@@ -3937,43 +3872,17 @@ packages:
       '@vue/shared': 3.2.45
       estree-walker: 2.0.2
       magic-string: 0.25.9
-    dev: true
-
-  /@vue/reactivity-transform@3.3.4:
-    resolution: {integrity: sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==}
-    dependencies:
-      '@babel/parser': 7.22.3
-      '@vue/compiler-core': 3.3.4
-      '@vue/shared': 3.3.4
-      estree-walker: 2.0.2
-      magic-string: 0.30.0
-    dev: false
 
   /@vue/reactivity@3.2.45:
     resolution: {integrity: sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==}
     dependencies:
       '@vue/shared': 3.2.45
-    dev: true
-
-  /@vue/reactivity@3.3.4:
-    resolution: {integrity: sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==}
-    dependencies:
-      '@vue/shared': 3.3.4
-    dev: false
 
   /@vue/runtime-core@3.2.45:
     resolution: {integrity: sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==}
     dependencies:
       '@vue/reactivity': 3.2.45
       '@vue/shared': 3.2.45
-    dev: true
-
-  /@vue/runtime-core@3.3.4:
-    resolution: {integrity: sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==}
-    dependencies:
-      '@vue/reactivity': 3.3.4
-      '@vue/shared': 3.3.4
-    dev: false
 
   /@vue/runtime-dom@3.2.45:
     resolution: {integrity: sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==}
@@ -3981,15 +3890,6 @@ packages:
       '@vue/runtime-core': 3.2.45
       '@vue/shared': 3.2.45
       csstype: 2.6.21
-    dev: true
-
-  /@vue/runtime-dom@3.3.4:
-    resolution: {integrity: sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==}
-    dependencies:
-      '@vue/runtime-core': 3.3.4
-      '@vue/shared': 3.3.4
-      csstype: 3.1.2
-    dev: false
 
   /@vue/server-renderer@3.2.45(vue@3.2.45):
     resolution: {integrity: sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==}
@@ -3999,25 +3899,9 @@ packages:
       '@vue/compiler-ssr': 3.2.45
       '@vue/shared': 3.2.45
       vue: 3.2.45
-    dev: true
-
-  /@vue/server-renderer@3.3.4(vue@3.3.4):
-    resolution: {integrity: sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==}
-    peerDependencies:
-      vue: 3.3.4
-    dependencies:
-      '@vue/compiler-ssr': 3.3.4
-      '@vue/shared': 3.3.4
-      vue: 3.3.4
-    dev: false
 
   /@vue/shared@3.2.45:
     resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==}
-    dev: true
-
-  /@vue/shared@3.3.4:
-    resolution: {integrity: sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==}
-    dev: false
 
   /@webassemblyjs/ast@1.11.1:
     resolution: {integrity: sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==}
@@ -6187,16 +6071,11 @@ packages:
 
   /csstype@2.6.21:
     resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==}
-    dev: true
 
   /csstype@3.1.1:
     resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
     dev: true
 
-  /csstype@3.1.2:
-    resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
-    dev: false
-
   /custom-event-polyfill@1.0.7:
     resolution: {integrity: sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==}
     dev: true
@@ -7549,14 +7428,14 @@ packages:
       readable-stream: 2.3.7
     dev: true
 
-  /focus-trap-vue@4.0.1(focus-trap@7.2.0)(vue@3.3.4):
+  /focus-trap-vue@4.0.1(focus-trap@7.2.0)(vue@3.2.45):
     resolution: {integrity: sha512-2iqOeoSvgq7Um6aL+255a/wXPskj6waLq2oKCa4gOnMORPo15JX7wN6J5bl1SMhMlTlkHXGSrQ9uJPJLPZDl5w==}
     peerDependencies:
       focus-trap: ^7.0.0
       vue: ^3.0.0
     dependencies:
       focus-trap: 7.2.0
-      vue: 3.3.4
+      vue: 3.2.45
     dev: false
 
   /focus-trap@7.2.0:
@@ -7716,7 +7595,7 @@ packages:
     resolution: {integrity: sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==}
     engines: {node: '>= 0.10'}
     dependencies:
-      graceful-fs: 4.2.10
+      graceful-fs: 4.2.11
       through2: 2.0.5
     dev: true
 
@@ -9795,7 +9674,7 @@ packages:
   /jsonfile@4.0.0:
     resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
     optionalDependencies:
-      graceful-fs: 4.2.10
+      graceful-fs: 4.2.11
 
   /jsonfile@5.0.0:
     resolution: {integrity: sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==}
@@ -9809,7 +9688,7 @@ packages:
     dependencies:
       universalify: 2.0.0
     optionalDependencies:
-      graceful-fs: 4.2.10
+      graceful-fs: 4.2.11
     dev: true
 
   /jsonld@6.0.0:
@@ -10435,14 +10314,6 @@ packages:
     resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
     dependencies:
       sourcemap-codec: 1.4.8
-    dev: true
-
-  /magic-string@0.30.0:
-    resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==}
-    engines: {node: '>=12'}
-    dependencies:
-      '@jridgewell/sourcemap-codec': 1.4.15
-    dev: false
 
   /mailcheck@1.1.1:
     resolution: {integrity: sha512-3WjL8+ZDouZwKlyJBMp/4LeziLFXgleOdsYu87piGcMLqhBzCsy2QFdbtAwv757TFC/rtqd738fgJw1tFQCSgA==}
@@ -12056,6 +11927,7 @@ packages:
       nanoid: 3.3.6
       picocolors: 1.0.0
       source-map-js: 1.0.2
+    dev: true
 
   /postgres-array@2.0.0:
     resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
@@ -13381,7 +13253,6 @@ packages:
   /sourcemap-codec@1.4.8:
     resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
     deprecated: Please use @jridgewell/sourcemap-codec instead
-    dev: true
 
   /sparkles@1.0.1:
     resolution: {integrity: sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==}
@@ -14760,7 +14631,7 @@ packages:
     dependencies:
       append-buffer: 1.0.2
       convert-source-map: 1.9.0
-      graceful-fs: 4.2.10
+      graceful-fs: 4.2.11
       normalize-path: 2.1.1
       now-and-later: 2.0.1
       remove-bom-buffer: 3.0.0
@@ -14872,17 +14743,6 @@ packages:
       '@vue/runtime-dom': 3.2.45
       '@vue/server-renderer': 3.2.45(vue@3.2.45)
       '@vue/shared': 3.2.45
-    dev: true
-
-  /vue@3.3.4:
-    resolution: {integrity: sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==}
-    dependencies:
-      '@vue/compiler-dom': 3.3.4
-      '@vue/compiler-sfc': 3.3.4
-      '@vue/runtime-dom': 3.3.4
-      '@vue/server-renderer': 3.3.4(vue@3.3.4)
-      '@vue/shared': 3.3.4
-    dev: false
 
   /vuedraggable@4.1.0(vue@3.2.45):
     resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}

From 354a3d9fa99f9a6fe3816eaa607f30580be066cc Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 19:32:33 -0700
Subject: [PATCH 085/198] up redis deps

---
 packages/backend/package.json |  4 ++--
 pnpm-lock.yaml                | 18 +++++++++---------
 2 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 3d483e003..d9d0b4ab4 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -68,7 +68,7 @@
 		"fluent-ffmpeg": "2.1.2",
 		"got": "12.5.3",
 		"hpagent": "0.1.2",
-		"ioredis": "5.2.4",
+		"ioredis": "5.3.2",
 		"ip-cidr": "3.0.11",
 		"is-svg": "4.3.2",
 		"js-yaml": "4.1.0",
@@ -171,7 +171,7 @@
 		"@types/qs": "6.9.7",
 		"@types/random-seed": "0.3.3",
 		"@types/ratelimiter": "3.4.4",
-		"@types/redis": "4.0.11",
+		"@types/redis": "4.6.6",
 		"@types/rename": "1.0.4",
 		"@types/sanitize-html": "2.8.0",
 		"@types/semver": "7.3.13",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d69f3142a..fa4763f26 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -217,8 +217,8 @@ importers:
         specifier: 0.1.2
         version: 0.1.2
       ioredis:
-        specifier: 5.2.4
-        version: 5.2.4
+        specifier: 5.3.2
+        version: 5.3.2
       ip-cidr:
         specifier: 3.0.11
         version: 3.0.11
@@ -398,7 +398,7 @@ importers:
         version: 14.0.0
       typeorm:
         specifier: 0.3.11
-        version: 0.3.11(ioredis@5.2.4)(pg@8.8.0)(ts-node@10.9.1)
+        version: 0.3.11(ioredis@5.3.2)(pg@8.8.0)(ts-node@10.9.1)
       ulid:
         specifier: 2.3.0
         version: 2.3.0
@@ -3217,7 +3217,7 @@ packages:
     resolution: {integrity: sha512-zJbJ3FVE17CNl5KXzdeSPtdltc4tMT3TzC6fxQS0sQngkbFZ6h+0uTafsRqu+eSLIugf6Yb0Ea0SUuRr42Nk9g==}
     deprecated: This is a stub types definition. ioredis provides its own type definitions, so you do not need this installed.
     dependencies:
-      ioredis: 5.2.4
+      ioredis: 5.3.2
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -4990,7 +4990,7 @@ packages:
       cron-parser: 4.7.1
       debuglog: 1.0.1
       get-port: 5.1.1
-      ioredis: 5.2.4
+      ioredis: 5.3.2
       lodash: 4.17.21
       msgpackr: 1.8.1
       p-timeout: 3.2.0
@@ -8500,8 +8500,8 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
-  /ioredis@5.2.4:
-    resolution: {integrity: sha512-qIpuAEt32lZJQ0XyrloCRdlEdUUNGG9i0UOk6zgzK6igyudNWqEBxfH6OlbnOOoBBvr1WB02mm8fR55CnikRng==}
+  /ioredis@5.3.2:
+    resolution: {integrity: sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==}
     engines: {node: '>=12.22.0'}
     dependencies:
       '@ioredis/commands': 1.2.0
@@ -14246,7 +14246,7 @@ packages:
   /typedarray@0.0.6:
     resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
 
-  /typeorm@0.3.11(ioredis@5.2.4)(pg@8.8.0)(ts-node@10.9.1):
+  /typeorm@0.3.11(ioredis@5.3.2)(pg@8.8.0)(ts-node@10.9.1):
     resolution: {integrity: sha512-pzdOyWbVuz/z8Ww6gqvBW4nylsM0KLdUCDExr2gR20/x1khGSVxQkjNV/3YqliG90jrWzrknYbYscpk8yxFJVg==}
     engines: {node: '>= 12.9.0'}
     hasBin: true
@@ -14313,7 +14313,7 @@ packages:
       debug: 4.3.4(supports-color@8.1.1)
       dotenv: 16.0.3
       glob: 7.2.3
-      ioredis: 5.2.4
+      ioredis: 5.3.2
       js-yaml: 4.1.0
       mkdirp: 1.0.4
       pg: 8.8.0

From d91a49e9669c8f912dd3678a1e21377c7da0595f Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 19:34:32 -0700
Subject: [PATCH 086/198] fix

---
 packages/backend/package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index d9d0b4ab4..82a3f6cc8 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -171,7 +171,7 @@
 		"@types/qs": "6.9.7",
 		"@types/random-seed": "0.3.3",
 		"@types/ratelimiter": "3.4.4",
-		"@types/redis": "4.6.6",
+		"@types/redis": "4.0.11",
 		"@types/rename": "1.0.4",
 		"@types/sanitize-html": "2.8.0",
 		"@types/semver": "7.3.13",

From d6ae979011acebf8396a17eea267b62a0f006c0c Mon Sep 17 00:00:00 2001
From: Freeplay <Freeplay@duck.com>
Date: Mon, 29 May 2023 22:40:53 -0400
Subject: [PATCH 087/198] swiper tweak

---
 packages/client/src/style.scss | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/style.scss b/packages/client/src/style.scss
index 62897209f..2fa2c861e 100644
--- a/packages/client/src/style.scss
+++ b/packages/client/src/style.scss
@@ -114,7 +114,7 @@ html, body {
 }
 
 .swiper-slide {
-	height: unset !important;
+	min-height: 100vh;
 }
 
 a {

From fa215bf777ec3aa602985186a01801081bd22604 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 19:45:42 -0700
Subject: [PATCH 088/198] Revert "refactor: add back old info display from
 mkv12"

This reverts commit c58692fa5861afba772b77ff243fda67af4bb6ce.
---
 .../src/pages/admin/overview.metrics.vue      | 157 +++++++-----------
 1 file changed, 58 insertions(+), 99 deletions(-)

diff --git a/packages/client/src/pages/admin/overview.metrics.vue b/packages/client/src/pages/admin/overview.metrics.vue
index fbef55c3d..cfd497439 100644
--- a/packages/client/src/pages/admin/overview.metrics.vue
+++ b/packages/client/src/pages/admin/overview.metrics.vue
@@ -1,72 +1,52 @@
 <template>
-	<div class="_panel ntjkdlsfk" :class="$style.root">
-		<div class="container env">
-			<div class="body">
-				<div class="number _panel">
-					<div class="label">Calckey</div>
-					<div class="value _monospace">{{ version }}</div>
-				</div>
-				<div v-if="serverInfo" class="number _panel">
-					<div class="label">Node.js</div>
-					<div class="value _monospace">{{ serverInfo.node }}</div>
-				</div>
-				<div v-if="serverInfo" class="number _panel">
-					<div class="label">PostgreSQL</div>
-					<div class="value _monospace">{{ serverInfo.psql }}</div>
-				</div>
-				<div v-if="serverInfo" class="number _panel">
-					<div class="label">Redis</div>
-					<div class="value _monospace">{{ serverInfo.redis }}</div>
-				</div>
-				<div class="_panel">
-					<XPie class="pie" :value="cpuUsage" />
-					<div>
-						<p><i class="ph-cpu ph-bold ph-lg"></i>CPU</p>
-						<p>{{ meta.cpu.cores }} Logical cores</p>
-						<p>{{ meta.cpu.model }}</p>
-					</div>
+	<div class="_panel" :class="$style.root">
+		<div class="ntjkdlsfk">
+			<div class="_panel">
+				<XPie class="pie" :value="cpuUsage" />
+				<div>
+					<p><i class="ph-cpu ph-bold ph-lg"></i>CPU</p>
+					<p>{{ meta.cpu.cores }} Logical cores</p>
+					<p>{{ meta.cpu.model }}</p>
 				</div>
+			</div>
 
-				<div class="_panel">
-					<XPie class="pie" :value="memUsage" />
-					<div>
-						<p><i class="ph-microchip ph-bold ph-lg"></i>RAM</p>
-						<p>Total: {{ bytes(memTotal, 1) }}</p>
-						<p>Used: {{ bytes(memUsed, 1) }}</p>
-						<p>Free: {{ bytes(memFree, 1) }}</p>
-					</div>
+			<div class="_panel">
+				<XPie class="pie" :value="memUsage" />
+				<div>
+					<p><i class="ph-microchip ph-bold ph-lg"></i>RAM</p>
+					<p>Total: {{ bytes(memTotal, 1) }}</p>
+					<p>Used: {{ bytes(memUsed, 1) }}</p>
+					<p>Free: {{ bytes(memFree, 1) }}</p>
 				</div>
+			</div>
 
-				<div class="_panel">
-					<XPie class="pie" :value="diskUsage" />
-					<div>
-						<p><i class="ph-hard-drives ph-bold ph-lg"></i>Disk</p>
-						<p>Total: {{ bytes(diskTotal, 1) }}</p>
-						<p>Free: {{ bytes(diskAvailable, 1) }}</p>
-						<p>Used: {{ bytes(diskUsed, 1) }}</p>
-					</div>
+			<div class="_panel">
+				<XPie class="pie" :value="diskUsage" />
+				<div>
+					<p><i class="ph-hard-drives ph-bold ph-lg"></i>Disk</p>
+					<p>Total: {{ bytes(diskTotal, 1) }}</p>
+					<p>Free: {{ bytes(diskAvailable, 1) }}</p>
+					<p>Used: {{ bytes(diskUsed, 1) }}</p>
 				</div>
+			</div>
 
-				<div class="_panel">
-					<XPie class="pie" :value="meiliProgress" />
-					<div>
-						<p>
-							<i class="ph-file-search ph-bold ph-lg"></i
-							>MeiliSearch
-						</p>
-						<p>
-							{{ i18n.ts._widgets.meiliStatus }}:
-							{{ meiliAvailable }}
-						</p>
-						<p>
-							{{ i18n.ts._widgets.meiliSize }}:
-							{{ bytes(meiliTotalSize, 1) }}
-						</p>
-						<p>
-							{{ i18n.ts._widgets.meiliIndexCount }}:
-							{{ meiliIndexCount }}
-						</p>
-					</div>
+			<div class="_panel">
+				<XPie class="pie" :value="meiliProgress" />
+				<div>
+					<p>
+						<i class="ph-file-search ph-bold ph-lg"></i>MeiliSearch
+					</p>
+					<p>
+						{{ i18n.ts._widgets.meiliStatus }}: {{ meiliAvailable }}
+					</p>
+					<p>
+						{{ i18n.ts._widgets.meiliSize }}:
+						{{ bytes(meiliTotalSize, 1) }}
+					</p>
+					<p>
+						{{ i18n.ts._widgets.meiliIndexCount }}:
+						{{ meiliIndexCount }}
+					</p>
 				</div>
 			</div>
 		</div>
@@ -80,11 +60,9 @@ import bytes from "@/filters/bytes";
 import { stream } from "@/stream";
 import * as os from "@/os";
 import { i18n } from "@/i18n";
-import { version } from "@/config";
 
 const meta = await os.api("server-info", {});
 const serverStats = await os.api("stats");
-const serverInfo = await os.api("admin/server-info");
 
 let cpuUsage: number = $ref(0);
 
@@ -138,45 +116,26 @@ onUnmounted(() => {
 	display: flex;
 	padding: 16px;
 
-	&.env {
-		> .body {
-			display: grid;
-			grid-gap: 16px;
-			grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
+	> ._panel {
+		> .pie {
+			height: 82px;
+			flex-shrink: 0;
+			margin-right: 16px;
+		}
 
-			> .number {
-				padding: 14px 20px;
+		> div {
+			flex: 1;
 
-				> .label {
-					opacity: 0.7;
-					font-size: 0.8em;
-				}
+			> p {
+				margin: 0;
+				font-size: 0.8em;
 
-				> .value {
-					font-size: 1.1em;
-				}
-			}
+				&:first-child {
+					font-weight: bold;
+					margin-bottom: 4px;
 
-			> .pie {
-				height: 82px;
-				flex-shrink: 0;
-				margin-right: 16px;
-			}
-
-			> div {
-				flex: 1;
-
-				> p {
-					margin: 0;
-					font-size: 0.8em;
-
-					&:first-child {
-						font-weight: bold;
-						margin-bottom: 4px;
-
-						> i {
-							margin-right: 4px;
-						}
+					> i {
+						margin-right: 4px;
 					}
 				}
 			}

From c8b36b170e07320c0a3207a151a6c1a05f6b24ff Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Mon, 29 May 2023 19:49:53 -0700
Subject: [PATCH 089/198] node v20

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 5b3eb98fd..e976807f9 100644
--- a/README.md
+++ b/README.md
@@ -76,7 +76,7 @@ If you have access to a server that supports one of the sources below, I recomme
 
 ## 🧑‍💻 Dependencies
 
-- 🐢 At least [NodeJS](https://nodejs.org/en/) v18.12.1 (v19 recommended)
+- 🐢 At least [NodeJS](https://nodejs.org/en/) v18.12.1 (v20 recommended)
   - Install with [nvm](https://github.com/nvm-sh/nvm)
 - 🐘 At least [PostgreSQL](https://www.postgresql.org/) v12
 - 🍱 At least [Redis](https://redis.io/) v6 (v7 recommend)

From 076ac63f0cca07ed7c2559db14c8c68e6f4c7a22 Mon Sep 17 00:00:00 2001
From: Kainoa Kanter <kainoa@t1c.dev>
Date: Tue, 30 May 2023 03:09:35 +0000
Subject: [PATCH 090/198] Delete '.node-version'

---
 .node-version | 1 -
 1 file changed, 1 deletion(-)
 delete mode 100644 .node-version

diff --git a/.node-version b/.node-version
deleted file mode 100644
index 7fd023741..000000000
--- a/.node-version
+++ /dev/null
@@ -1 +0,0 @@
-v16.15.0

From 172b37fe003b186e32375a5db086c6520158debd Mon Sep 17 00:00:00 2001
From: Freeplay <Freeplay@duck.com>
Date: Tue, 30 May 2023 10:15:18 -0400
Subject: [PATCH 091/198] fix border w/ wallpaper

---
 packages/client/src/ui/_common_/navbar.vue | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/packages/client/src/ui/_common_/navbar.vue b/packages/client/src/ui/_common_/navbar.vue
index 30ea4ccf1..68e4619eb 100644
--- a/packages/client/src/ui/_common_/navbar.vue
+++ b/packages/client/src/ui/_common_/navbar.vue
@@ -252,6 +252,9 @@ function more(ev: MouseEvent) {
 		#calckey_app > :not(.wallpaper) & {
 			background: var(--navBg);
 		}
+		#calckey_app > .wallpaper:not(.centered) & {
+			border-right: 1px solid var(--divider);
+		}
 		contain: strict;
 		display: flex;
 		flex-direction: column;

From 6a54c87260579bd986d0c12b9a8ec805adece5cf Mon Sep 17 00:00:00 2001
From: Kainoa Kanter <kainoa@t1c.dev>
Date: Wed, 31 May 2023 02:34:44 +0000
Subject: [PATCH 092/198] revert 3bc9577cf34166c1f136811aa668dbba63606c6b

revert Delete '.node-version'
---
 .node-version | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 .node-version

diff --git a/.node-version b/.node-version
new file mode 100644
index 000000000..7fd023741
--- /dev/null
+++ b/.node-version
@@ -0,0 +1 @@
+v16.15.0

From 43772d2331da7970a8dde4fd075c92fb6a42ce37 Mon Sep 17 00:00:00 2001
From: Kainoa Kanter <kainoa@t1c.dev>
Date: Wed, 31 May 2023 02:36:19 +0000
Subject: [PATCH 093/198] Update '.node-version'

---
 .node-version | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.node-version b/.node-version
index 7fd023741..8ddbc0c64 100644
--- a/.node-version
+++ b/.node-version
@@ -1 +1 @@
-v16.15.0
+v18.16.0

From 4f4ca5e53446bff373d10cc70b041b9522556c5f Mon Sep 17 00:00:00 2001
From: Kainoa Kanter <kainoa@t1c.dev>
Date: Wed, 31 May 2023 02:37:57 +0000
Subject: [PATCH 094/198] =?UTF-8?q?docs:=20=F0=9F=93=9D=20versions?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index e976807f9..d6575445f 100644
--- a/README.md
+++ b/README.md
@@ -76,9 +76,9 @@ If you have access to a server that supports one of the sources below, I recomme
 
 ## 🧑‍💻 Dependencies
 
-- 🐢 At least [NodeJS](https://nodejs.org/en/) v18.12.1 (v20 recommended)
+- 🐢 At least [NodeJS](https://nodejs.org/en/) v18.16.0 (v20 recommended)
   - Install with [nvm](https://github.com/nvm-sh/nvm)
-- 🐘 At least [PostgreSQL](https://www.postgresql.org/) v12
+- 🐘 At least [PostgreSQL](https://www.postgresql.org/) v12 (v14 recommende)
 - 🍱 At least [Redis](https://redis.io/) v6 (v7 recommend)
 - Web Proxy (one of the following)
   - 🍀 Nginx (recommended)

From 4896b4a976f68a5b3ed61e12c972bbc4a6daa5e5 Mon Sep 17 00:00:00 2001
From: Kainoa Kanter <kainoa@t1c.dev>
Date: Wed, 31 May 2023 02:40:23 +0000
Subject: [PATCH 095/198] =?UTF-8?q?docs:=20=F0=9F=93=9D=20typos?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index d6575445f..5f59211e1 100644
--- a/README.md
+++ b/README.md
@@ -78,8 +78,8 @@ If you have access to a server that supports one of the sources below, I recomme
 
 - 🐢 At least [NodeJS](https://nodejs.org/en/) v18.16.0 (v20 recommended)
   - Install with [nvm](https://github.com/nvm-sh/nvm)
-- 🐘 At least [PostgreSQL](https://www.postgresql.org/) v12 (v14 recommende)
-- 🍱 At least [Redis](https://redis.io/) v6 (v7 recommend)
+- 🐘 At least [PostgreSQL](https://www.postgresql.org/) v12 (v14 recommended)
+- 🍱 At least [Redis](https://redis.io/) v6 (v7 recommended)
 - Web Proxy (one of the following)
   - 🍀 Nginx (recommended)
   - 🦦 Caddy

From 91616081c27286194e2453bd667d06796bd952e4 Mon Sep 17 00:00:00 2001
From: Kainoa Kanter <kainoa@t1c.dev>
Date: Wed, 31 May 2023 02:41:29 +0000
Subject: [PATCH 096/198] =?UTF-8?q?docs:=20=F0=9F=93=9D=20full=20git=20clo?=
 =?UTF-8?q?ne?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 5f59211e1..5dcdc4861 100644
--- a/README.md
+++ b/README.md
@@ -104,7 +104,7 @@ If you have access to a server that supports one of the sources below, I recomme
 ## 👀 Get folder ready
 
 ```sh
-git clone --depth 1 https://codeberg.org/calckey/calckey.git
+git clone https://codeberg.org/calckey/calckey.git
 cd calckey/
 ```
 

From 6e4d6d48c4417bc14878c38b1ac05b721ed8e74d Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Tue, 30 May 2023 23:10:12 -0400
Subject: [PATCH 097/198] get array for alsoKnownAs

---
 .../src/server/api/endpoints/i/known-as.ts    | 63 ++++++++++---------
 .../src/server/api/endpoints/users/show.ts    | 35 ++++++-----
 .../client/src/pages/settings/migration.vue   | 54 +++++++++-------
 3 files changed, 88 insertions(+), 64 deletions(-)

diff --git a/packages/backend/src/server/api/endpoints/i/known-as.ts b/packages/backend/src/server/api/endpoints/i/known-as.ts
index 5e86e8b95..d4d9061b3 100644
--- a/packages/backend/src/server/api/endpoints/i/known-as.ts
+++ b/packages/backend/src/server/api/endpoints/i/known-as.ts
@@ -1,4 +1,4 @@
-import type { User, UserDetailedNotMeOnly } from "@/models/entities/user.js";
+import type { User } from "@/models/entities/user.js";
 import { Users } from "@/models/index.js";
 import { resolveUser } from "@/remote/resolve-user.js";
 import acceptAllFollowRequests from "@/services/following/requests/accept-all.js";
@@ -6,10 +6,9 @@ import { publishToFollowers } from "@/services/i/update.js";
 import { publishMainStream } from "@/services/stream.js";
 import { DAY } from "@/const.js";
 import { apiLogger } from "../../logger.js";
-import { UserProfiles } from "@/models/index.js";
-import config from "@/config/index.js";
 import define from "../../define.js";
 import { ApiError } from "../../error.js";
+import { parse } from "@/misc/acct.js";
 
 export const meta = {
 	tags: ["users"],
@@ -38,49 +37,57 @@ export const meta = {
 			code: "URI_NULL",
 			id: "bf326f31-d430-4f97-9933-5d61e4d48a23",
 		},
+		alreadyMoved: {
+			message: 'You have already moved your account.',
+			code: 'ALREADY_MOVED',
+			id: '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31',
+		},
+		yourself: {
+			message: 'You can\'t set yourself as your own alias.',
+			code: 'FORBIDDEN_TO_SET_YOURSELF',
+			id: '25c90186-4ab0-49c8-9bba-a1fa6c202ba4',
+		},
 	},
 } as const;
 
 export const paramDef = {
 	type: "object",
 	properties: {
-		alsoKnownAs: { type: "string" },
+		alsoKnownAs: {
+			type: 'array',
+			maxItems: 10,
+			uniqueItems: true,
+			items: { type: 'string' },
+		},
 	},
 	required: ["alsoKnownAs"],
 } as const;
 
 export default define(meta, paramDef, async (ps, user) => {
 	if (!ps.alsoKnownAs) throw new ApiError(meta.errors.noSuchUser);
+	if (user.movedToUri) throw new ApiError(meta.errors.alreadyMoved);
 
-	let unfiltered: string = ps.alsoKnownAs;
-	const updates = {} as Partial<User>;
+	const newAka = new Set<string>();
 
-	if (!unfiltered) {
-		updates.alsoKnownAs = null;
-	} else {
-		if (unfiltered.startsWith("acct:")) unfiltered = unfiltered.substring(5);
-		if (unfiltered.startsWith("@")) unfiltered = unfiltered.substring(1);
-		if (!unfiltered.includes("@")) throw new ApiError(meta.errors.notRemote);
+	for (const line of ps.alsoKnownAs) {
+		if (!line) throw new ApiError(meta.errors.noSuchUser);
+		const { username, host } = parse(line);
 
-		const userAddress: string[] = unfiltered.split("@");
-		const knownAs = await resolveUser(userAddress[0], userAddress[1]).catch(
-			(e) => {
-				apiLogger.warn(`failed to resolve remote user: ${e}`);
-				throw new ApiError(meta.errors.noSuchUser);
-			},
-		);
+		const aka = await resolveUser(username, host).catch((e) => {
+			apiLogger.warn(`failed to resolve remote user: ${e}`);
+			throw new ApiError(meta.errors.noSuchUser);
+		});
 
-		const toUrl: string | null = knownAs.uri;
-		if (!toUrl) {
-			throw new ApiError(meta.errors.uriNull);
-		}
-		if (updates.alsoKnownAs == null || updates.alsoKnownAs.length === 0) {
-			updates.alsoKnownAs = [toUrl];
-		} else {
-			updates.alsoKnownAs.push(toUrl);
-		}
+		if (aka.id === user.id) throw new ApiError(meta.errors.yourself);
+		if (!aka.uri) throw new ApiError(meta.errors.uriNull);
+
+		newAka.add(aka.uri);
 	}
 
+	const updates = {
+		alsoKnownAs: newAka.size > 0 ? Array.from(newAka) : null,
+	} as Partial<User>;
+
 	await Users.update(user.id, updates);
 
 	const iObj = await Users.pack<true, true>(user.id, user, {
diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts
index 49cac81fd..bead8df0a 100644
--- a/packages/backend/src/server/api/endpoints/users/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/show.ts
@@ -54,7 +54,7 @@ export const paramDef = {
 	anyOf: [
 		{
 			properties: {
-				userId: { type: "string", format: "misskey:id" },
+				userId: { type: "string" },
 			},
 			required: ["userId"],
 		},
@@ -65,7 +65,6 @@ export const paramDef = {
 					uniqueItems: true,
 					items: {
 						type: "string",
-						format: "misskey:id",
 					},
 				},
 			},
@@ -95,21 +94,27 @@ export default define(meta, paramDef, async (ps, me) => {
 			return [];
 		}
 
-		const users = await Users.findBy(
-			isAdminOrModerator
-				? {
-						id: In(ps.userIds),
-				  }
-				: {
-						id: In(ps.userIds),
-						isSuspended: false,
-				  },
-		);
+		const isUrl = ps.userIds[0].startsWith("http");
+		let users: User[];
+		if (isUrl) {
+			users = await Users.findBy(
+				isAdminOrModerator
+					? { uri: In(ps.userIds) }
+					: { uri: In(ps.userIds), isSuspended: false },
+			);
+		} else {
+			users = await Users.findBy(
+				isAdminOrModerator
+					? { id: In(ps.userIds) }
+					: { id: In(ps.userIds), isSuspended: false },
+			);
+		}
 
 		// リクエストされた通りに並べ替え
 		const _users: User[] = [];
 		for (const id of ps.userIds) {
-			_users.push(users.find((x) => x.id === id)!);
+			const res = users.find((x) => (isUrl ? x.uri === id : x.id === id));
+			if (res) _users.push(res);
 		}
 
 		return await Promise.all(
@@ -129,7 +134,9 @@ export default define(meta, paramDef, async (ps, me) => {
 		} else {
 			const q: FindOptionsWhere<User> =
 				ps.userId != null
-					? { id: ps.userId }
+					? ps.userId.startsWith("http")
+						? { uri: ps.userId }
+						: { id: ps.userId }
 					: { usernameLower: ps.username!.toLowerCase(), host: IsNull() };
 
 			user = await Users.findOneBy(q);
diff --git a/packages/client/src/pages/settings/migration.vue b/packages/client/src/pages/settings/migration.vue
index 6d5b435e3..610d1ce1f 100644
--- a/packages/client/src/pages/settings/migration.vue
+++ b/packages/client/src/pages/settings/migration.vue
@@ -2,14 +2,10 @@
 	<div class="_formRoot">
 		<FormSection>
 			<template #label>{{ i18n.ts.moveTo }}</template>
+			<FormInfo warn class="_formBlock">{{ i18n.ts.moveAccountDescription }}</FormInfo>
 			<FormInput v-model="moveToAccount" class="_formBlock">
-				<template #prefix
-					><i class="ph-airplane-takeoff ph-bold ph-lg"></i
-				></template>
+				<template #prefix><i class="ph-airplane-takeoff ph-bold ph-lg"></i></template>
 				<template #label>{{ i18n.ts.moveToLabel }}</template>
-				<template #caption>{{
-					i18n.ts.moveAccountDescription
-				}}</template>
 			</FormInput>
 			<FormButton primary danger @click="move(moveToAccount)">
 				{{ i18n.ts.moveAccount }}
@@ -18,19 +14,15 @@
 
 		<FormSection>
 			<template #label>{{ i18n.ts.moveFrom }}</template>
-			<FormInput v-model="accountAlias" class="_formBlock">
-				<template #prefix
-					><i class="ph-airplane-landing ph-bold ph-lg"></i
-				></template>
-				<template #label>{{ i18n.ts.moveFromLabel }}</template>
-				<template #caption>{{ i18n.ts.moveFromDescription }}</template>
+			<FormInfo warn class="_formBlock">{{ i18n.ts.moveFromDescription }}</FormInfo>
+			<FormInput v-for="(_, i) in accountAlias" v-model="accountAlias[i]" class="_formBlock">
+				<template #prefix><i class="ph-airplane-landing ph-bold ph-lg"></i></template>
+				<template #label>{{ `#${i + 1} ${i18n.ts.moveFromLabel}` }}</template>
 			</FormInput>
-			<FormButton
-				class="button"
-				inline
-				primary
-				@click="save(accountAlias.toString())"
-			>
+			<FormButton class="button" :disabled="accountAlias.length >= 10" inline style="margin-right: 8px" @click="add"><i
+					class="ph-plus ph-bold ph-lg"></i>
+				{{ i18n.ts.add }}</FormButton>
+			<FormButton class="button" inline primary @click="save">
 				<i class="ph-floppy-disk-back ph-bold ph-lg"></i>
 				{{ i18n.ts.save }}
 			</FormButton>
@@ -42,17 +34,35 @@
 import FormSection from "@/components/form/section.vue";
 import FormInput from "@/components/form/input.vue";
 import FormButton from "@/components/MkButton.vue";
+import FormInfo from "@/components/MkInfo.vue";
 import * as os from "@/os";
 import { i18n } from "@/i18n";
 import { definePageMetadata } from "@/scripts/page-metadata";
+import { $i } from "@/account";
+import { toString } from 'calckey-js/built/acct';
 
 let moveToAccount = $ref("");
-let accountAlias = $ref("");
+let accountAlias = $ref([""]);
 
-async function save(account): Promise<void> {
-	os.apiWithDialog("i/known-as", {
-		alsoKnownAs: account,
+await init();
+
+async function init() {
+	if ($i?.alsoKnownAs && $i.alsoKnownAs.length > 0) {
+		const aka = await os.api('users/show', { userIds: $i.alsoKnownAs });
+		accountAlias = (aka && aka.length > 0) ? aka.map(user => `@${toString(user)}`) : [''];
+	}
+}
+
+async function save(): Promise<void> {
+	const i = os.apiWithDialog("i/known-as", {
+		alsoKnownAs: accountAlias.map(e => e.trim()).filter(e => e !== ""),
 	});
+	$i.alsoKnownAs = i.alsoKnownAs;
+	await init();
+}
+
+function add(): void {
+	accountAlias.push('');
 }
 
 async function move(account): Promise<void> {

From 853940c5d6290d518fcee284b8645fe323920b7a Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Wed, 31 May 2023 01:02:41 -0400
Subject: [PATCH 098/198] use parse instead

---
 .../backend/src/server/api/endpoints/i/move.ts   | 16 +++++-----------
 1 file changed, 5 insertions(+), 11 deletions(-)

diff --git a/packages/backend/src/server/api/endpoints/i/move.ts b/packages/backend/src/server/api/endpoints/i/move.ts
index 3d947063f..ba2360b19 100644
--- a/packages/backend/src/server/api/endpoints/i/move.ts
+++ b/packages/backend/src/server/api/endpoints/i/move.ts
@@ -10,9 +10,9 @@ import deleteFollowing from "@/services/following/delete.js";
 import create from "@/services/following/create.js";
 import { getUser } from "@/server/api/common/getters.js";
 import { Followings, Users } from "@/models/index.js";
-import { UserProfiles } from "@/models/index.js";
 import config from "@/config/index.js";
 import { publishMainStream } from "@/services/stream.js";
+import { parse } from "@/misc/acct.js";
 
 export const meta = {
 	tags: ["users"],
@@ -95,17 +95,10 @@ export default define(meta, paramDef, async (ps, user) => {
 	if (user.isAdmin) throw new ApiError(meta.errors.adminForbidden);
 	if (user.movedToUri) throw new ApiError(meta.errors.alreadyMoved);
 
-	let unfiltered: string = ps.moveToAccount;
-	if (!unfiltered) {
-		throw new ApiError(meta.errors.noSuchMoveTarget);
-	}
+	const { username, host } = parse(ps.moveToAccount);
+	if (!host) throw new ApiError(meta.errors.notRemote);
 
-	if (unfiltered.startsWith("acct:")) unfiltered = unfiltered.substring(5);
-	if (unfiltered.startsWith("@")) unfiltered = unfiltered.substring(1);
-	if (!unfiltered.includes("@")) throw new ApiError(meta.errors.notRemote);
-
-	const userAddress: string[] = unfiltered.split("@");
-	const moveTo: User = await resolveUser(userAddress[0], userAddress[1]).catch(
+	const moveTo: User = await resolveUser(username, host).catch(
 		(e) => {
 			apiLogger.warn(`failed to resolve remote user: ${e}`);
 			throw new ApiError(meta.errors.noSuchMoveTarget);
@@ -134,6 +127,7 @@ export default define(meta, paramDef, async (ps, user) => {
 
 	if (!toUrl) toUrl = "";
 	updates.movedToUri = toUrl;
+	updates.alsoKnownAs = user.alsoKnownAs?.concat(toUrl) ?? [toUrl];
 
 	await Users.update(user.id, updates);
 	const iObj = await Users.pack<true, true>(user.id, user, {

From 34596dd0f7d3e1f455a3ee575d3aa251745aea79 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Wed, 31 May 2023 01:14:13 -0400
Subject: [PATCH 099/198] remove empty form input

---
 .../src/server/api/endpoints/i/known-as.ts    | 16 +++---
 .../src/server/api/endpoints/i/move.ts        | 10 ++--
 .../client/src/pages/settings/migration.vue   | 55 ++++++++++++++-----
 3 files changed, 52 insertions(+), 29 deletions(-)

diff --git a/packages/backend/src/server/api/endpoints/i/known-as.ts b/packages/backend/src/server/api/endpoints/i/known-as.ts
index d4d9061b3..0d0c06180 100644
--- a/packages/backend/src/server/api/endpoints/i/known-as.ts
+++ b/packages/backend/src/server/api/endpoints/i/known-as.ts
@@ -38,14 +38,14 @@ export const meta = {
 			id: "bf326f31-d430-4f97-9933-5d61e4d48a23",
 		},
 		alreadyMoved: {
-			message: 'You have already moved your account.',
-			code: 'ALREADY_MOVED',
-			id: '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31',
+			message: "You have already moved your account.",
+			code: "ALREADY_MOVED",
+			id: "56f20ec9-fd06-4fa5-841b-edd6d7d4fa31",
 		},
 		yourself: {
-			message: 'You can\'t set yourself as your own alias.',
-			code: 'FORBIDDEN_TO_SET_YOURSELF',
-			id: '25c90186-4ab0-49c8-9bba-a1fa6c202ba4',
+			message: "You can't set yourself as your own alias.",
+			code: "FORBIDDEN_TO_SET_YOURSELF",
+			id: "25c90186-4ab0-49c8-9bba-a1fa6c202ba4",
 		},
 	},
 } as const;
@@ -54,10 +54,10 @@ export const paramDef = {
 	type: "object",
 	properties: {
 		alsoKnownAs: {
-			type: 'array',
+			type: "array",
 			maxItems: 10,
 			uniqueItems: true,
-			items: { type: 'string' },
+			items: { type: "string" },
 		},
 	},
 	required: ["alsoKnownAs"],
diff --git a/packages/backend/src/server/api/endpoints/i/move.ts b/packages/backend/src/server/api/endpoints/i/move.ts
index ba2360b19..d972aaf1d 100644
--- a/packages/backend/src/server/api/endpoints/i/move.ts
+++ b/packages/backend/src/server/api/endpoints/i/move.ts
@@ -98,12 +98,10 @@ export default define(meta, paramDef, async (ps, user) => {
 	const { username, host } = parse(ps.moveToAccount);
 	if (!host) throw new ApiError(meta.errors.notRemote);
 
-	const moveTo: User = await resolveUser(username, host).catch(
-		(e) => {
-			apiLogger.warn(`failed to resolve remote user: ${e}`);
-			throw new ApiError(meta.errors.noSuchMoveTarget);
-		},
-	);
+	const moveTo: User = await resolveUser(username, host).catch((e) => {
+		apiLogger.warn(`failed to resolve remote user: ${e}`);
+		throw new ApiError(meta.errors.noSuchMoveTarget);
+	});
 	let fromUrl: string | null = user.uri;
 	if (!fromUrl) {
 		fromUrl = `${config.url}/users/${user.id}`;
diff --git a/packages/client/src/pages/settings/migration.vue b/packages/client/src/pages/settings/migration.vue
index 610d1ce1f..df65e6e25 100644
--- a/packages/client/src/pages/settings/migration.vue
+++ b/packages/client/src/pages/settings/migration.vue
@@ -2,9 +2,13 @@
 	<div class="_formRoot">
 		<FormSection>
 			<template #label>{{ i18n.ts.moveTo }}</template>
-			<FormInfo warn class="_formBlock">{{ i18n.ts.moveAccountDescription }}</FormInfo>
+			<FormInfo warn class="_formBlock">{{
+				i18n.ts.moveAccountDescription
+			}}</FormInfo>
 			<FormInput v-model="moveToAccount" class="_formBlock">
-				<template #prefix><i class="ph-airplane-takeoff ph-bold ph-lg"></i></template>
+				<template #prefix
+					><i class="ph-airplane-takeoff ph-bold ph-lg"></i
+				></template>
 				<template #label>{{ i18n.ts.moveToLabel }}</template>
 			</FormInput>
 			<FormButton primary danger @click="move(moveToAccount)">
@@ -14,14 +18,30 @@
 
 		<FormSection>
 			<template #label>{{ i18n.ts.moveFrom }}</template>
-			<FormInfo warn class="_formBlock">{{ i18n.ts.moveFromDescription }}</FormInfo>
-			<FormInput v-for="(_, i) in accountAlias" v-model="accountAlias[i]" class="_formBlock">
-				<template #prefix><i class="ph-airplane-landing ph-bold ph-lg"></i></template>
-				<template #label>{{ `#${i + 1} ${i18n.ts.moveFromLabel}` }}</template>
+			<FormInfo warn class="_formBlock">{{
+				i18n.ts.moveFromDescription
+			}}</FormInfo>
+			<FormInput
+				v-for="(_, i) in accountAlias"
+				v-model="accountAlias[i]"
+				class="_formBlock"
+			>
+				<template #prefix
+					><i class="ph-airplane-landing ph-bold ph-lg"></i
+				></template>
+				<template #label>{{
+					`#${i + 1} ${i18n.ts.moveFromLabel}`
+				}}</template>
 			</FormInput>
-			<FormButton class="button" :disabled="accountAlias.length >= 10" inline style="margin-right: 8px" @click="add"><i
-					class="ph-plus ph-bold ph-lg"></i>
-				{{ i18n.ts.add }}</FormButton>
+			<FormButton
+				class="button"
+				:disabled="accountAlias.length >= 10"
+				inline
+				style="margin-right: 8px"
+				@click="add"
+				><i class="ph-plus ph-bold ph-lg"></i>
+				{{ i18n.ts.add }}</FormButton
+			>
 			<FormButton class="button" inline primary @click="save">
 				<i class="ph-floppy-disk-back ph-bold ph-lg"></i>
 				{{ i18n.ts.save }}
@@ -39,7 +59,7 @@ import * as os from "@/os";
 import { i18n } from "@/i18n";
 import { definePageMetadata } from "@/scripts/page-metadata";
 import { $i } from "@/account";
-import { toString } from 'calckey-js/built/acct';
+import { toString } from "calckey-js/built/acct";
 
 let moveToAccount = $ref("");
 let accountAlias = $ref([""]);
@@ -48,21 +68,26 @@ await init();
 
 async function init() {
 	if ($i?.alsoKnownAs && $i.alsoKnownAs.length > 0) {
-		const aka = await os.api('users/show', { userIds: $i.alsoKnownAs });
-		accountAlias = (aka && aka.length > 0) ? aka.map(user => `@${toString(user)}`) : [''];
+		const aka = await os.api("users/show", { userIds: $i.alsoKnownAs });
+		accountAlias =
+			aka && aka.length > 0
+				? aka.map((user) => `@${toString(user)}`)
+				: [""];
+	} else {
+		accountAlias = [""];
 	}
 }
 
 async function save(): Promise<void> {
-	const i = os.apiWithDialog("i/known-as", {
-		alsoKnownAs: accountAlias.map(e => e.trim()).filter(e => e !== ""),
+	const i = await os.apiWithDialog("i/known-as", {
+		alsoKnownAs: accountAlias.map((e) => e.trim()).filter((e) => e !== ""),
 	});
 	$i.alsoKnownAs = i.alsoKnownAs;
 	await init();
 }
 
 function add(): void {
-	accountAlias.push('');
+	accountAlias.push("");
 }
 
 async function move(account): Promise<void> {

From badbecfe601e5c575a4ad3dbebc25bfe6542dd0a Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Wed, 31 May 2023 12:08:21 +0200
Subject: [PATCH 100/198] Less logging of every single WS message + use logger
 in Meilisearch

---
 packages/backend/src/db/meilisearch.ts          | 2 +-
 packages/backend/src/server/api/stream/index.ts | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/packages/backend/src/db/meilisearch.ts b/packages/backend/src/db/meilisearch.ts
index 01efaa0ba..33553b64c 100644
--- a/packages/backend/src/db/meilisearch.ts
+++ b/packages/backend/src/db/meilisearch.ts
@@ -258,7 +258,7 @@ export default hasConfig
 						primaryKey: "id",
 					})
 					.then(() =>
-						console.log(`sent ${indexingBatch.length} posts for indexing`),
+						logger.info(`sent ${indexingBatch.length} posts for indexing`),
 					);
 			},
 			serverStats: async () => {
diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts
index 055fe200b..b8482c568 100644
--- a/packages/backend/src/server/api/stream/index.ts
+++ b/packages/backend/src/server/api/stream/index.ts
@@ -394,7 +394,6 @@ export default class Connection {
 	 * クライアントにメッセージ送信
 	 */
 	public sendMessageToWs(type: string, payload: any) {
-		console.log(payload, this.isMastodonCompatible);
 		if (this.isMastodonCompatible) {
 			if (payload.type === "note") {
 				this.wsConnection.send(

From e1361a28f8c0ff30b378a8db667e1c0f3898d808 Mon Sep 17 00:00:00 2001
From: "Konni (im Schloss)" <hi@kartonrad.de>
Date: Wed, 31 May 2023 13:52:21 +0200
Subject: [PATCH 101/198] Fixed dev enviroment _> documented procedure

---
 .envrc              |  4 +-
 docs/development.md | 90 ++++++++++++++++++++++++++++++++++++++++++++-
 flake.lock          | 54 +++++++++++++--------------
 flake.nix           |  2 +-
 4 files changed, 118 insertions(+), 32 deletions(-)

diff --git a/.envrc b/.envrc
index 0dcc9e739..3ce7171a3 100644
--- a/.envrc
+++ b/.envrc
@@ -1,4 +1,4 @@
-if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then
-  source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs="
+if ! has nix_direnv_version || ! nix_direnv_version 2.3.0; then
+  source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.3.0/direnvrc" "sha256-Dmd+j63L84wuzgyjITIfSxSD57Tx7v51DMxVZOsiUD8="
 fi
 use flake . --impure
diff --git a/docs/development.md b/docs/development.md
index 41d1b3469..6d6c0ea8d 100644
--- a/docs/development.md
+++ b/docs/development.md
@@ -7,8 +7,8 @@ Please note, however, that this environment will not work on Windows outside of
 
 ### Prerequisites
 
-- Installed the [Nix Package Manager](https://nixos.org/download.html)
-- Installed [direnv](https://direnv.net/docs/installation.html) and added its hook to your shell.
+- Installed the [Nix Package Manager](https://nixos.org/download.html) (use the comman on their website)
+- Installed [direnv](https://direnv.net/docs/installation.html) and added its hook to your shell. (package manager)
 
 Once the repo is cloned to your computer, follow these next few steps inside the Calckey folder:
 
@@ -20,3 +20,89 @@ Once the repo is cloned to your computer, follow these next few steps inside the
 - You should now see the admin user creation screen!
 
 Note: When you want to restart a dev server, all you need to do is run `devenv up`, no other steps are necessary.
+
+# Possible Troubles with the dev enviroment
+(this doesn't have to be done under normal conditions, this is for future reference)
+
+### direnv
+If you have any trouble with `direnv allow`
+Check that the contents of `.envrc` have the same version of nix-direnv that is specified here:
+> nix-direnv under -> installation -> using direnv source url
+> https://github.com/nix-community/nix-direnv#direnv-source_url
+
+there should be no errors during `direnv allow`
+
+### outdated nix packages
+if `install-deps` or any subsequent command doesn't run due to versioning problems
+`flake.nix` and `flake.lock` may be outdated
+
+delete `flake.lock`, or better, run `nix flake update --extra-experimental-features flakes --extra-experimental-features nix-command`
+after that, run `direnv rebuild`
+
+if there are any errors, you might have to change `flake.nix`
+(because the available options can change between versions - consider getting support in [the matrix channel](https://matrix.to/#/#calckey:matrix.fedibird.com))
+
+### after changing a node version
+in my case, i had to change the node version from 19, to 18
+
+! before proceeding, make sure to delete all build artifacts! 
+remove `node_modules` and `built` folders, and maybe `.devenv` and `.direnv` as well
+manually, or run `npm cache clean --force` and `pnpm cleanall`
+
+### Windows Subsystem for Linux
+if `devenv up` terminates because of wrong folder permissions, 
+
+create the file `/etc/wsl.conf` in your distro and add
+```shell
+[automount]
+options = "metadata"
+```
+
+this allows `chmod` calls to actually have an effect.
+the build scripts DO actually set the permissions, it just needs to work in wsl.
+
+### devenv up
+devenv up may take a looong time. (some say this is fake news, maybe it was bad luck in my case)
+
+do not get spooked by this error:
+```
+> calckey@14.0.0-dev32 start /mnt/.../calckey
+> pnpm --filter backend run start
+
+
+> backend@ start /mnt/.../calckey/packages/backend
+> pnpm node ./built/index.js
+
+node:internal/modules/cjs/loader:1078
+  throw err;
+  ^
+
+Error: Cannot find module '/mnt/.../calckey/packages/backend/built/index.js'
+    at Module._resolveFilename (node:internal/modules/cjs/loader:1075:15)
+    at Module._load (node:internal/modules/cjs/loader:920:27)
+    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
+    at node:internal/main/run_main_module:23:47 {
+  code: 'MODULE_NOT_FOUND',
+  requireStack: []
+}
+
+Node.js v18.16.0
+undefined
+/mnt/.../calckey/packages/backend:
+ ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL  backend@ start: `pnpm node ./built/index.js`
+Exit status 1
+ ELIFECYCLE  Command failed with exit code 1.
+```
+
+the script is designed to constantly try to start the server, while the build is still running.
+this just means that the build isn't finished yet.
+
+at some point you should see a banner that says "Calckey" in big letters -
+then you're good to go and can run `migrate` (in another terminal)!
+
+if you don't see the banner, 
+and it's for some reason stuck on `Finished 'build' after 917 ms` for a view minutes,
+
+just leave devenv running and open another terminal in the folder 
+run `migrate` and then `pnpm --filter backend run start` by yourself
+the server should start
diff --git a/flake.lock b/flake.lock
index c07493140..f1ff69041 100644
--- a/flake.lock
+++ b/flake.lock
@@ -8,11 +8,11 @@
         "pre-commit-hooks": "pre-commit-hooks"
       },
       "locked": {
-        "lastModified": 1682953188,
-        "narHash": "sha256-MFH6yK7QnEV6+T96Pt++lH8ozDn4YqzaOXAS6u5h3mM=",
+        "lastModified": 1685521914,
+        "narHash": "sha256-0fdFP5IASLwJ0PSXrErW8PZon9TVYmi8VRF8OtjGkV4=",
         "owner": "cachix",
         "repo": "devenv",
-        "rev": "c388b8c57116a71174d26b09c0c38b4b6b5bac3a",
+        "rev": "e206d8f2e3e8d6aa943656052f15bdfea8146b8d",
         "type": "github"
       },
       "original": {
@@ -29,11 +29,11 @@
         "rust-analyzer-src": "rust-analyzer-src"
       },
       "locked": {
-        "lastModified": 1682922129,
-        "narHash": "sha256-qnhkfksuuSLbN5UJM+KSCMSRC13bXosr6Ed3NwerRno=",
+        "lastModified": 1685514167,
+        "narHash": "sha256-urRxF0ZGSNeZjM4kALNg3wTh7fBscbqQmS6S/HU7Wms=",
         "owner": "nix-community",
         "repo": "fenix",
-        "rev": "c1f90f80ba4d60bea60685dd4515fb22d53279cc",
+        "rev": "3abfea51663583186f687c49a157eab1639349ca",
         "type": "github"
       },
       "original": {
@@ -63,11 +63,11 @@
         "nixpkgs-lib": "nixpkgs-lib"
       },
       "locked": {
-        "lastModified": 1680392223,
-        "narHash": "sha256-n3g7QFr85lDODKt250rkZj2IFS3i4/8HBU2yKHO3tqw=",
+        "lastModified": 1685457039,
+        "narHash": "sha256-bEFtQm+YyLxQjKQAaBHJyPN1z2wbhBnr2g1NJWSYjwM=",
         "owner": "hercules-ci",
         "repo": "flake-parts",
-        "rev": "dcc36e45d054d7bb554c9cdab69093debd91a0b5",
+        "rev": "80717d11615b6f42d1ad2e18ead51193fc15de69",
         "type": "github"
       },
       "original": {
@@ -155,11 +155,11 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1677534593,
-        "narHash": "sha256-PuZSAHeq4/9pP/uYH1FcagQ3nLm/DrDrvKi/xC9glvw=",
+        "lastModified": 1678875422,
+        "narHash": "sha256-T3o6NcQPwXjxJMn2shz86Chch4ljXgZn746c2caGxd8=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "3ad64d9e2d5bf80c877286102355b1625891ae9a",
+        "rev": "126f49a01de5b7e35a43fd43f891ecf6d3a51459",
         "type": "github"
       },
       "original": {
@@ -172,11 +172,11 @@
     "nixpkgs-lib": {
       "locked": {
         "dir": "lib",
-        "lastModified": 1680213900,
-        "narHash": "sha256-cIDr5WZIj3EkKyCgj/6j3HBH4Jj1W296z7HTcWj1aMA=",
+        "lastModified": 1682879489,
+        "narHash": "sha256-sASwo8gBt7JDnOOstnps90K1wxmVfyhsTPPNTGBPjjg=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "e3652e0735fbec227f342712f180f4f21f0594f2",
+        "rev": "da45bf6ec7bbcc5d1e14d3795c025199f28e0de0",
         "type": "github"
       },
       "original": {
@@ -205,11 +205,11 @@
     },
     "nixpkgs-stable": {
       "locked": {
-        "lastModified": 1673800717,
-        "narHash": "sha256-SFHraUqLSu5cC6IxTprex/nTsI81ZQAtDvlBvGDWfnA=",
+        "lastModified": 1678872516,
+        "narHash": "sha256-/E1YwtMtFAu2KUQKV/1+KFuReYPANM2Rzehk84VxVoc=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "2f9fd351ec37f5d479556cd48be4ca340da59b8f",
+        "rev": "9b8e5abb18324c7fe9f07cb100c3cd4a29cda8b8",
         "type": "github"
       },
       "original": {
@@ -221,11 +221,11 @@
     },
     "nixpkgs_2": {
       "locked": {
-        "lastModified": 1682929865,
-        "narHash": "sha256-jxVrgnf5QNjO+XoxDxUWtN2G5xyJSGZ5SWDQFxMuHxc=",
+        "lastModified": 1685399834,
+        "narHash": "sha256-Lt7//5snriXSdJo5hlVcDkpERL1piiih0UXIz1RUcC4=",
         "owner": "nixos",
         "repo": "nixpkgs",
-        "rev": "f2e9a130461950270f87630b11132323706b4d91",
+        "rev": "58c85835512b0db938600b6fe13cc3e3dc4b364e",
         "type": "github"
       },
       "original": {
@@ -250,11 +250,11 @@
         "nixpkgs-stable": "nixpkgs-stable"
       },
       "locked": {
-        "lastModified": 1677160285,
-        "narHash": "sha256-tBzpCjMP+P3Y3nKLYvdBkXBg3KvTMo3gvi8tLQaqXVY=",
+        "lastModified": 1682596858,
+        "narHash": "sha256-Hf9XVpqaGqe/4oDGr30W8HlsWvJXtMsEPHDqHZA6dDg=",
         "owner": "cachix",
         "repo": "pre-commit-hooks.nix",
-        "rev": "2bd861ab81469428d9c823ef72c4bb08372dd2c4",
+        "rev": "fb58866e20af98779017134319b5663b8215d912",
         "type": "github"
       },
       "original": {
@@ -274,11 +274,11 @@
     "rust-analyzer-src": {
       "flake": false,
       "locked": {
-        "lastModified": 1682886915,
-        "narHash": "sha256-FPQKPvlHIU2DsDF6GMoRtrZhil0vHi6MFd8vpKEx/n8=",
+        "lastModified": 1685465261,
+        "narHash": "sha256-aJ2nUinUrNcFi+pb47bS5IIAeSiUEEPLJY8W4Q8Pcjk=",
         "owner": "rust-lang",
         "repo": "rust-analyzer",
-        "rev": "3a27518fee5a723005299cf49e2d58a842a261ca",
+        "rev": "d2b3caa5b5694125fad04a9699e919444439f6a2",
         "type": "github"
       },
       "original": {
diff --git a/flake.nix b/flake.nix
index 73d8fe02f..8553456ea 100644
--- a/flake.nix
+++ b/flake.nix
@@ -41,7 +41,7 @@
 							languages.typescript.enable = true;
 							# Enable javascript for NPM and PNPM
 							languages.javascript.enable = true;
-							languages.javascript.package = pkgs.nodejs_19;
+							languages.javascript.package = pkgs.nodejs_18;
 							# Enable stable Rust for the backend
 							languages.rust.enable = true;
 							languages.rust.version = "stable";

From 6013a0b309cf1f5e5ea52e37233bd4cca3ec07d4 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Wed, 31 May 2023 12:06:26 -0700
Subject: [PATCH 102/198] chore: :arrow_up: up bull

---
 packages/backend/package.json |  2 +-
 pnpm-lock.yaml                | 11 ++++++-----
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 82a3f6cc8..c084d67bc 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -50,7 +50,7 @@
 		"axios": "^1.3.2",
 		"bcryptjs": "2.4.3",
 		"blurhash": "1.1.5",
-		"bull": "4.10.2",
+		"bull": "4.10.4",
 		"cacheable-lookup": "7.0.0",
 		"calckey-js": "workspace:*",
 		"cbor": "8.1.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index fa4763f26..1c06f87dd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -163,8 +163,8 @@ importers:
         specifier: 1.1.5
         version: 1.1.5
       bull:
-        specifier: 4.10.2
-        version: 4.10.2
+        specifier: 4.10.4
+        version: 4.10.4
       cacheable-lookup:
         specifier: 7.0.0
         version: 7.0.0
@@ -4983,8 +4983,8 @@ packages:
       node-gyp-build: 4.6.0
     dev: false
 
-  /bull@4.10.2:
-    resolution: {integrity: sha512-xa65xtWjQsLqYU/eNaXxq9VRG8xd6qNsQEjR7yjYuae05xKrzbVMVj2QgrYsTMmSs/vsqJjHqHSRRiW1+IkGXQ==}
+  /bull@4.10.4:
+    resolution: {integrity: sha512-o9m/7HjS/Or3vqRd59evBlWCXd9Lp+ALppKseoSKHaykK46SmRjAilX98PgmOz1yeVaurt8D5UtvEt4bUjM3eA==}
     engines: {node: '>=12'}
     dependencies:
       cron-parser: 4.7.1
@@ -4993,7 +4993,6 @@ packages:
       ioredis: 5.3.2
       lodash: 4.17.21
       msgpackr: 1.8.1
-      p-timeout: 3.2.0
       semver: 7.3.8
       uuid: 8.3.2
     transitivePeerDependencies:
@@ -11326,6 +11325,7 @@ packages:
   /p-finally@1.0.0:
     resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==}
     engines: {node: '>=4'}
+    dev: true
 
   /p-limit@2.3.0:
     resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
@@ -11370,6 +11370,7 @@ packages:
     engines: {node: '>=8'}
     dependencies:
       p-finally: 1.0.0
+    dev: true
 
   /p-try@2.2.0:
     resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}

From 87967b5662049ad5da9bf0423a8b1bc4928c258d Mon Sep 17 00:00:00 2001
From: Freeplay <freeplay@duck.com>
Date: Wed, 31 May 2023 16:28:07 -0400
Subject: [PATCH 103/198] button styling

---
 packages/client/src/components/MkInfo.vue     |  6 +++-
 .../src/components/global/MkPageHeader.vue    | 24 ++-------------
 packages/client/src/style.scss                | 30 +++++++++++++++++++
 3 files changed, 37 insertions(+), 23 deletions(-)

diff --git a/packages/client/src/components/MkInfo.vue b/packages/client/src/components/MkInfo.vue
index d8bf68fc1..481f9fbd1 100644
--- a/packages/client/src/components/MkInfo.vue
+++ b/packages/client/src/components/MkInfo.vue
@@ -10,7 +10,7 @@
 		<button
 			v-if="closeable"
 			v-tooltip="i18n.ts.close"
-			class="_button close"
+			class="_buttonIcon close"
 			@click.stop="close"
 		>
 			<i class="ph-x ph-bold ph-lg"></i>
@@ -48,6 +48,9 @@ function close() {
 	background: var(--infoBg);
 	color: var(--infoFg);
 	border-radius: var(--radius);
+	display: flex;
+	align-items: center;
+	gap: .4em;
 
 	&.warn {
 		background: var(--infoWarnBg);
@@ -55,6 +58,7 @@ function close() {
 	}
 
 	&.card {
+		display: inline;
 		background: var(--panel);
 		color: var(--fg);
 		padding: 48px;
diff --git a/packages/client/src/components/global/MkPageHeader.vue b/packages/client/src/components/global/MkPageHeader.vue
index 67549a654..ae703092f 100644
--- a/packages/client/src/components/global/MkPageHeader.vue
+++ b/packages/client/src/components/global/MkPageHeader.vue
@@ -11,7 +11,7 @@
 			<div class="buttons">
 				<button
 					v-if="props.displayBackButton"
-					class="_button button icon backButton"
+					class="_buttonIcon button icon backButton"
 					@click.stop="goBack()"
 					@touchstart="preventDrag"
 					v-tooltip.noDelay="i18n.ts.goBack"
@@ -110,7 +110,7 @@
 			<template v-for="action in actions">
 				<button
 					v-tooltip.noDelay="action.text"
-					class="_button button"
+					class="_buttonIcon button"
 					:class="{ highlighted: action.highlighted }"
 					@click.stop="action.handler"
 					@touchstart="preventDrag"
@@ -393,26 +393,6 @@ onUnmounted(() => {
 			}
 		}
 
-		> .button/*, @at-root .backButton*/ {
-			/* I don't know how to get this to work */
-			display: flex;
-			align-items: center;
-			justify-content: center;
-			height: calc(var(--height) - (var(--margin) * 2));
-			width: calc(var(--height) - (var(--margin) * 2));
-			box-sizing: border-box;
-			position: relative;
-			border-radius: 5px;
-
-			&:hover {
-				background: rgba(0, 0, 0, 0.05);
-			}
-
-			&.highlighted {
-				color: var(--accent);
-			}
-		}
-
 		> .fullButton {
 			& + .fullButton {
 				margin-left: 12px;
diff --git a/packages/client/src/style.scss b/packages/client/src/style.scss
index 2fa2c861e..1dec7d3b7 100644
--- a/packages/client/src/style.scss
+++ b/packages/client/src/style.scss
@@ -219,6 +219,36 @@ hr {
 	}
 }
 
+._buttonIcon {
+	@extend ._button;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	width: calc(var(--height) - (var(--margin) * 2));
+	height: calc(var(--height) - (var(--margin) * 2));
+	box-sizing: border-box;
+	position: relative;
+	border-radius: 5px;
+
+	&::before {
+		content: "";
+		position: absolute;
+		width: 2.7em;
+		height: 2.7em;
+		border-radius: inherit;
+		transition: background 0.2s;
+	}
+
+	&:hover:before {
+		// background: rgba(0, 0, 0, 0.05);
+		background: var(--buttonBg);
+	}
+
+	&.highlighted::before {
+		color: var(--accent);
+	}
+}
+
 ._buttonPrimary {
 	@extend ._button;
 	color: var(--fgOnAccent);

From 867c4cf0d75053325052d420434acb2251dc06a9 Mon Sep 17 00:00:00 2001
From: Freeplay <freeplay@duck.com>
Date: Wed, 31 May 2023 16:41:31 -0400
Subject: [PATCH 104/198] fix

---
 packages/client/src/style.scss | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/packages/client/src/style.scss b/packages/client/src/style.scss
index 1dec7d3b7..eefa610d0 100644
--- a/packages/client/src/style.scss
+++ b/packages/client/src/style.scss
@@ -229,6 +229,7 @@ hr {
 	box-sizing: border-box;
 	position: relative;
 	border-radius: 5px;
+	outline: none;
 
 	&::before {
 		content: "";
@@ -239,12 +240,13 @@ hr {
 		transition: background 0.2s;
 	}
 
-	&:hover:before {
-		// background: rgba(0, 0, 0, 0.05);
+	&:hover:before, &:focus::before, &.highlighted::before {
 		background: var(--buttonBg);
 	}
-
-	&.highlighted::before {
+	&:focus-visible::before {
+		outline: auto;
+	}
+	&.highlighted {
 		color: var(--accent);
 	}
 }

From 9f2fb71ec0fa849405f02b7eb9ab1cecf766b70a Mon Sep 17 00:00:00 2001
From: Freeplay <freeplay@duck.com>
Date: Wed, 31 May 2023 17:02:58 -0400
Subject: [PATCH 105/198] Fix #10236

---
 packages/client/src/components/MkNote.vue           |  1 +
 packages/client/src/components/MkNoteSub.vue        |  1 +
 packages/client/src/components/MkSubNoteContent.vue | 10 ++++++----
 3 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/packages/client/src/components/MkNote.vue b/packages/client/src/components/MkNote.vue
index af3e1c4ee..589b83a51 100644
--- a/packages/client/src/components/MkNote.vue
+++ b/packages/client/src/components/MkNote.vue
@@ -9,6 +9,7 @@
 		class="tkcbzcuz note-container"
 		:tabindex="!isDeleted ? '-1' : null"
 		:class="{ renote: isRenote }"
+		:id="appearNote.id"
 	>
 		<MkNoteSub
 			v-if="appearNote.reply && !detailedView"
diff --git a/packages/client/src/components/MkNoteSub.vue b/packages/client/src/components/MkNoteSub.vue
index f4637948d..dd69145a0 100644
--- a/packages/client/src/components/MkNoteSub.vue
+++ b/packages/client/src/components/MkNoteSub.vue
@@ -30,6 +30,7 @@
 						:note="note"
 						:parentId="parentId"
 						:conversation="conversation"
+						:detailedView="detailedView"
 						@focusfooter="footerEl.focus()"
 					/>
 					<div v-if="translating || translation" class="translation">
diff --git a/packages/client/src/components/MkSubNoteContent.vue b/packages/client/src/components/MkSubNoteContent.vue
index d6263cfd2..e651b3923 100644
--- a/packages/client/src/components/MkSubNoteContent.vue
+++ b/packages/client/src/components/MkSubNoteContent.vue
@@ -2,7 +2,7 @@
 	<p v-if="note.cw != null" class="cw">
 		<MkA
 			v-if="conversation && note.renoteId == parentId"
-			:to="`#${parentId}`"
+			:to="detailedView ? `#${parentId}` : `${notePage(note)}#${parentId}`"
 			behavior="browser"
 			class="reply-icon"
 			@click.stop
@@ -11,7 +11,7 @@
 		</MkA>
 		<MkA
 			v-else-if="!detailed && note.replyId"
-			:to="`#${note.replyId}`"
+			:to="detailedView ? `#${note.replyId}` :`${notePage(note)}#${note.replyId}`"
 			behavior="browser"
 			v-tooltip="i18n.ts.jumpToPrevious"
 			class="reply-icon"
@@ -66,7 +66,7 @@
 				<template v-if="!note.cw">
 					<MkA
 						v-if="conversation && note.renoteId == parentId"
-						:to="`#${parentId}`"
+						:to="detailedView ? `#${parentId}` : `${notePage(note)}#${parentId}`"
 						behavior="browser"
 						class="reply-icon"
 						@click.stop
@@ -75,7 +75,7 @@
 					</MkA>
 					<MkA
 						v-else-if="!detailed && note.replyId"
-						:to="`#${note.replyId}`"
+						:to="detailedView ? `#${note.replyId}` :`${notePage(note)}#${note.replyId}`"
 						behavior="browser"
 						v-tooltip="i18n.ts.jumpToPrevious"
 						class="reply-icon"
@@ -173,11 +173,13 @@ import MkUrlPreview from "@/components/MkUrlPreview.vue";
 import XShowMoreButton from "@/components/MkShowMoreButton.vue";
 import XCwButton from "@/components/MkCwButton.vue";
 import MkButton from "@/components/MkButton.vue";
+import { notePage } from "@/filters/note";
 import { extractUrlFromMfm } from "@/scripts/extract-url-from-mfm";
 import { extractMfmWithAnimation } from "@/scripts/extract-mfm";
 import { i18n } from "@/i18n";
 import { defaultStore } from "@/store";
 
+
 const props = defineProps<{
 	note: misskey.entities.Note;
 	parentId?;

From e492a581b397f6495e8ba8613844f360074002d8 Mon Sep 17 00:00:00 2001
From: Freeplay <freeplay@duck.com>
Date: Wed, 31 May 2023 17:59:03 -0400
Subject: [PATCH 106/198] lemme just update that

---
 packages/client/src/pages/about-calckey.vue | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/client/src/pages/about-calckey.vue b/packages/client/src/pages/about-calckey.vue
index 4e511a676..71dbeb87b 100644
--- a/packages/client/src/pages/about-calckey.vue
+++ b/packages/client/src/pages/about-calckey.vue
@@ -104,9 +104,9 @@
 								><Mfm
 									:text="'@panos@calckey.social (Project Coordinator)'"
 							/></FormLink>
-							<FormLink to="/@freeplay@bz.pawdev.me"
+							<FormLink to="/@freeplay@calckey.social"
 								><Mfm
-									:text="'@freeplay@bz.pawdev.me (UI/UX Designer)'"
+									:text="'@freeplay@calckey.social (UI)'"
 							/></FormLink>
 							<FormLink
 								to="https://www.youtube.com/c/Henkiwashere"

From 0c9b1014791f73fdd6c7425c12f3f90f8d92d2c9 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 1 Jun 2023 07:24:49 +0900
Subject: [PATCH 107/198] switch position of social and recommended

---
 packages/client/src/pages/timeline.vue | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/packages/client/src/pages/timeline.vue b/packages/client/src/pages/timeline.vue
index a44f36c55..1947caaef 100644
--- a/packages/client/src/pages/timeline.vue
+++ b/packages/client/src/pages/timeline.vue
@@ -109,12 +109,12 @@ let timelines = ["home"];
 if (isLocalTimelineAvailable) {
 	timelines.push("local");
 }
-if (isRecommendedTimelineAvailable) {
-	timelines.push("recommended");
-}
 if (isLocalTimelineAvailable) {
 	timelines.push("social");
 }
+if (isRecommendedTimelineAvailable) {
+	timelines.push("recommended");
+}
 if (isGlobalTimelineAvailable) {
 	timelines.push("global");
 }
@@ -255,16 +255,6 @@ const headerTabs = $computed(() => [
 				},
 		  ]
 		: []),
-	...(isRecommendedTimelineAvailable
-		? [
-				{
-					key: "recommended",
-					title: i18n.ts._timelines.recommended,
-					icon: "ph-thumbs-up ph-bold ph-lg",
-					iconOnly: true,
-				},
-		  ]
-		: []),
 	...(isLocalTimelineAvailable
 		? [
 				{
@@ -275,6 +265,16 @@ const headerTabs = $computed(() => [
 				},
 		  ]
 		: []),
+	...(isRecommendedTimelineAvailable
+		? [
+				{
+					key: "recommended",
+					title: i18n.ts._timelines.recommended,
+					icon: "ph-thumbs-up ph-bold ph-lg",
+					iconOnly: true,
+				},
+		  ]
+		: []),
 	...(isGlobalTimelineAvailable
 		? [
 				{

From 6e09a56c2dc9a080fbfb44cc96e778870c8da028 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 1 Jun 2023 07:29:54 +0900
Subject: [PATCH 108/198] no change, but more comfortable

---
 packages/client/src/pages/timeline.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/pages/timeline.vue b/packages/client/src/pages/timeline.vue
index 1947caaef..5d96d6dfd 100644
--- a/packages/client/src/pages/timeline.vue
+++ b/packages/client/src/pages/timeline.vue
@@ -195,7 +195,7 @@ async function chooseAntenna(ev: MouseEvent): Promise<void> {
 }
 
 function saveSrc(
-	newSrc: "home" | "local" | "recommended" | "social" | "global"
+	newSrc: "home" | "local" | "social" | "recommended" | "global"
 ): void {
 	defaultStore.set("tl", {
 		...defaultStore.state.tl,

From 8c6f98cfc3f3cb0567f2e5ed1d1bf0db154faf0f Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 1 Jun 2023 07:55:59 +0900
Subject: [PATCH 109/198] Fix exported settings being not importable

---
 packages/client/src/pages/settings/preferences-backups.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/pages/settings/preferences-backups.vue b/packages/client/src/pages/settings/preferences-backups.vue
index a5f606e0e..595fe4f03 100644
--- a/packages/client/src/pages/settings/preferences-backups.vue
+++ b/packages/client/src/pages/settings/preferences-backups.vue
@@ -173,7 +173,7 @@ function validate(profile: unknown): void {
 	if (!isObject(profile)) throw new Error("not an object");
 
 	// Check if unnecessary properties exist
-	if (Object.keys(profile).some((key) => !profileProps.includes(key)))
+	if (Object.keys(profile).some((key) => !profileProps.includes(key) && key !== "host"))
 		throw new Error("Unnecessary properties exist");
 
 	if (!profile.name) throw new Error("Missing required prop: name");

From a4b5726eccda8df8e1be1ced7c4fc8c8a120dbbb Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 1 Jun 2023 08:01:24 +0900
Subject: [PATCH 110/198] format

---
 packages/client/src/pages/settings/preferences-backups.vue | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/packages/client/src/pages/settings/preferences-backups.vue b/packages/client/src/pages/settings/preferences-backups.vue
index 595fe4f03..14bb27f91 100644
--- a/packages/client/src/pages/settings/preferences-backups.vue
+++ b/packages/client/src/pages/settings/preferences-backups.vue
@@ -173,7 +173,11 @@ function validate(profile: unknown): void {
 	if (!isObject(profile)) throw new Error("not an object");
 
 	// Check if unnecessary properties exist
-	if (Object.keys(profile).some((key) => !profileProps.includes(key) && key !== "host"))
+	if (
+		Object.keys(profile).some(
+			(key) => !profileProps.includes(key) && key !== "host"
+		)
+	)
 		throw new Error("Unnecessary properties exist");
 
 	if (!profile.name) throw new Error("Missing required prop: name");

From 80d23791fb05845b100fd8014ac360eff3708249 Mon Sep 17 00:00:00 2001
From: Freeplay <freeplay@duck.com>
Date: Wed, 31 May 2023 19:35:02 -0400
Subject: [PATCH 111/198] fix

---
 packages/client/src/components/MkInfo.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/components/MkInfo.vue b/packages/client/src/components/MkInfo.vue
index 481f9fbd1..f07594ca7 100644
--- a/packages/client/src/components/MkInfo.vue
+++ b/packages/client/src/components/MkInfo.vue
@@ -58,7 +58,7 @@ function close() {
 	}
 
 	&.card {
-		display: inline;
+		display: block;
 		background: var(--panel);
 		color: var(--fg);
 		padding: 48px;

From 4fb9b0bbf7cbf3cc54ac892d6588c1c49cb87fc7 Mon Sep 17 00:00:00 2001
From: Freeplay <freeplay@duck.com>
Date: Wed, 31 May 2023 19:43:58 -0400
Subject: [PATCH 112/198] hide broken logged-out header link for now

---
 packages/client/src/ui/visitor/header.vue | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/client/src/ui/visitor/header.vue b/packages/client/src/ui/visitor/header.vue
index e79bd288e..dbeeb3d2c 100644
--- a/packages/client/src/ui/visitor/header.vue
+++ b/packages/client/src/ui/visitor/header.vue
@@ -6,14 +6,14 @@
 					><i class="ph-house ph-bold ph-lg icon"></i
 					>{{ i18n.ts.home }}</MkA
 				>
-				<MkA
+				<!-- <MkA
 					v-if="isTimelineAvailable"
 					to="/timeline"
 					class="link"
 					active-class="active"
 					><i class="ph-chats-circle ph-bold ph-lg icon"></i
 					>{{ i18n.ts.timeline }}</MkA
-				>
+				> -->
 				<MkA to="/explore" class="link" active-class="active"
 					><i class="ph-compass ph-bold ph-lg icon"></i
 					>{{ i18n.ts.explore }}</MkA

From 5417a97255727cf5decfa9f105a618c6d78b5583 Mon Sep 17 00:00:00 2001
From: Freeplay <freeplay@duck.com>
Date: Wed, 31 May 2023 22:10:44 -0400
Subject: [PATCH 113/198] Fix #10219

---
 packages/client/src/components/MkPageWindow.vue | 1 +
 packages/client/src/components/MkWindow.vue     | 6 ++++++
 2 files changed, 7 insertions(+)

diff --git a/packages/client/src/components/MkPageWindow.vue b/packages/client/src/components/MkPageWindow.vue
index bb5066b7e..703494144 100644
--- a/packages/client/src/components/MkPageWindow.vue
+++ b/packages/client/src/components/MkPageWindow.vue
@@ -9,6 +9,7 @@
 		:buttons-right="buttonsRight"
 		:contextmenu="contextmenu"
 		@closed="$emit('closed')"
+		class="page-window"
 	>
 		<template #header>
 			<template v-if="pageMetadata?.value">
diff --git a/packages/client/src/components/MkWindow.vue b/packages/client/src/components/MkWindow.vue
index 5aa3adef2..e6509acf9 100644
--- a/packages/client/src/components/MkWindow.vue
+++ b/packages/client/src/components/MkWindow.vue
@@ -9,6 +9,7 @@
 			ref="rootEl"
 			class="ebkgocck"
 			:class="{ maximized }"
+			v-bind="$attrs"
 		>
 			<div
 				class="body _shadow _narrow_"
@@ -596,6 +597,11 @@ defineExpose({
 		}
 	}
 
+	&.page-window > .body > .body {
+		background: var(--bg);
+		scrollbar-gutter: stable;
+	}
+
 	> .handle {
 		$size: 8px;
 

From a7df4e399727736c2bd148349c6ecda1026ab3af Mon Sep 17 00:00:00 2001
From: Freeplay <freeplay@duck.com>
Date: Wed, 31 May 2023 22:38:14 -0400
Subject: [PATCH 114/198] hide back button when not needed

---
 packages/client/src/components/MkPageWindow.vue        | 1 +
 packages/client/src/components/global/MkPageHeader.vue | 4 +++-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/packages/client/src/components/MkPageWindow.vue b/packages/client/src/components/MkPageWindow.vue
index 703494144..a5951fd1d 100644
--- a/packages/client/src/components/MkPageWindow.vue
+++ b/packages/client/src/components/MkPageWindow.vue
@@ -97,6 +97,7 @@ provideMetadataReceiver((info) => {
 	pageMetadata = info;
 });
 provide("shouldOmitHeaderTitle", true);
+provide("shouldBackButton", false);
 provide("shouldHeaderThin", true);
 
 const contextmenu = $computed(() => [
diff --git a/packages/client/src/components/global/MkPageHeader.vue b/packages/client/src/components/global/MkPageHeader.vue
index ae703092f..1a8c74500 100644
--- a/packages/client/src/components/global/MkPageHeader.vue
+++ b/packages/client/src/components/global/MkPageHeader.vue
@@ -10,7 +10,7 @@
 		<div class="left">
 			<div class="buttons">
 				<button
-					v-if="props.displayBackButton"
+					v-if="displayBackButton"
 					class="_buttonIcon button icon backButton"
 					@click.stop="goBack()"
 					@touchstart="preventDrag"
@@ -164,6 +164,8 @@ const props = defineProps<{
 	to?: string;
 }>();
 
+const displayBackButton = props.displayBackButton && history.length > 2 && inject("shouldBackButton", true);
+
 const emit = defineEmits<{
 	(ev: "update:tab", key: string);
 }>();

From 72d12f50f65da2b9f7212e329d4faa52521ae14b Mon Sep 17 00:00:00 2001
From: Freeplay <freeplay@duck.com>
Date: Wed, 31 May 2023 23:35:25 -0400
Subject: [PATCH 115/198] Add expand on note click option

---
 locales/en-US.yml                                 | 2 ++
 packages/client/src/components/MkNote.vue         | 5 +++--
 packages/client/src/components/MkNoteDetailed.vue | 4 ----
 packages/client/src/components/MkNoteSub.vue      | 9 ++++++---
 packages/client/src/pages/settings/general.vue    | 8 ++++++++
 packages/client/src/store.ts                      | 4 ++++
 6 files changed, 23 insertions(+), 9 deletions(-)

diff --git a/locales/en-US.yml b/locales/en-US.yml
index ece9a9201..a1c406c5d 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -598,6 +598,8 @@ scratchpadDescription: "The scratchpad provides an environment for AiScript expe
 output: "Output"
 script: "Script"
 disablePagesScript: "Disable AiScript on Pages"
+expandOnNoteClick: "Expand post on click"
+expandOnNoteClickDesc: "If disabled, you can still expand posts in the right-click menu or by clicking the timestamp."
 updateRemoteUser: "Update remote user information"
 deleteAllFiles: "Delete all files"
 deleteAllFilesConfirm: "Are you sure that you want to delete all files?"
diff --git a/packages/client/src/components/MkNote.vue b/packages/client/src/components/MkNote.vue
index 589b83a51..669fd67ef 100644
--- a/packages/client/src/components/MkNote.vue
+++ b/packages/client/src/components/MkNote.vue
@@ -68,6 +68,7 @@
 			class="article"
 			@contextmenu.stop="onContextmenu"
 			@click="noteClick"
+			:style="{ cursor: expandOnNoteClick && !detailedView ? 'pointer' : '' }"
 		>
 			<div class="main">
 				<div class="header-container">
@@ -313,6 +314,7 @@ const muted = ref(getWordSoftMute(note, $i, defaultStore.state.mutedWords));
 const translation = ref(null);
 const translating = ref(false);
 const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
+const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
 
 const keymap = {
 	r: () => reply(true),
@@ -501,7 +503,7 @@ function scrollIntoView() {
 }
 
 function noteClick(e) {
-	if (document.getSelection().type === "Range" || props.detailedView) {
+	if (document.getSelection().type === "Range" || props.detailedView || !expandOnNoteClick) {
 		e.stopPropagation();
 	} else {
 		router.push(notePage(appearNote));
@@ -704,7 +706,6 @@ defineExpose({
 		position: relative;
 		overflow: clip;
 		padding: 4px 32px 10px;
-		cursor: pointer;
 
 		&:first-child,
 		&:nth-child(2) {
diff --git a/packages/client/src/components/MkNoteDetailed.vue b/packages/client/src/components/MkNoteDetailed.vue
index 68e33d701..ff8264a60 100644
--- a/packages/client/src/components/MkNoteDetailed.vue
+++ b/packages/client/src/components/MkNoteDetailed.vue
@@ -534,12 +534,8 @@ onUnmounted(() => {
 
 	> .reply {
 		border-top: solid 0.5px var(--divider);
-		cursor: pointer;
 		padding-top: 24px;
 		padding-bottom: 10px;
-		@media (pointer: coarse) {
-			cursor: default;
-		}
 	}
 
 	// Hover
diff --git a/packages/client/src/components/MkNoteSub.vue b/packages/client/src/components/MkNoteSub.vue
index dd69145a0..60fbbe11e 100644
--- a/packages/client/src/components/MkNoteSub.vue
+++ b/packages/client/src/components/MkNoteSub.vue
@@ -14,7 +14,10 @@
 		@contextmenu.stop="onContextmenu"
 	>
 		<div v-if="conversation && depth > 1" class="line"></div>
-		<div class="main" @click="noteClick">
+		<div class="main" 
+			@click="noteClick"
+			:style="{ cursor: expandOnNoteClick ? 'pointer' : '' }"
+		>
 			<div class="avatar-container">
 				<MkAvatar class="avatar" :user="appearNote.user" />
 				<div
@@ -258,6 +261,7 @@ const replies: misskey.entities.Note[] =
 		)
 		.reverse() ?? [];
 const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
+const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
 
 useNoteCapture({
 	rootEl: el,
@@ -397,7 +401,7 @@ function blur() {
 }
 
 function noteClick(e) {
-	if (document.getSelection().type === "Range") {
+	if (document.getSelection().type === "Range" || !expandOnNoteClick) {
 		e.stopPropagation();
 	} else {
 		router.push(notePage(props.note));
@@ -422,7 +426,6 @@ function noteClick(e) {
 
 	> .main {
 		display: flex;
-		cursor: pointer;
 
 		> .avatar-container {
 			margin-right: 8px;
diff --git a/packages/client/src/pages/settings/general.vue b/packages/client/src/pages/settings/general.vue
index f120551b8..5486503b1 100644
--- a/packages/client/src/pages/settings/general.vue
+++ b/packages/client/src/pages/settings/general.vue
@@ -54,6 +54,10 @@
 			<FormSwitch v-model="disablePagesScript" class="_formBlock">{{
 				i18n.ts.disablePagesScript
 			}}</FormSwitch>
+			<FormSwitch v-model="expandOnNoteClick" class="_formBlock">{{ 
+				i18n.ts.expandOnNoteClick
+			}}<template #caption>{{ i18n.ts.expandOnNoteClickDesc }}</template>
+			</FormSwitch>
 			<FormSwitch v-model="profile.showTimelineReplies" class="_formBlock"
 				>{{ i18n.ts.flagShowTimelineReplies
 				}}<template #caption
@@ -299,6 +303,9 @@ const nsfw = computed(defaultStore.makeGetterSetter("nsfw"));
 const disablePagesScript = computed(
 	defaultStore.makeGetterSetter("disablePagesScript")
 );
+const expandOnNoteClick = computed(
+	defaultStore.makeGetterSetter("expandOnNoteClick")
+);
 const showFixedPostForm = computed(
 	defaultStore.makeGetterSetter("showFixedPostForm")
 );
@@ -366,6 +373,7 @@ watch(
 		seperateRenoteQuote,
 		showAdminUpdates,
 		autoplayMfm,
+		expandOnNoteClick,
 	],
 	async () => {
 		await reloadAsk();
diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts
index b6897a65c..68d299aa4 100644
--- a/packages/client/src/store.ts
+++ b/packages/client/src/store.ts
@@ -162,6 +162,10 @@ export const defaultStore = markRaw(
 			where: "device",
 			default: true,
 		},
+		expandOnNoteClick: {
+			where: "device",
+			default: true,
+		},
 		nsfw: {
 			where: "device",
 			default: "respect" as "respect" | "force" | "ignore",

From 61a29b1e92f67eab9014c51469e532f2a512fcc9 Mon Sep 17 00:00:00 2001
From: Freeplay <freeplay@duck.com>
Date: Wed, 31 May 2023 23:38:08 -0400
Subject: [PATCH 116/198] typo

---
 locales/en-US.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/locales/en-US.yml b/locales/en-US.yml
index a1c406c5d..251995cf4 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -598,8 +598,8 @@ scratchpadDescription: "The scratchpad provides an environment for AiScript expe
 output: "Output"
 script: "Script"
 disablePagesScript: "Disable AiScript on Pages"
-expandOnNoteClick: "Expand post on click"
-expandOnNoteClickDesc: "If disabled, you can still expand posts in the right-click menu or by clicking the timestamp."
+expandOnNoteClick: "Open post on click"
+expandOnNoteClickDesc: "If disabled, you can still open posts in the right-click menu or by clicking the timestamp."
 updateRemoteUser: "Update remote user information"
 deleteAllFiles: "Delete all files"
 deleteAllFilesConfirm: "Are you sure that you want to delete all files?"

From e05b0d0e97a0a00c72327ba11b8724c0da3678ae Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Thu, 1 Jun 2023 20:53:13 +0200
Subject: [PATCH 117/198] Import emojis without meta.json

---
 .../processors/db/import-custom-emojis.ts     | 127 +++++++++++++-----
 1 file changed, 91 insertions(+), 36 deletions(-)

diff --git a/packages/backend/src/queue/processors/db/import-custom-emojis.ts b/packages/backend/src/queue/processors/db/import-custom-emojis.ts
index e2454405f..77eb130a5 100644
--- a/packages/backend/src/queue/processors/db/import-custom-emojis.ts
+++ b/packages/backend/src/queue/processors/db/import-custom-emojis.ts
@@ -11,6 +11,7 @@ import { addFile } from "@/services/drive/add-file.js";
 import { genId } from "@/misc/gen-id.js";
 import { db } from "@/db/postgre.js";
 import probeImageSize from "probe-image-size";
+import * as path from "path";
 
 const logger = queueLogger.createSubLogger("import-custom-emojis");
 
@@ -29,11 +30,11 @@ export async function importCustomEmojis(
 		return;
 	}
 
-	const [path, cleanup] = await createTempDir();
+	const [tempPath, cleanup] = await createTempDir();
 
-	logger.info(`Temp dir is ${path}`);
+	logger.info(`Temp dir is ${tempPath}`);
 
-	const destPath = `${path}/emojis.zip`;
+	const destPath = `${tempPath}/emojis.zip`;
 
 	try {
 		fs.writeFileSync(destPath, "", "binary");
@@ -46,44 +47,98 @@ export async function importCustomEmojis(
 		throw e;
 	}
 
-	const outputPath = `${path}/emojis`;
+	const outputPath = `${tempPath}/emojis`;
 	const unzipStream = fs.createReadStream(destPath);
 	const zip = new AdmZip(destPath);
 	zip.extractAllToAsync(outputPath, true, false, async (error) => {
 		if (error) throw error;
-		const metaRaw = fs.readFileSync(`${outputPath}/meta.json`, "utf-8");
-		const meta = JSON.parse(metaRaw);
 
-		for (const record of meta.emojis) {
-			if (!record.downloaded) continue;
-			const emojiInfo = record.emoji;
-			const emojiPath = `${outputPath}/${record.fileName}`;
-			await Emojis.delete({
-				name: emojiInfo.name,
-			});
-			const driveFile = await addFile({
-				user: null,
-				path: emojiPath,
-				name: record.fileName,
-				force: true,
-			});
-			const file = fs.createReadStream(emojiPath);
-			const size = await probeImageSize(file);
-			file.destroy();
-			await Emojis.insert({
-				id: genId(),
-				updatedAt: new Date(),
-				name: emojiInfo.name,
-				category: emojiInfo.category,
-				host: null,
-				aliases: emojiInfo.aliases,
-				originalUrl: driveFile.url,
-				publicUrl: driveFile.webpublicUrl ?? driveFile.url,
-				type: driveFile.webpublicType ?? driveFile.type,
-				license: emojiInfo.license,
-				width: size.width || null,
-				height: size.height || null,
-			}).then((x) => Emojis.findOneByOrFail(x.identifiers[0]));
+		if (fs.existsSync(`${outputPath}/meta.json`)) {
+			logger.info("starting emoji import with metadata")
+			const metaRaw = fs.readFileSync(`${outputPath}/meta.json`, "utf-8");
+			const meta = JSON.parse(metaRaw);
+
+			for (const record of meta.emojis) {
+				if (!record.downloaded) continue;
+				const emojiInfo = record.emoji;
+				const emojiPath = `${outputPath}/${record.fileName}`;
+				await Emojis.delete({
+					name: emojiInfo.name,
+				});
+				const driveFile = await addFile({
+					user: null,
+					path: emojiPath,
+					name: record.fileName,
+					force: true,
+				});
+				const file = fs.createReadStream(emojiPath);
+				const size = await probeImageSize(file);
+				file.destroy();
+				await Emojis.insert({
+					id: genId(),
+					updatedAt: new Date(),
+					name: emojiInfo.name,
+					category: emojiInfo.category,
+					host: null,
+					aliases: emojiInfo.aliases,
+					originalUrl: driveFile.url,
+					publicUrl: driveFile.webpublicUrl ?? driveFile.url,
+					type: driveFile.webpublicType ?? driveFile.type,
+					license: emojiInfo.license,
+					width: size.width || null,
+					height: size.height || null,
+				}).then((x) => Emojis.findOneByOrFail(x.identifiers[0]));
+			}
+		} else {
+			logger.info("starting emoji import without metadata")
+			// Since we lack metadata, we import into a randomized category name instead
+			let categoryName = genId();
+
+			let containedEmojis = fs.readdirSync(outputPath);
+
+			// Filter directories
+			containedEmojis = containedEmojis.filter(
+				(emoji) => !fs.lstatSync(`${outputPath}/${emoji}`).isDirectory(),
+			);
+
+			// Filter out accidental JSON files
+			containedEmojis = containedEmojis.filter((emoji) =>
+				emoji.match(/\.(json)$/i),
+			);
+
+			// only go one layer deep, recursion would be weird
+			containedEmojis.filter((emoji) => !emoji.includes("/"));
+
+			for (const emojiPath of containedEmojis) {
+				// strip extension and get filename to use as name
+				const name = path.basename(emojiPath, path.extname(emojiPath));
+				await Emojis.delete({
+					name: name,
+				});
+				const driveFile = await addFile({
+					user: null,
+					path: emojiPath,
+					name: path.basename(emojiPath),
+					force: true,
+				});
+				const file = fs.createReadStream(emojiPath);
+				const size = await probeImageSize(file);
+				file.destroy();
+				await Emojis.insert({
+					id: genId(),
+					updatedAt: new Date(),
+					name: name,
+					category: categoryName,
+					host: null,
+					aliases: [],
+					originalUrl: driveFile.url,
+					publicUrl: driveFile.webpublicUrl ?? driveFile.url,
+					type: driveFile.webpublicType ?? driveFile.type,
+					license: null,
+					width: size.width || null,
+					height: size.height || null,
+				}).then((x) => Emojis.findOneByOrFail(x.identifiers[0]));
+			}
 		}
 
 		await db.queryResultCache!.remove(["meta_emojis"]);

From 33001fa45c0e6b346bbb2de49c6718e6f8ecf65d Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Thu, 1 Jun 2023 11:53:32 -0700
Subject: [PATCH 118/198] fix: locale key

---
 locales/en-US.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/locales/en-US.yml b/locales/en-US.yml
index ece9a9201..e26f6ed2b 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -2031,6 +2031,6 @@ _experiments:
   postEditingCaption: "Shows the option for users to edit their existing posts via\
     \ the post options menu."
   enablePostImports: "Enable post imports"
-  postImportsDescription: "Allows users to import their posts from past Calckey,\
+  postImportsCaption: "Allows users to import their posts from past Calckey,\
     \ Misskey, Mastodon, Akkoma, and Pleroma accounts. It may cause slowdowns during\
     \ load if your queue is bottlenecked."

From cd12015aaf42d49e90ae7e238e0d3649d3f0bf89 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Thu, 1 Jun 2023 11:55:13 -0700
Subject: [PATCH 119/198] chore: formatting

---
 packages/client/src/components/MkInfo.vue     |  2 +-
 .../src/components/MkSubNoteContent.vue       | 23 +++++++++++++++----
 .../src/components/global/MkPageHeader.vue    |  5 +++-
 packages/client/src/pages/about-calckey.vue   | 13 ++++++-----
 4 files changed, 30 insertions(+), 13 deletions(-)

diff --git a/packages/client/src/components/MkInfo.vue b/packages/client/src/components/MkInfo.vue
index f07594ca7..ba3a606ab 100644
--- a/packages/client/src/components/MkInfo.vue
+++ b/packages/client/src/components/MkInfo.vue
@@ -50,7 +50,7 @@ function close() {
 	border-radius: var(--radius);
 	display: flex;
 	align-items: center;
-	gap: .4em;
+	gap: 0.4em;
 
 	&.warn {
 		background: var(--infoWarnBg);
diff --git a/packages/client/src/components/MkSubNoteContent.vue b/packages/client/src/components/MkSubNoteContent.vue
index e651b3923..0b6f920cb 100644
--- a/packages/client/src/components/MkSubNoteContent.vue
+++ b/packages/client/src/components/MkSubNoteContent.vue
@@ -2,7 +2,9 @@
 	<p v-if="note.cw != null" class="cw">
 		<MkA
 			v-if="conversation && note.renoteId == parentId"
-			:to="detailedView ? `#${parentId}` : `${notePage(note)}#${parentId}`"
+			:to="
+				detailedView ? `#${parentId}` : `${notePage(note)}#${parentId}`
+			"
 			behavior="browser"
 			class="reply-icon"
 			@click.stop
@@ -11,7 +13,11 @@
 		</MkA>
 		<MkA
 			v-else-if="!detailed && note.replyId"
-			:to="detailedView ? `#${note.replyId}` :`${notePage(note)}#${note.replyId}`"
+			:to="
+				detailedView
+					? `#${note.replyId}`
+					: `${notePage(note)}#${note.replyId}`
+			"
 			behavior="browser"
 			v-tooltip="i18n.ts.jumpToPrevious"
 			class="reply-icon"
@@ -66,7 +72,11 @@
 				<template v-if="!note.cw">
 					<MkA
 						v-if="conversation && note.renoteId == parentId"
-						:to="detailedView ? `#${parentId}` : `${notePage(note)}#${parentId}`"
+						:to="
+							detailedView
+								? `#${parentId}`
+								: `${notePage(note)}#${parentId}`
+						"
 						behavior="browser"
 						class="reply-icon"
 						@click.stop
@@ -75,7 +85,11 @@
 					</MkA>
 					<MkA
 						v-else-if="!detailed && note.replyId"
-						:to="detailedView ? `#${note.replyId}` :`${notePage(note)}#${note.replyId}`"
+						:to="
+							detailedView
+								? `#${note.replyId}`
+								: `${notePage(note)}#${note.replyId}`
+						"
 						behavior="browser"
 						v-tooltip="i18n.ts.jumpToPrevious"
 						class="reply-icon"
@@ -179,7 +193,6 @@ import { extractMfmWithAnimation } from "@/scripts/extract-mfm";
 import { i18n } from "@/i18n";
 import { defaultStore } from "@/store";
 
-
 const props = defineProps<{
 	note: misskey.entities.Note;
 	parentId?;
diff --git a/packages/client/src/components/global/MkPageHeader.vue b/packages/client/src/components/global/MkPageHeader.vue
index 1a8c74500..a000b16bc 100644
--- a/packages/client/src/components/global/MkPageHeader.vue
+++ b/packages/client/src/components/global/MkPageHeader.vue
@@ -164,7 +164,10 @@ const props = defineProps<{
 	to?: string;
 }>();
 
-const displayBackButton = props.displayBackButton && history.length > 2 && inject("shouldBackButton", true);
+const displayBackButton =
+	props.displayBackButton &&
+	history.length > 2 &&
+	inject("shouldBackButton", true);
 
 const emit = defineEmits<{
 	(ev: "update:tab", key: string);
diff --git a/packages/client/src/pages/about-calckey.vue b/packages/client/src/pages/about-calckey.vue
index 71dbeb87b..f0faa4066 100644
--- a/packages/client/src/pages/about-calckey.vue
+++ b/packages/client/src/pages/about-calckey.vue
@@ -97,17 +97,18 @@
 								><Mfm
 									:text="'$[sparkle @kainoa@calckey.social] (Main developer)'"
 							/></FormLink>
-							<FormLink to="/@cleo@bz.pawdev.me"
-								><Mfm :text="'@cleo@bz.pawdev.me (Maintainer)'"
+							<FormLink to="/@april@calckey.social"
+								><Mfm
+									:text="'@april@calckey.social (Backend)'"
+							/></FormLink>
+							<FormLink to="/@freeplay@calckey.social"
+								><Mfm
+									:text="'@freeplay@calckey.social (UI/UX)'"
 							/></FormLink>
 							<FormLink to="/@panos@calckey.social"
 								><Mfm
 									:text="'@panos@calckey.social (Project Coordinator)'"
 							/></FormLink>
-							<FormLink to="/@freeplay@calckey.social"
-								><Mfm
-									:text="'@freeplay@calckey.social (UI)'"
-							/></FormLink>
 							<FormLink
 								to="https://www.youtube.com/c/Henkiwashere"
 								external

From 6146ca270a6e99160d799dcd0f8ef74e3bf41132 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Thu, 1 Jun 2023 21:01:15 +0200
Subject: [PATCH 120/198] more logging

---
 .../backend/src/queue/processors/db/import-custom-emojis.ts   | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/packages/backend/src/queue/processors/db/import-custom-emojis.ts b/packages/backend/src/queue/processors/db/import-custom-emojis.ts
index 77eb130a5..d80ff988b 100644
--- a/packages/backend/src/queue/processors/db/import-custom-emojis.ts
+++ b/packages/backend/src/queue/processors/db/import-custom-emojis.ts
@@ -112,6 +112,8 @@ export async function importCustomEmojis(
 			for (const emojiPath of containedEmojis) {
 				// strip extension and get filename to use as name
 				const name = path.basename(emojiPath, path.extname(emojiPath));
+				logger.info(`importing ${name}`)
+
 				await Emojis.delete({
 					name: name,
 				});
@@ -124,6 +126,8 @@ export async function importCustomEmojis(
 				const file = fs.createReadStream(emojiPath);
 				const size = await probeImageSize(file);
 				file.destroy();
+				logger.info(`emoji size: ${size.width}x${size.height}`)
+
 				await Emojis.insert({
 					id: genId(),
 					updatedAt: new Date(),

From e705b240058da574895077dbeab4be7f1f3961ea Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Thu, 1 Jun 2023 21:12:03 +0200
Subject: [PATCH 121/198] me when I lack a brain

---
 .../backend/src/queue/processors/db/import-custom-emojis.ts    | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/packages/backend/src/queue/processors/db/import-custom-emojis.ts b/packages/backend/src/queue/processors/db/import-custom-emojis.ts
index d80ff988b..7dbb525d3 100644
--- a/packages/backend/src/queue/processors/db/import-custom-emojis.ts
+++ b/packages/backend/src/queue/processors/db/import-custom-emojis.ts
@@ -106,9 +106,6 @@ export async function importCustomEmojis(
 				emoji.match(/\.(json)$/i),
 			);
 
-			// only go one layer deep, recursion would be weird
-			containedEmojis.filter((emoji) => !emoji.includes("/"));
-
 			for (const emojiPath of containedEmojis) {
 				// strip extension and get filename to use as name
 				const name = path.basename(emojiPath, path.extname(emojiPath));

From 2c2532b87d302dae2d2d0e0b3d92ec1a98e4e45b Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Thu, 1 Jun 2023 21:30:23 +0200
Subject: [PATCH 122/198] missing inversion + use proper emoji path

---
 .../queue/processors/db/import-custom-emojis.ts   | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

diff --git a/packages/backend/src/queue/processors/db/import-custom-emojis.ts b/packages/backend/src/queue/processors/db/import-custom-emojis.ts
index 7dbb525d3..a40f2c5cb 100644
--- a/packages/backend/src/queue/processors/db/import-custom-emojis.ts
+++ b/packages/backend/src/queue/processors/db/import-custom-emojis.ts
@@ -96,19 +96,16 @@ export async function importCustomEmojis(
 
 			let containedEmojis = fs.readdirSync(outputPath);
 
-			// Filter directories
-			containedEmojis = containedEmojis.filter(
-				(emoji) => !fs.lstatSync(`${outputPath}/${emoji}`).isDirectory(),
-			);
-
 			// Filter out accidental JSON files
 			containedEmojis = containedEmojis.filter((emoji) =>
-				emoji.match(/\.(json)$/i),
+				!emoji.match(/\.(json)$/i),
 			);
 
-			for (const emojiPath of containedEmojis) {
+			for (const emojiFilename of containedEmojis) {
 				// strip extension and get filename to use as name
-				const name = path.basename(emojiPath, path.extname(emojiPath));
+				const name = path.basename(emojiFilename, path.extname(emojiFilename));
+				const emojiPath = `${outputPath}/${emojiFilename}`;
+
 				logger.info(`importing ${name}`)
 
 				await Emojis.delete({
@@ -117,7 +114,7 @@ export async function importCustomEmojis(
 				const driveFile = await addFile({
 					user: null,
 					path: emojiPath,
-					name: path.basename(emojiPath),
+					name: path.basename(emojiFilename),
 					force: true,
 				});
 				const file = fs.createReadStream(emojiPath);

From cc2f5b516fd0e21e3d63905cc96d3d84369261f8 Mon Sep 17 00:00:00 2001
From: PrivateGER <privateger@privateger.me>
Date: Thu, 1 Jun 2023 21:38:37 +0200
Subject: [PATCH 123/198] formatter

---
 .../src/queue/processors/db/import-custom-emojis.ts  | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/packages/backend/src/queue/processors/db/import-custom-emojis.ts b/packages/backend/src/queue/processors/db/import-custom-emojis.ts
index a40f2c5cb..9e8b3b174 100644
--- a/packages/backend/src/queue/processors/db/import-custom-emojis.ts
+++ b/packages/backend/src/queue/processors/db/import-custom-emojis.ts
@@ -54,7 +54,7 @@ export async function importCustomEmojis(
 		if (error) throw error;
 
 		if (fs.existsSync(`${outputPath}/meta.json`)) {
-			logger.info("starting emoji import with metadata")
+			logger.info("starting emoji import with metadata");
 			const metaRaw = fs.readFileSync(`${outputPath}/meta.json`, "utf-8");
 			const meta = JSON.parse(metaRaw);
 
@@ -90,15 +90,15 @@ export async function importCustomEmojis(
 				}).then((x) => Emojis.findOneByOrFail(x.identifiers[0]));
 			}
 		} else {
-			logger.info("starting emoji import without metadata")
+			logger.info("starting emoji import without metadata");
 			// Since we lack metadata, we import into a randomized category name instead
 			let categoryName = genId();
 
 			let containedEmojis = fs.readdirSync(outputPath);
 
 			// Filter out accidental JSON files
-			containedEmojis = containedEmojis.filter((emoji) =>
-				!emoji.match(/\.(json)$/i),
+			containedEmojis = containedEmojis.filter(
+				(emoji) => !emoji.match(/\.(json)$/i),
 			);
 
 			for (const emojiFilename of containedEmojis) {
@@ -106,7 +106,7 @@ export async function importCustomEmojis(
 				const name = path.basename(emojiFilename, path.extname(emojiFilename));
 				const emojiPath = `${outputPath}/${emojiFilename}`;
 
-				logger.info(`importing ${name}`)
+				logger.info(`importing ${name}`);
 
 				await Emojis.delete({
 					name: name,
@@ -120,7 +120,7 @@ export async function importCustomEmojis(
 				const file = fs.createReadStream(emojiPath);
 				const size = await probeImageSize(file);
 				file.destroy();
-				logger.info(`emoji size: ${size.width}x${size.height}`)
+				logger.info(`emoji size: ${size.width}x${size.height}`);
 
 				await Emojis.insert({
 					id: genId(),

From 9a322cc3649dd0af98a9afd79da0d69e7cbe665b Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Sat, 3 Jun 2023 04:16:36 +0900
Subject: [PATCH 124/198] instance -> server

---
 CALCKEY.md         | 12 ++++++------
 CODE_OF_CONDUCT.md |  2 +-
 README.md          | 18 +++++++++---------
 docs/docker.md     |  8 ++++----
 docs/kubernetes.md |  4 ++--
 5 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/CALCKEY.md b/CALCKEY.md
index 55d37a56e..d1585adc3 100644
--- a/CALCKEY.md
+++ b/CALCKEY.md
@@ -11,7 +11,7 @@
   - Federate with note edits
   - User "choices" (recommended users) like Mastodon and Soapbox
   - Join Reason system like Mastodon/Pleroma
-  - Option to publicize instance blocks
+  - Option to publicize server blocks
   - Build flag to remove NSFW/AI stuff
   - Filter notifications by user
   - Exclude self from antenna
@@ -19,7 +19,7 @@
   - MFM button
   - Personal notes for all accounts
   - Fully revamp non-logged-in screen
-  - Lookup/details for post/file/instance
+  - Lookup/details for post/file/server
   - [Rat mode?](https://stop.voring.me/notes/933fx97bmd)
 
 ## Work in progress
@@ -43,7 +43,7 @@
   - Upgrade packages with security vunrabilities
 - Saner defaults
 - Fediverse account migration
-- Recommended instances timeline
+- Recommended servers timeline
 - OCR image captioning
 - Improve mobile UX
   - Swipe through pages on mobile
@@ -71,7 +71,7 @@
 - Better welcome screen (not logged in)
 - vue-plyr as video/audio player
 - Ability to turn off "Connection lost" message
-- Raw instance info only for moderators
+- Raw server info only for moderators
 - New spinner animation
 - Spinner instead of "Loading..."
 - SearchX instead of Google
@@ -98,7 +98,7 @@
 - Obliteration of Ai-chan
 - Switch to [Calckey.js](https://codeberg.org/calckey/calckey.js)
 - Woozy mode 🥴
-- Improve blocking instances
+- Improve blocking servers
 - Release notes
 - New post style
 - Admins set default reaction emoji
@@ -117,7 +117,7 @@
 - Sonic search
 - Popular color schemes, including Nord, Gruvbox, and Catppuccin
 - Non-nyaify cat mode
-- Post imports from other Calckey/Misskey/Mastodon/Pleroma/Akkoma instances
+- Post imports from other Calckey/Misskey/Mastodon/Pleroma/Akkoma servers
 - Improve Classic mode
 - Proper Helm/Kubernetes config
 - Multiple boost visibilities
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 143c63d29..95e2bd421 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -59,7 +59,7 @@ representative at an online or offline event.
 
 ## Enforcement
 
-Instances of abusive, harassing, or otherwise unacceptable behavior may be
+Servers of abusive, harassing, or otherwise unacceptable behavior may be
 reported to the community leaders responsible for enforcement at
 @thatonecalculator on Codeberg,
 `@kainoa@calckey.social` on the Fediverse,
diff --git a/README.md b/README.md
index 5dcdc4861..f4d7c4150 100644
--- a/README.md
+++ b/README.md
@@ -23,15 +23,15 @@
 # ✨ About Calckey
 
 - Calckey is based off of Misskey, a powerful microblogging server on ActivityPub with features such as emoji reactions, a customizable web UI, rich chatting, and much more!
-- Calckey adds many quality of life changes and bug fixes for users and instance admins alike.
+- Calckey adds many quality of life changes and bug fixes for users and server admins alike.
 - Read **[this document](./CALCKEY.md)** all for current and future differences.
 - Notable differences:
   - Improved UI/UX (especially on mobile)
   - Improved notifications
-  - Improved instance security
+  - Improved server security
   - Improved accessibility
   - Improved threads
-  - Recommended Instances timeline
+  - Recommended Servers timeline
   - OCR image captioning
   - New and improved Groups
   - Better intro tutorial
@@ -50,10 +50,10 @@
 - 💸 OpenCollective: <https://opencollective.com/Calckey>
 - 💸 Liberapay: <https://liberapay.com/ThatOneCalculator>
   - Donate publicly to get your name on the Patron list!
-- 🚢 Flagship instance: <https://calckey.social>
+- 🚢 Flagship server: <https://calckey.social>
 - 📣 Official account: <https://i.calckey.cloud/@calckey>
 - 💁 Matrix support room: <https://matrix.to/#/#calckey:matrix.fedibird.com>
-- 📜 Instance list: <https://calckey.fediverse.observer/list>
+- 📜 Server list: <https://calckey.fediverse.observer/list>
 - 📖 JoinFediverse Wiki: <https://joinfediverse.wiki/What_is_Calckey%3F>
 - 🐋 Docker Hub: <https://hub.docker.com/r/thatonecalculator/calckey>
 - ✍️ Weblate: <https://hosted.weblate.org/engage/calckey/>
@@ -177,13 +177,13 @@ Please don't use ElasticSearch unless you already have an ElasticSearch setup an
 ## 💅 Customize
 
 - To add custom CSS for all users, edit `./custom/assets/instance.css`.
-- To add static assets (such as images for the splash screen), place them in the `./custom/assets/` directory. They'll then be available on `https://yourinstance.tld/static-assets/filename.ext`.
+- To add static assets (such as images for the splash screen), place them in the `./custom/assets/` directory. They'll then be available on `https://yourserver.tld/static-assets/filename.ext`.
 - To add custom locales, place them in the `./custom/locales/` directory. If you name your custom locale the same as an existing locale, it will overwrite it. If you give it a unique name, it will be added to the list. Also make sure that the first part of the filename matches the locale you're basing it on. (Example: `en-FOO.yml`)
 - To add custom error images, place them in the `./custom/assets/badges` directory, replacing the files already there.
 - To add custom sounds, place only mp3 files in the `./custom/assets/sounds` directory.
 - To update custom assets without rebuilding, just run `pnpm run gulp`.
 
-## 🧑‍🔬 Configuring a new instance
+## 🧑‍🔬 Configuring a new server
 
 - Run `cp .config/example.yml .config/default.yml`
 - Edit `.config/default.yml`, making sure to fill out required fields.
@@ -198,7 +198,7 @@ For migrating from Misskey v13, Misskey v12, and Foundkey, read [this document](
 ### 🍀 Nginx (recommended)
 
 - Run `sudo cp ./calckey.nginx.conf /etc/nginx/sites-available/ && cd /etc/nginx/sites-available/`
-- Edit `calckey.nginx.conf` to reflect your instance properly
+- Edit `calckey.nginx.conf` to reflect your server properly
 - Run `sudo ln -s ./calckey.nginx.conf ../sites-enabled/calckey.nginx.conf`
 - Run `sudo nginx -t` to validate that the config is valid, then restart the NGINX service.
 
@@ -218,7 +218,7 @@ example.tld {
 > Apache has some known problems with Calckey. Only use it if you have to.
 
 - Run `sudo cp ./calckey.apache.conf /etc/apache2/sites-available/ && cd /etc/apache2/sites-available/`
-- Edit `calckey.apache.conf` to reflect your instance properly
+- Edit `calckey.apache.conf` to reflect your server properly
 - Run `sudo a2ensite calckey.apache` to enable the site
 - Run `sudo service apache2 restart` to reload apache2 configuration
 ## 🚀 Build and launch!
diff --git a/docs/docker.md b/docs/docker.md
index 8c42ee54d..0c625a4b3 100644
--- a/docs/docker.md
+++ b/docs/docker.md
@@ -1,4 +1,4 @@
-# 🐳 Running a Calckey instance with Docker
+# 🐳 Running a Calckey server with Docker
 
 ## Pre-built docker container
 [thatonecalculator/calckey](https://hub.docker.com/r/thatonecalculator/calckey)
@@ -8,7 +8,7 @@
 There is a `docker-compose.yml` in the root of the project that you can use to build the container from source
 
 - .config/docker.env (**db config settings**)
-- .config/default.yml (**calckey instance settings**)
+- .config/default.yml (**calckey server settings**)
 
 ## Configuring
 
@@ -20,7 +20,7 @@ Rename the files:
 
 then edit them according to your environment.
 You can configure `docker.env` with anything you like, but you will have to pay attention to the `default.yml` file:
-- `url` should be set to the URL you will be hosting the web interface for the instance at.
+- `url` should be set to the URL you will be hosting the web interface for the server at.
 - `host`, `db`, `user`, `pass` will have to be configured in the `PostgreSQL configuration` section - `host` is the name of the postgres container (eg: *calckey_db_1*), and the others should match your `docker.env`.
 - `host`will need to be configured in the *Redis configuration* section - it is the name of the redis container (eg: *calckey_redis_1*)
 - `auth` will need to be configured in the *Sonic* section - cannot be the default `SecretPassword`
@@ -36,7 +36,7 @@ Copy `docker-compose.yml` and the `config/` to a directory, then run the **docke
 
 NOTE: This will take some time to come fully online, even after download and extracting the container images, and it may emit some error messages before completing successfully. Specifically, the `db` container needs to initialize and so isn't available to the `web` container right away. Only once the `db` container comes online does the `web` container start building and initializing the calckey tables.
 
-Once the instance is up you can use a web browser to access the web interface at `http://serverip:3000` (where `serverip` is the IP of the server you are running the calckey instance on).
+Once the server is up you can use a web browser to access the web interface at `http://serverip:3000` (where `serverip` is the IP of the server you are running the calckey server on).
 
 ## Docker for development
 
diff --git a/docs/kubernetes.md b/docs/kubernetes.md
index 710d0dee0..5cb6e5d83 100644
--- a/docs/kubernetes.md
+++ b/docs/kubernetes.md
@@ -1,4 +1,4 @@
-# Running a Calckey instance with Kubernetes and Helm
+# Running a Calckey server with Kubernetes and Helm
 
 This is a [Helm](https://helm.sh/) chart directory in the root of the project
 that you can use to deploy calckey to a Kubernetes cluster
@@ -27,7 +27,7 @@ helm upgrade \
     -f .config/helm_values.yml
 ```
 
-4. Watch your calckey instance spin up:
+4. Watch your calckey server spin up:
 ```shell
 kubectl -n calckey get po -w
 ```

From 30e5fd38e63e021a61fb0d2fb5caec5bfa876a7e Mon Sep 17 00:00:00 2001
From: Freeplay <freeplay@duck.com>
Date: Fri, 2 Jun 2023 17:23:09 -0400
Subject: [PATCH 125/198] hide close button bottom fade for now

---
 packages/client/src/components/MkSubNoteContent.vue | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/client/src/components/MkSubNoteContent.vue b/packages/client/src/components/MkSubNoteContent.vue
index 0b6f920cb..5c965972a 100644
--- a/packages/client/src/components/MkSubNoteContent.vue
+++ b/packages/client/src/components/MkSubNoteContent.vue
@@ -168,10 +168,10 @@
 				<i class="ph-stop ph-bold"></i> {{ i18n.ts._mfm.stop }}
 			</template>
 		</MkButton>
-		<div
+		<!-- <div
 			v-if="(isLong && !collapsed) || (props.note.cw && showContent)"
 			class="fade"
-		></div>
+		></div> -->
 	</div>
 </template>
 

From e73f7f3b26291ddd362ab76f60cfd8cf0681de37 Mon Sep 17 00:00:00 2001
From: Kainoa Kanter <kainoa@t1c.dev>
Date: Fri, 2 Jun 2023 21:36:47 +0000
Subject: [PATCH 126/198] Update 'CODE_OF_CONDUCT.md'

---
 CODE_OF_CONDUCT.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 95e2bd421..143c63d29 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -59,7 +59,7 @@ representative at an online or offline event.
 
 ## Enforcement
 
-Servers of abusive, harassing, or otherwise unacceptable behavior may be
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
 reported to the community leaders responsible for enforcement at
 @thatonecalculator on Codeberg,
 `@kainoa@calckey.social` on the Fediverse,

From 8234c271234dd731ae9d307b0700b799fe1e7160 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Fri, 2 Jun 2023 14:52:44 -0700
Subject: [PATCH 127/198] fix: show message on error alert if text is null

---
 packages/client/src/os.ts | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts
index 0aa823d05..0636bfd3e 100644
--- a/packages/client/src/os.ts
+++ b/packages/client/src/os.ts
@@ -277,6 +277,9 @@ export function alert(props: {
 	text?: string | null;
 }): Promise<void> {
 	return new Promise((resolve, reject) => {
+		if (props.text == null && props.type === "error") {
+			props.text = "An unknown error occured!"
+		}
 		popup(
 			MkDialog,
 			props,

From 198b87820df8c4aed1678e2e7c404c57e85eeb16 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Fri, 2 Jun 2023 14:53:28 -0700
Subject: [PATCH 128/198] chore: formatting

---
 packages/client/src/components/MkNote.vue      | 10 ++++++++--
 packages/client/src/components/MkNoteSub.vue   |  3 ++-
 packages/client/src/os.ts                      |  2 +-
 packages/client/src/pages/about-calckey.vue    |  3 +--
 packages/client/src/pages/settings/general.vue |  8 +++++---
 5 files changed, 17 insertions(+), 9 deletions(-)

diff --git a/packages/client/src/components/MkNote.vue b/packages/client/src/components/MkNote.vue
index 669fd67ef..7eca22a73 100644
--- a/packages/client/src/components/MkNote.vue
+++ b/packages/client/src/components/MkNote.vue
@@ -68,7 +68,9 @@
 			class="article"
 			@contextmenu.stop="onContextmenu"
 			@click="noteClick"
-			:style="{ cursor: expandOnNoteClick && !detailedView ? 'pointer' : '' }"
+			:style="{
+				cursor: expandOnNoteClick && !detailedView ? 'pointer' : '',
+			}"
 		>
 			<div class="main">
 				<div class="header-container">
@@ -503,7 +505,11 @@ function scrollIntoView() {
 }
 
 function noteClick(e) {
-	if (document.getSelection().type === "Range" || props.detailedView || !expandOnNoteClick) {
+	if (
+		document.getSelection().type === "Range" ||
+		props.detailedView ||
+		!expandOnNoteClick
+	) {
 		e.stopPropagation();
 	} else {
 		router.push(notePage(appearNote));
diff --git a/packages/client/src/components/MkNoteSub.vue b/packages/client/src/components/MkNoteSub.vue
index 60fbbe11e..c26af1668 100644
--- a/packages/client/src/components/MkNoteSub.vue
+++ b/packages/client/src/components/MkNoteSub.vue
@@ -14,7 +14,8 @@
 		@contextmenu.stop="onContextmenu"
 	>
 		<div v-if="conversation && depth > 1" class="line"></div>
-		<div class="main" 
+		<div
+			class="main"
 			@click="noteClick"
 			:style="{ cursor: expandOnNoteClick ? 'pointer' : '' }"
 		>
diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts
index 0636bfd3e..6831d72fa 100644
--- a/packages/client/src/os.ts
+++ b/packages/client/src/os.ts
@@ -278,7 +278,7 @@ export function alert(props: {
 }): Promise<void> {
 	return new Promise((resolve, reject) => {
 		if (props.text == null && props.type === "error") {
-			props.text = "An unknown error occured!"
+			props.text = "An unknown error occured!";
 		}
 		popup(
 			MkDialog,
diff --git a/packages/client/src/pages/about-calckey.vue b/packages/client/src/pages/about-calckey.vue
index f0faa4066..1e16356a5 100644
--- a/packages/client/src/pages/about-calckey.vue
+++ b/packages/client/src/pages/about-calckey.vue
@@ -98,8 +98,7 @@
 									:text="'$[sparkle @kainoa@calckey.social] (Main developer)'"
 							/></FormLink>
 							<FormLink to="/@april@calckey.social"
-								><Mfm
-									:text="'@april@calckey.social (Backend)'"
+								><Mfm :text="'@april@calckey.social (Backend)'"
 							/></FormLink>
 							<FormLink to="/@freeplay@calckey.social"
 								><Mfm
diff --git a/packages/client/src/pages/settings/general.vue b/packages/client/src/pages/settings/general.vue
index 5486503b1..030126a96 100644
--- a/packages/client/src/pages/settings/general.vue
+++ b/packages/client/src/pages/settings/general.vue
@@ -54,9 +54,11 @@
 			<FormSwitch v-model="disablePagesScript" class="_formBlock">{{
 				i18n.ts.disablePagesScript
 			}}</FormSwitch>
-			<FormSwitch v-model="expandOnNoteClick" class="_formBlock">{{ 
-				i18n.ts.expandOnNoteClick
-			}}<template #caption>{{ i18n.ts.expandOnNoteClickDesc }}</template>
+			<FormSwitch v-model="expandOnNoteClick" class="_formBlock"
+				>{{ i18n.ts.expandOnNoteClick
+				}}<template #caption>{{
+					i18n.ts.expandOnNoteClickDesc
+				}}</template>
 			</FormSwitch>
 			<FormSwitch v-model="profile.showTimelineReplies" class="_formBlock"
 				>{{ i18n.ts.flagShowTimelineReplies

From e18b06c74d8cb5f699df2053c6dafb3be9ffd2cc Mon Sep 17 00:00:00 2001
From: Freeplay <freeplay@duck.com>
Date: Fri, 2 Jun 2023 18:14:39 -0400
Subject: [PATCH 129/198] fix gap

---
 packages/client/src/pages/user/home.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/pages/user/home.vue b/packages/client/src/pages/user/home.vue
index 277d5f5ef..a7da75dc6 100644
--- a/packages/client/src/pages/user/home.vue
+++ b/packages/client/src/pages/user/home.vue
@@ -261,7 +261,7 @@
 					</div>
 				</div>
 
-				<div class="contents">
+				<div class="contents _gap">
 					<div v-if="user.pinnedNotes.length > 0" class="_gap">
 						<XNote
 							v-for="note in user.pinnedNotes"

From 3f45ec30abb1ed620eb7004ee5785e201211dc94 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Fri, 2 Jun 2023 15:45:05 -0700
Subject: [PATCH 130/198] fix: :recycle: use locale for error

https://calckey.social/notes/9fippqiwhl287b5m
---
 packages/client/src/os.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts
index 6831d72fa..ed911978b 100644
--- a/packages/client/src/os.ts
+++ b/packages/client/src/os.ts
@@ -11,6 +11,7 @@ import MkToast from "@/components/MkToast.vue";
 import MkDialog from "@/components/MkDialog.vue";
 import { MenuItem } from "@/types/menu";
 import { $i } from "@/account";
+import { i18n } from "./i18n";
 
 export const pendingApiRequestsCount = ref(0);
 
@@ -278,7 +279,7 @@ export function alert(props: {
 }): Promise<void> {
 	return new Promise((resolve, reject) => {
 		if (props.text == null && props.type === "error") {
-			props.text = "An unknown error occured!";
+			props.text = i18n.ts.somethingHappened;
 		}
 		popup(
 			MkDialog,

From 96ff1e5e55b704dafb7b6c5a35bd4f65afb1bfa4 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Fri, 2 Jun 2023 16:10:57 -0700
Subject: [PATCH 131/198] chore: update example config

---
 .config/example.yml | 65 +++++++++++++++++++++++----------------------
 1 file changed, 33 insertions(+), 32 deletions(-)

diff --git a/.config/example.yml b/.config/example.yml
index ba53bde43..16fa67142 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -2,32 +2,31 @@
 # Calckey configuration
 #━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
+# After starting your server, please don't change the URL! Doing so will break federation.
+
 #   ┌─────┐
 #───┘ URL └─────────────────────────────────────────────────────
 
 # Final accessible URL seen by a user.
-url: https://example.tld/
-
-# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
-# URL SETTINGS AFTER THAT!
+url: https://example.com/
 
 #   ┌───────────────────────┐
 #───┘ Port and TLS settings └───────────────────────────────────
 
 #
-# Misskey requires a reverse proxy to support HTTPS connections.
+# Calckey requires a reverse proxy to support HTTPS connections.
 #
-#                 +----- https://example.tld/ ------------+
+#                 +----- https://example.com/ ------------+
 #   +------+      |+-------------+      +----------------+|
-#   | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
+#   | User | ---> || Proxy (443) | ---> | Calckey (3000) ||
 #   +------+      |+-------------+      +----------------+|
 #                 +---------------------------------------+
 #
-#   You need to set up a reverse proxy. (e.g. nginx)
+#   You need to set up a reverse proxy. (e.g. nginx, caddy)
 #   An encrypted connection with HTTPS is highly recommended
 #   because tokens may be transferred in GET requests.
 
-# The port that your Misskey server should listen on.
+# The port that your Calckey server should listen on.
 port: 3000
 
 #   ┌──────────────────────────┐
@@ -62,6 +61,17 @@ redis:
   #prefix: example-prefix
   #db: 1
 
+# Please configure either MeiliSearch *or* Sonic.
+# If both MeiliSearch and Sonic configurations are present, MeiliSearch will take precedence.
+
+#   ┌───────────────────────────┐
+#───┘ MeiliSearch configuration └─────────────────────────────────────
+#meilisearch:
+#  host: meilisearch
+#  port: 7700
+#  ssl: false
+#  apiKey:
+
 #   ┌─────────────────────┐
 #───┘ Sonic configuration └─────────────────────────────────────
 
@@ -72,23 +82,6 @@ redis:
 #  collection: notes
 #  bucket: default
 
-#   ┌─────────────────────────────┐
-#───┘ Elasticsearch configuration └─────────────────────────────
-
-#elasticsearch:
-#  host: localhost
-#  port: 9200
-#  ssl: false
-#  user:
-#  pass:
-
-#   ┌───────────────────────────┐
-#───┘ Meilisearch configuration └─────────────────────────────────────
-#meilisearch:
-#  host: meilisearch
-#  port: 7700
-#  ssl: false
-#  apiKey:
 
 #   ┌───────────────┐
 #───┘ ID generation └───────────────────────────────────────────
@@ -108,10 +101,10 @@ redis:
 #   ┌─────────────────────┐
 #───┘ Other configuration └─────────────────────────────────────
 
-# Max note length, should be < 8000.
+# Maximum length of a post (default 3000, max 8192)
 #maxNoteLength: 3000
 
-# Maximum lenght of an image caption or file comment (default 1500, max 8192)
+# Maximum length of an image caption (default 1500, max 8192)
 #maxCaptionLength: 1500
 
 # Reserved usernames that only the administrator can register with
@@ -185,13 +178,21 @@ reservedUsernames: [
 # Upload or download file size limits (bytes)
 #maxFileSize: 262144000
 
+#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+# Congrats, you've reached the end of the config file needed for most deployments!
+# Enjoy your Calckey server!
+#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+
+
+
+#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 # Managed hosting settings
-# !!!!!!!!!!
-# >>>>>> NORMAL SELF-HOSTERS, STAY AWAY! <<<<<<
-# >>>>>> YOU DON'T NEED THIS! <<<<<<
-# !!!!!!!!!!
+# >>> NORMAL SELF-HOSTERS, STAY AWAY! <<<
+# >>> YOU DON'T NEED THIS! <<<
 # Each category is optional, but if each item in each category is mandatory!
 # If you mess this up, that's on you, you've been warned...
+#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
 #maxUserSignups: 100
 #isManagedHosting: true

From 3370a14f62bac8f614ce857c967409c68caa48be Mon Sep 17 00:00:00 2001
From: Freeplay <freeplay@duck.com>
Date: Fri, 2 Jun 2023 21:04:51 -0400
Subject: [PATCH 132/198] aria to acc menu button

---
 packages/client/src/pages/user/home.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/pages/user/home.vue b/packages/client/src/pages/user/home.vue
index a7da75dc6..2ea4bc971 100644
--- a/packages/client/src/pages/user/home.vue
+++ b/packages/client/src/pages/user/home.vue
@@ -134,7 +134,7 @@
 						</div>
 						<div class="follow-container">
 							<div class="actions">
-								<button class="menu _button" @click="menu">
+								<button class="menu _button" @click="menu" v-tooltip="i18n.ts.menu">
 									<i
 										class="ph-dots-three-outline ph-bold ph-lg"
 									></i>

From 55f526d989a5d3b97acba0e68960a1f2efb09d0a Mon Sep 17 00:00:00 2001
From: Freeplay <freeplay@duck.com>
Date: Fri, 2 Jun 2023 22:54:23 -0400
Subject: [PATCH 133/198] fix mfm-cheat-sheet styling

---
 .../client/src/components/MkCheatSheetDialog.vue   |  8 ++------
 packages/client/src/pages/mfm-cheat-sheet.vue      | 14 ++++++++------
 2 files changed, 10 insertions(+), 12 deletions(-)

diff --git a/packages/client/src/components/MkCheatSheetDialog.vue b/packages/client/src/components/MkCheatSheetDialog.vue
index 04b8cec6e..519955d48 100644
--- a/packages/client/src/components/MkCheatSheetDialog.vue
+++ b/packages/client/src/components/MkCheatSheetDialog.vue
@@ -6,12 +6,7 @@
 		@closed="$emit('closed')"
 	>
 		<template #header>{{ i18n.ts._mfm.cheatSheet }}</template>
-
-		<div class="_monolithic_">
-			<div class="_section">
-				<XCheatSheet />
-			</div>
-		</div>
+		<XCheatSheet :popup="true" style="background: var(--bg)"/>
 	</XModalWindow>
 </template>
 
@@ -41,4 +36,5 @@ function close(res) {
 .fade-leave-to {
 	opacity: 0;
 }
+
 </style>
diff --git a/packages/client/src/pages/mfm-cheat-sheet.vue b/packages/client/src/pages/mfm-cheat-sheet.vue
index a22d7ce1e..e9fb213b7 100644
--- a/packages/client/src/pages/mfm-cheat-sheet.vue
+++ b/packages/client/src/pages/mfm-cheat-sheet.vue
@@ -1,8 +1,8 @@
 <template>
 	<MkStickyContainer>
-		<template #header><MkPageHeader /></template>
+		<template #header><MkPageHeader v-if="!popup" /></template>
 		<MkSpacer :content-max="800">
-			<div :class="$style.root">
+			<div class="mfm-cheat-sheet">
 				<div>{{ i18n.ts._mfm.intro }}</div>
 				<br />
 				<div class="section _block">
@@ -449,6 +449,10 @@ import { definePageMetadata } from "@/scripts/page-metadata";
 import { i18n } from "@/i18n";
 import { instance } from "@/instance";
 
+defineProps<{
+	popup?: boolean
+}>();
+
 let preview_mention = $ref("@example");
 let preview_hashtag = $ref("#test");
 let preview_link = $ref(`[${i18n.ts._mfm.dummy}](https://calckey.org)`);
@@ -514,10 +518,8 @@ definePageMetadata({
 });
 </script>
 
-<style lang="scss" module>
-.root {
-	background: var(--bg);
-
+<style lang="scss" scoped>
+.mfm-cheat-sheet {
 	> .section {
 		> .title {
 			position: sticky;

From 494a68e605f31ee5a9c0b6dd55e6147a43ad9869 Mon Sep 17 00:00:00 2001
From: Freeplay <freeplay@duck.com>
Date: Fri, 2 Jun 2023 22:57:04 -0400
Subject: [PATCH 134/198] tweak

---
 packages/client/src/pages/mfm-cheat-sheet.vue | 1 +
 1 file changed, 1 insertion(+)

diff --git a/packages/client/src/pages/mfm-cheat-sheet.vue b/packages/client/src/pages/mfm-cheat-sheet.vue
index e9fb213b7..1bbe36915 100644
--- a/packages/client/src/pages/mfm-cheat-sheet.vue
+++ b/packages/client/src/pages/mfm-cheat-sheet.vue
@@ -536,6 +536,7 @@ definePageMetadata({
 			> p {
 				margin: 0;
 				padding: 16px;
+				padding-top: 0;
 			}
 
 			> .preview {

From d94c276a8c88857c6c37aa44c29867b8f6636fd6 Mon Sep 17 00:00:00 2001
From: mappi <mappi@mizuiromoon.com>
Date: Fri, 2 Jun 2023 16:31:25 +0900
Subject: [PATCH 135/198] fix: vue-plyr audio tag

Co-authored-by: mappi <mappi-pr@github.com>
---
 packages/client/src/components/MkNote.vue | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/packages/client/src/components/MkNote.vue b/packages/client/src/components/MkNote.vue
index 7eca22a73..1325809f3 100644
--- a/packages/client/src/components/MkNote.vue
+++ b/packages/client/src/components/MkNote.vue
@@ -381,6 +381,8 @@ const currentClipPage = inject<Ref<misskey.entities.Clip> | null>(
 function onContextmenu(ev: MouseEvent): void {
 	const isLink = (el: HTMLElement) => {
 		if (el.tagName === "A") return true;
+		// The Audio element's context menu is the browser default, such as for selecting playback speed.
+		if (el.tagName === 'AUDIO') return true;
 		if (el.parentElement) {
 			return isLink(el.parentElement);
 		}

From 5c305a85e6eb7509439edf70db9d1c0802ae53ba Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Fri, 2 Jun 2023 20:06:04 -0700
Subject: [PATCH 136/198] chore: formatting

---
 packages/client/src/components/MkCheatSheetDialog.vue | 3 +--
 packages/client/src/components/MkNote.vue             | 2 +-
 packages/client/src/pages/mfm-cheat-sheet.vue         | 2 +-
 packages/client/src/pages/user/home.vue               | 6 +++++-
 4 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/packages/client/src/components/MkCheatSheetDialog.vue b/packages/client/src/components/MkCheatSheetDialog.vue
index 519955d48..3b723cde5 100644
--- a/packages/client/src/components/MkCheatSheetDialog.vue
+++ b/packages/client/src/components/MkCheatSheetDialog.vue
@@ -6,7 +6,7 @@
 		@closed="$emit('closed')"
 	>
 		<template #header>{{ i18n.ts._mfm.cheatSheet }}</template>
-		<XCheatSheet :popup="true" style="background: var(--bg)"/>
+		<XCheatSheet :popup="true" style="background: var(--bg)" />
 	</XModalWindow>
 </template>
 
@@ -36,5 +36,4 @@ function close(res) {
 .fade-leave-to {
 	opacity: 0;
 }
-
 </style>
diff --git a/packages/client/src/components/MkNote.vue b/packages/client/src/components/MkNote.vue
index 1325809f3..ddf805fb3 100644
--- a/packages/client/src/components/MkNote.vue
+++ b/packages/client/src/components/MkNote.vue
@@ -382,7 +382,7 @@ function onContextmenu(ev: MouseEvent): void {
 	const isLink = (el: HTMLElement) => {
 		if (el.tagName === "A") return true;
 		// The Audio element's context menu is the browser default, such as for selecting playback speed.
-		if (el.tagName === 'AUDIO') return true;
+		if (el.tagName === "AUDIO") return true;
 		if (el.parentElement) {
 			return isLink(el.parentElement);
 		}
diff --git a/packages/client/src/pages/mfm-cheat-sheet.vue b/packages/client/src/pages/mfm-cheat-sheet.vue
index 1bbe36915..6c284bee0 100644
--- a/packages/client/src/pages/mfm-cheat-sheet.vue
+++ b/packages/client/src/pages/mfm-cheat-sheet.vue
@@ -450,7 +450,7 @@ import { i18n } from "@/i18n";
 import { instance } from "@/instance";
 
 defineProps<{
-	popup?: boolean
+	popup?: boolean;
 }>();
 
 let preview_mention = $ref("@example");
diff --git a/packages/client/src/pages/user/home.vue b/packages/client/src/pages/user/home.vue
index 2ea4bc971..1926fee88 100644
--- a/packages/client/src/pages/user/home.vue
+++ b/packages/client/src/pages/user/home.vue
@@ -134,7 +134,11 @@
 						</div>
 						<div class="follow-container">
 							<div class="actions">
-								<button class="menu _button" @click="menu" v-tooltip="i18n.ts.menu">
+								<button
+									class="menu _button"
+									@click="menu"
+									v-tooltip="i18n.ts.menu"
+								>
 									<i
 										class="ph-dots-three-outline ph-bold ph-lg"
 									></i>

From 623f796ee930e4f0500f4348bd4abf26714b3f54 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Thu, 25 May 2023 02:34:48 -0400
Subject: [PATCH 137/198] add entities and two schemas

---
 packages/backend/native-utils/.editorconfig   |   3 +
 packages/backend/native-utils/Cargo.toml      |   3 +
 packages/backend/native-utils/build.rs        |   2 +-
 .../native-utils/crates/model/Cargo.toml      |  17 +
 .../model/src/entity/abuse_user_report.rs     |  56 +++
 .../crates/model/src/entity/access_token.rs   |  70 +++
 .../crates/model/src/entity/ad.rs             |  27 ++
 .../crates/model/src/entity/announcement.rs   |  33 ++
 .../model/src/entity/announcement_read.rs     |  51 +++
 .../crates/model/src/entity/antenna.rs        |  93 ++++
 .../crates/model/src/entity/antenna_note.rs   |  50 +++
 .../crates/model/src/entity/app.rs            |  57 +++
 .../model/src/entity/attestation_challenge.rs |  38 ++
 .../crates/model/src/entity/auth_session.rs   |  52 +++
 .../crates/model/src/entity/blocking.rs       |  39 ++
 .../crates/model/src/entity/channel.rs        |  83 ++++
 .../model/src/entity/channel_following.rs     |  51 +++
 .../model/src/entity/channel_note_pining.rs   |  51 +++
 .../crates/model/src/entity/clip.rs           |  47 ++
 .../crates/model/src/entity/clip_note.rs      |  49 ++
 .../crates/model/src/entity/drive_file.rs     | 114 +++++
 .../crates/model/src/entity/drive_folder.rs   |  54 +++
 .../crates/model/src/entity/emoji.rs          |  31 ++
 .../crates/model/src/entity/follow_request.rs |  61 +++
 .../crates/model/src/entity/following.rs      |  51 +++
 .../crates/model/src/entity/gallery_like.rs   |  51 +++
 .../crates/model/src/entity/gallery_post.rs   |  54 +++
 .../crates/model/src/entity/hashtag.rs        |  41 ++
 .../crates/model/src/entity/instance.rs       |  59 +++
 .../model/src/entity/messaging_message.rs     |  76 ++++
 .../crates/model/src/entity/meta.rs           | 211 +++++++++
 .../crates/model/src/entity/migrations.rs     |  18 +
 .../crates/model/src/entity/mod.rs            |  75 ++++
 .../crates/model/src/entity/moderation_log.rs |  38 ++
 .../crates/model/src/entity/muted_note.rs     |  51 +++
 .../crates/model/src/entity/muting.rs         |  41 ++
 .../crates/model/src/entity/note.rs           | 235 ++++++++++
 .../crates/model/src/entity/note_edit.rs      |  40 ++
 .../crates/model/src/entity/note_favorite.rs  |  51 +++
 .../crates/model/src/entity/note_reaction.rs  |  52 +++
 .../model/src/entity/note_thread_muting.rs    |  37 ++
 .../crates/model/src/entity/note_unread.rs    |  57 +++
 .../crates/model/src/entity/note_watching.rs  |  53 +++
 .../crates/model/src/entity/notification.rs   | 115 +++++
 .../crates/model/src/entity/page.rs           |  90 ++++
 .../crates/model/src/entity/page_like.rs      |  51 +++
 .../src/entity/password_reset_request.rs      |  36 ++
 .../crates/model/src/entity/poll.rs           |  43 ++
 .../crates/model/src/entity/poll_vote.rs      |  52 +++
 .../crates/model/src/entity/prelude.rs        |  72 +++
 .../crates/model/src/entity/promo_note.rs     |  35 ++
 .../crates/model/src/entity/promo_read.rs     |  51 +++
 .../model/src/entity/registration_ticket.rs   |  19 +
 .../crates/model/src/entity/registry_item.rs  |  42 ++
 .../crates/model/src/entity/relay.rs          |  19 +
 .../crates/model/src/entity/renote_muting.rs  |  22 +
 .../crates/model/src/entity/reversi_game.rs   |  68 +++
 .../model/src/entity/reversi_matching.rs      |  39 ++
 .../model/src/entity/sea_orm_active_enums.rs  | 175 ++++++++
 .../crates/model/src/entity/signin.rs         |  39 ++
 .../model/src/entity/sw_subscription.rs       |  40 ++
 .../crates/model/src/entity/used_username.rs  |  18 +
 .../crates/model/src/entity/user.rs           | 425 ++++++++++++++++++
 .../crates/model/src/entity/user_group.rs     |  70 +++
 .../model/src/entity/user_group_invitation.rs |  59 +++
 .../model/src/entity/user_group_invite.rs     |  51 +++
 .../model/src/entity/user_group_joining.rs    |  59 +++
 .../crates/model/src/entity/user_ip.rs        |  21 +
 .../crates/model/src/entity/user_keypair.rs   |  35 ++
 .../crates/model/src/entity/user_list.rs      |  52 +++
 .../model/src/entity/user_list_joining.rs     |  51 +++
 .../model/src/entity/user_note_pining.rs      |  51 +++
 .../crates/model/src/entity/user_pending.rs   |  22 +
 .../crates/model/src/entity/user_profile.rs   | 111 +++++
 .../crates/model/src/entity/user_publickey.rs |  35 ++
 .../model/src/entity/user_security_key.rs     |  38 ++
 .../crates/model/src/entity/webhook.rs        |  44 ++
 .../native-utils/crates/model/src/lib.rs      |   3 +
 .../model/src/repository/abuse_user_report.rs |   1 +
 .../crates/model/src/repository/mod.rs        |   1 +
 .../crates/model/src/schema/antenna.rs        | 128 ++++++
 .../crates/model/src/schema/app.rs            | 108 +++++
 .../crates/model/src/schema/mod.rs            |  21 +
 packages/backend/native-utils/rustfmt.toml    |   2 -
 packages/backend/native-utils/src/lib.rs      |   1 -
 .../backend/native-utils/src/mastodon_api.rs  |  78 ++--
 86 files changed, 4873 insertions(+), 43 deletions(-)
 create mode 100644 packages/backend/native-utils/.editorconfig
 create mode 100644 packages/backend/native-utils/crates/model/Cargo.toml
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/abuse_user_report.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/access_token.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/ad.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/announcement.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/announcement_read.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/antenna.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/antenna_note.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/app.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/attestation_challenge.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/auth_session.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/blocking.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/channel.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/channel_following.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/channel_note_pining.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/clip.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/clip_note.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/drive_file.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/drive_folder.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/emoji.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/follow_request.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/following.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/gallery_like.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/gallery_post.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/hashtag.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/instance.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/messaging_message.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/meta.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/migrations.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/mod.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/moderation_log.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/muted_note.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/muting.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/note.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/note_edit.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/note_favorite.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/note_reaction.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/note_thread_muting.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/note_unread.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/note_watching.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/notification.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/page.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/page_like.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/password_reset_request.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/poll.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/poll_vote.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/prelude.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/promo_note.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/promo_read.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/registration_ticket.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/registry_item.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/relay.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/renote_muting.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/reversi_game.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/reversi_matching.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/sea_orm_active_enums.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/signin.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/sw_subscription.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/used_username.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/user.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/user_group.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/user_group_invitation.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/user_group_invite.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/user_group_joining.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/user_ip.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/user_keypair.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/user_list.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/user_list_joining.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/user_note_pining.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/user_pending.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/user_profile.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/user_publickey.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/user_security_key.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/webhook.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/lib.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/repository/abuse_user_report.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/repository/mod.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/schema/antenna.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/schema/app.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/schema/mod.rs
 delete mode 100644 packages/backend/native-utils/rustfmt.toml

diff --git a/packages/backend/native-utils/.editorconfig b/packages/backend/native-utils/.editorconfig
new file mode 100644
index 000000000..889b72e11
--- /dev/null
+++ b/packages/backend/native-utils/.editorconfig
@@ -0,0 +1,3 @@
+[*.rs]
+indent_style = space
+indent_size = 4
diff --git a/packages/backend/native-utils/Cargo.toml b/packages/backend/native-utils/Cargo.toml
index 4f7fb4c39..6f3c0e23f 100644
--- a/packages/backend/native-utils/Cargo.toml
+++ b/packages/backend/native-utils/Cargo.toml
@@ -3,6 +3,9 @@ edition = "2021"
 name = "native-utils"
 version = "0.0.0"
 
+[workspace]
+members = ["crates/*"]
+
 [lib]
 crate-type = ["cdylib"]
 
diff --git a/packages/backend/native-utils/build.rs b/packages/backend/native-utils/build.rs
index 1f866b6a3..9fc236788 100644
--- a/packages/backend/native-utils/build.rs
+++ b/packages/backend/native-utils/build.rs
@@ -1,5 +1,5 @@
 extern crate napi_build;
 
 fn main() {
-  napi_build::setup();
+    napi_build::setup();
 }
diff --git a/packages/backend/native-utils/crates/model/Cargo.toml b/packages/backend/native-utils/crates/model/Cargo.toml
new file mode 100644
index 000000000..7b492a89d
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "model"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+chrono = "0.4.24"
+jsonschema = "0.17.0"
+once_cell = "1.17.1"
+schemars = { version = "0.8.12", features = ["chrono"] }
+sea-orm = { version = "0.11.3", features = ["postgres-array", "sqlx-postgres", "runtime-tokio-rustls", "mock"] }
+serde = { version = "1.0.163", features = ["derive"] }
+serde_json = "1.0.96"
+tokio = { version = "1.28.1", features = ["sync"] }
+utoipa = "3.3.0"
diff --git a/packages/backend/native-utils/crates/model/src/entity/abuse_user_report.rs b/packages/backend/native-utils/crates/model/src/entity/abuse_user_report.rs
new file mode 100644
index 000000000..163a686ab
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/abuse_user_report.rs
@@ -0,0 +1,56 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "abuse_user_report")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "targetUserId")]
+    pub target_user_id: String,
+    #[sea_orm(column_name = "reporterId")]
+    pub reporter_id: String,
+    #[sea_orm(column_name = "assigneeId")]
+    pub assignee_id: Option<String>,
+    pub resolved: bool,
+    pub comment: String,
+    #[sea_orm(column_name = "targetUserHost")]
+    pub target_user_host: Option<String>,
+    #[sea_orm(column_name = "reporterHost")]
+    pub reporter_host: Option<String>,
+    pub forwarded: bool,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::ReporterId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User3,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::AssigneeId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "SetNull"
+    )]
+    User2,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::TargetUserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User1,
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/access_token.rs b/packages/backend/native-utils/crates/model/src/entity/access_token.rs
new file mode 100644
index 000000000..ba3a43d64
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/access_token.rs
@@ -0,0 +1,70 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "access_token")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    pub token: String,
+    pub hash: String,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "appId")]
+    pub app_id: Option<String>,
+    #[sea_orm(column_name = "lastUsedAt")]
+    pub last_used_at: Option<DateTimeWithTimeZone>,
+    pub session: Option<String>,
+    pub name: Option<String>,
+    pub description: Option<String>,
+    #[sea_orm(column_name = "iconUrl")]
+    pub icon_url: Option<String>,
+    pub permission: Vec<String>,
+    pub fetched: bool,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::app::Entity",
+        from = "Column::AppId",
+        to = "super::app::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    App,
+    #[sea_orm(has_many = "super::notification::Entity")]
+    Notification,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::app::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::App.def()
+    }
+}
+
+impl Related<super::notification::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Notification.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/ad.rs b/packages/backend/native-utils/crates/model/src/entity/ad.rs
new file mode 100644
index 000000000..4e31c7b0d
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/ad.rs
@@ -0,0 +1,27 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "ad")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "expiresAt")]
+    pub expires_at: DateTimeWithTimeZone,
+    pub place: String,
+    pub priority: String,
+    pub url: String,
+    #[sea_orm(column_name = "imageUrl")]
+    pub image_url: String,
+    pub memo: String,
+    pub ratio: i32,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/announcement.rs b/packages/backend/native-utils/crates/model/src/entity/announcement.rs
new file mode 100644
index 000000000..0f02a1ca9
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/announcement.rs
@@ -0,0 +1,33 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "announcement")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    pub text: String,
+    pub title: String,
+    #[sea_orm(column_name = "imageUrl")]
+    pub image_url: Option<String>,
+    #[sea_orm(column_name = "updatedAt")]
+    pub updated_at: Option<DateTimeWithTimeZone>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(has_many = "super::announcement_read::Entity")]
+    AnnouncementRead,
+}
+
+impl Related<super::announcement_read::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::AnnouncementRead.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/announcement_read.rs b/packages/backend/native-utils/crates/model/src/entity/announcement_read.rs
new file mode 100644
index 000000000..ad7dcc6f2
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/announcement_read.rs
@@ -0,0 +1,51 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "announcement_read")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "announcementId")]
+    pub announcement_id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::announcement::Entity",
+        from = "Column::AnnouncementId",
+        to = "super::announcement::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Announcement,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::announcement::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Announcement.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/antenna.rs b/packages/backend/native-utils/crates/model/src/entity/antenna.rs
new file mode 100644
index 000000000..f9c040b59
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/antenna.rs
@@ -0,0 +1,93 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use super::sea_orm_active_enums::AntennaSrcEnum;
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "antenna")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    pub name: String,
+    pub src: AntennaSrcEnum,
+    #[sea_orm(column_name = "userListId")]
+    pub user_list_id: Option<String>,
+    #[sea_orm(column_type = "JsonBinary")]
+    pub keywords: Json,
+    #[sea_orm(column_name = "withFile")]
+    pub with_file: bool,
+    pub expression: Option<String>,
+    pub notify: bool,
+    #[sea_orm(column_name = "caseSensitive")]
+    pub case_sensitive: bool,
+    #[sea_orm(column_name = "withReplies")]
+    pub with_replies: bool,
+    #[sea_orm(column_name = "userGroupJoiningId")]
+    pub user_group_joining_id: Option<String>,
+    pub users: Vec<String>,
+    #[sea_orm(column_name = "excludeKeywords", column_type = "JsonBinary")]
+    pub exclude_keywords: Json,
+    #[sea_orm(column_type = "JsonBinary")]
+    pub instances: Json,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(has_many = "super::antenna_note::Entity")]
+    AntennaNote,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+    #[sea_orm(
+        belongs_to = "super::user_group_joining::Entity",
+        from = "Column::UserGroupJoiningId",
+        to = "super::user_group_joining::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    UserGroupJoining,
+    #[sea_orm(
+        belongs_to = "super::user_list::Entity",
+        from = "Column::UserListId",
+        to = "super::user_list::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    UserList,
+}
+
+impl Related<super::antenna_note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::AntennaNote.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl Related<super::user_group_joining::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserGroupJoining.def()
+    }
+}
+
+impl Related<super::user_list::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserList.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/antenna_note.rs b/packages/backend/native-utils/crates/model/src/entity/antenna_note.rs
new file mode 100644
index 000000000..ecf7b88f7
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/antenna_note.rs
@@ -0,0 +1,50 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "antenna_note")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "noteId")]
+    pub note_id: String,
+    #[sea_orm(column_name = "antennaId")]
+    pub antenna_id: String,
+    pub read: bool,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::antenna::Entity",
+        from = "Column::AntennaId",
+        to = "super::antenna::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Antenna,
+    #[sea_orm(
+        belongs_to = "super::note::Entity",
+        from = "Column::NoteId",
+        to = "super::note::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Note,
+}
+
+impl Related<super::antenna::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Antenna.def()
+    }
+}
+
+impl Related<super::note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Note.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/app.rs b/packages/backend/native-utils/crates/model/src/entity/app.rs
new file mode 100644
index 000000000..bd6ae5acf
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/app.rs
@@ -0,0 +1,57 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "app")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: Option<String>,
+    pub secret: String,
+    pub name: String,
+    pub description: String,
+    pub permission: Vec<String>,
+    #[sea_orm(column_name = "callbackUrl")]
+    pub callback_url: Option<String>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(has_many = "super::access_token::Entity")]
+    AccessToken,
+    #[sea_orm(has_many = "super::auth_session::Entity")]
+    AuthSession,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "SetNull"
+    )]
+    User,
+}
+
+impl Related<super::access_token::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::AccessToken.def()
+    }
+}
+
+impl Related<super::auth_session::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::AuthSession.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/attestation_challenge.rs b/packages/backend/native-utils/crates/model/src/entity/attestation_challenge.rs
new file mode 100644
index 000000000..25da1ae57
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/attestation_challenge.rs
@@ -0,0 +1,38 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "attestation_challenge")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "userId", primary_key, auto_increment = false)]
+    pub user_id: String,
+    pub challenge: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "registrationChallenge")]
+    pub registration_challenge: bool,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/auth_session.rs b/packages/backend/native-utils/crates/model/src/entity/auth_session.rs
new file mode 100644
index 000000000..c3b2fab54
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/auth_session.rs
@@ -0,0 +1,52 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "auth_session")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    pub token: String,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: Option<String>,
+    #[sea_orm(column_name = "appId")]
+    pub app_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::app::Entity",
+        from = "Column::AppId",
+        to = "super::app::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    App,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::app::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::App.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/blocking.rs b/packages/backend/native-utils/crates/model/src/entity/blocking.rs
new file mode 100644
index 000000000..c9092e50d
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/blocking.rs
@@ -0,0 +1,39 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "blocking")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "blockeeId")]
+    pub blockee_id: String,
+    #[sea_orm(column_name = "blockerId")]
+    pub blocker_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::BlockerId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User2,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::BlockeeId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User1,
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/channel.rs b/packages/backend/native-utils/crates/model/src/entity/channel.rs
new file mode 100644
index 000000000..b39b57f56
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/channel.rs
@@ -0,0 +1,83 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "channel")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "lastNotedAt")]
+    pub last_noted_at: Option<DateTimeWithTimeZone>,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: Option<String>,
+    pub name: String,
+    pub description: Option<String>,
+    #[sea_orm(column_name = "bannerId")]
+    pub banner_id: Option<String>,
+    #[sea_orm(column_name = "notesCount")]
+    pub notes_count: i32,
+    #[sea_orm(column_name = "usersCount")]
+    pub users_count: i32,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(has_many = "super::channel_following::Entity")]
+    ChannelFollowing,
+    #[sea_orm(has_many = "super::channel_note_pining::Entity")]
+    ChannelNotePining,
+    #[sea_orm(
+        belongs_to = "super::drive_file::Entity",
+        from = "Column::BannerId",
+        to = "super::drive_file::Column::Id",
+        on_update = "NoAction",
+        on_delete = "SetNull"
+    )]
+    DriveFile,
+    #[sea_orm(has_many = "super::note::Entity")]
+    Note,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "SetNull"
+    )]
+    User,
+}
+
+impl Related<super::channel_following::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::ChannelFollowing.def()
+    }
+}
+
+impl Related<super::channel_note_pining::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::ChannelNotePining.def()
+    }
+}
+
+impl Related<super::drive_file::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::DriveFile.def()
+    }
+}
+
+impl Related<super::note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Note.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/channel_following.rs b/packages/backend/native-utils/crates/model/src/entity/channel_following.rs
new file mode 100644
index 000000000..a415b6c32
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/channel_following.rs
@@ -0,0 +1,51 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "channel_following")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "followeeId")]
+    pub followee_id: String,
+    #[sea_orm(column_name = "followerId")]
+    pub follower_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::channel::Entity",
+        from = "Column::FolloweeId",
+        to = "super::channel::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Channel,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::FollowerId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::channel::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Channel.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/channel_note_pining.rs b/packages/backend/native-utils/crates/model/src/entity/channel_note_pining.rs
new file mode 100644
index 000000000..16f80b91d
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/channel_note_pining.rs
@@ -0,0 +1,51 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "channel_note_pining")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "channelId")]
+    pub channel_id: String,
+    #[sea_orm(column_name = "noteId")]
+    pub note_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::channel::Entity",
+        from = "Column::ChannelId",
+        to = "super::channel::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Channel,
+    #[sea_orm(
+        belongs_to = "super::note::Entity",
+        from = "Column::NoteId",
+        to = "super::note::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Note,
+}
+
+impl Related<super::channel::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Channel.def()
+    }
+}
+
+impl Related<super::note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Note.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/clip.rs b/packages/backend/native-utils/crates/model/src/entity/clip.rs
new file mode 100644
index 000000000..6cf1ac1c8
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/clip.rs
@@ -0,0 +1,47 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "clip")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    pub name: String,
+    #[sea_orm(column_name = "isPublic")]
+    pub is_public: bool,
+    pub description: Option<String>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(has_many = "super::clip_note::Entity")]
+    ClipNote,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::clip_note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::ClipNote.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/clip_note.rs b/packages/backend/native-utils/crates/model/src/entity/clip_note.rs
new file mode 100644
index 000000000..ba7114c3b
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/clip_note.rs
@@ -0,0 +1,49 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "clip_note")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "noteId")]
+    pub note_id: String,
+    #[sea_orm(column_name = "clipId")]
+    pub clip_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::clip::Entity",
+        from = "Column::ClipId",
+        to = "super::clip::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Clip,
+    #[sea_orm(
+        belongs_to = "super::note::Entity",
+        from = "Column::NoteId",
+        to = "super::note::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Note,
+}
+
+impl Related<super::clip::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Clip.def()
+    }
+}
+
+impl Related<super::note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Note.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/drive_file.rs b/packages/backend/native-utils/crates/model/src/entity/drive_file.rs
new file mode 100644
index 000000000..5b3d17b00
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/drive_file.rs
@@ -0,0 +1,114 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "drive_file")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: Option<String>,
+    #[sea_orm(column_name = "userHost")]
+    pub user_host: Option<String>,
+    pub md5: String,
+    pub name: String,
+    pub r#type: String,
+    pub size: i32,
+    pub comment: Option<String>,
+    #[sea_orm(column_type = "JsonBinary")]
+    pub properties: Json,
+    #[sea_orm(column_name = "storedInternal")]
+    pub stored_internal: bool,
+    pub url: String,
+    #[sea_orm(column_name = "thumbnailUrl")]
+    pub thumbnail_url: Option<String>,
+    #[sea_orm(column_name = "webpublicUrl")]
+    pub webpublic_url: Option<String>,
+    #[sea_orm(column_name = "accessKey")]
+    pub access_key: Option<String>,
+    #[sea_orm(column_name = "thumbnailAccessKey")]
+    pub thumbnail_access_key: Option<String>,
+    #[sea_orm(column_name = "webpublicAccessKey")]
+    pub webpublic_access_key: Option<String>,
+    pub uri: Option<String>,
+    pub src: Option<String>,
+    #[sea_orm(column_name = "folderId")]
+    pub folder_id: Option<String>,
+    #[sea_orm(column_name = "isSensitive")]
+    pub is_sensitive: bool,
+    #[sea_orm(column_name = "isLink")]
+    pub is_link: bool,
+    pub blurhash: Option<String>,
+    #[sea_orm(column_name = "webpublicType")]
+    pub webpublic_type: Option<String>,
+    #[sea_orm(column_name = "requestHeaders", column_type = "JsonBinary", nullable)]
+    pub request_headers: Option<Json>,
+    #[sea_orm(column_name = "requestIp")]
+    pub request_ip: Option<String>,
+    #[sea_orm(column_name = "maybeSensitive")]
+    pub maybe_sensitive: bool,
+    #[sea_orm(column_name = "maybePorn")]
+    pub maybe_porn: bool,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(has_many = "super::channel::Entity")]
+    Channel,
+    #[sea_orm(
+        belongs_to = "super::drive_folder::Entity",
+        from = "Column::FolderId",
+        to = "super::drive_folder::Column::Id",
+        on_update = "NoAction",
+        on_delete = "SetNull"
+    )]
+    DriveFolder,
+    #[sea_orm(has_many = "super::messaging_message::Entity")]
+    MessagingMessage,
+    #[sea_orm(has_many = "super::page::Entity")]
+    Page,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "SetNull"
+    )]
+    User,
+}
+
+impl Related<super::channel::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Channel.def()
+    }
+}
+
+impl Related<super::drive_folder::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::DriveFolder.def()
+    }
+}
+
+impl Related<super::messaging_message::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::MessagingMessage.def()
+    }
+}
+
+impl Related<super::page::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Page.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/drive_folder.rs b/packages/backend/native-utils/crates/model/src/entity/drive_folder.rs
new file mode 100644
index 000000000..9756f7053
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/drive_folder.rs
@@ -0,0 +1,54 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "drive_folder")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    pub name: String,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: Option<String>,
+    #[sea_orm(column_name = "parentId")]
+    pub parent_id: Option<String>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(has_many = "super::drive_file::Entity")]
+    DriveFile,
+    #[sea_orm(
+        belongs_to = "Entity",
+        from = "Column::ParentId",
+        to = "Column::Id",
+        on_update = "NoAction",
+        on_delete = "SetNull"
+    )]
+    SelfRef,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::drive_file::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::DriveFile.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/emoji.rs b/packages/backend/native-utils/crates/model/src/entity/emoji.rs
new file mode 100644
index 000000000..9dff7c719
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/emoji.rs
@@ -0,0 +1,31 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "emoji")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "updatedAt")]
+    pub updated_at: Option<DateTimeWithTimeZone>,
+    pub name: String,
+    pub host: Option<String>,
+    #[sea_orm(column_name = "originalUrl")]
+    pub original_url: String,
+    pub uri: Option<String>,
+    pub r#type: Option<String>,
+    pub aliases: Vec<String>,
+    pub category: Option<String>,
+    #[sea_orm(column_name = "publicUrl")]
+    pub public_url: String,
+    pub license: Option<String>,
+    pub width: Option<i32>,
+    pub height: Option<i32>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/follow_request.rs b/packages/backend/native-utils/crates/model/src/entity/follow_request.rs
new file mode 100644
index 000000000..32e31a09d
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/follow_request.rs
@@ -0,0 +1,61 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "follow_request")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "followeeId")]
+    pub followee_id: String,
+    #[sea_orm(column_name = "followerId")]
+    pub follower_id: String,
+    #[sea_orm(column_name = "requestId")]
+    pub request_id: Option<String>,
+    #[sea_orm(column_name = "followerHost")]
+    pub follower_host: Option<String>,
+    #[sea_orm(column_name = "followerInbox")]
+    pub follower_inbox: Option<String>,
+    #[sea_orm(column_name = "followerSharedInbox")]
+    pub follower_shared_inbox: Option<String>,
+    #[sea_orm(column_name = "followeeHost")]
+    pub followee_host: Option<String>,
+    #[sea_orm(column_name = "followeeInbox")]
+    pub followee_inbox: Option<String>,
+    #[sea_orm(column_name = "followeeSharedInbox")]
+    pub followee_shared_inbox: Option<String>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(has_many = "super::notification::Entity")]
+    Notification,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::FolloweeId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User2,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::FollowerId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User1,
+}
+
+impl Related<super::notification::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Notification.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/following.rs b/packages/backend/native-utils/crates/model/src/entity/following.rs
new file mode 100644
index 000000000..6f339e05d
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/following.rs
@@ -0,0 +1,51 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "following")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "followeeId")]
+    pub followee_id: String,
+    #[sea_orm(column_name = "followerId")]
+    pub follower_id: String,
+    #[sea_orm(column_name = "followerHost")]
+    pub follower_host: Option<String>,
+    #[sea_orm(column_name = "followerInbox")]
+    pub follower_inbox: Option<String>,
+    #[sea_orm(column_name = "followerSharedInbox")]
+    pub follower_shared_inbox: Option<String>,
+    #[sea_orm(column_name = "followeeHost")]
+    pub followee_host: Option<String>,
+    #[sea_orm(column_name = "followeeInbox")]
+    pub followee_inbox: Option<String>,
+    #[sea_orm(column_name = "followeeSharedInbox")]
+    pub followee_shared_inbox: Option<String>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::FolloweeId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User2,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::FollowerId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User1,
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/gallery_like.rs b/packages/backend/native-utils/crates/model/src/entity/gallery_like.rs
new file mode 100644
index 000000000..0fdbf07ef
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/gallery_like.rs
@@ -0,0 +1,51 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "gallery_like")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "postId")]
+    pub post_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::gallery_post::Entity",
+        from = "Column::PostId",
+        to = "super::gallery_post::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    GalleryPost,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::gallery_post::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::GalleryPost.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/gallery_post.rs b/packages/backend/native-utils/crates/model/src/entity/gallery_post.rs
new file mode 100644
index 000000000..a23ebbd8f
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/gallery_post.rs
@@ -0,0 +1,54 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "gallery_post")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "updatedAt")]
+    pub updated_at: DateTimeWithTimeZone,
+    pub title: String,
+    pub description: Option<String>,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "fileIds")]
+    pub file_ids: Vec<String>,
+    #[sea_orm(column_name = "isSensitive")]
+    pub is_sensitive: bool,
+    #[sea_orm(column_name = "likedCount")]
+    pub liked_count: i32,
+    pub tags: Vec<String>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(has_many = "super::gallery_like::Entity")]
+    GalleryLike,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::gallery_like::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::GalleryLike.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/hashtag.rs b/packages/backend/native-utils/crates/model/src/entity/hashtag.rs
new file mode 100644
index 000000000..a83ebbf29
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/hashtag.rs
@@ -0,0 +1,41 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "hashtag")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    pub name: String,
+    #[sea_orm(column_name = "mentionedUserIds")]
+    pub mentioned_user_ids: Vec<String>,
+    #[sea_orm(column_name = "mentionedUsersCount")]
+    pub mentioned_users_count: i32,
+    #[sea_orm(column_name = "mentionedLocalUserIds")]
+    pub mentioned_local_user_ids: Vec<String>,
+    #[sea_orm(column_name = "mentionedLocalUsersCount")]
+    pub mentioned_local_users_count: i32,
+    #[sea_orm(column_name = "mentionedRemoteUserIds")]
+    pub mentioned_remote_user_ids: Vec<String>,
+    #[sea_orm(column_name = "mentionedRemoteUsersCount")]
+    pub mentioned_remote_users_count: i32,
+    #[sea_orm(column_name = "attachedUserIds")]
+    pub attached_user_ids: Vec<String>,
+    #[sea_orm(column_name = "attachedUsersCount")]
+    pub attached_users_count: i32,
+    #[sea_orm(column_name = "attachedLocalUserIds")]
+    pub attached_local_user_ids: Vec<String>,
+    #[sea_orm(column_name = "attachedLocalUsersCount")]
+    pub attached_local_users_count: i32,
+    #[sea_orm(column_name = "attachedRemoteUserIds")]
+    pub attached_remote_user_ids: Vec<String>,
+    #[sea_orm(column_name = "attachedRemoteUsersCount")]
+    pub attached_remote_users_count: i32,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/instance.rs b/packages/backend/native-utils/crates/model/src/entity/instance.rs
new file mode 100644
index 000000000..405648efc
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/instance.rs
@@ -0,0 +1,59 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "instance")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "caughtAt")]
+    pub caught_at: DateTimeWithTimeZone,
+    pub host: String,
+    #[sea_orm(column_name = "usersCount")]
+    pub users_count: i32,
+    #[sea_orm(column_name = "notesCount")]
+    pub notes_count: i32,
+    #[sea_orm(column_name = "followingCount")]
+    pub following_count: i32,
+    #[sea_orm(column_name = "followersCount")]
+    pub followers_count: i32,
+    #[sea_orm(column_name = "latestRequestSentAt")]
+    pub latest_request_sent_at: Option<DateTimeWithTimeZone>,
+    #[sea_orm(column_name = "latestStatus")]
+    pub latest_status: Option<i32>,
+    #[sea_orm(column_name = "latestRequestReceivedAt")]
+    pub latest_request_received_at: Option<DateTimeWithTimeZone>,
+    #[sea_orm(column_name = "lastCommunicatedAt")]
+    pub last_communicated_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "isNotResponding")]
+    pub is_not_responding: bool,
+    #[sea_orm(column_name = "softwareName")]
+    pub software_name: Option<String>,
+    #[sea_orm(column_name = "softwareVersion")]
+    pub software_version: Option<String>,
+    #[sea_orm(column_name = "openRegistrations")]
+    pub open_registrations: Option<bool>,
+    pub name: Option<String>,
+    pub description: Option<String>,
+    #[sea_orm(column_name = "maintainerName")]
+    pub maintainer_name: Option<String>,
+    #[sea_orm(column_name = "maintainerEmail")]
+    pub maintainer_email: Option<String>,
+    #[sea_orm(column_name = "infoUpdatedAt")]
+    pub info_updated_at: Option<DateTimeWithTimeZone>,
+    #[sea_orm(column_name = "isSuspended")]
+    pub is_suspended: bool,
+    #[sea_orm(column_name = "iconUrl")]
+    pub icon_url: Option<String>,
+    #[sea_orm(column_name = "themeColor")]
+    pub theme_color: Option<String>,
+    #[sea_orm(column_name = "faviconUrl")]
+    pub favicon_url: Option<String>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/messaging_message.rs b/packages/backend/native-utils/crates/model/src/entity/messaging_message.rs
new file mode 100644
index 000000000..cfb896371
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/messaging_message.rs
@@ -0,0 +1,76 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "messaging_message")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "recipientId")]
+    pub recipient_id: Option<String>,
+    pub text: Option<String>,
+    #[sea_orm(column_name = "isRead")]
+    pub is_read: bool,
+    #[sea_orm(column_name = "fileId")]
+    pub file_id: Option<String>,
+    #[sea_orm(column_name = "groupId")]
+    pub group_id: Option<String>,
+    pub reads: Vec<String>,
+    pub uri: Option<String>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::drive_file::Entity",
+        from = "Column::FileId",
+        to = "super::drive_file::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    DriveFile,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User2,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::RecipientId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User1,
+    #[sea_orm(
+        belongs_to = "super::user_group::Entity",
+        from = "Column::GroupId",
+        to = "super::user_group::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    UserGroup,
+}
+
+impl Related<super::drive_file::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::DriveFile.def()
+    }
+}
+
+impl Related<super::user_group::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserGroup.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/meta.rs b/packages/backend/native-utils/crates/model/src/entity/meta.rs
new file mode 100644
index 000000000..768c725aa
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/meta.rs
@@ -0,0 +1,211 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use super::sea_orm_active_enums::MetaSensitivemediadetectionEnum;
+use super::sea_orm_active_enums::MetaSensitivemediadetectionsensitivityEnum;
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "meta")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    pub name: Option<String>,
+    pub description: Option<String>,
+    #[sea_orm(column_name = "maintainerName")]
+    pub maintainer_name: Option<String>,
+    #[sea_orm(column_name = "maintainerEmail")]
+    pub maintainer_email: Option<String>,
+    #[sea_orm(column_name = "disableRegistration")]
+    pub disable_registration: bool,
+    #[sea_orm(column_name = "disableLocalTimeline")]
+    pub disable_local_timeline: bool,
+    #[sea_orm(column_name = "disableGlobalTimeline")]
+    pub disable_global_timeline: bool,
+    #[sea_orm(column_name = "useStarForReactionFallback")]
+    pub use_star_for_reaction_fallback: bool,
+    pub langs: Vec<String>,
+    #[sea_orm(column_name = "hiddenTags")]
+    pub hidden_tags: Vec<String>,
+    #[sea_orm(column_name = "blockedHosts")]
+    pub blocked_hosts: Vec<String>,
+    #[sea_orm(column_name = "mascotImageUrl")]
+    pub mascot_image_url: Option<String>,
+    #[sea_orm(column_name = "bannerUrl")]
+    pub banner_url: Option<String>,
+    #[sea_orm(column_name = "errorImageUrl")]
+    pub error_image_url: Option<String>,
+    #[sea_orm(column_name = "iconUrl")]
+    pub icon_url: Option<String>,
+    #[sea_orm(column_name = "cacheRemoteFiles")]
+    pub cache_remote_files: bool,
+    #[sea_orm(column_name = "enableRecaptcha")]
+    pub enable_recaptcha: bool,
+    #[sea_orm(column_name = "recaptchaSiteKey")]
+    pub recaptcha_site_key: Option<String>,
+    #[sea_orm(column_name = "recaptchaSecretKey")]
+    pub recaptcha_secret_key: Option<String>,
+    #[sea_orm(column_name = "localDriveCapacityMb")]
+    pub local_drive_capacity_mb: i32,
+    #[sea_orm(column_name = "remoteDriveCapacityMb")]
+    pub remote_drive_capacity_mb: i32,
+    #[sea_orm(column_name = "summalyProxy")]
+    pub summaly_proxy: Option<String>,
+    #[sea_orm(column_name = "enableEmail")]
+    pub enable_email: bool,
+    pub email: Option<String>,
+    #[sea_orm(column_name = "smtpSecure")]
+    pub smtp_secure: bool,
+    #[sea_orm(column_name = "smtpHost")]
+    pub smtp_host: Option<String>,
+    #[sea_orm(column_name = "smtpPort")]
+    pub smtp_port: Option<i32>,
+    #[sea_orm(column_name = "smtpUser")]
+    pub smtp_user: Option<String>,
+    #[sea_orm(column_name = "smtpPass")]
+    pub smtp_pass: Option<String>,
+    #[sea_orm(column_name = "enableServiceWorker")]
+    pub enable_service_worker: bool,
+    #[sea_orm(column_name = "swPublicKey")]
+    pub sw_public_key: Option<String>,
+    #[sea_orm(column_name = "swPrivateKey")]
+    pub sw_private_key: Option<String>,
+    #[sea_orm(column_name = "enableTwitterIntegration")]
+    pub enable_twitter_integration: bool,
+    #[sea_orm(column_name = "twitterConsumerKey")]
+    pub twitter_consumer_key: Option<String>,
+    #[sea_orm(column_name = "twitterConsumerSecret")]
+    pub twitter_consumer_secret: Option<String>,
+    #[sea_orm(column_name = "enableGithubIntegration")]
+    pub enable_github_integration: bool,
+    #[sea_orm(column_name = "githubClientId")]
+    pub github_client_id: Option<String>,
+    #[sea_orm(column_name = "githubClientSecret")]
+    pub github_client_secret: Option<String>,
+    #[sea_orm(column_name = "enableDiscordIntegration")]
+    pub enable_discord_integration: bool,
+    #[sea_orm(column_name = "discordClientId")]
+    pub discord_client_id: Option<String>,
+    #[sea_orm(column_name = "discordClientSecret")]
+    pub discord_client_secret: Option<String>,
+    #[sea_orm(column_name = "pinnedUsers")]
+    pub pinned_users: Vec<String>,
+    #[sea_orm(column_name = "ToSUrl")]
+    pub to_s_url: Option<String>,
+    #[sea_orm(column_name = "repositoryUrl")]
+    pub repository_url: String,
+    #[sea_orm(column_name = "feedbackUrl")]
+    pub feedback_url: Option<String>,
+    #[sea_orm(column_name = "useObjectStorage")]
+    pub use_object_storage: bool,
+    #[sea_orm(column_name = "objectStorageBucket")]
+    pub object_storage_bucket: Option<String>,
+    #[sea_orm(column_name = "objectStoragePrefix")]
+    pub object_storage_prefix: Option<String>,
+    #[sea_orm(column_name = "objectStorageBaseUrl")]
+    pub object_storage_base_url: Option<String>,
+    #[sea_orm(column_name = "objectStorageEndpoint")]
+    pub object_storage_endpoint: Option<String>,
+    #[sea_orm(column_name = "objectStorageRegion")]
+    pub object_storage_region: Option<String>,
+    #[sea_orm(column_name = "objectStorageAccessKey")]
+    pub object_storage_access_key: Option<String>,
+    #[sea_orm(column_name = "objectStorageSecretKey")]
+    pub object_storage_secret_key: Option<String>,
+    #[sea_orm(column_name = "objectStoragePort")]
+    pub object_storage_port: Option<i32>,
+    #[sea_orm(column_name = "objectStorageUseSSL")]
+    pub object_storage_use_ssl: bool,
+    #[sea_orm(column_name = "proxyAccountId")]
+    pub proxy_account_id: Option<String>,
+    #[sea_orm(column_name = "objectStorageUseProxy")]
+    pub object_storage_use_proxy: bool,
+    #[sea_orm(column_name = "enableHcaptcha")]
+    pub enable_hcaptcha: bool,
+    #[sea_orm(column_name = "hcaptchaSiteKey")]
+    pub hcaptcha_site_key: Option<String>,
+    #[sea_orm(column_name = "hcaptchaSecretKey")]
+    pub hcaptcha_secret_key: Option<String>,
+    #[sea_orm(column_name = "objectStorageSetPublicRead")]
+    pub object_storage_set_public_read: bool,
+    #[sea_orm(column_name = "pinnedPages")]
+    pub pinned_pages: Vec<String>,
+    #[sea_orm(column_name = "backgroundImageUrl")]
+    pub background_image_url: Option<String>,
+    #[sea_orm(column_name = "logoImageUrl")]
+    pub logo_image_url: Option<String>,
+    #[sea_orm(column_name = "pinnedClipId")]
+    pub pinned_clip_id: Option<String>,
+    #[sea_orm(column_name = "objectStorageS3ForcePathStyle")]
+    pub object_storage_s3_force_path_style: bool,
+    #[sea_orm(column_name = "allowedHosts")]
+    pub allowed_hosts: Option<Vec<String>>,
+    #[sea_orm(column_name = "secureMode")]
+    pub secure_mode: Option<bool>,
+    #[sea_orm(column_name = "privateMode")]
+    pub private_mode: Option<bool>,
+    #[sea_orm(column_name = "deeplAuthKey")]
+    pub deepl_auth_key: Option<String>,
+    #[sea_orm(column_name = "deeplIsPro")]
+    pub deepl_is_pro: bool,
+    #[sea_orm(column_name = "emailRequiredForSignup")]
+    pub email_required_for_signup: bool,
+    #[sea_orm(column_name = "themeColor")]
+    pub theme_color: Option<String>,
+    #[sea_orm(column_name = "defaultLightTheme")]
+    pub default_light_theme: Option<String>,
+    #[sea_orm(column_name = "defaultDarkTheme")]
+    pub default_dark_theme: Option<String>,
+    #[sea_orm(column_name = "sensitiveMediaDetection")]
+    pub sensitive_media_detection: MetaSensitivemediadetectionEnum,
+    #[sea_orm(column_name = "sensitiveMediaDetectionSensitivity")]
+    pub sensitive_media_detection_sensitivity: MetaSensitivemediadetectionsensitivityEnum,
+    #[sea_orm(column_name = "setSensitiveFlagAutomatically")]
+    pub set_sensitive_flag_automatically: bool,
+    #[sea_orm(column_name = "enableIpLogging")]
+    pub enable_ip_logging: bool,
+    #[sea_orm(column_name = "enableSensitiveMediaDetectionForVideos")]
+    pub enable_sensitive_media_detection_for_videos: bool,
+    #[sea_orm(column_name = "enableActiveEmailValidation")]
+    pub enable_active_email_validation: bool,
+    #[sea_orm(column_name = "customMOTD")]
+    pub custom_motd: Vec<String>,
+    #[sea_orm(column_name = "customSplashIcons")]
+    pub custom_splash_icons: Vec<String>,
+    #[sea_orm(column_name = "disableRecommendedTimeline")]
+    pub disable_recommended_timeline: bool,
+    #[sea_orm(column_name = "recommendedInstances")]
+    pub recommended_instances: Vec<String>,
+    #[sea_orm(column_name = "enableGuestTimeline")]
+    pub enable_guest_timeline: bool,
+    #[sea_orm(column_name = "defaultReaction")]
+    pub default_reaction: String,
+    #[sea_orm(column_name = "libreTranslateApiUrl")]
+    pub libre_translate_api_url: Option<String>,
+    #[sea_orm(column_name = "libreTranslateApiKey")]
+    pub libre_translate_api_key: Option<String>,
+    #[sea_orm(column_name = "silencedHosts")]
+    pub silenced_hosts: Vec<String>,
+    #[sea_orm(column_name = "experimentalFeatures", column_type = "JsonBinary")]
+    pub experimental_features: Json,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::ProxyAccountId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "SetNull"
+    )]
+    User,
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/migrations.rs b/packages/backend/native-utils/crates/model/src/entity/migrations.rs
new file mode 100644
index 000000000..c53c3fc89
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/migrations.rs
@@ -0,0 +1,18 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "migrations")]
+pub struct Model {
+    #[sea_orm(primary_key)]
+    pub id: i32,
+    pub timestamp: i64,
+    pub name: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/mod.rs b/packages/backend/native-utils/crates/model/src/entity/mod.rs
new file mode 100644
index 000000000..077f9fa6e
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/mod.rs
@@ -0,0 +1,75 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+pub mod prelude;
+
+pub mod abuse_user_report;
+pub mod access_token;
+pub mod ad;
+pub mod announcement;
+pub mod announcement_read;
+pub mod antenna;
+pub mod antenna_note;
+pub mod app;
+pub mod attestation_challenge;
+pub mod auth_session;
+pub mod blocking;
+pub mod channel;
+pub mod channel_following;
+pub mod channel_note_pining;
+pub mod clip;
+pub mod clip_note;
+pub mod drive_file;
+pub mod drive_folder;
+pub mod emoji;
+pub mod follow_request;
+pub mod following;
+pub mod gallery_like;
+pub mod gallery_post;
+pub mod hashtag;
+pub mod instance;
+pub mod messaging_message;
+pub mod meta;
+pub mod migrations;
+pub mod moderation_log;
+pub mod muted_note;
+pub mod muting;
+pub mod note;
+pub mod note_edit;
+pub mod note_favorite;
+pub mod note_reaction;
+pub mod note_thread_muting;
+pub mod note_unread;
+pub mod note_watching;
+pub mod notification;
+pub mod page;
+pub mod page_like;
+pub mod password_reset_request;
+pub mod poll;
+pub mod poll_vote;
+pub mod promo_note;
+pub mod promo_read;
+pub mod registration_ticket;
+pub mod registry_item;
+pub mod relay;
+pub mod renote_muting;
+pub mod reversi_game;
+pub mod reversi_matching;
+pub mod sea_orm_active_enums;
+pub mod signin;
+pub mod sw_subscription;
+pub mod used_username;
+pub mod user;
+pub mod user_group;
+pub mod user_group_invitation;
+pub mod user_group_invite;
+pub mod user_group_joining;
+pub mod user_ip;
+pub mod user_keypair;
+pub mod user_list;
+pub mod user_list_joining;
+pub mod user_note_pining;
+pub mod user_pending;
+pub mod user_profile;
+pub mod user_publickey;
+pub mod user_security_key;
+pub mod webhook;
diff --git a/packages/backend/native-utils/crates/model/src/entity/moderation_log.rs b/packages/backend/native-utils/crates/model/src/entity/moderation_log.rs
new file mode 100644
index 000000000..eb49d4f15
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/moderation_log.rs
@@ -0,0 +1,38 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "moderation_log")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    pub r#type: String,
+    #[sea_orm(column_type = "JsonBinary")]
+    pub info: Json,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/muted_note.rs b/packages/backend/native-utils/crates/model/src/entity/muted_note.rs
new file mode 100644
index 000000000..17328a829
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/muted_note.rs
@@ -0,0 +1,51 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use super::sea_orm_active_enums::MutedNoteReasonEnum;
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "muted_note")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "noteId")]
+    pub note_id: String,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    pub reason: MutedNoteReasonEnum,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::note::Entity",
+        from = "Column::NoteId",
+        to = "super::note::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Note,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Note.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/muting.rs b/packages/backend/native-utils/crates/model/src/entity/muting.rs
new file mode 100644
index 000000000..60020b60b
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/muting.rs
@@ -0,0 +1,41 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "muting")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "muteeId")]
+    pub mutee_id: String,
+    #[sea_orm(column_name = "muterId")]
+    pub muter_id: String,
+    #[sea_orm(column_name = "expiresAt")]
+    pub expires_at: Option<DateTimeWithTimeZone>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::MuterId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User2,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::MuteeId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User1,
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/note.rs b/packages/backend/native-utils/crates/model/src/entity/note.rs
new file mode 100644
index 000000000..cc3ec289f
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/note.rs
@@ -0,0 +1,235 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use super::sea_orm_active_enums::NoteVisibilityEnum;
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "note")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "replyId")]
+    pub reply_id: Option<String>,
+    #[sea_orm(column_name = "renoteId")]
+    pub renote_id: Option<String>,
+    #[sea_orm(column_type = "Text", nullable)]
+    pub text: Option<String>,
+    pub name: Option<String>,
+    pub cw: Option<String>,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "localOnly")]
+    pub local_only: bool,
+    #[sea_orm(column_name = "renoteCount")]
+    pub renote_count: i16,
+    #[sea_orm(column_name = "repliesCount")]
+    pub replies_count: i16,
+    #[sea_orm(column_type = "JsonBinary")]
+    pub reactions: Json,
+    pub visibility: NoteVisibilityEnum,
+    pub uri: Option<String>,
+    pub score: i32,
+    #[sea_orm(column_name = "fileIds")]
+    pub file_ids: Vec<String>,
+    #[sea_orm(column_name = "attachedFileTypes")]
+    pub attached_file_types: Vec<String>,
+    #[sea_orm(column_name = "visibleUserIds")]
+    pub visible_user_ids: Vec<String>,
+    pub mentions: Vec<String>,
+    #[sea_orm(column_name = "mentionedRemoteUsers", column_type = "Text")]
+    pub mentioned_remote_users: String,
+    pub emojis: Vec<String>,
+    pub tags: Vec<String>,
+    #[sea_orm(column_name = "hasPoll")]
+    pub has_poll: bool,
+    #[sea_orm(column_name = "userHost")]
+    pub user_host: Option<String>,
+    #[sea_orm(column_name = "replyUserId")]
+    pub reply_user_id: Option<String>,
+    #[sea_orm(column_name = "replyUserHost")]
+    pub reply_user_host: Option<String>,
+    #[sea_orm(column_name = "renoteUserId")]
+    pub renote_user_id: Option<String>,
+    #[sea_orm(column_name = "renoteUserHost")]
+    pub renote_user_host: Option<String>,
+    pub url: Option<String>,
+    #[sea_orm(column_name = "channelId")]
+    pub channel_id: Option<String>,
+    #[sea_orm(column_name = "threadId")]
+    pub thread_id: Option<String>,
+    #[sea_orm(column_name = "updatedAt")]
+    pub updated_at: Option<DateTimeWithTimeZone>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(has_many = "super::antenna_note::Entity")]
+    AntennaNote,
+    #[sea_orm(
+        belongs_to = "super::channel::Entity",
+        from = "Column::ChannelId",
+        to = "super::channel::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Channel,
+    #[sea_orm(has_many = "super::channel_note_pining::Entity")]
+    ChannelNotePining,
+    #[sea_orm(has_many = "super::clip_note::Entity")]
+    ClipNote,
+    #[sea_orm(has_many = "super::muted_note::Entity")]
+    MutedNote,
+    #[sea_orm(
+        belongs_to = "Entity",
+        from = "Column::ReplyId",
+        to = "Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    SelfRef2,
+    #[sea_orm(
+        belongs_to = "Entity",
+        from = "Column::RenoteId",
+        to = "Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    SelfRef1,
+    #[sea_orm(has_many = "super::note_edit::Entity")]
+    NoteEdit,
+    #[sea_orm(has_many = "super::note_favorite::Entity")]
+    NoteFavorite,
+    #[sea_orm(has_many = "super::note_reaction::Entity")]
+    NoteReaction,
+    #[sea_orm(has_many = "super::note_unread::Entity")]
+    NoteUnread,
+    #[sea_orm(has_many = "super::note_watching::Entity")]
+    NoteWatching,
+    #[sea_orm(has_many = "super::notification::Entity")]
+    Notification,
+    #[sea_orm(has_one = "super::poll::Entity")]
+    Poll,
+    #[sea_orm(has_many = "super::poll_vote::Entity")]
+    PollVote,
+    #[sea_orm(has_one = "super::promo_note::Entity")]
+    PromoNote,
+    #[sea_orm(has_many = "super::promo_read::Entity")]
+    PromoRead,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+    #[sea_orm(has_many = "super::user_note_pining::Entity")]
+    UserNotePining,
+}
+
+impl Related<super::antenna_note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::AntennaNote.def()
+    }
+}
+
+impl Related<super::channel::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Channel.def()
+    }
+}
+
+impl Related<super::channel_note_pining::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::ChannelNotePining.def()
+    }
+}
+
+impl Related<super::clip_note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::ClipNote.def()
+    }
+}
+
+impl Related<super::muted_note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::MutedNote.def()
+    }
+}
+
+impl Related<super::note_edit::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::NoteEdit.def()
+    }
+}
+
+impl Related<super::note_favorite::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::NoteFavorite.def()
+    }
+}
+
+impl Related<super::note_reaction::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::NoteReaction.def()
+    }
+}
+
+impl Related<super::note_unread::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::NoteUnread.def()
+    }
+}
+
+impl Related<super::note_watching::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::NoteWatching.def()
+    }
+}
+
+impl Related<super::notification::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Notification.def()
+    }
+}
+
+impl Related<super::poll::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Poll.def()
+    }
+}
+
+impl Related<super::poll_vote::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::PollVote.def()
+    }
+}
+
+impl Related<super::promo_note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::PromoNote.def()
+    }
+}
+
+impl Related<super::promo_read::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::PromoRead.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl Related<super::user_note_pining::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserNotePining.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_edit.rs b/packages/backend/native-utils/crates/model/src/entity/note_edit.rs
new file mode 100644
index 000000000..7b87613dd
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/note_edit.rs
@@ -0,0 +1,40 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "note_edit")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "noteId")]
+    pub note_id: String,
+    #[sea_orm(column_type = "Text", nullable)]
+    pub text: Option<String>,
+    pub cw: Option<String>,
+    #[sea_orm(column_name = "fileIds")]
+    pub file_ids: Vec<String>,
+    #[sea_orm(column_name = "updatedAt")]
+    pub updated_at: DateTimeWithTimeZone,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::note::Entity",
+        from = "Column::NoteId",
+        to = "super::note::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Note,
+}
+
+impl Related<super::note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Note.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_favorite.rs b/packages/backend/native-utils/crates/model/src/entity/note_favorite.rs
new file mode 100644
index 000000000..b5c96a9a6
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/note_favorite.rs
@@ -0,0 +1,51 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "note_favorite")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "noteId")]
+    pub note_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::note::Entity",
+        from = "Column::NoteId",
+        to = "super::note::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Note,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Note.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_reaction.rs b/packages/backend/native-utils/crates/model/src/entity/note_reaction.rs
new file mode 100644
index 000000000..52526aa9f
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/note_reaction.rs
@@ -0,0 +1,52 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "note_reaction")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "noteId")]
+    pub note_id: String,
+    pub reaction: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::note::Entity",
+        from = "Column::NoteId",
+        to = "super::note::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Note,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Note.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_thread_muting.rs b/packages/backend/native-utils/crates/model/src/entity/note_thread_muting.rs
new file mode 100644
index 000000000..572a03750
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/note_thread_muting.rs
@@ -0,0 +1,37 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "note_thread_muting")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "threadId")]
+    pub thread_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_unread.rs b/packages/backend/native-utils/crates/model/src/entity/note_unread.rs
new file mode 100644
index 000000000..80fae4335
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/note_unread.rs
@@ -0,0 +1,57 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "note_unread")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "noteId")]
+    pub note_id: String,
+    #[sea_orm(column_name = "noteUserId")]
+    pub note_user_id: String,
+    #[sea_orm(column_name = "isSpecified")]
+    pub is_specified: bool,
+    #[sea_orm(column_name = "isMentioned")]
+    pub is_mentioned: bool,
+    #[sea_orm(column_name = "noteChannelId")]
+    pub note_channel_id: Option<String>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::note::Entity",
+        from = "Column::NoteId",
+        to = "super::note::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Note,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Note.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_watching.rs b/packages/backend/native-utils/crates/model/src/entity/note_watching.rs
new file mode 100644
index 000000000..1ff7af52e
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/note_watching.rs
@@ -0,0 +1,53 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "note_watching")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "noteId")]
+    pub note_id: String,
+    #[sea_orm(column_name = "noteUserId")]
+    pub note_user_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::note::Entity",
+        from = "Column::NoteId",
+        to = "super::note::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Note,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Note.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/notification.rs b/packages/backend/native-utils/crates/model/src/entity/notification.rs
new file mode 100644
index 000000000..c2d9115f3
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/notification.rs
@@ -0,0 +1,115 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use super::sea_orm_active_enums::NotificationTypeEnum;
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "notification")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "notifieeId")]
+    pub notifiee_id: String,
+    #[sea_orm(column_name = "notifierId")]
+    pub notifier_id: Option<String>,
+    #[sea_orm(column_name = "isRead")]
+    pub is_read: bool,
+    #[sea_orm(column_name = "noteId")]
+    pub note_id: Option<String>,
+    pub reaction: Option<String>,
+    pub choice: Option<i32>,
+    #[sea_orm(column_name = "followRequestId")]
+    pub follow_request_id: Option<String>,
+    pub r#type: NotificationTypeEnum,
+    #[sea_orm(column_name = "userGroupInvitationId")]
+    pub user_group_invitation_id: Option<String>,
+    #[sea_orm(column_name = "customBody")]
+    pub custom_body: Option<String>,
+    #[sea_orm(column_name = "customHeader")]
+    pub custom_header: Option<String>,
+    #[sea_orm(column_name = "customIcon")]
+    pub custom_icon: Option<String>,
+    #[sea_orm(column_name = "appAccessTokenId")]
+    pub app_access_token_id: Option<String>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::access_token::Entity",
+        from = "Column::AppAccessTokenId",
+        to = "super::access_token::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    AccessToken,
+    #[sea_orm(
+        belongs_to = "super::follow_request::Entity",
+        from = "Column::FollowRequestId",
+        to = "super::follow_request::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    FollowRequest,
+    #[sea_orm(
+        belongs_to = "super::note::Entity",
+        from = "Column::NoteId",
+        to = "super::note::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Note,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::NotifierId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User2,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::NotifieeId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User1,
+    #[sea_orm(
+        belongs_to = "super::user_group_invitation::Entity",
+        from = "Column::UserGroupInvitationId",
+        to = "super::user_group_invitation::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    UserGroupInvitation,
+}
+
+impl Related<super::access_token::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::AccessToken.def()
+    }
+}
+
+impl Related<super::follow_request::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::FollowRequest.def()
+    }
+}
+
+impl Related<super::note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Note.def()
+    }
+}
+
+impl Related<super::user_group_invitation::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserGroupInvitation.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/page.rs b/packages/backend/native-utils/crates/model/src/entity/page.rs
new file mode 100644
index 000000000..32f082daf
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/page.rs
@@ -0,0 +1,90 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use super::sea_orm_active_enums::PageVisibilityEnum;
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "page")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "updatedAt")]
+    pub updated_at: DateTimeWithTimeZone,
+    pub title: String,
+    pub name: String,
+    pub summary: Option<String>,
+    #[sea_orm(column_name = "alignCenter")]
+    pub align_center: bool,
+    pub font: String,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "eyeCatchingImageId")]
+    pub eye_catching_image_id: Option<String>,
+    #[sea_orm(column_type = "JsonBinary")]
+    pub content: Json,
+    #[sea_orm(column_type = "JsonBinary")]
+    pub variables: Json,
+    pub visibility: PageVisibilityEnum,
+    #[sea_orm(column_name = "visibleUserIds")]
+    pub visible_user_ids: Vec<String>,
+    #[sea_orm(column_name = "likedCount")]
+    pub liked_count: i32,
+    #[sea_orm(column_name = "hideTitleWhenPinned")]
+    pub hide_title_when_pinned: bool,
+    pub script: String,
+    #[sea_orm(column_name = "isPublic")]
+    pub is_public: bool,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::drive_file::Entity",
+        from = "Column::EyeCatchingImageId",
+        to = "super::drive_file::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    DriveFile,
+    #[sea_orm(has_many = "super::page_like::Entity")]
+    PageLike,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+    #[sea_orm(has_one = "super::user_profile::Entity")]
+    UserProfile,
+}
+
+impl Related<super::drive_file::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::DriveFile.def()
+    }
+}
+
+impl Related<super::page_like::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::PageLike.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl Related<super::user_profile::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserProfile.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/page_like.rs b/packages/backend/native-utils/crates/model/src/entity/page_like.rs
new file mode 100644
index 000000000..e9de78eb6
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/page_like.rs
@@ -0,0 +1,51 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "page_like")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "pageId")]
+    pub page_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::page::Entity",
+        from = "Column::PageId",
+        to = "super::page::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Page,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::page::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Page.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/password_reset_request.rs b/packages/backend/native-utils/crates/model/src/entity/password_reset_request.rs
new file mode 100644
index 000000000..472b93db9
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/password_reset_request.rs
@@ -0,0 +1,36 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "password_reset_request")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    pub token: String,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/poll.rs b/packages/backend/native-utils/crates/model/src/entity/poll.rs
new file mode 100644
index 000000000..0facabd9a
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/poll.rs
@@ -0,0 +1,43 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use super::sea_orm_active_enums::PollNotevisibilityEnum;
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "poll")]
+pub struct Model {
+    #[sea_orm(column_name = "noteId", primary_key, auto_increment = false, unique)]
+    pub note_id: String,
+    #[sea_orm(column_name = "expiresAt")]
+    pub expires_at: Option<DateTimeWithTimeZone>,
+    pub multiple: bool,
+    pub choices: Vec<String>,
+    pub votes: Vec<i32>,
+    #[sea_orm(column_name = "noteVisibility")]
+    pub note_visibility: PollNotevisibilityEnum,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "userHost")]
+    pub user_host: Option<String>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::note::Entity",
+        from = "Column::NoteId",
+        to = "super::note::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Note,
+}
+
+impl Related<super::note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Note.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/poll_vote.rs b/packages/backend/native-utils/crates/model/src/entity/poll_vote.rs
new file mode 100644
index 000000000..29b6928ef
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/poll_vote.rs
@@ -0,0 +1,52 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "poll_vote")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "noteId")]
+    pub note_id: String,
+    pub choice: i32,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::note::Entity",
+        from = "Column::NoteId",
+        to = "super::note::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Note,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Note.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/prelude.rs b/packages/backend/native-utils/crates/model/src/entity/prelude.rs
new file mode 100644
index 000000000..7cab688bb
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/prelude.rs
@@ -0,0 +1,72 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+pub use super::abuse_user_report::Entity as AbuseUserReport;
+pub use super::access_token::Entity as AccessToken;
+pub use super::ad::Entity as Ad;
+pub use super::announcement::Entity as Announcement;
+pub use super::announcement_read::Entity as AnnouncementRead;
+pub use super::antenna::Entity as Antenna;
+pub use super::antenna_note::Entity as AntennaNote;
+pub use super::app::Entity as App;
+pub use super::attestation_challenge::Entity as AttestationChallenge;
+pub use super::auth_session::Entity as AuthSession;
+pub use super::blocking::Entity as Blocking;
+pub use super::channel::Entity as Channel;
+pub use super::channel_following::Entity as ChannelFollowing;
+pub use super::channel_note_pining::Entity as ChannelNotePining;
+pub use super::clip::Entity as Clip;
+pub use super::clip_note::Entity as ClipNote;
+pub use super::drive_file::Entity as DriveFile;
+pub use super::drive_folder::Entity as DriveFolder;
+pub use super::emoji::Entity as Emoji;
+pub use super::follow_request::Entity as FollowRequest;
+pub use super::following::Entity as Following;
+pub use super::gallery_like::Entity as GalleryLike;
+pub use super::gallery_post::Entity as GalleryPost;
+pub use super::hashtag::Entity as Hashtag;
+pub use super::instance::Entity as Instance;
+pub use super::messaging_message::Entity as MessagingMessage;
+pub use super::meta::Entity as Meta;
+pub use super::migrations::Entity as Migrations;
+pub use super::moderation_log::Entity as ModerationLog;
+pub use super::muted_note::Entity as MutedNote;
+pub use super::muting::Entity as Muting;
+pub use super::note::Entity as Note;
+pub use super::note_edit::Entity as NoteEdit;
+pub use super::note_favorite::Entity as NoteFavorite;
+pub use super::note_reaction::Entity as NoteReaction;
+pub use super::note_thread_muting::Entity as NoteThreadMuting;
+pub use super::note_unread::Entity as NoteUnread;
+pub use super::note_watching::Entity as NoteWatching;
+pub use super::notification::Entity as Notification;
+pub use super::page::Entity as Page;
+pub use super::page_like::Entity as PageLike;
+pub use super::password_reset_request::Entity as PasswordResetRequest;
+pub use super::poll::Entity as Poll;
+pub use super::poll_vote::Entity as PollVote;
+pub use super::promo_note::Entity as PromoNote;
+pub use super::promo_read::Entity as PromoRead;
+pub use super::registration_ticket::Entity as RegistrationTicket;
+pub use super::registry_item::Entity as RegistryItem;
+pub use super::relay::Entity as Relay;
+pub use super::renote_muting::Entity as RenoteMuting;
+pub use super::reversi_game::Entity as ReversiGame;
+pub use super::reversi_matching::Entity as ReversiMatching;
+pub use super::signin::Entity as Signin;
+pub use super::sw_subscription::Entity as SwSubscription;
+pub use super::used_username::Entity as UsedUsername;
+pub use super::user::Entity as User;
+pub use super::user_group::Entity as UserGroup;
+pub use super::user_group_invitation::Entity as UserGroupInvitation;
+pub use super::user_group_invite::Entity as UserGroupInvite;
+pub use super::user_group_joining::Entity as UserGroupJoining;
+pub use super::user_ip::Entity as UserIp;
+pub use super::user_keypair::Entity as UserKeypair;
+pub use super::user_list::Entity as UserList;
+pub use super::user_list_joining::Entity as UserListJoining;
+pub use super::user_note_pining::Entity as UserNotePining;
+pub use super::user_pending::Entity as UserPending;
+pub use super::user_profile::Entity as UserProfile;
+pub use super::user_publickey::Entity as UserPublickey;
+pub use super::user_security_key::Entity as UserSecurityKey;
+pub use super::webhook::Entity as Webhook;
diff --git a/packages/backend/native-utils/crates/model/src/entity/promo_note.rs b/packages/backend/native-utils/crates/model/src/entity/promo_note.rs
new file mode 100644
index 000000000..6e8de9866
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/promo_note.rs
@@ -0,0 +1,35 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "promo_note")]
+pub struct Model {
+    #[sea_orm(column_name = "noteId", primary_key, auto_increment = false, unique)]
+    pub note_id: String,
+    #[sea_orm(column_name = "expiresAt")]
+    pub expires_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::note::Entity",
+        from = "Column::NoteId",
+        to = "super::note::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Note,
+}
+
+impl Related<super::note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Note.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/promo_read.rs b/packages/backend/native-utils/crates/model/src/entity/promo_read.rs
new file mode 100644
index 000000000..807ad4a27
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/promo_read.rs
@@ -0,0 +1,51 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "promo_read")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "noteId")]
+    pub note_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::note::Entity",
+        from = "Column::NoteId",
+        to = "super::note::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Note,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Note.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/registration_ticket.rs b/packages/backend/native-utils/crates/model/src/entity/registration_ticket.rs
new file mode 100644
index 000000000..8d7e4ba8d
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/registration_ticket.rs
@@ -0,0 +1,19 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "registration_ticket")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    pub code: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/registry_item.rs b/packages/backend/native-utils/crates/model/src/entity/registry_item.rs
new file mode 100644
index 000000000..8aa188210
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/registry_item.rs
@@ -0,0 +1,42 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "registry_item")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "updatedAt")]
+    pub updated_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    pub key: String,
+    pub scope: Vec<String>,
+    pub domain: Option<String>,
+    #[sea_orm(column_type = "JsonBinary", nullable)]
+    pub value: Option<Json>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/relay.rs b/packages/backend/native-utils/crates/model/src/entity/relay.rs
new file mode 100644
index 000000000..731d12e8c
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/relay.rs
@@ -0,0 +1,19 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use super::sea_orm_active_enums::RelayStatusEnum;
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "relay")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    pub inbox: String,
+    pub status: RelayStatusEnum,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/renote_muting.rs b/packages/backend/native-utils/crates/model/src/entity/renote_muting.rs
new file mode 100644
index 000000000..b63c989a6
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/renote_muting.rs
@@ -0,0 +1,22 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "renote_muting")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "muteeId")]
+    pub mutee_id: String,
+    #[sea_orm(column_name = "muterId")]
+    pub muter_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/reversi_game.rs b/packages/backend/native-utils/crates/model/src/entity/reversi_game.rs
new file mode 100644
index 000000000..56f83e543
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/reversi_game.rs
@@ -0,0 +1,68 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "reversi_game")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "startedAt")]
+    pub started_at: Option<DateTimeWithTimeZone>,
+    #[sea_orm(column_name = "user1Id")]
+    pub user1_id: String,
+    #[sea_orm(column_name = "user2Id")]
+    pub user2_id: String,
+    #[sea_orm(column_name = "user1Accepted")]
+    pub user1_accepted: bool,
+    #[sea_orm(column_name = "user2Accepted")]
+    pub user2_accepted: bool,
+    pub black: Option<i32>,
+    #[sea_orm(column_name = "isStarted")]
+    pub is_started: bool,
+    #[sea_orm(column_name = "isEnded")]
+    pub is_ended: bool,
+    #[sea_orm(column_name = "winnerId")]
+    pub winner_id: Option<String>,
+    pub surrendered: Option<String>,
+    #[sea_orm(column_type = "JsonBinary")]
+    pub logs: Json,
+    pub map: Vec<String>,
+    pub bw: String,
+    #[sea_orm(column_name = "isLlotheo")]
+    pub is_llotheo: bool,
+    #[sea_orm(column_name = "canPutEverywhere")]
+    pub can_put_everywhere: bool,
+    #[sea_orm(column_name = "loopedBoard")]
+    pub looped_board: bool,
+    #[sea_orm(column_type = "JsonBinary", nullable)]
+    pub form1: Option<Json>,
+    #[sea_orm(column_type = "JsonBinary", nullable)]
+    pub form2: Option<Json>,
+    pub crc32: Option<String>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::User2Id",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User2,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::User1Id",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User1,
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/reversi_matching.rs b/packages/backend/native-utils/crates/model/src/entity/reversi_matching.rs
new file mode 100644
index 000000000..9261b6482
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/reversi_matching.rs
@@ -0,0 +1,39 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "reversi_matching")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "parentId")]
+    pub parent_id: String,
+    #[sea_orm(column_name = "childId")]
+    pub child_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::ParentId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User2,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::ChildId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User1,
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/sea_orm_active_enums.rs b/packages/backend/native-utils/crates/model/src/entity/sea_orm_active_enums.rs
new file mode 100644
index 000000000..747cd953a
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/sea_orm_active_enums.rs
@@ -0,0 +1,175 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "antenna_src_enum")]
+pub enum AntennaSrcEnum {
+    #[sea_orm(string_value = "all")]
+    All,
+    #[sea_orm(string_value = "group")]
+    Group,
+    #[sea_orm(string_value = "home")]
+    Home,
+    #[sea_orm(string_value = "instances")]
+    Instances,
+    #[sea_orm(string_value = "list")]
+    List,
+    #[sea_orm(string_value = "users")]
+    Users,
+}
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[sea_orm(
+    rs_type = "String",
+    db_type = "Enum",
+    enum_name = "meta_sensitivemediadetection_enum"
+)]
+pub enum MetaSensitivemediadetectionEnum {
+    #[sea_orm(string_value = "all")]
+    All,
+    #[sea_orm(string_value = "local")]
+    Local,
+    #[sea_orm(string_value = "none")]
+    None,
+    #[sea_orm(string_value = "remote")]
+    Remote,
+}
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[sea_orm(
+    rs_type = "String",
+    db_type = "Enum",
+    enum_name = "meta_sensitivemediadetectionsensitivity_enum"
+)]
+pub enum MetaSensitivemediadetectionsensitivityEnum {
+    #[sea_orm(string_value = "high")]
+    High,
+    #[sea_orm(string_value = "low")]
+    Low,
+    #[sea_orm(string_value = "medium")]
+    Medium,
+    #[sea_orm(string_value = "veryHigh")]
+    VeryHigh,
+    #[sea_orm(string_value = "veryLow")]
+    VeryLow,
+}
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[sea_orm(
+    rs_type = "String",
+    db_type = "Enum",
+    enum_name = "muted_note_reason_enum"
+)]
+pub enum MutedNoteReasonEnum {
+    #[sea_orm(string_value = "manual")]
+    Manual,
+    #[sea_orm(string_value = "other")]
+    Other,
+    #[sea_orm(string_value = "spam")]
+    Spam,
+    #[sea_orm(string_value = "word")]
+    Word,
+}
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[sea_orm(
+    rs_type = "String",
+    db_type = "Enum",
+    enum_name = "note_visibility_enum"
+)]
+pub enum NoteVisibilityEnum {
+    #[sea_orm(string_value = "followers")]
+    Followers,
+    #[sea_orm(string_value = "hidden")]
+    Hidden,
+    #[sea_orm(string_value = "home")]
+    Home,
+    #[sea_orm(string_value = "public")]
+    Public,
+    #[sea_orm(string_value = "specified")]
+    Specified,
+}
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[sea_orm(
+    rs_type = "String",
+    db_type = "Enum",
+    enum_name = "notification_type_enum"
+)]
+pub enum NotificationTypeEnum {
+    #[sea_orm(string_value = "app")]
+    App,
+    #[sea_orm(string_value = "follow")]
+    Follow,
+    #[sea_orm(string_value = "followRequestAccepted")]
+    FollowRequestAccepted,
+    #[sea_orm(string_value = "groupInvited")]
+    GroupInvited,
+    #[sea_orm(string_value = "mention")]
+    Mention,
+    #[sea_orm(string_value = "pollEnded")]
+    PollEnded,
+    #[sea_orm(string_value = "pollVote")]
+    PollVote,
+    #[sea_orm(string_value = "quote")]
+    Quote,
+    #[sea_orm(string_value = "reaction")]
+    Reaction,
+    #[sea_orm(string_value = "receiveFollowRequest")]
+    ReceiveFollowRequest,
+    #[sea_orm(string_value = "renote")]
+    Renote,
+    #[sea_orm(string_value = "reply")]
+    Reply,
+}
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[sea_orm(
+    rs_type = "String",
+    db_type = "Enum",
+    enum_name = "page_visibility_enum"
+)]
+pub enum PageVisibilityEnum {
+    #[sea_orm(string_value = "followers")]
+    Followers,
+    #[sea_orm(string_value = "public")]
+    Public,
+    #[sea_orm(string_value = "specified")]
+    Specified,
+}
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[sea_orm(
+    rs_type = "String",
+    db_type = "Enum",
+    enum_name = "poll_notevisibility_enum"
+)]
+pub enum PollNotevisibilityEnum {
+    #[sea_orm(string_value = "followers")]
+    Followers,
+    #[sea_orm(string_value = "home")]
+    Home,
+    #[sea_orm(string_value = "public")]
+    Public,
+    #[sea_orm(string_value = "specified")]
+    Specified,
+}
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "relay_status_enum")]
+pub enum RelayStatusEnum {
+    #[sea_orm(string_value = "accepted")]
+    Accepted,
+    #[sea_orm(string_value = "rejected")]
+    Rejected,
+    #[sea_orm(string_value = "requesting")]
+    Requesting,
+}
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[sea_orm(
+    rs_type = "String",
+    db_type = "Enum",
+    enum_name = "user_profile_ffvisibility_enum"
+)]
+pub enum UserProfileFfvisibilityEnum {
+    #[sea_orm(string_value = "followers")]
+    Followers,
+    #[sea_orm(string_value = "private")]
+    Private,
+    #[sea_orm(string_value = "public")]
+    Public,
+}
diff --git a/packages/backend/native-utils/crates/model/src/entity/signin.rs b/packages/backend/native-utils/crates/model/src/entity/signin.rs
new file mode 100644
index 000000000..d6f730892
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/signin.rs
@@ -0,0 +1,39 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "signin")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    pub ip: String,
+    #[sea_orm(column_type = "JsonBinary")]
+    pub headers: Json,
+    pub success: bool,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/sw_subscription.rs b/packages/backend/native-utils/crates/model/src/entity/sw_subscription.rs
new file mode 100644
index 000000000..0d2ac26d9
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/sw_subscription.rs
@@ -0,0 +1,40 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "sw_subscription")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    pub endpoint: String,
+    pub auth: String,
+    pub publickey: String,
+    #[sea_orm(column_name = "sendReadMessage")]
+    pub send_read_message: bool,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/used_username.rs b/packages/backend/native-utils/crates/model/src/entity/used_username.rs
new file mode 100644
index 000000000..cf325cfa5
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/used_username.rs
@@ -0,0 +1,18 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "used_username")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub username: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/user.rs b/packages/backend/native-utils/crates/model/src/entity/user.rs
new file mode 100644
index 000000000..cd1153994
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/user.rs
@@ -0,0 +1,425 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "user")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "updatedAt")]
+    pub updated_at: Option<DateTimeWithTimeZone>,
+    #[sea_orm(column_name = "lastFetchedAt")]
+    pub last_fetched_at: Option<DateTimeWithTimeZone>,
+    pub username: String,
+    #[sea_orm(column_name = "usernameLower")]
+    pub username_lower: String,
+    pub name: Option<String>,
+    #[sea_orm(column_name = "followersCount")]
+    pub followers_count: i32,
+    #[sea_orm(column_name = "followingCount")]
+    pub following_count: i32,
+    #[sea_orm(column_name = "notesCount")]
+    pub notes_count: i32,
+    #[sea_orm(column_name = "avatarId", unique)]
+    pub avatar_id: Option<String>,
+    #[sea_orm(column_name = "bannerId", unique)]
+    pub banner_id: Option<String>,
+    pub tags: Vec<String>,
+    #[sea_orm(column_name = "isSuspended")]
+    pub is_suspended: bool,
+    #[sea_orm(column_name = "isSilenced")]
+    pub is_silenced: bool,
+    #[sea_orm(column_name = "isLocked")]
+    pub is_locked: bool,
+    #[sea_orm(column_name = "isBot")]
+    pub is_bot: bool,
+    #[sea_orm(column_name = "isCat")]
+    pub is_cat: bool,
+    #[sea_orm(column_name = "isAdmin")]
+    pub is_admin: bool,
+    #[sea_orm(column_name = "isModerator")]
+    pub is_moderator: bool,
+    pub emojis: Vec<String>,
+    pub host: Option<String>,
+    pub inbox: Option<String>,
+    #[sea_orm(column_name = "sharedInbox")]
+    pub shared_inbox: Option<String>,
+    pub featured: Option<String>,
+    pub uri: Option<String>,
+    #[sea_orm(unique)]
+    pub token: Option<String>,
+    #[sea_orm(column_name = "isExplorable")]
+    pub is_explorable: bool,
+    #[sea_orm(column_name = "followersUri")]
+    pub followers_uri: Option<String>,
+    #[sea_orm(column_name = "lastActiveDate")]
+    pub last_active_date: Option<DateTimeWithTimeZone>,
+    #[sea_orm(column_name = "hideOnlineStatus")]
+    pub hide_online_status: bool,
+    #[sea_orm(column_name = "isDeleted")]
+    pub is_deleted: bool,
+    #[sea_orm(column_name = "showTimelineReplies")]
+    pub show_timeline_replies: bool,
+    #[sea_orm(column_name = "driveCapacityOverrideMb")]
+    pub drive_capacity_override_mb: Option<i32>,
+    #[sea_orm(column_name = "movedToUri")]
+    pub moved_to_uri: Option<String>,
+    #[sea_orm(column_name = "alsoKnownAs", column_type = "Text", nullable)]
+    pub also_known_as: Option<String>,
+    #[sea_orm(column_name = "speakAsCat")]
+    pub speak_as_cat: bool,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(has_many = "super::access_token::Entity")]
+    AccessToken,
+    #[sea_orm(has_many = "super::announcement_read::Entity")]
+    AnnouncementRead,
+    #[sea_orm(has_many = "super::antenna::Entity")]
+    Antenna,
+    #[sea_orm(has_many = "super::app::Entity")]
+    App,
+    #[sea_orm(has_many = "super::attestation_challenge::Entity")]
+    AttestationChallenge,
+    #[sea_orm(has_many = "super::auth_session::Entity")]
+    AuthSession,
+    #[sea_orm(has_many = "super::channel::Entity")]
+    Channel,
+    #[sea_orm(has_many = "super::channel_following::Entity")]
+    ChannelFollowing,
+    #[sea_orm(has_many = "super::clip::Entity")]
+    Clip,
+    #[sea_orm(
+        belongs_to = "super::drive_file::Entity",
+        from = "Column::AvatarId",
+        to = "super::drive_file::Column::Id",
+        on_update = "NoAction",
+        on_delete = "SetNull"
+    )]
+    DriveFile2,
+    #[sea_orm(
+        belongs_to = "super::drive_file::Entity",
+        from = "Column::BannerId",
+        to = "super::drive_file::Column::Id",
+        on_update = "NoAction",
+        on_delete = "SetNull"
+    )]
+    DriveFile1,
+    #[sea_orm(has_many = "super::drive_folder::Entity")]
+    DriveFolder,
+    #[sea_orm(has_many = "super::gallery_like::Entity")]
+    GalleryLike,
+    #[sea_orm(has_many = "super::gallery_post::Entity")]
+    GalleryPost,
+    #[sea_orm(has_many = "super::meta::Entity")]
+    Meta,
+    #[sea_orm(has_many = "super::moderation_log::Entity")]
+    ModerationLog,
+    #[sea_orm(has_many = "super::muted_note::Entity")]
+    MutedNote,
+    #[sea_orm(has_many = "super::note::Entity")]
+    Note,
+    #[sea_orm(has_many = "super::note_favorite::Entity")]
+    NoteFavorite,
+    #[sea_orm(has_many = "super::note_reaction::Entity")]
+    NoteReaction,
+    #[sea_orm(has_many = "super::note_thread_muting::Entity")]
+    NoteThreadMuting,
+    #[sea_orm(has_many = "super::note_unread::Entity")]
+    NoteUnread,
+    #[sea_orm(has_many = "super::note_watching::Entity")]
+    NoteWatching,
+    #[sea_orm(has_many = "super::page::Entity")]
+    Page,
+    #[sea_orm(has_many = "super::page_like::Entity")]
+    PageLike,
+    #[sea_orm(has_many = "super::password_reset_request::Entity")]
+    PasswordResetRequest,
+    #[sea_orm(has_many = "super::poll_vote::Entity")]
+    PollVote,
+    #[sea_orm(has_many = "super::promo_read::Entity")]
+    PromoRead,
+    #[sea_orm(has_many = "super::registry_item::Entity")]
+    RegistryItem,
+    #[sea_orm(has_many = "super::signin::Entity")]
+    Signin,
+    #[sea_orm(has_many = "super::sw_subscription::Entity")]
+    SwSubscription,
+    #[sea_orm(has_many = "super::user_group::Entity")]
+    UserGroup,
+    #[sea_orm(has_many = "super::user_group_invitation::Entity")]
+    UserGroupInvitation,
+    #[sea_orm(has_many = "super::user_group_invite::Entity")]
+    UserGroupInvite,
+    #[sea_orm(has_many = "super::user_group_joining::Entity")]
+    UserGroupJoining,
+    #[sea_orm(has_one = "super::user_keypair::Entity")]
+    UserKeypair,
+    #[sea_orm(has_many = "super::user_list::Entity")]
+    UserList,
+    #[sea_orm(has_many = "super::user_list_joining::Entity")]
+    UserListJoining,
+    #[sea_orm(has_many = "super::user_note_pining::Entity")]
+    UserNotePining,
+    #[sea_orm(has_one = "super::user_profile::Entity")]
+    UserProfile,
+    #[sea_orm(has_one = "super::user_publickey::Entity")]
+    UserPublickey,
+    #[sea_orm(has_many = "super::user_security_key::Entity")]
+    UserSecurityKey,
+    #[sea_orm(has_many = "super::webhook::Entity")]
+    Webhook,
+}
+
+impl Related<super::access_token::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::AccessToken.def()
+    }
+}
+
+impl Related<super::announcement_read::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::AnnouncementRead.def()
+    }
+}
+
+impl Related<super::antenna::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Antenna.def()
+    }
+}
+
+impl Related<super::app::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::App.def()
+    }
+}
+
+impl Related<super::attestation_challenge::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::AttestationChallenge.def()
+    }
+}
+
+impl Related<super::auth_session::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::AuthSession.def()
+    }
+}
+
+impl Related<super::channel::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Channel.def()
+    }
+}
+
+impl Related<super::channel_following::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::ChannelFollowing.def()
+    }
+}
+
+impl Related<super::clip::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Clip.def()
+    }
+}
+
+impl Related<super::drive_folder::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::DriveFolder.def()
+    }
+}
+
+impl Related<super::gallery_like::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::GalleryLike.def()
+    }
+}
+
+impl Related<super::gallery_post::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::GalleryPost.def()
+    }
+}
+
+impl Related<super::meta::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Meta.def()
+    }
+}
+
+impl Related<super::moderation_log::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::ModerationLog.def()
+    }
+}
+
+impl Related<super::muted_note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::MutedNote.def()
+    }
+}
+
+impl Related<super::note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Note.def()
+    }
+}
+
+impl Related<super::note_favorite::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::NoteFavorite.def()
+    }
+}
+
+impl Related<super::note_reaction::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::NoteReaction.def()
+    }
+}
+
+impl Related<super::note_thread_muting::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::NoteThreadMuting.def()
+    }
+}
+
+impl Related<super::note_unread::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::NoteUnread.def()
+    }
+}
+
+impl Related<super::note_watching::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::NoteWatching.def()
+    }
+}
+
+impl Related<super::page::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Page.def()
+    }
+}
+
+impl Related<super::page_like::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::PageLike.def()
+    }
+}
+
+impl Related<super::password_reset_request::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::PasswordResetRequest.def()
+    }
+}
+
+impl Related<super::poll_vote::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::PollVote.def()
+    }
+}
+
+impl Related<super::promo_read::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::PromoRead.def()
+    }
+}
+
+impl Related<super::registry_item::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::RegistryItem.def()
+    }
+}
+
+impl Related<super::signin::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Signin.def()
+    }
+}
+
+impl Related<super::sw_subscription::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::SwSubscription.def()
+    }
+}
+
+impl Related<super::user_group::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserGroup.def()
+    }
+}
+
+impl Related<super::user_group_invitation::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserGroupInvitation.def()
+    }
+}
+
+impl Related<super::user_group_invite::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserGroupInvite.def()
+    }
+}
+
+impl Related<super::user_group_joining::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserGroupJoining.def()
+    }
+}
+
+impl Related<super::user_keypair::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserKeypair.def()
+    }
+}
+
+impl Related<super::user_list::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserList.def()
+    }
+}
+
+impl Related<super::user_list_joining::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserListJoining.def()
+    }
+}
+
+impl Related<super::user_note_pining::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserNotePining.def()
+    }
+}
+
+impl Related<super::user_profile::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserProfile.def()
+    }
+}
+
+impl Related<super::user_publickey::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserPublickey.def()
+    }
+}
+
+impl Related<super::user_security_key::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserSecurityKey.def()
+    }
+}
+
+impl Related<super::webhook::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Webhook.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_group.rs b/packages/backend/native-utils/crates/model/src/entity/user_group.rs
new file mode 100644
index 000000000..278e9997d
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/user_group.rs
@@ -0,0 +1,70 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "user_group")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    pub name: String,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "isPrivate")]
+    pub is_private: bool,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(has_many = "super::messaging_message::Entity")]
+    MessagingMessage,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+    #[sea_orm(has_many = "super::user_group_invitation::Entity")]
+    UserGroupInvitation,
+    #[sea_orm(has_many = "super::user_group_invite::Entity")]
+    UserGroupInvite,
+    #[sea_orm(has_many = "super::user_group_joining::Entity")]
+    UserGroupJoining,
+}
+
+impl Related<super::messaging_message::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::MessagingMessage.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl Related<super::user_group_invitation::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserGroupInvitation.def()
+    }
+}
+
+impl Related<super::user_group_invite::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserGroupInvite.def()
+    }
+}
+
+impl Related<super::user_group_joining::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserGroupJoining.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_group_invitation.rs b/packages/backend/native-utils/crates/model/src/entity/user_group_invitation.rs
new file mode 100644
index 000000000..62538db37
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/user_group_invitation.rs
@@ -0,0 +1,59 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "user_group_invitation")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "userGroupId")]
+    pub user_group_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(has_many = "super::notification::Entity")]
+    Notification,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+    #[sea_orm(
+        belongs_to = "super::user_group::Entity",
+        from = "Column::UserGroupId",
+        to = "super::user_group::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    UserGroup,
+}
+
+impl Related<super::notification::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Notification.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl Related<super::user_group::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserGroup.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_group_invite.rs b/packages/backend/native-utils/crates/model/src/entity/user_group_invite.rs
new file mode 100644
index 000000000..7ddfa021f
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/user_group_invite.rs
@@ -0,0 +1,51 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "user_group_invite")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "userGroupId")]
+    pub user_group_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+    #[sea_orm(
+        belongs_to = "super::user_group::Entity",
+        from = "Column::UserGroupId",
+        to = "super::user_group::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    UserGroup,
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl Related<super::user_group::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserGroup.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_group_joining.rs b/packages/backend/native-utils/crates/model/src/entity/user_group_joining.rs
new file mode 100644
index 000000000..65cb000df
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/user_group_joining.rs
@@ -0,0 +1,59 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "user_group_joining")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "userGroupId")]
+    pub user_group_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(has_many = "super::antenna::Entity")]
+    Antenna,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+    #[sea_orm(
+        belongs_to = "super::user_group::Entity",
+        from = "Column::UserGroupId",
+        to = "super::user_group::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    UserGroup,
+}
+
+impl Related<super::antenna::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Antenna.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl Related<super::user_group::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserGroup.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_ip.rs b/packages/backend/native-utils/crates/model/src/entity/user_ip.rs
new file mode 100644
index 000000000..d108b29fe
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/user_ip.rs
@@ -0,0 +1,21 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "user_ip")]
+pub struct Model {
+    #[sea_orm(primary_key)]
+    pub id: i32,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    pub ip: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_keypair.rs b/packages/backend/native-utils/crates/model/src/entity/user_keypair.rs
new file mode 100644
index 000000000..a9a07b1d9
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/user_keypair.rs
@@ -0,0 +1,35 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "user_keypair")]
+pub struct Model {
+    #[sea_orm(column_name = "userId", primary_key, auto_increment = false, unique)]
+    pub user_id: String,
+    #[sea_orm(column_name = "publicKey")]
+    pub public_key: String,
+    #[sea_orm(column_name = "privateKey")]
+    pub private_key: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_list.rs b/packages/backend/native-utils/crates/model/src/entity/user_list.rs
new file mode 100644
index 000000000..6a2f93241
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/user_list.rs
@@ -0,0 +1,52 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "user_list")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    pub name: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(has_many = "super::antenna::Entity")]
+    Antenna,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+    #[sea_orm(has_many = "super::user_list_joining::Entity")]
+    UserListJoining,
+}
+
+impl Related<super::antenna::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Antenna.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl Related<super::user_list_joining::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserListJoining.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_list_joining.rs b/packages/backend/native-utils/crates/model/src/entity/user_list_joining.rs
new file mode 100644
index 000000000..fddcfdb54
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/user_list_joining.rs
@@ -0,0 +1,51 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "user_list_joining")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "userListId")]
+    pub user_list_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+    #[sea_orm(
+        belongs_to = "super::user_list::Entity",
+        from = "Column::UserListId",
+        to = "super::user_list::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    UserList,
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl Related<super::user_list::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::UserList.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_note_pining.rs b/packages/backend/native-utils/crates/model/src/entity/user_note_pining.rs
new file mode 100644
index 000000000..24e976e22
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/user_note_pining.rs
@@ -0,0 +1,51 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "user_note_pining")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "noteId")]
+    pub note_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::note::Entity",
+        from = "Column::NoteId",
+        to = "super::note::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    Note,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::note::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Note.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_pending.rs b/packages/backend/native-utils/crates/model/src/entity/user_pending.rs
new file mode 100644
index 000000000..e9134d9ee
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/user_pending.rs
@@ -0,0 +1,22 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "user_pending")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    pub code: String,
+    pub username: String,
+    pub email: String,
+    pub password: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_profile.rs b/packages/backend/native-utils/crates/model/src/entity/user_profile.rs
new file mode 100644
index 000000000..d22dcbebf
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/user_profile.rs
@@ -0,0 +1,111 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use super::sea_orm_active_enums::UserProfileFfvisibilityEnum;
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "user_profile")]
+pub struct Model {
+    #[sea_orm(column_name = "userId", primary_key, auto_increment = false, unique)]
+    pub user_id: String,
+    pub location: Option<String>,
+    pub birthday: Option<String>,
+    pub description: Option<String>,
+    #[sea_orm(column_type = "JsonBinary")]
+    pub fields: Json,
+    pub url: Option<String>,
+    pub email: Option<String>,
+    #[sea_orm(column_name = "emailVerifyCode")]
+    pub email_verify_code: Option<String>,
+    #[sea_orm(column_name = "emailVerified")]
+    pub email_verified: bool,
+    #[sea_orm(column_name = "twoFactorTempSecret")]
+    pub two_factor_temp_secret: Option<String>,
+    #[sea_orm(column_name = "twoFactorSecret")]
+    pub two_factor_secret: Option<String>,
+    #[sea_orm(column_name = "twoFactorEnabled")]
+    pub two_factor_enabled: bool,
+    pub password: Option<String>,
+    #[sea_orm(column_name = "clientData", column_type = "JsonBinary")]
+    pub client_data: Json,
+    #[sea_orm(column_name = "autoAcceptFollowed")]
+    pub auto_accept_followed: bool,
+    #[sea_orm(column_name = "alwaysMarkNsfw")]
+    pub always_mark_nsfw: bool,
+    #[sea_orm(column_name = "carefulBot")]
+    pub careful_bot: bool,
+    #[sea_orm(column_name = "userHost")]
+    pub user_host: Option<String>,
+    #[sea_orm(column_name = "securityKeysAvailable")]
+    pub security_keys_available: bool,
+    #[sea_orm(column_name = "usePasswordLessLogin")]
+    pub use_password_less_login: bool,
+    #[sea_orm(column_name = "pinnedPageId", unique)]
+    pub pinned_page_id: Option<String>,
+    #[sea_orm(column_type = "JsonBinary")]
+    pub room: Json,
+    #[sea_orm(column_type = "JsonBinary")]
+    pub integrations: Json,
+    #[sea_orm(column_name = "injectFeaturedNote")]
+    pub inject_featured_note: bool,
+    #[sea_orm(column_name = "enableWordMute")]
+    pub enable_word_mute: bool,
+    #[sea_orm(column_name = "mutedWords", column_type = "JsonBinary")]
+    pub muted_words: Json,
+    #[sea_orm(column_name = "mutingNotificationTypes")]
+    pub muting_notification_types: Vec<String>,
+    #[sea_orm(column_name = "noCrawle")]
+    pub no_crawle: bool,
+    #[sea_orm(column_name = "receiveAnnouncementEmail")]
+    pub receive_announcement_email: bool,
+    #[sea_orm(column_name = "emailNotificationTypes", column_type = "JsonBinary")]
+    pub email_notification_types: Json,
+    pub lang: Option<String>,
+    #[sea_orm(column_name = "mutedInstances", column_type = "JsonBinary")]
+    pub muted_instances: Json,
+    #[sea_orm(column_name = "publicReactions")]
+    pub public_reactions: bool,
+    #[sea_orm(column_name = "ffVisibility")]
+    pub ff_visibility: UserProfileFfvisibilityEnum,
+    #[sea_orm(column_name = "autoSensitive")]
+    pub auto_sensitive: bool,
+    #[sea_orm(column_name = "moderationNote")]
+    pub moderation_note: String,
+    #[sea_orm(column_name = "preventAiLearning")]
+    pub prevent_ai_learning: bool,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::page::Entity",
+        from = "Column::PinnedPageId",
+        to = "super::page::Column::Id",
+        on_update = "NoAction",
+        on_delete = "SetNull"
+    )]
+    Page,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::page::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Page.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_publickey.rs b/packages/backend/native-utils/crates/model/src/entity/user_publickey.rs
new file mode 100644
index 000000000..5cf6857e2
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/user_publickey.rs
@@ -0,0 +1,35 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "user_publickey")]
+pub struct Model {
+    #[sea_orm(column_name = "userId", primary_key, auto_increment = false, unique)]
+    pub user_id: String,
+    #[sea_orm(column_name = "keyId")]
+    pub key_id: String,
+    #[sea_orm(column_name = "keyPem")]
+    pub key_pem: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_security_key.rs b/packages/backend/native-utils/crates/model/src/entity/user_security_key.rs
new file mode 100644
index 000000000..531b59ab8
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/user_security_key.rs
@@ -0,0 +1,38 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "user_security_key")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    #[sea_orm(column_name = "publicKey")]
+    pub public_key: String,
+    #[sea_orm(column_name = "lastUsed")]
+    pub last_used: DateTimeWithTimeZone,
+    pub name: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/webhook.rs b/packages/backend/native-utils/crates/model/src/entity/webhook.rs
new file mode 100644
index 000000000..39508f7fa
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/webhook.rs
@@ -0,0 +1,44 @@
+//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
+
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[sea_orm(table_name = "webhook")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: String,
+    #[sea_orm(column_name = "createdAt")]
+    pub created_at: DateTimeWithTimeZone,
+    #[sea_orm(column_name = "userId")]
+    pub user_id: String,
+    pub name: String,
+    pub on: Vec<String>,
+    pub url: String,
+    pub secret: String,
+    pub active: bool,
+    #[sea_orm(column_name = "latestSentAt")]
+    pub latest_sent_at: Option<DateTimeWithTimeZone>,
+    #[sea_orm(column_name = "latestStatus")]
+    pub latest_status: Option<i32>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Cascade"
+    )]
+    User,
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/lib.rs b/packages/backend/native-utils/crates/model/src/lib.rs
new file mode 100644
index 000000000..b14d29c34
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/lib.rs
@@ -0,0 +1,3 @@
+pub mod entity;
+pub mod repository;
+pub mod schema;
diff --git a/packages/backend/native-utils/crates/model/src/repository/abuse_user_report.rs b/packages/backend/native-utils/crates/model/src/repository/abuse_user_report.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/repository/abuse_user_report.rs
@@ -0,0 +1 @@
+
diff --git a/packages/backend/native-utils/crates/model/src/repository/mod.rs b/packages/backend/native-utils/crates/model/src/repository/mod.rs
new file mode 100644
index 000000000..f7a590081
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/repository/mod.rs
@@ -0,0 +1 @@
+pub mod abuse_user_report;
diff --git a/packages/backend/native-utils/crates/model/src/schema/antenna.rs b/packages/backend/native-utils/crates/model/src/schema/antenna.rs
new file mode 100644
index 000000000..3bffe4f5f
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/schema/antenna.rs
@@ -0,0 +1,128 @@
+use jsonschema::JSONSchema;
+use once_cell::sync::Lazy;
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use utoipa::ToSchema;
+
+use super::Schema;
+
+#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct Antenna {
+    pub id: String,
+    pub created_at: chrono::DateTime<chrono::Utc>,
+    pub name: String,
+    pub keywords: Vec<Vec<String>>,
+    pub exclude_keywords: Vec<Vec<String>>,
+    #[schema(inline)]
+    pub src: AntennaSrcEnum,
+    pub user_list_id: Option<String>,
+    pub user_group_id: Option<String>,
+    pub users: Vec<String>,
+    pub instances: Vec<String>,
+    #[serde(default)]
+    pub case_sensitive: bool,
+    #[serde(default)]
+    pub notify: bool,
+    #[serde(default)]
+    pub with_replies: bool,
+    #[serde(default)]
+    pub with_file: bool,
+    #[serde(default)]
+    pub has_unread_note: bool,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
+#[serde(rename_all = "lowercase")]
+pub enum AntennaSrcEnum {
+    Home,
+    All,
+    Users,
+    List,
+    Group,
+    Instances,
+}
+
+impl Schema<Self> for Antenna {}
+
+pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| Antenna::validator());
+
+#[cfg(test)]
+mod tests {
+    use serde_json::json;
+
+    use super::VALIDATOR;
+
+    #[test]
+    fn valid() {
+        let instance = json!({
+            "id": "9f4x0bkx1u",
+            "createdAt": "2023-05-24T06:56:14.323Z",
+            "name": "Valid Antenna",
+            "keywords": [["first", "keyword"], ["second"]],
+            "excludeKeywords": [["excluding", "keywrods"], ["from", "antenna"]],
+            "src": "users",
+            // "userListId" and "userGroupId" can be null or be omitted
+            "userListId": null,
+            "users": ["9f4yjw6m13", "9f4yk2cp6d"],
+            "instances": [],
+            // "caseSensitive", "notify", "withReplies", "withFile", and
+            // "hasUnreadNote" are false if ommited
+            "notify": false,
+            "withReplies": false,
+            "withFile": false,
+            "hasUnreadNote": false,
+        });
+
+        assert!(VALIDATOR.is_valid(&instance));
+    }
+
+    #[test]
+    fn invalid() {
+        let instance = json!({
+            // "id" is required
+            "id": null,
+            // trailing "Z" is missing
+            "createdAt": "2023-05-24T07:36:34.389",
+            // "name" is required
+            // "keywords" must be an array
+            "keywords": "invalid keyword",
+            // "excludeKeywords" is required
+            "excludeKeywords": null,
+            // "src" should be one of "home", "all", "users", "list", "group", and
+            // "instances"
+            "src": "invalid_src",
+            // "userListId" is string
+            "userListId": ["9f4ziiqfxw"],
+            // "users" must be an array of strings
+            "users": [1, "9f4ykyuza6"],
+            "instances": ["9f4ykyuybo"],
+            // "caseSensitive" is boolean
+            "caseSensitive": 0,
+            "notify": true,
+            "withReplies": true,
+            "withFile": true,
+            "hasUnreadNote": true,
+        });
+
+        let result = VALIDATOR
+            .validate(&instance)
+            .expect_err("validation must fail");
+        let mut paths: Vec<String> = result.map(|e| e.schema_path.to_string()).collect();
+        paths.sort();
+        assert_eq!(
+            paths,
+            vec![
+                "/properties/caseSensitive/type",
+                "/properties/createdAt/format",
+                "/properties/excludeKeywords/type",
+                "/properties/id/type",
+                "/properties/keywords/type",
+                "/properties/src/enum",
+                "/properties/userListId/type",
+                "/properties/users/items/type",
+                "/required"
+            ]
+        );
+    }
+}
diff --git a/packages/backend/native-utils/crates/model/src/schema/app.rs b/packages/backend/native-utils/crates/model/src/schema/app.rs
new file mode 100644
index 000000000..861bdc48a
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/schema/app.rs
@@ -0,0 +1,108 @@
+use jsonschema::JSONSchema;
+use once_cell::sync::Lazy;
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use utoipa::ToSchema;
+
+use super::Schema;
+
+#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct App {
+    pub id: String,
+    pub name: String,
+    #[schemars(url)]
+    pub callback_url: Option<String>,
+    #[schema(inline)]
+    pub permission: Vec<Permission>,
+    pub secret: Option<String>,
+    pub is_authorized: Option<bool>,
+}
+
+/// This represents `permissions` in `packages/calckey-js/src/consts.ts`.
+#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
+pub enum Permission {
+    #[serde(rename = "read:account")]
+    ReadAccount,
+    #[serde(rename = "write:account")]
+    WriteAccount,
+    #[serde(rename = "read:blocks")]
+    ReadBlocks,
+    #[serde(rename = "write:blocks")]
+    WriteBlocks,
+    #[serde(rename = "read:drive")]
+    ReadDrive,
+    #[serde(rename = "write:drive")]
+    WriteDrive,
+    #[serde(rename = "read:favorites")]
+    ReadFavorites,
+    #[serde(rename = "write:favorites")]
+    WriteFavorites,
+    #[serde(rename = "read:following")]
+    ReadFollowing,
+    #[serde(rename = "write:following")]
+    WriteFollowing,
+    #[serde(rename = "read:messaging")]
+    ReadMessaging,
+    #[serde(rename = "write:messaging")]
+    WriteMessaging,
+    #[serde(rename = "read:mutes")]
+    ReadMutes,
+    #[serde(rename = "write:mutes")]
+    WriteMutes,
+    #[serde(rename = "read:notes")]
+    ReadNotes,
+    #[serde(rename = "write:notes")]
+    WriteNotes,
+    #[serde(rename = "read:notifications")]
+    ReadNotifications,
+    #[serde(rename = "write:notifications")]
+    WriteNotifications,
+    #[serde(rename = "read:reactions")]
+    ReadReactions,
+    #[serde(rename = "write:reactions")]
+    WriteReactions,
+    #[serde(rename = "write:votes")]
+    WriteVotes,
+    #[serde(rename = "read:pages")]
+    ReadPages,
+    #[serde(rename = "write:pages")]
+    WritePages,
+    #[serde(rename = "read:page-likes")]
+    ReadPageLikes,
+    #[serde(rename = "write:page-likes")]
+    WritePageLikes,
+    #[serde(rename = "read:user-groups")]
+    ReadUserGroups,
+    #[serde(rename = "write:user-groups")]
+    WriteUserGroups,
+    #[serde(rename = "read:channels")]
+    ReadChannels,
+    #[serde(rename = "write:channels")]
+    WriteChannels,
+    #[serde(rename = "read:gallery")]
+    ReadGallery,
+    #[serde(rename = "write:gallery")]
+    WriteGallery,
+    #[serde(rename = "read:gallery-likes")]
+    ReadGalleryLikes,
+    #[serde(rename = "write:gallery-likes")]
+    WriteGalleryLikes,
+}
+
+impl Schema<Self> for App {}
+
+pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| App::validator());
+
+#[cfg(test)]
+mod tests {
+    #[test]
+    fn valid() {
+        todo!();
+    }
+
+    #[test]
+    fn invalid() {
+        todo!();
+    }
+}
diff --git a/packages/backend/native-utils/crates/model/src/schema/mod.rs b/packages/backend/native-utils/crates/model/src/schema/mod.rs
new file mode 100644
index 000000000..cc495aac1
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/schema/mod.rs
@@ -0,0 +1,21 @@
+pub mod antenna;
+pub mod app;
+
+use jsonschema::JSONSchema;
+use schemars::{schema_for, JsonSchema};
+
+/// Structs of schema defitions implement this trait in order to
+/// provide the JSON Schema validator [`jsonschema::JSONSchema`].
+trait Schema<T: JsonSchema> {
+    /// Returns the validator of [JSON Schema Draft
+    /// 7](https://json-schema.org/specification-links.html#draft-7) with the
+    /// default settings of [`schemars::gen::SchemaSettings`].
+    fn validator() -> JSONSchema {
+        let root = schema_for!(T);
+        let schema = serde_json::to_value(&root).expect("Schema definition invalid");
+        JSONSchema::options()
+            .with_draft(jsonschema::Draft::Draft7)
+            .compile(&schema)
+            .expect("Unable to compile schema")
+    }
+}
diff --git a/packages/backend/native-utils/rustfmt.toml b/packages/backend/native-utils/rustfmt.toml
deleted file mode 100644
index cab5731ed..000000000
--- a/packages/backend/native-utils/rustfmt.toml
+++ /dev/null
@@ -1,2 +0,0 @@
-tab_spaces = 2
-edition = "2021"
diff --git a/packages/backend/native-utils/src/lib.rs b/packages/backend/native-utils/src/lib.rs
index bc5b9fc7c..e13190140 100644
--- a/packages/backend/native-utils/src/lib.rs
+++ b/packages/backend/native-utils/src/lib.rs
@@ -1,2 +1 @@
-
 pub mod mastodon_api;
diff --git a/packages/backend/native-utils/src/mastodon_api.rs b/packages/backend/native-utils/src/mastodon_api.rs
index 36b4eb984..7a3ea455a 100644
--- a/packages/backend/native-utils/src/mastodon_api.rs
+++ b/packages/backend/native-utils/src/mastodon_api.rs
@@ -7,64 +7,64 @@ static CHAR_COLLECTION: &str = "0123456789abcdefghijklmnopqrstuvwxyz";
 
 #[napi]
 pub enum IdConvertType {
-  MastodonId,
-  CalckeyId,
+    MastodonId,
+    CalckeyId,
 }
 
 #[napi]
 pub fn convert_id(in_id: String, id_convert_type: IdConvertType) -> napi::Result<String> {
-  use IdConvertType::*;
-  match id_convert_type {
-    MastodonId => {
-      let mut out: i64 = 0;
-      for (i, c) in in_id.to_lowercase().chars().rev().enumerate() {
-        out += num_from_char(c)? as i64 * 36_i64.pow(i as u32);
-      }
+    use IdConvertType::*;
+    match id_convert_type {
+        MastodonId => {
+            let mut out: i64 = 0;
+            for (i, c) in in_id.to_lowercase().chars().rev().enumerate() {
+                out += num_from_char(c)? as i64 * 36_i64.pow(i as u32);
+            }
 
-      Ok(out.to_string())
-    }
-    CalckeyId => {
-      let mut input: i64 = match in_id.parse() {
-        Ok(s) => s,
-        Err(_) => {
-          return Err(Error::new(
-            Status::InvalidArg,
-            "Unable to parse ID as MasstodonId",
-          ))
+            Ok(out.to_string())
         }
-      };
-      let mut out = String::new();
+        CalckeyId => {
+            let mut input: i64 = match in_id.parse() {
+                Ok(s) => s,
+                Err(_) => {
+                    return Err(Error::new(
+                        Status::InvalidArg,
+                        "Unable to parse ID as MasstodonId",
+                    ))
+                }
+            };
+            let mut out = String::new();
 
-      while input != 0 {
-        out.insert(0, char_from_num((input % 36) as u8)?);
-        input /= 36;
-      }
+            while input != 0 {
+                out.insert(0, char_from_num((input % 36) as u8)?);
+                input /= 36;
+            }
 
-      Ok(out)
+            Ok(out)
+        }
     }
-  }
 }
 
 // -- end --
 
 #[inline(always)]
 fn num_from_char(character: char) -> napi::Result<u8> {
-  for (i, c) in CHAR_COLLECTION.chars().enumerate() {
-    if c == character {
-      return Ok(i as u8);
+    for (i, c) in CHAR_COLLECTION.chars().enumerate() {
+        if c == character {
+            return Ok(i as u8);
+        }
     }
-  }
 
-  Err(Error::new(
-    Status::InvalidArg,
-    "Invalid character in parsed base36 id",
-  ))
+    Err(Error::new(
+        Status::InvalidArg,
+        "Invalid character in parsed base36 id",
+    ))
 }
 
 #[inline(always)]
 fn char_from_num(number: u8) -> napi::Result<char> {
-  CHAR_COLLECTION
-    .chars()
-    .nth(number as usize)
-    .ok_or(Error::from_status(Status::Unknown))
+    CHAR_COLLECTION
+        .chars()
+        .nth(number as usize)
+        .ok_or(Error::from_status(Status::Unknown))
 }

From de3eb6589a21355f34187172ce7ba9498a03dfb4 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Thu, 25 May 2023 08:55:20 -0400
Subject: [PATCH 138/198] add repository trait

---
 .../native-utils/crates/database/Cargo.toml   | 15 ++++++
 .../native-utils/crates/database/src/error.rs |  9 ++++
 .../native-utils/crates/database/src/lib.rs   | 18 +++++++
 .../native-utils/crates/model/Cargo.toml      |  4 ++
 .../native-utils/crates/model/src/error.rs    |  9 ++++
 .../native-utils/crates/model/src/lib.rs      |  3 +-
 .../model/src/repository/abuse_user_report.rs |  1 -
 .../crates/model/src/repository/antenna.rs    | 47 +++++++++++++++++++
 .../crates/model/src/repository/mod.rs        | 12 ++++-
 .../crates/model/src/schema/antenna.rs        | 33 ++++++++-----
 .../crates/model/src/schema/app.rs            |  5 +-
 .../crates/model/src/schema/mod.rs            | 30 ++++++++++++
 12 files changed, 169 insertions(+), 17 deletions(-)
 create mode 100644 packages/backend/native-utils/crates/database/Cargo.toml
 create mode 100644 packages/backend/native-utils/crates/database/src/error.rs
 create mode 100644 packages/backend/native-utils/crates/database/src/lib.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/error.rs
 delete mode 100644 packages/backend/native-utils/crates/model/src/repository/abuse_user_report.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/repository/antenna.rs

diff --git a/packages/backend/native-utils/crates/database/Cargo.toml b/packages/backend/native-utils/crates/database/Cargo.toml
new file mode 100644
index 000000000..69e0c73f7
--- /dev/null
+++ b/packages/backend/native-utils/crates/database/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "database"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[features]
+default = ["napi"]
+napi = []
+
+[dependencies]
+once_cell = "1.17.1"
+sea-orm = { version = "0.11.3", features = ["sqlx-postgres", "runtime-tokio-rustls"] }
+thiserror = "1.0.40"
diff --git a/packages/backend/native-utils/crates/database/src/error.rs b/packages/backend/native-utils/crates/database/src/error.rs
new file mode 100644
index 000000000..b70964f2e
--- /dev/null
+++ b/packages/backend/native-utils/crates/database/src/error.rs
@@ -0,0 +1,9 @@
+use sea_orm::error::DbErr;
+
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+    #[error("The database connections have not been initialized yet")]
+    Uninitialized,
+    #[error("ORM error: {0}")]
+    OrmError(#[from] DbErr),
+}
diff --git a/packages/backend/native-utils/crates/database/src/lib.rs b/packages/backend/native-utils/crates/database/src/lib.rs
new file mode 100644
index 000000000..3c9db912b
--- /dev/null
+++ b/packages/backend/native-utils/crates/database/src/lib.rs
@@ -0,0 +1,18 @@
+pub mod error;
+
+use once_cell::sync::OnceCell;
+use sea_orm::{Database, DatabaseConnection};
+
+use crate::error::Error;
+
+static DB_CONN: OnceCell<DatabaseConnection> = OnceCell::new();
+
+pub async fn init_database(connection_uri: impl Into<String>) -> Result<(), Error> {
+    let conn = Database::connect(connection_uri.into()).await?;
+    DB_CONN.get_or_init(move || conn);
+    Ok(())
+}
+
+pub fn get_database() -> Result<&'static DatabaseConnection, Error> {
+    DB_CONN.get().ok_or(Error::Uninitialized)
+}
diff --git a/packages/backend/native-utils/crates/model/Cargo.toml b/packages/backend/native-utils/crates/model/Cargo.toml
index 7b492a89d..832375ec7 100644
--- a/packages/backend/native-utils/crates/model/Cargo.toml
+++ b/packages/backend/native-utils/crates/model/Cargo.toml
@@ -6,12 +6,16 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
+async-trait = "0.1.68"
 chrono = "0.4.24"
+database = { path = "../database" }
 jsonschema = "0.17.0"
 once_cell = "1.17.1"
+parse-display = "0.8.0"
 schemars = { version = "0.8.12", features = ["chrono"] }
 sea-orm = { version = "0.11.3", features = ["postgres-array", "sqlx-postgres", "runtime-tokio-rustls", "mock"] }
 serde = { version = "1.0.163", features = ["derive"] }
 serde_json = "1.0.96"
+thiserror = "1.0.40"
 tokio = { version = "1.28.1", features = ["sync"] }
 utoipa = "3.3.0"
diff --git a/packages/backend/native-utils/crates/model/src/error.rs b/packages/backend/native-utils/crates/model/src/error.rs
new file mode 100644
index 000000000..f75c0119a
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/error.rs
@@ -0,0 +1,9 @@
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+    #[error("Failed to parse string")]
+    ParseError(#[from] parse_display::ParseError),
+    #[error("Failed to get database connection")]
+    DatabaseConnectionError(#[from] database::error::Error),
+    #[error("Database operation error: {0}")]
+    DatabaseOperationError(#[from] sea_orm::DbErr),
+}
diff --git a/packages/backend/native-utils/crates/model/src/lib.rs b/packages/backend/native-utils/crates/model/src/lib.rs
index b14d29c34..61ba77a59 100644
--- a/packages/backend/native-utils/crates/model/src/lib.rs
+++ b/packages/backend/native-utils/crates/model/src/lib.rs
@@ -1,3 +1,4 @@
 pub mod entity;
-pub mod repository;
+pub mod error;
 pub mod schema;
+pub mod repository;
diff --git a/packages/backend/native-utils/crates/model/src/repository/abuse_user_report.rs b/packages/backend/native-utils/crates/model/src/repository/abuse_user_report.rs
deleted file mode 100644
index 8b1378917..000000000
--- a/packages/backend/native-utils/crates/model/src/repository/abuse_user_report.rs
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/packages/backend/native-utils/crates/model/src/repository/antenna.rs b/packages/backend/native-utils/crates/model/src/repository/antenna.rs
new file mode 100644
index 000000000..32e17e244
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/repository/antenna.rs
@@ -0,0 +1,47 @@
+use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
+use async_trait::async_trait;
+
+use crate::entity::{antenna, antenna_note, user_group_joining};
+use crate::error::Error;
+use crate::schema::{antenna::Antenna, json_to_keyword, json_to_string_list};
+
+use super::Repository;
+
+#[async_trait]
+impl Repository<Antenna> for antenna::Model {
+    async fn pack(self) -> Result<Antenna, Error> {
+        let db = database::get_database()?;
+        let has_unread_note = antenna_note::Entity::find()
+            .filter(antenna_note::Column::AntennaId.eq(self.id.to_owned()))
+            .filter(antenna_note::Column::Read.eq(false))
+            .one(db)
+            .await?
+            .is_some();
+        let user_group_joining = match self.user_group_joining_id {
+            None => None,
+            Some(id) => user_group_joining::Entity::find_by_id(id).one(db).await?,
+        };
+        let user_group_id = match user_group_joining {
+            None => None,
+            Some(m) => Some(m.user_group_id),
+        };
+
+        Ok(Antenna {
+            id: self.id,
+            created_at: self.created_at.into(),
+            name: self.name,
+            keywords: json_to_keyword(&self.keywords),
+            exclude_keywords: json_to_keyword(&self.exclude_keywords),
+            src: self.src.try_into()?,
+            user_list_id: self.user_list_id,
+            user_group_id,
+            users: self.users,
+            instances: json_to_string_list(&self.instances),
+            case_sensitive: self.case_sensitive,
+            notify: self.notify,
+            with_replies: self.with_replies,
+            with_file: self.with_file,
+            has_unread_note,
+        })
+    }
+}
diff --git a/packages/backend/native-utils/crates/model/src/repository/mod.rs b/packages/backend/native-utils/crates/model/src/repository/mod.rs
index f7a590081..b720d1d48 100644
--- a/packages/backend/native-utils/crates/model/src/repository/mod.rs
+++ b/packages/backend/native-utils/crates/model/src/repository/mod.rs
@@ -1 +1,11 @@
-pub mod abuse_user_report;
+pub mod antenna;
+
+use async_trait::async_trait;
+use schemars::JsonSchema;
+
+use crate::error::Error;
+
+#[async_trait]
+trait Repository<T: JsonSchema> {
+    async fn pack(self) -> Result<T, Error>;
+}
diff --git a/packages/backend/native-utils/crates/model/src/schema/antenna.rs b/packages/backend/native-utils/crates/model/src/schema/antenna.rs
index 3bffe4f5f..a8065d09e 100644
--- a/packages/backend/native-utils/crates/model/src/schema/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/schema/antenna.rs
@@ -1,25 +1,26 @@
 use jsonschema::JSONSchema;
 use once_cell::sync::Lazy;
+use parse_display::FromStr;
 use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
 use utoipa::ToSchema;
 
-use super::Schema;
+use super::{Keyword, Schema, StringList};
+use crate::entity::sea_orm_active_enums::AntennaSrcEnum;
 
-#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
+#[derive(Debug, JsonSchema, ToSchema)]
 #[serde(rename_all = "camelCase")]
 pub struct Antenna {
     pub id: String,
     pub created_at: chrono::DateTime<chrono::Utc>,
     pub name: String,
-    pub keywords: Vec<Vec<String>>,
-    pub exclude_keywords: Vec<Vec<String>>,
+    pub keywords: Keyword,
+    pub exclude_keywords: Keyword,
     #[schema(inline)]
-    pub src: AntennaSrcEnum,
+    pub src: AntennaSrc,
     pub user_list_id: Option<String>,
     pub user_group_id: Option<String>,
-    pub users: Vec<String>,
-    pub instances: Vec<String>,
+    pub users: StringList,
+    pub instances: StringList,
     #[serde(default)]
     pub case_sensitive: bool,
     #[serde(default)]
@@ -32,9 +33,10 @@ pub struct Antenna {
     pub has_unread_note: bool,
 }
 
-#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
+#[derive(Debug, FromStr, JsonSchema, ToSchema)]
 #[serde(rename_all = "lowercase")]
-pub enum AntennaSrcEnum {
+#[display(style = "lowercase")]
+pub enum AntennaSrc {
     Home,
     All,
     Users,
@@ -43,9 +45,18 @@ pub enum AntennaSrcEnum {
     Instances,
 }
 
-impl Schema<Self> for Antenna {}
+impl TryFrom<AntennaSrcEnum> for AntennaSrc {
+    type Error = parse_display::ParseError;
 
+    fn try_from(value: AntennaSrcEnum) -> Result<Self, Self::Error> {
+        value.to_string().parse()
+    }
+}
+
+// ---- TODO: could be macro
+impl Schema<Self> for Antenna {}
 pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| Antenna::validator());
+// ----
 
 #[cfg(test)]
 mod tests {
diff --git a/packages/backend/native-utils/crates/model/src/schema/app.rs b/packages/backend/native-utils/crates/model/src/schema/app.rs
index 861bdc48a..adc404eb9 100644
--- a/packages/backend/native-utils/crates/model/src/schema/app.rs
+++ b/packages/backend/native-utils/crates/model/src/schema/app.rs
@@ -1,12 +1,11 @@
 use jsonschema::JSONSchema;
 use once_cell::sync::Lazy;
 use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
 use utoipa::ToSchema;
 
 use super::Schema;
 
-#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
+#[derive(Debug, JsonSchema, ToSchema)]
 #[serde(rename_all = "camelCase")]
 pub struct App {
     pub id: String,
@@ -20,7 +19,7 @@ pub struct App {
 }
 
 /// This represents `permissions` in `packages/calckey-js/src/consts.ts`.
-#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
+#[derive(Debug, JsonSchema, ToSchema)]
 pub enum Permission {
     #[serde(rename = "read:account")]
     ReadAccount,
diff --git a/packages/backend/native-utils/crates/model/src/schema/mod.rs b/packages/backend/native-utils/crates/model/src/schema/mod.rs
index cc495aac1..2a0853814 100644
--- a/packages/backend/native-utils/crates/model/src/schema/mod.rs
+++ b/packages/backend/native-utils/crates/model/src/schema/mod.rs
@@ -3,6 +3,10 @@ pub mod app;
 
 use jsonschema::JSONSchema;
 use schemars::{schema_for, JsonSchema};
+use serde_json::Value;
+
+type Keyword = Vec<Vec<String>>;
+type StringList = Vec<String>;
 
 /// Structs of schema defitions implement this trait in order to
 /// provide the JSON Schema validator [`jsonschema::JSONSchema`].
@@ -19,3 +23,29 @@ trait Schema<T: JsonSchema> {
             .expect("Unable to compile schema")
     }
 }
+
+pub(crate) fn json_to_keyword(value: &Value) -> Keyword {
+    match value.as_array() {
+        None => vec![vec![]],
+        Some(or_vec) => or_vec
+            .iter()
+            .map(|and_val| match and_val.as_array() {
+                None => vec![],
+                Some(and_vec) => and_vec
+                    .iter()
+                    .map(|word| word.as_str().unwrap_or_default().to_string())
+                    .collect(),
+            })
+            .collect(),
+    }
+}
+
+pub(crate) fn json_to_string_list(value: &Value) -> StringList {
+    match value.as_array() {
+        None => vec![],
+        Some(v) => v
+            .iter()
+            .map(|s| s.as_str().unwrap_or_default().to_string())
+            .collect(),
+    }
+}

From 36436991e2de36a27633fb97cce2bdfd301f043e Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Thu, 25 May 2023 08:55:41 -0400
Subject: [PATCH 139/198] remove unused serde

---
 .../model/src/entity/abuse_user_report.rs     |  3 +--
 .../crates/model/src/entity/access_token.rs   |  3 +--
 .../crates/model/src/entity/ad.rs             |  3 +--
 .../crates/model/src/entity/announcement.rs   |  3 +--
 .../model/src/entity/announcement_read.rs     |  3 +--
 .../crates/model/src/entity/antenna.rs        |  3 +--
 .../crates/model/src/entity/antenna_note.rs   |  3 +--
 .../crates/model/src/entity/app.rs            |  3 +--
 .../model/src/entity/attestation_challenge.rs |  3 +--
 .../crates/model/src/entity/auth_session.rs   |  3 +--
 .../crates/model/src/entity/blocking.rs       |  3 +--
 .../crates/model/src/entity/channel.rs        |  3 +--
 .../model/src/entity/channel_following.rs     |  3 +--
 .../model/src/entity/channel_note_pining.rs   |  3 +--
 .../crates/model/src/entity/clip.rs           |  3 +--
 .../crates/model/src/entity/clip_note.rs      |  3 +--
 .../crates/model/src/entity/drive_file.rs     |  3 +--
 .../crates/model/src/entity/drive_folder.rs   |  3 +--
 .../crates/model/src/entity/emoji.rs          |  3 +--
 .../crates/model/src/entity/follow_request.rs |  3 +--
 .../crates/model/src/entity/following.rs      |  3 +--
 .../crates/model/src/entity/gallery_like.rs   |  3 +--
 .../crates/model/src/entity/gallery_post.rs   |  3 +--
 .../crates/model/src/entity/hashtag.rs        |  3 +--
 .../crates/model/src/entity/instance.rs       |  3 +--
 .../model/src/entity/messaging_message.rs     |  3 +--
 .../crates/model/src/entity/meta.rs           |  3 +--
 .../crates/model/src/entity/migrations.rs     |  3 +--
 .../crates/model/src/entity/moderation_log.rs |  3 +--
 .../crates/model/src/entity/muted_note.rs     |  3 +--
 .../crates/model/src/entity/muting.rs         |  3 +--
 .../crates/model/src/entity/note.rs           |  3 +--
 .../crates/model/src/entity/note_edit.rs      |  3 +--
 .../crates/model/src/entity/note_favorite.rs  |  3 +--
 .../crates/model/src/entity/note_reaction.rs  |  3 +--
 .../model/src/entity/note_thread_muting.rs    |  3 +--
 .../crates/model/src/entity/note_unread.rs    |  3 +--
 .../crates/model/src/entity/note_watching.rs  |  3 +--
 .../crates/model/src/entity/notification.rs   |  3 +--
 .../crates/model/src/entity/page.rs           |  3 +--
 .../crates/model/src/entity/page_like.rs      |  3 +--
 .../src/entity/password_reset_request.rs      |  3 +--
 .../crates/model/src/entity/poll.rs           |  3 +--
 .../crates/model/src/entity/poll_vote.rs      |  3 +--
 .../crates/model/src/entity/promo_note.rs     |  3 +--
 .../crates/model/src/entity/promo_read.rs     |  3 +--
 .../model/src/entity/registration_ticket.rs   |  3 +--
 .../crates/model/src/entity/registry_item.rs  |  3 +--
 .../crates/model/src/entity/relay.rs          |  3 +--
 .../crates/model/src/entity/renote_muting.rs  |  3 +--
 .../crates/model/src/entity/reversi_game.rs   |  3 +--
 .../model/src/entity/reversi_matching.rs      |  3 +--
 .../model/src/entity/sea_orm_active_enums.rs  | 21 +++++++++----------
 .../crates/model/src/entity/signin.rs         |  3 +--
 .../model/src/entity/sw_subscription.rs       |  3 +--
 .../crates/model/src/entity/used_username.rs  |  3 +--
 .../crates/model/src/entity/user.rs           |  3 +--
 .../crates/model/src/entity/user_group.rs     |  3 +--
 .../model/src/entity/user_group_invitation.rs |  3 +--
 .../model/src/entity/user_group_invite.rs     |  3 +--
 .../model/src/entity/user_group_joining.rs    |  3 +--
 .../crates/model/src/entity/user_ip.rs        |  3 +--
 .../crates/model/src/entity/user_keypair.rs   |  3 +--
 .../crates/model/src/entity/user_list.rs      |  3 +--
 .../model/src/entity/user_list_joining.rs     |  3 +--
 .../model/src/entity/user_note_pining.rs      |  3 +--
 .../crates/model/src/entity/user_pending.rs   |  3 +--
 .../crates/model/src/entity/user_profile.rs   |  3 +--
 .../crates/model/src/entity/user_publickey.rs |  3 +--
 .../model/src/entity/user_security_key.rs     |  3 +--
 .../crates/model/src/entity/webhook.rs        |  3 +--
 71 files changed, 80 insertions(+), 151 deletions(-)

diff --git a/packages/backend/native-utils/crates/model/src/entity/abuse_user_report.rs b/packages/backend/native-utils/crates/model/src/entity/abuse_user_report.rs
index 163a686ab..270837973 100644
--- a/packages/backend/native-utils/crates/model/src/entity/abuse_user_report.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/abuse_user_report.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "abuse_user_report")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/access_token.rs b/packages/backend/native-utils/crates/model/src/entity/access_token.rs
index ba3a43d64..fa9894414 100644
--- a/packages/backend/native-utils/crates/model/src/entity/access_token.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/access_token.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "access_token")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/ad.rs b/packages/backend/native-utils/crates/model/src/entity/ad.rs
index 4e31c7b0d..708ed69ce 100644
--- a/packages/backend/native-utils/crates/model/src/entity/ad.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/ad.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "ad")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/announcement.rs b/packages/backend/native-utils/crates/model/src/entity/announcement.rs
index 0f02a1ca9..3e9b91687 100644
--- a/packages/backend/native-utils/crates/model/src/entity/announcement.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/announcement.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "announcement")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/announcement_read.rs b/packages/backend/native-utils/crates/model/src/entity/announcement_read.rs
index ad7dcc6f2..7fc51d475 100644
--- a/packages/backend/native-utils/crates/model/src/entity/announcement_read.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/announcement_read.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "announcement_read")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/antenna.rs b/packages/backend/native-utils/crates/model/src/entity/antenna.rs
index f9c040b59..513d15e83 100644
--- a/packages/backend/native-utils/crates/model/src/entity/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/antenna.rs
@@ -2,9 +2,8 @@
 
 use super::sea_orm_active_enums::AntennaSrcEnum;
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "antenna")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/antenna_note.rs b/packages/backend/native-utils/crates/model/src/entity/antenna_note.rs
index ecf7b88f7..d4c850bfc 100644
--- a/packages/backend/native-utils/crates/model/src/entity/antenna_note.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/antenna_note.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "antenna_note")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/app.rs b/packages/backend/native-utils/crates/model/src/entity/app.rs
index bd6ae5acf..5f3d5d131 100644
--- a/packages/backend/native-utils/crates/model/src/entity/app.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/app.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "app")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/attestation_challenge.rs b/packages/backend/native-utils/crates/model/src/entity/attestation_challenge.rs
index 25da1ae57..135a4f1fb 100644
--- a/packages/backend/native-utils/crates/model/src/entity/attestation_challenge.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/attestation_challenge.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "attestation_challenge")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/auth_session.rs b/packages/backend/native-utils/crates/model/src/entity/auth_session.rs
index c3b2fab54..83aecbaa6 100644
--- a/packages/backend/native-utils/crates/model/src/entity/auth_session.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/auth_session.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "auth_session")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/blocking.rs b/packages/backend/native-utils/crates/model/src/entity/blocking.rs
index c9092e50d..4667e60c6 100644
--- a/packages/backend/native-utils/crates/model/src/entity/blocking.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/blocking.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "blocking")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/channel.rs b/packages/backend/native-utils/crates/model/src/entity/channel.rs
index b39b57f56..9132d9d4e 100644
--- a/packages/backend/native-utils/crates/model/src/entity/channel.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/channel.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "channel")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/channel_following.rs b/packages/backend/native-utils/crates/model/src/entity/channel_following.rs
index a415b6c32..bd1b16dce 100644
--- a/packages/backend/native-utils/crates/model/src/entity/channel_following.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/channel_following.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "channel_following")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/channel_note_pining.rs b/packages/backend/native-utils/crates/model/src/entity/channel_note_pining.rs
index 16f80b91d..2c9089ac4 100644
--- a/packages/backend/native-utils/crates/model/src/entity/channel_note_pining.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/channel_note_pining.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "channel_note_pining")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/clip.rs b/packages/backend/native-utils/crates/model/src/entity/clip.rs
index 6cf1ac1c8..209bd047e 100644
--- a/packages/backend/native-utils/crates/model/src/entity/clip.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/clip.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "clip")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/clip_note.rs b/packages/backend/native-utils/crates/model/src/entity/clip_note.rs
index ba7114c3b..953c5511c 100644
--- a/packages/backend/native-utils/crates/model/src/entity/clip_note.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/clip_note.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "clip_note")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/drive_file.rs b/packages/backend/native-utils/crates/model/src/entity/drive_file.rs
index 5b3d17b00..abc191ba3 100644
--- a/packages/backend/native-utils/crates/model/src/entity/drive_file.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/drive_file.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "drive_file")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/drive_folder.rs b/packages/backend/native-utils/crates/model/src/entity/drive_folder.rs
index 9756f7053..f0b716283 100644
--- a/packages/backend/native-utils/crates/model/src/entity/drive_folder.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/drive_folder.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "drive_folder")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/emoji.rs b/packages/backend/native-utils/crates/model/src/entity/emoji.rs
index 9dff7c719..a89f0b22d 100644
--- a/packages/backend/native-utils/crates/model/src/entity/emoji.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/emoji.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "emoji")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/follow_request.rs b/packages/backend/native-utils/crates/model/src/entity/follow_request.rs
index 32e31a09d..af763baa6 100644
--- a/packages/backend/native-utils/crates/model/src/entity/follow_request.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/follow_request.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "follow_request")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/following.rs b/packages/backend/native-utils/crates/model/src/entity/following.rs
index 6f339e05d..087ca270b 100644
--- a/packages/backend/native-utils/crates/model/src/entity/following.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/following.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "following")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/gallery_like.rs b/packages/backend/native-utils/crates/model/src/entity/gallery_like.rs
index 0fdbf07ef..186c92703 100644
--- a/packages/backend/native-utils/crates/model/src/entity/gallery_like.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/gallery_like.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "gallery_like")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/gallery_post.rs b/packages/backend/native-utils/crates/model/src/entity/gallery_post.rs
index a23ebbd8f..bb68eda62 100644
--- a/packages/backend/native-utils/crates/model/src/entity/gallery_post.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/gallery_post.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "gallery_post")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/hashtag.rs b/packages/backend/native-utils/crates/model/src/entity/hashtag.rs
index a83ebbf29..9a6e44623 100644
--- a/packages/backend/native-utils/crates/model/src/entity/hashtag.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/hashtag.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "hashtag")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/instance.rs b/packages/backend/native-utils/crates/model/src/entity/instance.rs
index 405648efc..3f3af2a5d 100644
--- a/packages/backend/native-utils/crates/model/src/entity/instance.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/instance.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "instance")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/messaging_message.rs b/packages/backend/native-utils/crates/model/src/entity/messaging_message.rs
index cfb896371..0fbfc1ffb 100644
--- a/packages/backend/native-utils/crates/model/src/entity/messaging_message.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/messaging_message.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "messaging_message")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/meta.rs b/packages/backend/native-utils/crates/model/src/entity/meta.rs
index 768c725aa..33ab911ba 100644
--- a/packages/backend/native-utils/crates/model/src/entity/meta.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/meta.rs
@@ -3,9 +3,8 @@
 use super::sea_orm_active_enums::MetaSensitivemediadetectionEnum;
 use super::sea_orm_active_enums::MetaSensitivemediadetectionsensitivityEnum;
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "meta")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/migrations.rs b/packages/backend/native-utils/crates/model/src/entity/migrations.rs
index c53c3fc89..c03df1180 100644
--- a/packages/backend/native-utils/crates/model/src/entity/migrations.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/migrations.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "migrations")]
 pub struct Model {
     #[sea_orm(primary_key)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/moderation_log.rs b/packages/backend/native-utils/crates/model/src/entity/moderation_log.rs
index eb49d4f15..330685392 100644
--- a/packages/backend/native-utils/crates/model/src/entity/moderation_log.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/moderation_log.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "moderation_log")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/muted_note.rs b/packages/backend/native-utils/crates/model/src/entity/muted_note.rs
index 17328a829..1740e9078 100644
--- a/packages/backend/native-utils/crates/model/src/entity/muted_note.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/muted_note.rs
@@ -2,9 +2,8 @@
 
 use super::sea_orm_active_enums::MutedNoteReasonEnum;
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "muted_note")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/muting.rs b/packages/backend/native-utils/crates/model/src/entity/muting.rs
index 60020b60b..83885034c 100644
--- a/packages/backend/native-utils/crates/model/src/entity/muting.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/muting.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "muting")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/note.rs b/packages/backend/native-utils/crates/model/src/entity/note.rs
index cc3ec289f..2e733edae 100644
--- a/packages/backend/native-utils/crates/model/src/entity/note.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/note.rs
@@ -2,9 +2,8 @@
 
 use super::sea_orm_active_enums::NoteVisibilityEnum;
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "note")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_edit.rs b/packages/backend/native-utils/crates/model/src/entity/note_edit.rs
index 7b87613dd..5e98b3da4 100644
--- a/packages/backend/native-utils/crates/model/src/entity/note_edit.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/note_edit.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "note_edit")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_favorite.rs b/packages/backend/native-utils/crates/model/src/entity/note_favorite.rs
index b5c96a9a6..42f3c400f 100644
--- a/packages/backend/native-utils/crates/model/src/entity/note_favorite.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/note_favorite.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "note_favorite")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_reaction.rs b/packages/backend/native-utils/crates/model/src/entity/note_reaction.rs
index 52526aa9f..c740d994f 100644
--- a/packages/backend/native-utils/crates/model/src/entity/note_reaction.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/note_reaction.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "note_reaction")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_thread_muting.rs b/packages/backend/native-utils/crates/model/src/entity/note_thread_muting.rs
index 572a03750..f1dbfb598 100644
--- a/packages/backend/native-utils/crates/model/src/entity/note_thread_muting.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/note_thread_muting.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "note_thread_muting")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_unread.rs b/packages/backend/native-utils/crates/model/src/entity/note_unread.rs
index 80fae4335..746815611 100644
--- a/packages/backend/native-utils/crates/model/src/entity/note_unread.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/note_unread.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "note_unread")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_watching.rs b/packages/backend/native-utils/crates/model/src/entity/note_watching.rs
index 1ff7af52e..4a87a4495 100644
--- a/packages/backend/native-utils/crates/model/src/entity/note_watching.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/note_watching.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "note_watching")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/notification.rs b/packages/backend/native-utils/crates/model/src/entity/notification.rs
index c2d9115f3..4500e59d9 100644
--- a/packages/backend/native-utils/crates/model/src/entity/notification.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/notification.rs
@@ -2,9 +2,8 @@
 
 use super::sea_orm_active_enums::NotificationTypeEnum;
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "notification")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/page.rs b/packages/backend/native-utils/crates/model/src/entity/page.rs
index 32f082daf..efb794944 100644
--- a/packages/backend/native-utils/crates/model/src/entity/page.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/page.rs
@@ -2,9 +2,8 @@
 
 use super::sea_orm_active_enums::PageVisibilityEnum;
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "page")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/page_like.rs b/packages/backend/native-utils/crates/model/src/entity/page_like.rs
index e9de78eb6..3d3d2f3ac 100644
--- a/packages/backend/native-utils/crates/model/src/entity/page_like.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/page_like.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "page_like")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/password_reset_request.rs b/packages/backend/native-utils/crates/model/src/entity/password_reset_request.rs
index 472b93db9..3b24d70d9 100644
--- a/packages/backend/native-utils/crates/model/src/entity/password_reset_request.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/password_reset_request.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "password_reset_request")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/poll.rs b/packages/backend/native-utils/crates/model/src/entity/poll.rs
index 0facabd9a..a4d9e2df1 100644
--- a/packages/backend/native-utils/crates/model/src/entity/poll.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/poll.rs
@@ -2,9 +2,8 @@
 
 use super::sea_orm_active_enums::PollNotevisibilityEnum;
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "poll")]
 pub struct Model {
     #[sea_orm(column_name = "noteId", primary_key, auto_increment = false, unique)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/poll_vote.rs b/packages/backend/native-utils/crates/model/src/entity/poll_vote.rs
index 29b6928ef..1b8b3ba1c 100644
--- a/packages/backend/native-utils/crates/model/src/entity/poll_vote.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/poll_vote.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "poll_vote")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/promo_note.rs b/packages/backend/native-utils/crates/model/src/entity/promo_note.rs
index 6e8de9866..aa5eb2f3d 100644
--- a/packages/backend/native-utils/crates/model/src/entity/promo_note.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/promo_note.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "promo_note")]
 pub struct Model {
     #[sea_orm(column_name = "noteId", primary_key, auto_increment = false, unique)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/promo_read.rs b/packages/backend/native-utils/crates/model/src/entity/promo_read.rs
index 807ad4a27..d7dcacfb8 100644
--- a/packages/backend/native-utils/crates/model/src/entity/promo_read.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/promo_read.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "promo_read")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/registration_ticket.rs b/packages/backend/native-utils/crates/model/src/entity/registration_ticket.rs
index 8d7e4ba8d..f71c87327 100644
--- a/packages/backend/native-utils/crates/model/src/entity/registration_ticket.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/registration_ticket.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "registration_ticket")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/registry_item.rs b/packages/backend/native-utils/crates/model/src/entity/registry_item.rs
index 8aa188210..6de0c740f 100644
--- a/packages/backend/native-utils/crates/model/src/entity/registry_item.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/registry_item.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "registry_item")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/relay.rs b/packages/backend/native-utils/crates/model/src/entity/relay.rs
index 731d12e8c..736b48b78 100644
--- a/packages/backend/native-utils/crates/model/src/entity/relay.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/relay.rs
@@ -2,9 +2,8 @@
 
 use super::sea_orm_active_enums::RelayStatusEnum;
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "relay")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/renote_muting.rs b/packages/backend/native-utils/crates/model/src/entity/renote_muting.rs
index b63c989a6..b5e7d38f2 100644
--- a/packages/backend/native-utils/crates/model/src/entity/renote_muting.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/renote_muting.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "renote_muting")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/reversi_game.rs b/packages/backend/native-utils/crates/model/src/entity/reversi_game.rs
index 56f83e543..1e5359280 100644
--- a/packages/backend/native-utils/crates/model/src/entity/reversi_game.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/reversi_game.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "reversi_game")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/reversi_matching.rs b/packages/backend/native-utils/crates/model/src/entity/reversi_matching.rs
index 9261b6482..aafdf13f6 100644
--- a/packages/backend/native-utils/crates/model/src/entity/reversi_matching.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/reversi_matching.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "reversi_matching")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/sea_orm_active_enums.rs b/packages/backend/native-utils/crates/model/src/entity/sea_orm_active_enums.rs
index 747cd953a..14ef7002a 100644
--- a/packages/backend/native-utils/crates/model/src/entity/sea_orm_active_enums.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/sea_orm_active_enums.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
 #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "antenna_src_enum")]
 pub enum AntennaSrcEnum {
     #[sea_orm(string_value = "all")]
@@ -19,7 +18,7 @@ pub enum AntennaSrcEnum {
     #[sea_orm(string_value = "users")]
     Users,
 }
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
 #[sea_orm(
     rs_type = "String",
     db_type = "Enum",
@@ -35,7 +34,7 @@ pub enum MetaSensitivemediadetectionEnum {
     #[sea_orm(string_value = "remote")]
     Remote,
 }
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
 #[sea_orm(
     rs_type = "String",
     db_type = "Enum",
@@ -53,7 +52,7 @@ pub enum MetaSensitivemediadetectionsensitivityEnum {
     #[sea_orm(string_value = "veryLow")]
     VeryLow,
 }
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
 #[sea_orm(
     rs_type = "String",
     db_type = "Enum",
@@ -69,7 +68,7 @@ pub enum MutedNoteReasonEnum {
     #[sea_orm(string_value = "word")]
     Word,
 }
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
 #[sea_orm(
     rs_type = "String",
     db_type = "Enum",
@@ -87,7 +86,7 @@ pub enum NoteVisibilityEnum {
     #[sea_orm(string_value = "specified")]
     Specified,
 }
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
 #[sea_orm(
     rs_type = "String",
     db_type = "Enum",
@@ -119,7 +118,7 @@ pub enum NotificationTypeEnum {
     #[sea_orm(string_value = "reply")]
     Reply,
 }
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
 #[sea_orm(
     rs_type = "String",
     db_type = "Enum",
@@ -133,7 +132,7 @@ pub enum PageVisibilityEnum {
     #[sea_orm(string_value = "specified")]
     Specified,
 }
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
 #[sea_orm(
     rs_type = "String",
     db_type = "Enum",
@@ -149,7 +148,7 @@ pub enum PollNotevisibilityEnum {
     #[sea_orm(string_value = "specified")]
     Specified,
 }
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
 #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "relay_status_enum")]
 pub enum RelayStatusEnum {
     #[sea_orm(string_value = "accepted")]
@@ -159,7 +158,7 @@ pub enum RelayStatusEnum {
     #[sea_orm(string_value = "requesting")]
     Requesting,
 }
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
 #[sea_orm(
     rs_type = "String",
     db_type = "Enum",
diff --git a/packages/backend/native-utils/crates/model/src/entity/signin.rs b/packages/backend/native-utils/crates/model/src/entity/signin.rs
index d6f730892..6220973c1 100644
--- a/packages/backend/native-utils/crates/model/src/entity/signin.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/signin.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "signin")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/sw_subscription.rs b/packages/backend/native-utils/crates/model/src/entity/sw_subscription.rs
index 0d2ac26d9..eaa332d8c 100644
--- a/packages/backend/native-utils/crates/model/src/entity/sw_subscription.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/sw_subscription.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "sw_subscription")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/used_username.rs b/packages/backend/native-utils/crates/model/src/entity/used_username.rs
index cf325cfa5..e9e8eb097 100644
--- a/packages/backend/native-utils/crates/model/src/entity/used_username.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/used_username.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "used_username")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user.rs b/packages/backend/native-utils/crates/model/src/entity/user.rs
index cd1153994..79b9fa692 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "user")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_group.rs b/packages/backend/native-utils/crates/model/src/entity/user_group.rs
index 278e9997d..680f78b9e 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_group.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_group.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "user_group")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_group_invitation.rs b/packages/backend/native-utils/crates/model/src/entity/user_group_invitation.rs
index 62538db37..5a6f6f4a1 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_group_invitation.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_group_invitation.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "user_group_invitation")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_group_invite.rs b/packages/backend/native-utils/crates/model/src/entity/user_group_invite.rs
index 7ddfa021f..786dd1f31 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_group_invite.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_group_invite.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "user_group_invite")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_group_joining.rs b/packages/backend/native-utils/crates/model/src/entity/user_group_joining.rs
index 65cb000df..2baa0b9a7 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_group_joining.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_group_joining.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "user_group_joining")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_ip.rs b/packages/backend/native-utils/crates/model/src/entity/user_ip.rs
index d108b29fe..872cfd860 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_ip.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_ip.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "user_ip")]
 pub struct Model {
     #[sea_orm(primary_key)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_keypair.rs b/packages/backend/native-utils/crates/model/src/entity/user_keypair.rs
index a9a07b1d9..df23b506b 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_keypair.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_keypair.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "user_keypair")]
 pub struct Model {
     #[sea_orm(column_name = "userId", primary_key, auto_increment = false, unique)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_list.rs b/packages/backend/native-utils/crates/model/src/entity/user_list.rs
index 6a2f93241..ff05f2c44 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_list.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_list.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "user_list")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_list_joining.rs b/packages/backend/native-utils/crates/model/src/entity/user_list_joining.rs
index fddcfdb54..27899a8c5 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_list_joining.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_list_joining.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "user_list_joining")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_note_pining.rs b/packages/backend/native-utils/crates/model/src/entity/user_note_pining.rs
index 24e976e22..bcb3ec8b0 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_note_pining.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_note_pining.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "user_note_pining")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_pending.rs b/packages/backend/native-utils/crates/model/src/entity/user_pending.rs
index e9134d9ee..1fb3b4fdc 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_pending.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_pending.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "user_pending")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_profile.rs b/packages/backend/native-utils/crates/model/src/entity/user_profile.rs
index d22dcbebf..3a12469d8 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_profile.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_profile.rs
@@ -2,9 +2,8 @@
 
 use super::sea_orm_active_enums::UserProfileFfvisibilityEnum;
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "user_profile")]
 pub struct Model {
     #[sea_orm(column_name = "userId", primary_key, auto_increment = false, unique)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_publickey.rs b/packages/backend/native-utils/crates/model/src/entity/user_publickey.rs
index 5cf6857e2..c3c6dbf1f 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_publickey.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_publickey.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "user_publickey")]
 pub struct Model {
     #[sea_orm(column_name = "userId", primary_key, auto_increment = false, unique)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_security_key.rs b/packages/backend/native-utils/crates/model/src/entity/user_security_key.rs
index 531b59ab8..cbb31e1b0 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_security_key.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_security_key.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "user_security_key")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/webhook.rs b/packages/backend/native-utils/crates/model/src/entity/webhook.rs
index 39508f7fa..952dcbadb 100644
--- a/packages/backend/native-utils/crates/model/src/entity/webhook.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/webhook.rs
@@ -1,9 +1,8 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
 use sea_orm::entity::prelude::*;
-use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "webhook")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]

From c4e41e3d327254f8e97fd8ae673b6f35d3279d9c Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Thu, 25 May 2023 08:56:57 -0400
Subject: [PATCH 140/198] format

---
 packages/backend/native-utils/crates/model/src/lib.rs           | 2 +-
 .../backend/native-utils/crates/model/src/repository/antenna.rs | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/backend/native-utils/crates/model/src/lib.rs b/packages/backend/native-utils/crates/model/src/lib.rs
index 61ba77a59..6e86ec052 100644
--- a/packages/backend/native-utils/crates/model/src/lib.rs
+++ b/packages/backend/native-utils/crates/model/src/lib.rs
@@ -1,4 +1,4 @@
 pub mod entity;
 pub mod error;
-pub mod schema;
 pub mod repository;
+pub mod schema;
diff --git a/packages/backend/native-utils/crates/model/src/repository/antenna.rs b/packages/backend/native-utils/crates/model/src/repository/antenna.rs
index 32e17e244..dc6cb5a01 100644
--- a/packages/backend/native-utils/crates/model/src/repository/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/repository/antenna.rs
@@ -1,5 +1,5 @@
-use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
 use async_trait::async_trait;
+use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
 
 use crate::entity::{antenna, antenna_note, user_group_joining};
 use crate::error::Error;

From f50c3c906b67df2bfc01567cca9e323a45b6b1a5 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Thu, 25 May 2023 09:23:26 -0400
Subject: [PATCH 141/198] remove unused feature

---
 packages/backend/native-utils/crates/database/Cargo.toml | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/packages/backend/native-utils/crates/database/Cargo.toml b/packages/backend/native-utils/crates/database/Cargo.toml
index 69e0c73f7..b88f9b324 100644
--- a/packages/backend/native-utils/crates/database/Cargo.toml
+++ b/packages/backend/native-utils/crates/database/Cargo.toml
@@ -5,10 +5,6 @@ edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
-[features]
-default = ["napi"]
-napi = []
-
 [dependencies]
 once_cell = "1.17.1"
 sea-orm = { version = "0.11.3", features = ["sqlx-postgres", "runtime-tokio-rustls"] }

From ea20db46942f3eee35e21b54327e32858e582a3c Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Thu, 25 May 2023 09:42:59 -0400
Subject: [PATCH 142/198] add mock database

---
 .../native-utils/crates/database/Cargo.toml   |  2 +-
 .../native-utils/crates/database/src/lib.rs   | 22 ++++++++++++++++---
 2 files changed, 20 insertions(+), 4 deletions(-)

diff --git a/packages/backend/native-utils/crates/database/Cargo.toml b/packages/backend/native-utils/crates/database/Cargo.toml
index b88f9b324..8498f52f2 100644
--- a/packages/backend/native-utils/crates/database/Cargo.toml
+++ b/packages/backend/native-utils/crates/database/Cargo.toml
@@ -7,5 +7,5 @@ edition = "2021"
 
 [dependencies]
 once_cell = "1.17.1"
-sea-orm = { version = "0.11.3", features = ["sqlx-postgres", "runtime-tokio-rustls"] }
+sea-orm = { version = "0.11.3", features = ["sqlx-postgres", "runtime-tokio-rustls", "mock"] }
 thiserror = "1.0.40"
diff --git a/packages/backend/native-utils/crates/database/src/lib.rs b/packages/backend/native-utils/crates/database/src/lib.rs
index 3c9db912b..e92d6bcfc 100644
--- a/packages/backend/native-utils/crates/database/src/lib.rs
+++ b/packages/backend/native-utils/crates/database/src/lib.rs
@@ -1,11 +1,13 @@
 pub mod error;
 
-use once_cell::sync::OnceCell;
-use sea_orm::{Database, DatabaseConnection};
+use once_cell::sync::{Lazy, OnceCell};
+use sea_orm::{Database, DatabaseBackend, DatabaseConnection, MockDatabase};
 
 use crate::error::Error;
 
 static DB_CONN: OnceCell<DatabaseConnection> = OnceCell::new();
+static DB_MOCK: Lazy<DatabaseConnection> =
+    Lazy::new(|| MockDatabase::new(DatabaseBackend::Postgres).into_connection());
 
 pub async fn init_database(connection_uri: impl Into<String>) -> Result<(), Error> {
     let conn = Database::connect(connection_uri.into()).await?;
@@ -14,5 +16,19 @@ pub async fn init_database(connection_uri: impl Into<String>) -> Result<(), Erro
 }
 
 pub fn get_database() -> Result<&'static DatabaseConnection, Error> {
-    DB_CONN.get().ok_or(Error::Uninitialized)
+    if cfg!(test) {
+        Ok(&DB_MOCK)
+    } else {
+        DB_CONN.get().ok_or(Error::Uninitialized)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::get_database;
+
+    #[test]
+    fn can_get_mock_without_initialization() {
+        assert!(get_database().is_ok());
+    }
 }

From 0ad9f00cacaf6290ab0d3983c8aa9067fb19fa95 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Thu, 25 May 2023 12:11:59 -0400
Subject: [PATCH 143/198] add utility crate

---
 .../native-utils/crates/database/src/lib.rs   |  4 +--
 .../native-utils/crates/util/Cargo.toml       | 11 +++++++
 .../native-utils/crates/util/src/id.rs        | 31 +++++++++++++++++++
 .../native-utils/crates/util/src/lib.rs       |  1 +
 4 files changed, 45 insertions(+), 2 deletions(-)
 create mode 100644 packages/backend/native-utils/crates/util/Cargo.toml
 create mode 100644 packages/backend/native-utils/crates/util/src/id.rs
 create mode 100644 packages/backend/native-utils/crates/util/src/lib.rs

diff --git a/packages/backend/native-utils/crates/database/src/lib.rs b/packages/backend/native-utils/crates/database/src/lib.rs
index e92d6bcfc..5506c702a 100644
--- a/packages/backend/native-utils/crates/database/src/lib.rs
+++ b/packages/backend/native-utils/crates/database/src/lib.rs
@@ -28,7 +28,7 @@ mod tests {
     use super::get_database;
 
     #[test]
-    fn can_get_mock_without_initialization() {
-        assert!(get_database().is_ok());
+    fn can_get_mock() {
+        get_database().unwrap().as_mock_connection();
     }
 }
diff --git a/packages/backend/native-utils/crates/util/Cargo.toml b/packages/backend/native-utils/crates/util/Cargo.toml
new file mode 100644
index 000000000..8010d27f4
--- /dev/null
+++ b/packages/backend/native-utils/crates/util/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "util"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+chrono = "0.4.24"
+radix_fmt = "1.0.0"
+rand = "0.8.5"
diff --git a/packages/backend/native-utils/crates/util/src/id.rs b/packages/backend/native-utils/crates/util/src/id.rs
new file mode 100644
index 000000000..3dc713668
--- /dev/null
+++ b/packages/backend/native-utils/crates/util/src/id.rs
@@ -0,0 +1,31 @@
+use chrono::{DateTime, Utc};
+use radix_fmt::radix_36;
+
+const TIME_2000: i64 = 946_684_800_000;
+
+/// FIXME: Should we continue aid, or use other (more secure and scalable) guids
+/// such as [Cuid2](https://github.com/paralleldrive/cuid2)?
+pub fn create_aid(date: DateTime<Utc>) -> String {
+    let time = date.timestamp_millis() - TIME_2000;
+    let time = if time < 0 { 0 } else { time };
+    let num: i16 = rand::random();
+    let mut noise = format!("{:0>2}", radix_36(num).to_string());
+    let noise = noise.split_off(noise.len() - 2);
+    format!("{:0>8}{}", radix_36(time).to_string(), noise,)
+}
+
+#[cfg(test)]
+mod tests {
+    use chrono::{TimeZone, Utc};
+
+    use super::create_aid;
+
+    #[test]
+    fn generate_aid() {
+        let date = Utc.with_ymd_and_hms(2023, 5, 25, 11, 49, 37).unwrap();
+        let aid = create_aid(date);
+        assert_eq!(aid.len(), 10);
+        assert!(aid.starts_with("9f6mynag"));
+        assert_ne!(create_aid(Utc::now()), create_aid(Utc::now()));
+    }
+}
diff --git a/packages/backend/native-utils/crates/util/src/lib.rs b/packages/backend/native-utils/crates/util/src/lib.rs
new file mode 100644
index 000000000..fd6bb6c43
--- /dev/null
+++ b/packages/backend/native-utils/crates/util/src/lib.rs
@@ -0,0 +1 @@
+pub mod id;

From ab58f69c311f0f49d19cbc0a875c3fbb3eadd0ff Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Thu, 25 May 2023 21:40:04 -0400
Subject: [PATCH 144/198] return mock db when mock feature is enabled

---
 .../native-utils/crates/database/Cargo.toml   |  9 ++++-
 .../native-utils/crates/database/src/error.rs |  2 +-
 .../native-utils/crates/database/src/lib.rs   | 33 ++++++++++++-------
 .../native-utils/crates/model/Cargo.toml      |  7 +++-
 .../native-utils/crates/model/src/error.rs    |  2 +-
 5 files changed, 37 insertions(+), 16 deletions(-)

diff --git a/packages/backend/native-utils/crates/database/Cargo.toml b/packages/backend/native-utils/crates/database/Cargo.toml
index 8498f52f2..e4c60623e 100644
--- a/packages/backend/native-utils/crates/database/Cargo.toml
+++ b/packages/backend/native-utils/crates/database/Cargo.toml
@@ -5,7 +5,14 @@ edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
+[features]
+mock = []
+
 [dependencies]
 once_cell = "1.17.1"
-sea-orm = { version = "0.11.3", features = ["sqlx-postgres", "runtime-tokio-rustls", "mock"] }
+sea-orm = { version = "0.11.3", features = ["sqlx-postgres", "runtime-tokio-rustls"] }
 thiserror = "1.0.40"
+tokio = { version = "1.28.1", features = ["macros"] }
+
+[dev-dependencies]
+sea-orm = { version = "0.11.3", features = ["sqlx-postgres", "sqlx-sqlite", "runtime-tokio-rustls", "mock"] }
diff --git a/packages/backend/native-utils/crates/database/src/error.rs b/packages/backend/native-utils/crates/database/src/error.rs
index b70964f2e..babdd6831 100644
--- a/packages/backend/native-utils/crates/database/src/error.rs
+++ b/packages/backend/native-utils/crates/database/src/error.rs
@@ -1,6 +1,6 @@
 use sea_orm::error::DbErr;
 
-#[derive(thiserror::Error, Debug)]
+#[derive(thiserror::Error, Debug, PartialEq, Eq)]
 pub enum Error {
     #[error("The database connections have not been initialized yet")]
     Uninitialized,
diff --git a/packages/backend/native-utils/crates/database/src/lib.rs b/packages/backend/native-utils/crates/database/src/lib.rs
index 5506c702a..756be123e 100644
--- a/packages/backend/native-utils/crates/database/src/lib.rs
+++ b/packages/backend/native-utils/crates/database/src/lib.rs
@@ -1,13 +1,15 @@
 pub mod error;
 
-use once_cell::sync::{Lazy, OnceCell};
-use sea_orm::{Database, DatabaseBackend, DatabaseConnection, MockDatabase};
+use sea_orm::{Database, DatabaseConnection};
 
 use crate::error::Error;
 
-static DB_CONN: OnceCell<DatabaseConnection> = OnceCell::new();
-static DB_MOCK: Lazy<DatabaseConnection> =
-    Lazy::new(|| MockDatabase::new(DatabaseBackend::Postgres).into_connection());
+static DB_CONN: once_cell::sync::OnceCell<DatabaseConnection> = once_cell::sync::OnceCell::new();
+
+#[cfg(feature = "mock")]
+static DB_MOCK: once_cell::sync::Lazy<DatabaseConnection> = once_cell::sync::Lazy::new(|| {
+    sea_orm::MockDatabase::new(sea_orm::DatabaseBackend::Postgres).into_connection()
+});
 
 pub async fn init_database(connection_uri: impl Into<String>) -> Result<(), Error> {
     let conn = Database::connect(connection_uri.into()).await?;
@@ -16,19 +18,26 @@ pub async fn init_database(connection_uri: impl Into<String>) -> Result<(), Erro
 }
 
 pub fn get_database() -> Result<&'static DatabaseConnection, Error> {
-    if cfg!(test) {
-        Ok(&DB_MOCK)
-    } else {
-        DB_CONN.get().ok_or(Error::Uninitialized)
-    }
+    #[cfg(feature = "mock")]
+    return Ok(&DB_MOCK);
+    #[cfg(not(feature = "mock"))]
+    DB_CONN.get().ok_or(Error::Uninitialized)
 }
 
 #[cfg(test)]
 mod tests {
     use super::get_database;
+    use crate::{error::Error, init_database};
 
     #[test]
-    fn can_get_mock() {
-        get_database().unwrap().as_mock_connection();
+    fn error_uninitialized() {
+        assert_eq!(get_database().unwrap_err(), Error::Uninitialized);
+    }
+
+    #[tokio::test]
+    async fn connect_in_memory_sqlite() -> Result<(), Error> {
+        init_database("sqlite::memory:").await?;
+        get_database()?;
+        Ok(())
     }
 }
diff --git a/packages/backend/native-utils/crates/model/Cargo.toml b/packages/backend/native-utils/crates/model/Cargo.toml
index 832375ec7..8714afdb4 100644
--- a/packages/backend/native-utils/crates/model/Cargo.toml
+++ b/packages/backend/native-utils/crates/model/Cargo.toml
@@ -13,9 +13,14 @@ jsonschema = "0.17.0"
 once_cell = "1.17.1"
 parse-display = "0.8.0"
 schemars = { version = "0.8.12", features = ["chrono"] }
-sea-orm = { version = "0.11.3", features = ["postgres-array", "sqlx-postgres", "runtime-tokio-rustls", "mock"] }
+sea-orm = { version = "0.11.3", features = ["postgres-array", "sqlx-postgres", "runtime-tokio-rustls"] }
 serde = { version = "1.0.163", features = ["derive"] }
 serde_json = "1.0.96"
 thiserror = "1.0.40"
 tokio = { version = "1.28.1", features = ["sync"] }
+util = { path = "../util" }
 utoipa = "3.3.0"
+
+[dev-dependencies]
+database = { path = "../database", features = ["mock"] }
+sea-orm = { version = "0.11.3", features = ["postgres-array", "sqlx-postgres", "runtime-tokio-rustls", "mock"] }
diff --git a/packages/backend/native-utils/crates/model/src/error.rs b/packages/backend/native-utils/crates/model/src/error.rs
index f75c0119a..379ecb784 100644
--- a/packages/backend/native-utils/crates/model/src/error.rs
+++ b/packages/backend/native-utils/crates/model/src/error.rs
@@ -1,4 +1,4 @@
-#[derive(thiserror::Error, Debug)]
+#[derive(thiserror::Error, Debug, PartialEq, Eq)]
 pub enum Error {
     #[error("Failed to parse string")]
     ParseError(#[from] parse_display::ParseError),

From 3b6692cdf09a73056a1637b5b1020a9d99d215f6 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Thu, 25 May 2023 21:49:50 -0400
Subject: [PATCH 145/198] fix features

---
 packages/backend/native-utils/crates/database/Cargo.toml | 4 ++--
 packages/backend/native-utils/crates/model/Cargo.toml    | 1 -
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/packages/backend/native-utils/crates/database/Cargo.toml b/packages/backend/native-utils/crates/database/Cargo.toml
index e4c60623e..4ff4da2b7 100644
--- a/packages/backend/native-utils/crates/database/Cargo.toml
+++ b/packages/backend/native-utils/crates/database/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [features]
-mock = []
+mock = ["sea-orm/mock"]
 
 [dependencies]
 once_cell = "1.17.1"
@@ -15,4 +15,4 @@ thiserror = "1.0.40"
 tokio = { version = "1.28.1", features = ["macros"] }
 
 [dev-dependencies]
-sea-orm = { version = "0.11.3", features = ["sqlx-postgres", "sqlx-sqlite", "runtime-tokio-rustls", "mock"] }
+sea-orm = { version = "0.11.3", features = ["sqlx-sqlite"] }
diff --git a/packages/backend/native-utils/crates/model/Cargo.toml b/packages/backend/native-utils/crates/model/Cargo.toml
index 8714afdb4..63f53c630 100644
--- a/packages/backend/native-utils/crates/model/Cargo.toml
+++ b/packages/backend/native-utils/crates/model/Cargo.toml
@@ -23,4 +23,3 @@ utoipa = "3.3.0"
 
 [dev-dependencies]
 database = { path = "../database", features = ["mock"] }
-sea-orm = { version = "0.11.3", features = ["postgres-array", "sqlx-postgres", "runtime-tokio-rustls", "mock"] }

From 1601b8985c618c091ef9dcd4d96ab797547f8ccc Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 26 May 2023 04:00:09 -0400
Subject: [PATCH 146/198] change aid to cuid2

---
 .../native-utils/crates/util/Cargo.toml       |  6 +--
 .../native-utils/crates/util/src/id.rs        | 48 +++++++++++--------
 2 files changed, 31 insertions(+), 23 deletions(-)

diff --git a/packages/backend/native-utils/crates/util/Cargo.toml b/packages/backend/native-utils/crates/util/Cargo.toml
index 8010d27f4..3c28e248f 100644
--- a/packages/backend/native-utils/crates/util/Cargo.toml
+++ b/packages/backend/native-utils/crates/util/Cargo.toml
@@ -6,6 +6,6 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-chrono = "0.4.24"
-radix_fmt = "1.0.0"
-rand = "0.8.5"
+cuid2 = "0.1.0"
+once_cell = "1.17.1"
+thiserror = "1.0.40"
diff --git a/packages/backend/native-utils/crates/util/src/id.rs b/packages/backend/native-utils/crates/util/src/id.rs
index 3dc713668..f82b64652 100644
--- a/packages/backend/native-utils/crates/util/src/id.rs
+++ b/packages/backend/native-utils/crates/util/src/id.rs
@@ -1,31 +1,39 @@
-use chrono::{DateTime, Utc};
-use radix_fmt::radix_36;
+//! ID generation utility based on [cuid2]
 
-const TIME_2000: i64 = 946_684_800_000;
+use cuid2::CuidConstructor;
+use once_cell::sync::OnceCell;
 
-/// FIXME: Should we continue aid, or use other (more secure and scalable) guids
-/// such as [Cuid2](https://github.com/paralleldrive/cuid2)?
-pub fn create_aid(date: DateTime<Utc>) -> String {
-    let time = date.timestamp_millis() - TIME_2000;
-    let time = if time < 0 { 0 } else { time };
-    let num: i16 = rand::random();
-    let mut noise = format!("{:0>2}", radix_36(num).to_string());
-    let noise = noise.split_off(noise.len() - 2);
-    format!("{:0>8}{}", radix_36(time).to_string(), noise,)
+#[derive(thiserror::Error, Debug, PartialEq, Eq)]
+#[error("ID generator has not been initialized yet")]
+pub struct ErrorUninitialized;
+
+static GENERATOR: OnceCell<CuidConstructor> = OnceCell::new();
+
+pub fn init_id(length: u16) {
+    GENERATOR.get_or_init(move || CuidConstructor::new().with_length(length));
+}
+
+pub fn create_id() -> Result<String, ErrorUninitialized> {
+    match GENERATOR.get() {
+        None => Err(ErrorUninitialized),
+        Some(gen) => Ok(gen.create_id()),
+    }
 }
 
 #[cfg(test)]
 mod tests {
-    use chrono::{TimeZone, Utc};
+    use std::thread;
 
-    use super::create_aid;
+    use crate::id;
 
     #[test]
-    fn generate_aid() {
-        let date = Utc.with_ymd_and_hms(2023, 5, 25, 11, 49, 37).unwrap();
-        let aid = create_aid(date);
-        assert_eq!(aid.len(), 10);
-        assert!(aid.starts_with("9f6mynag"));
-        assert_ne!(create_aid(Utc::now()), create_aid(Utc::now()));
+    fn can_generate() {
+        assert_eq!(id::create_id(), Err(id::ErrorUninitialized));
+        id::init_id(12);
+        assert_eq!(id::create_id().unwrap().len(), 12);
+        assert_ne!(id::create_id().unwrap(), id::create_id().unwrap());
+        let id1 = thread::spawn(|| id::create_id().unwrap());
+        let id2 = thread::spawn(|| id::create_id().unwrap());
+        assert_ne!(id1.join().unwrap(), id2.join().unwrap())
     }
 }

From a42d6e2e2df7d4100ec410d3247bd6e78573c22b Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Sat, 27 May 2023 02:37:34 -0400
Subject: [PATCH 147/198] add random string generator

---
 .../native-utils/crates/util/Cargo.toml       |  1 +
 .../native-utils/crates/util/src/lib.rs       |  1 +
 .../native-utils/crates/util/src/random.rs    | 25 +++++++++++++++++++
 3 files changed, 27 insertions(+)
 create mode 100644 packages/backend/native-utils/crates/util/src/random.rs

diff --git a/packages/backend/native-utils/crates/util/Cargo.toml b/packages/backend/native-utils/crates/util/Cargo.toml
index 3c28e248f..ad5486b4b 100644
--- a/packages/backend/native-utils/crates/util/Cargo.toml
+++ b/packages/backend/native-utils/crates/util/Cargo.toml
@@ -8,4 +8,5 @@ edition = "2021"
 [dependencies]
 cuid2 = "0.1.0"
 once_cell = "1.17.1"
+rand = "0.8.5"
 thiserror = "1.0.40"
diff --git a/packages/backend/native-utils/crates/util/src/lib.rs b/packages/backend/native-utils/crates/util/src/lib.rs
index fd6bb6c43..1be5a7fd1 100644
--- a/packages/backend/native-utils/crates/util/src/lib.rs
+++ b/packages/backend/native-utils/crates/util/src/lib.rs
@@ -1 +1,2 @@
 pub mod id;
+pub mod random;
diff --git a/packages/backend/native-utils/crates/util/src/random.rs b/packages/backend/native-utils/crates/util/src/random.rs
new file mode 100644
index 000000000..fb2f02147
--- /dev/null
+++ b/packages/backend/native-utils/crates/util/src/random.rs
@@ -0,0 +1,25 @@
+use rand::{distributions::Alphanumeric, thread_rng, Rng};
+
+pub fn gen_string(length: u16) -> String {
+    thread_rng()
+        .sample_iter(Alphanumeric)
+        .take(length.into())
+        .map(char::from)
+        .collect()
+}
+
+#[cfg(test)]
+mod tests {
+    use std::thread;
+
+    use super::gen_string;
+
+    #[test]
+    fn can_generate_string() {
+        assert_eq!(gen_string(16).len(), 16);
+        assert_ne!(gen_string(16), gen_string(16));
+        let s1 = thread::spawn(|| gen_string(16));
+        let s2 = thread::spawn(|| gen_string(16));
+        assert_ne!(s1.join().unwrap(), s2.join().unwrap());
+    }
+}

From 9c832d00f9e42e6e3a8581d99b0fc2fd00042951 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Sat, 27 May 2023 03:35:09 -0400
Subject: [PATCH 148/198] remove mock database

---
 packages/backend/native-utils/crates/database/Cargo.toml | 6 ------
 packages/backend/native-utils/crates/database/src/lib.rs | 8 --------
 packages/backend/native-utils/crates/model/Cargo.toml    | 3 ---
 3 files changed, 17 deletions(-)

diff --git a/packages/backend/native-utils/crates/database/Cargo.toml b/packages/backend/native-utils/crates/database/Cargo.toml
index 4ff4da2b7..440a76cf5 100644
--- a/packages/backend/native-utils/crates/database/Cargo.toml
+++ b/packages/backend/native-utils/crates/database/Cargo.toml
@@ -5,14 +5,8 @@ edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
-[features]
-mock = ["sea-orm/mock"]
-
 [dependencies]
 once_cell = "1.17.1"
 sea-orm = { version = "0.11.3", features = ["sqlx-postgres", "runtime-tokio-rustls"] }
 thiserror = "1.0.40"
 tokio = { version = "1.28.1", features = ["macros"] }
-
-[dev-dependencies]
-sea-orm = { version = "0.11.3", features = ["sqlx-sqlite"] }
diff --git a/packages/backend/native-utils/crates/database/src/lib.rs b/packages/backend/native-utils/crates/database/src/lib.rs
index 756be123e..d0e7b2577 100644
--- a/packages/backend/native-utils/crates/database/src/lib.rs
+++ b/packages/backend/native-utils/crates/database/src/lib.rs
@@ -6,11 +6,6 @@ use crate::error::Error;
 
 static DB_CONN: once_cell::sync::OnceCell<DatabaseConnection> = once_cell::sync::OnceCell::new();
 
-#[cfg(feature = "mock")]
-static DB_MOCK: once_cell::sync::Lazy<DatabaseConnection> = once_cell::sync::Lazy::new(|| {
-    sea_orm::MockDatabase::new(sea_orm::DatabaseBackend::Postgres).into_connection()
-});
-
 pub async fn init_database(connection_uri: impl Into<String>) -> Result<(), Error> {
     let conn = Database::connect(connection_uri.into()).await?;
     DB_CONN.get_or_init(move || conn);
@@ -18,9 +13,6 @@ pub async fn init_database(connection_uri: impl Into<String>) -> Result<(), Erro
 }
 
 pub fn get_database() -> Result<&'static DatabaseConnection, Error> {
-    #[cfg(feature = "mock")]
-    return Ok(&DB_MOCK);
-    #[cfg(not(feature = "mock"))]
     DB_CONN.get().ok_or(Error::Uninitialized)
 }
 
diff --git a/packages/backend/native-utils/crates/model/Cargo.toml b/packages/backend/native-utils/crates/model/Cargo.toml
index 63f53c630..87f712aa7 100644
--- a/packages/backend/native-utils/crates/model/Cargo.toml
+++ b/packages/backend/native-utils/crates/model/Cargo.toml
@@ -20,6 +20,3 @@ thiserror = "1.0.40"
 tokio = { version = "1.28.1", features = ["sync"] }
 util = { path = "../util" }
 utoipa = "3.3.0"
-
-[dev-dependencies]
-database = { path = "../database", features = ["mock"] }

From ba95b61b7fc90e9780322335d3b5c4407a9ed2b7 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Sat, 27 May 2023 05:50:07 -0400
Subject: [PATCH 149/198] add integration test in model

---
 .../native-utils/crates/database/src/lib.rs   |  4 +-
 .../crates/model/src/repository/mod.rs        |  2 +-
 .../crates/model/src/schema/antenna.rs        | 21 +++--
 .../crates/model/src/schema/app.rs            |  8 +-
 .../native-utils/crates/model/tests/common.rs | 88 +++++++++++++++++++
 .../crates/model/tests/repository/antenna.rs  | 60 +++++++++++++
 .../crates/model/tests/repository/mod.rs      |  1 +
 .../native-utils/crates/util/src/id.rs        |  2 +-
 .../native-utils/crates/util/src/random.rs    |  2 +-
 9 files changed, 173 insertions(+), 15 deletions(-)
 create mode 100644 packages/backend/native-utils/crates/model/tests/common.rs
 create mode 100644 packages/backend/native-utils/crates/model/tests/repository/antenna.rs
 create mode 100644 packages/backend/native-utils/crates/model/tests/repository/mod.rs

diff --git a/packages/backend/native-utils/crates/database/src/lib.rs b/packages/backend/native-utils/crates/database/src/lib.rs
index d0e7b2577..36d4a532a 100644
--- a/packages/backend/native-utils/crates/database/src/lib.rs
+++ b/packages/backend/native-utils/crates/database/src/lib.rs
@@ -22,12 +22,12 @@ mod tests {
     use crate::{error::Error, init_database};
 
     #[test]
-    fn error_uninitialized() {
+    fn unit_lib_error_uninitialized() {
         assert_eq!(get_database().unwrap_err(), Error::Uninitialized);
     }
 
     #[tokio::test]
-    async fn connect_in_memory_sqlite() -> Result<(), Error> {
+    async fn unit_lib_connect_in_memory_sqlite() -> Result<(), Error> {
         init_database("sqlite::memory:").await?;
         get_database()?;
         Ok(())
diff --git a/packages/backend/native-utils/crates/model/src/repository/mod.rs b/packages/backend/native-utils/crates/model/src/repository/mod.rs
index b720d1d48..01e11412d 100644
--- a/packages/backend/native-utils/crates/model/src/repository/mod.rs
+++ b/packages/backend/native-utils/crates/model/src/repository/mod.rs
@@ -6,6 +6,6 @@ use schemars::JsonSchema;
 use crate::error::Error;
 
 #[async_trait]
-trait Repository<T: JsonSchema> {
+pub trait Repository<T: JsonSchema> {
     async fn pack(self) -> Result<T, Error>;
 }
diff --git a/packages/backend/native-utils/crates/model/src/schema/antenna.rs b/packages/backend/native-utils/crates/model/src/schema/antenna.rs
index a8065d09e..3f4c40ccb 100644
--- a/packages/backend/native-utils/crates/model/src/schema/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/schema/antenna.rs
@@ -7,7 +7,7 @@ use utoipa::ToSchema;
 use super::{Keyword, Schema, StringList};
 use crate::entity::sea_orm_active_enums::AntennaSrcEnum;
 
-#[derive(Debug, JsonSchema, ToSchema)]
+#[derive(Clone, Debug, PartialEq, Eq, JsonSchema, ToSchema)]
 #[serde(rename_all = "camelCase")]
 pub struct Antenna {
     pub id: String,
@@ -33,9 +33,10 @@ pub struct Antenna {
     pub has_unread_note: bool,
 }
 
-#[derive(Debug, FromStr, JsonSchema, ToSchema)]
-#[serde(rename_all = "lowercase")]
-#[display(style = "lowercase")]
+#[derive(Clone, Debug, FromStr, PartialEq, Eq, JsonSchema, ToSchema)]
+#[serde(rename_all = "camelCase")]
+#[display(style = "camelCase")]
+#[display("'{}'")]
 pub enum AntennaSrc {
     Home,
     All,
@@ -62,10 +63,18 @@ pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| Antenna::validator());
 mod tests {
     use serde_json::json;
 
+    use crate::{entity::sea_orm_active_enums::AntennaSrcEnum, schema::antenna::AntennaSrc};
+
     use super::VALIDATOR;
 
     #[test]
-    fn valid() {
+    fn unit_schema_src_from_active_enum() {
+        let src = AntennaSrc::try_from(AntennaSrcEnum::All).unwrap();
+        assert_eq!(src, AntennaSrc::All);
+    }
+
+    #[test]
+    fn unit_schema_antenna_valid() {
         let instance = json!({
             "id": "9f4x0bkx1u",
             "createdAt": "2023-05-24T06:56:14.323Z",
@@ -89,7 +98,7 @@ mod tests {
     }
 
     #[test]
-    fn invalid() {
+    fn unit_schema_antenna_invalid() {
         let instance = json!({
             // "id" is required
             "id": null,
diff --git a/packages/backend/native-utils/crates/model/src/schema/app.rs b/packages/backend/native-utils/crates/model/src/schema/app.rs
index adc404eb9..0b18ebeda 100644
--- a/packages/backend/native-utils/crates/model/src/schema/app.rs
+++ b/packages/backend/native-utils/crates/model/src/schema/app.rs
@@ -5,7 +5,7 @@ use utoipa::ToSchema;
 
 use super::Schema;
 
-#[derive(Debug, JsonSchema, ToSchema)]
+#[derive(Clone, Debug, PartialEq, Eq, JsonSchema, ToSchema)]
 #[serde(rename_all = "camelCase")]
 pub struct App {
     pub id: String,
@@ -19,7 +19,7 @@ pub struct App {
 }
 
 /// This represents `permissions` in `packages/calckey-js/src/consts.ts`.
-#[derive(Debug, JsonSchema, ToSchema)]
+#[derive(Clone, Debug, PartialEq, Eq, JsonSchema, ToSchema)]
 pub enum Permission {
     #[serde(rename = "read:account")]
     ReadAccount,
@@ -96,12 +96,12 @@ pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| App::validator());
 #[cfg(test)]
 mod tests {
     #[test]
-    fn valid() {
+    fn unit_schema_app_valid() {
         todo!();
     }
 
     #[test]
-    fn invalid() {
+    fn unit_shcmea_app_invalid() {
         todo!();
     }
 }
diff --git a/packages/backend/native-utils/crates/model/tests/common.rs b/packages/backend/native-utils/crates/model/tests/common.rs
new file mode 100644
index 000000000..9fd8815d3
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/tests/common.rs
@@ -0,0 +1,88 @@
+extern crate model;
+
+mod repository;
+
+use chrono::Utc;
+use model::entity::{antenna, sea_orm_active_enums::AntennaSrcEnum, user};
+use sea_orm::{
+    ActiveModelTrait, ActiveValue::Set, DatabaseConnection, DbErr, EntityTrait, TransactionTrait,
+};
+use serde_json::json;
+use std::env;
+use util::{
+    id::{create_id, init_id},
+    random::gen_string,
+};
+
+/// Insert predefined entries in the database.
+async fn prepare() {
+    let conn_uri = env::var("DATABASE_URL")
+        .unwrap_or("postgres://calckey:calckey@localhost/calckey".to_string());
+    database::init_database(conn_uri)
+        .await
+        .expect("Unable to initialize database connection");
+    let db = database::get_database().expect("Unable to get database connection from pool");
+    setup_model(db).await;
+}
+
+/// Delete all entries in the database.
+async fn cleanup() {
+    let db = database::get_database().unwrap();
+    db.transaction::<_, (), DbErr>(|txn| {
+        Box::pin(async move {
+            user::Entity::delete_many().exec(txn).await.unwrap();
+            antenna::Entity::delete_many().exec(txn).await.unwrap();
+
+            Ok(())
+        })
+    })
+    .await
+    .expect("Unable to delete predefined models");
+}
+
+async fn setup_model(db: &DatabaseConnection) {
+    init_id(12);
+
+    db.transaction::<_, (), DbErr>(|txn| {
+        Box::pin(async move {
+            let user_id = create_id().unwrap();
+            let name = "Alice";
+            let user_model = user::ActiveModel {
+                id: Set(user_id.to_owned()),
+                created_at: Set(Utc::now().into()),
+                username: Set(name.to_lowercase().to_string()),
+                username_lower: Set(name.to_lowercase().to_string()),
+                name: Set(Some(name.to_string())),
+                token: Set(Some(gen_string(16))),
+                is_admin: Set(true),
+                ..Default::default()
+            };
+            user_model.insert(txn).await?;
+            let antenna_model = antenna::ActiveModel {
+                id: Set(create_id().unwrap()),
+                created_at: Set(Utc::now().into()),
+                user_id: Set(user_id.to_owned()),
+                name: Set("Test Antenna".to_string()),
+                src: Set(AntennaSrcEnum::All),
+                keywords: Set(json!([["foo", "bar"], ["foobar"]])),
+                exclude_keywords: Set(json!([["abc"], ["def", "ghi"]])),
+                with_file: Set(false),
+                notify: Set(true),
+                case_sensitive: Set(true),
+                with_replies: Set(false),
+                ..Default::default()
+            };
+            antenna_model.insert(txn).await?;
+
+            Ok(())
+        })
+    })
+    .await
+    .expect("Unable to setup predefined models");
+}
+
+#[tokio::test]
+async fn inte_common_prepare_and_cleanup() {
+    prepare().await;
+    cleanup().await;
+}
diff --git a/packages/backend/native-utils/crates/model/tests/repository/antenna.rs b/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
new file mode 100644
index 000000000..42fd3b4bd
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
@@ -0,0 +1,60 @@
+use model::{
+    entity::{antenna, user},
+    repository::Repository,
+    schema,
+};
+use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
+
+use crate::{cleanup, prepare};
+
+#[tokio::test]
+async fn inte_repository_antenna_can_pack() {
+    prepare().await;
+
+    let db = database::get_database().unwrap();
+
+    let alice_antenna = user::Entity::find()
+        .filter(user::Column::Username.eq("alice"))
+        .find_also_related(antenna::Entity)
+        .one(db)
+        .await
+        .unwrap()
+        .expect("alice not found")
+        .1
+        .expect("alice's antenna not found");
+
+    let packed = alice_antenna
+        .to_owned()
+        .pack()
+        .await
+        .expect("Unable to pack");
+
+    assert_eq!(
+        packed,
+        schema::antenna::Antenna {
+            id: alice_antenna.id,
+            created_at: alice_antenna.created_at.into(),
+            name: "Test Antenna".to_string(),
+            keywords: vec![
+                vec!["foo".to_string(), "bar".to_string()],
+                vec!["foobar".to_string()]
+            ],
+            exclude_keywords: vec![
+                vec!["abc".to_string()],
+                vec!["def".to_string(), "ghi".to_string()]
+            ],
+            src: schema::antenna::AntennaSrc::All,
+            user_list_id: None,
+            user_group_id: None,
+            users: vec![],
+            instances: vec![],
+            case_sensitive: true,
+            notify: true,
+            with_replies: false,
+            with_file: false,
+            has_unread_note: false,
+        }
+    );
+
+    cleanup().await;
+}
diff --git a/packages/backend/native-utils/crates/model/tests/repository/mod.rs b/packages/backend/native-utils/crates/model/tests/repository/mod.rs
new file mode 100644
index 000000000..c11ef7687
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/tests/repository/mod.rs
@@ -0,0 +1 @@
+mod antenna;
diff --git a/packages/backend/native-utils/crates/util/src/id.rs b/packages/backend/native-utils/crates/util/src/id.rs
index f82b64652..94f20a847 100644
--- a/packages/backend/native-utils/crates/util/src/id.rs
+++ b/packages/backend/native-utils/crates/util/src/id.rs
@@ -27,7 +27,7 @@ mod tests {
     use crate::id;
 
     #[test]
-    fn can_generate() {
+    fn unit_id_can_generate() {
         assert_eq!(id::create_id(), Err(id::ErrorUninitialized));
         id::init_id(12);
         assert_eq!(id::create_id().unwrap().len(), 12);
diff --git a/packages/backend/native-utils/crates/util/src/random.rs b/packages/backend/native-utils/crates/util/src/random.rs
index fb2f02147..c197298c7 100644
--- a/packages/backend/native-utils/crates/util/src/random.rs
+++ b/packages/backend/native-utils/crates/util/src/random.rs
@@ -15,7 +15,7 @@ mod tests {
     use super::gen_string;
 
     #[test]
-    fn can_generate_string() {
+    fn unit_random_can_generate_string() {
         assert_eq!(gen_string(16).len(), 16);
         assert_ne!(gen_string(16), gen_string(16));
         let s1 = thread::spawn(|| gen_string(16));

From 4e4280e02eb5a3fcd0d692919af7168349af2308 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Sat, 27 May 2023 06:28:27 -0400
Subject: [PATCH 150/198] add tests

---
 packages/backend/native-utils/crates/database/src/lib.rs | 9 +--------
 packages/backend/native-utils/package.json               | 4 +++-
 2 files changed, 4 insertions(+), 9 deletions(-)

diff --git a/packages/backend/native-utils/crates/database/src/lib.rs b/packages/backend/native-utils/crates/database/src/lib.rs
index 36d4a532a..28c0a6ed4 100644
--- a/packages/backend/native-utils/crates/database/src/lib.rs
+++ b/packages/backend/native-utils/crates/database/src/lib.rs
@@ -19,17 +19,10 @@ pub fn get_database() -> Result<&'static DatabaseConnection, Error> {
 #[cfg(test)]
 mod tests {
     use super::get_database;
-    use crate::{error::Error, init_database};
+    use crate::error::Error;
 
     #[test]
     fn unit_lib_error_uninitialized() {
         assert_eq!(get_database().unwrap_err(), Error::Uninitialized);
     }
-
-    #[tokio::test]
-    async fn unit_lib_connect_in_memory_sqlite() -> Result<(), Error> {
-        init_database("sqlite::memory:").await?;
-        get_database()?;
-        Ok(())
-    }
 }
diff --git a/packages/backend/native-utils/package.json b/packages/backend/native-utils/package.json
index 787d1bd89..c82826d69 100644
--- a/packages/backend/native-utils/package.json
+++ b/packages/backend/native-utils/package.json
@@ -39,6 +39,8 @@
     "prepublishOnly": "napi prepublish -t npm",
     "test": "ava",
     "universal": "napi universal",
-    "version": "napi version"
+    "version": "napi version",
+    "cargo:unit": "cargo test --workspace unit",
+    "cargo:integration": "cargo test --workspace inte -- --test-threads=1"
   }
 }

From f851bc8f406b56d6393eb2a0bab5aaef657c3038 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Sat, 27 May 2023 06:52:15 -0400
Subject: [PATCH 151/198] rename test modules

---
 .../native-utils/crates/database/src/lib.rs   |   4 +-
 .../crates/model/src/schema/antenna.rs        |   8 +-
 .../crates/model/src/schema/app.rs            |   6 +-
 .../native-utils/crates/model/tests/common.rs |  12 +-
 .../crates/model/tests/repository/antenna.rs  | 106 +++++++++---------
 .../native-utils/crates/util/src/id.rs        |   4 +-
 .../native-utils/crates/util/src/random.rs    |   4 +-
 packages/backend/native-utils/package.json    |   4 +-
 8 files changed, 77 insertions(+), 71 deletions(-)

diff --git a/packages/backend/native-utils/crates/database/src/lib.rs b/packages/backend/native-utils/crates/database/src/lib.rs
index 28c0a6ed4..6907a1281 100644
--- a/packages/backend/native-utils/crates/database/src/lib.rs
+++ b/packages/backend/native-utils/crates/database/src/lib.rs
@@ -17,12 +17,12 @@ pub fn get_database() -> Result<&'static DatabaseConnection, Error> {
 }
 
 #[cfg(test)]
-mod tests {
+mod unit_test {
     use super::get_database;
     use crate::error::Error;
 
     #[test]
-    fn unit_lib_error_uninitialized() {
+    fn error_uninitialized() {
         assert_eq!(get_database().unwrap_err(), Error::Uninitialized);
     }
 }
diff --git a/packages/backend/native-utils/crates/model/src/schema/antenna.rs b/packages/backend/native-utils/crates/model/src/schema/antenna.rs
index 3f4c40ccb..d5f19a96f 100644
--- a/packages/backend/native-utils/crates/model/src/schema/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/schema/antenna.rs
@@ -60,7 +60,7 @@ pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| Antenna::validator());
 // ----
 
 #[cfg(test)]
-mod tests {
+mod unit_test {
     use serde_json::json;
 
     use crate::{entity::sea_orm_active_enums::AntennaSrcEnum, schema::antenna::AntennaSrc};
@@ -68,13 +68,13 @@ mod tests {
     use super::VALIDATOR;
 
     #[test]
-    fn unit_schema_src_from_active_enum() {
+    fn src_from_active_enum() {
         let src = AntennaSrc::try_from(AntennaSrcEnum::All).unwrap();
         assert_eq!(src, AntennaSrc::All);
     }
 
     #[test]
-    fn unit_schema_antenna_valid() {
+    fn antenna_valid() {
         let instance = json!({
             "id": "9f4x0bkx1u",
             "createdAt": "2023-05-24T06:56:14.323Z",
@@ -98,7 +98,7 @@ mod tests {
     }
 
     #[test]
-    fn unit_schema_antenna_invalid() {
+    fn antenna_invalid() {
         let instance = json!({
             // "id" is required
             "id": null,
diff --git a/packages/backend/native-utils/crates/model/src/schema/app.rs b/packages/backend/native-utils/crates/model/src/schema/app.rs
index 0b18ebeda..b45cad6d6 100644
--- a/packages/backend/native-utils/crates/model/src/schema/app.rs
+++ b/packages/backend/native-utils/crates/model/src/schema/app.rs
@@ -94,14 +94,14 @@ impl Schema<Self> for App {}
 pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| App::validator());
 
 #[cfg(test)]
-mod tests {
+mod unit_test {
     #[test]
-    fn unit_schema_app_valid() {
+    fn valid() {
         todo!();
     }
 
     #[test]
-    fn unit_shcmea_app_invalid() {
+    fn invalid() {
         todo!();
     }
 }
diff --git a/packages/backend/native-utils/crates/model/tests/common.rs b/packages/backend/native-utils/crates/model/tests/common.rs
index 9fd8815d3..38c616071 100644
--- a/packages/backend/native-utils/crates/model/tests/common.rs
+++ b/packages/backend/native-utils/crates/model/tests/common.rs
@@ -81,8 +81,12 @@ async fn setup_model(db: &DatabaseConnection) {
     .expect("Unable to setup predefined models");
 }
 
-#[tokio::test]
-async fn inte_common_prepare_and_cleanup() {
-    prepare().await;
-    cleanup().await;
+mod it_test {
+    use super::{cleanup, prepare};
+
+    #[tokio::test]
+    async fn can_prepare_and_cleanup() {
+        prepare().await;
+        cleanup().await;
+    }
 }
diff --git a/packages/backend/native-utils/crates/model/tests/repository/antenna.rs b/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
index 42fd3b4bd..7f3aac74d 100644
--- a/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
+++ b/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
@@ -1,60 +1,62 @@
-use model::{
-    entity::{antenna, user},
-    repository::Repository,
-    schema,
-};
-use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
+mod it_test {
+    use model::{
+        entity::{antenna, user},
+        repository::Repository,
+        schema,
+    };
+    use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
 
-use crate::{cleanup, prepare};
+    use crate::{cleanup, prepare};
 
-#[tokio::test]
-async fn inte_repository_antenna_can_pack() {
-    prepare().await;
+    #[tokio::test]
+    async fn can_pack() {
+        prepare().await;
 
-    let db = database::get_database().unwrap();
+        let db = database::get_database().unwrap();
 
-    let alice_antenna = user::Entity::find()
-        .filter(user::Column::Username.eq("alice"))
-        .find_also_related(antenna::Entity)
-        .one(db)
-        .await
-        .unwrap()
-        .expect("alice not found")
-        .1
-        .expect("alice's antenna not found");
+        let alice_antenna = user::Entity::find()
+            .filter(user::Column::Username.eq("alice"))
+            .find_also_related(antenna::Entity)
+            .one(db)
+            .await
+            .unwrap()
+            .expect("alice not found")
+            .1
+            .expect("alice's antenna not found");
 
-    let packed = alice_antenna
-        .to_owned()
-        .pack()
-        .await
-        .expect("Unable to pack");
+        let packed = alice_antenna
+            .to_owned()
+            .pack()
+            .await
+            .expect("Unable to pack");
 
-    assert_eq!(
-        packed,
-        schema::antenna::Antenna {
-            id: alice_antenna.id,
-            created_at: alice_antenna.created_at.into(),
-            name: "Test Antenna".to_string(),
-            keywords: vec![
-                vec!["foo".to_string(), "bar".to_string()],
-                vec!["foobar".to_string()]
-            ],
-            exclude_keywords: vec![
-                vec!["abc".to_string()],
-                vec!["def".to_string(), "ghi".to_string()]
-            ],
-            src: schema::antenna::AntennaSrc::All,
-            user_list_id: None,
-            user_group_id: None,
-            users: vec![],
-            instances: vec![],
-            case_sensitive: true,
-            notify: true,
-            with_replies: false,
-            with_file: false,
-            has_unread_note: false,
-        }
-    );
+        assert_eq!(
+            packed,
+            schema::antenna::Antenna {
+                id: alice_antenna.id,
+                created_at: alice_antenna.created_at.into(),
+                name: "Test Antenna".to_string(),
+                keywords: vec![
+                    vec!["foo".to_string(), "bar".to_string()],
+                    vec!["foobar".to_string()]
+                ],
+                exclude_keywords: vec![
+                    vec!["abc".to_string()],
+                    vec!["def".to_string(), "ghi".to_string()]
+                ],
+                src: schema::antenna::AntennaSrc::All,
+                user_list_id: None,
+                user_group_id: None,
+                users: vec![],
+                instances: vec![],
+                case_sensitive: true,
+                notify: true,
+                with_replies: false,
+                with_file: false,
+                has_unread_note: false,
+            }
+        );
 
-    cleanup().await;
+        cleanup().await;
+    }
 }
diff --git a/packages/backend/native-utils/crates/util/src/id.rs b/packages/backend/native-utils/crates/util/src/id.rs
index 94f20a847..2831ef79d 100644
--- a/packages/backend/native-utils/crates/util/src/id.rs
+++ b/packages/backend/native-utils/crates/util/src/id.rs
@@ -21,13 +21,13 @@ pub fn create_id() -> Result<String, ErrorUninitialized> {
 }
 
 #[cfg(test)]
-mod tests {
+mod unit_test {
     use std::thread;
 
     use crate::id;
 
     #[test]
-    fn unit_id_can_generate() {
+    fn can_generate_unique_ids() {
         assert_eq!(id::create_id(), Err(id::ErrorUninitialized));
         id::init_id(12);
         assert_eq!(id::create_id().unwrap().len(), 12);
diff --git a/packages/backend/native-utils/crates/util/src/random.rs b/packages/backend/native-utils/crates/util/src/random.rs
index c197298c7..1c4a20d50 100644
--- a/packages/backend/native-utils/crates/util/src/random.rs
+++ b/packages/backend/native-utils/crates/util/src/random.rs
@@ -9,13 +9,13 @@ pub fn gen_string(length: u16) -> String {
 }
 
 #[cfg(test)]
-mod tests {
+mod unit_test {
     use std::thread;
 
     use super::gen_string;
 
     #[test]
-    fn unit_random_can_generate_string() {
+    fn can_generate_unique_strings() {
         assert_eq!(gen_string(16).len(), 16);
         assert_ne!(gen_string(16), gen_string(16));
         let s1 = thread::spawn(|| gen_string(16));
diff --git a/packages/backend/native-utils/package.json b/packages/backend/native-utils/package.json
index c82826d69..60f14260d 100644
--- a/packages/backend/native-utils/package.json
+++ b/packages/backend/native-utils/package.json
@@ -40,7 +40,7 @@
     "test": "ava",
     "universal": "napi universal",
     "version": "napi version",
-    "cargo:unit": "cargo test --workspace unit",
-    "cargo:integration": "cargo test --workspace inte -- --test-threads=1"
+    "cargo:unit": "cargo test --workspace unit_test",
+    "cargo:integration": "cargo test --workspace it_test -- --test-threads=1"
   }
 }

From bc69e2df8733ba6d33926023e89ad6084517952b Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Sat, 27 May 2023 07:02:10 -0400
Subject: [PATCH 152/198] todo unread note check

---
 .../native-utils/crates/model/tests/repository/antenna.rs   | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/packages/backend/native-utils/crates/model/tests/repository/antenna.rs b/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
index 7f3aac74d..a7e490733 100644
--- a/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
+++ b/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
@@ -11,7 +11,6 @@ mod it_test {
     #[tokio::test]
     async fn can_pack() {
         prepare().await;
-
         let db = database::get_database().unwrap();
 
         let alice_antenna = user::Entity::find()
@@ -59,4 +58,9 @@ mod it_test {
 
         cleanup().await;
     }
+
+    #[tokio::test]
+    async fn unread_note() {
+        todo!();
+    }
 }

From 29e914c9c3aaa4e4bf88c7bb6767a8a9811f6849 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Wed, 31 May 2023 12:09:30 -0400
Subject: [PATCH 153/198] add newtype

---
 .../native-utils/crates/model/Cargo.toml      |  1 +
 .../crates/model/src/entity/antenna.rs        | 10 ++--
 .../crates/model/src/entity/mod.rs            |  1 +
 .../crates/model/src/entity/newtype/macros.rs | 51 +++++++++++++++++++
 .../crates/model/src/entity/newtype/mod.rs    | 17 +++++++
 .../crates/model/src/repository/antenna.rs    |  8 +--
 .../crates/model/src/schema/antenna.rs        | 12 ++---
 .../crates/model/src/schema/mod.rs            | 30 -----------
 .../native-utils/crates/model/tests/common.rs | 13 +++--
 .../crates/model/tests/repository/antenna.rs  | 10 ++--
 10 files changed, 101 insertions(+), 52 deletions(-)
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/newtype/macros.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs

diff --git a/packages/backend/native-utils/crates/model/Cargo.toml b/packages/backend/native-utils/crates/model/Cargo.toml
index 87f712aa7..47d2e1553 100644
--- a/packages/backend/native-utils/crates/model/Cargo.toml
+++ b/packages/backend/native-utils/crates/model/Cargo.toml
@@ -9,6 +9,7 @@ edition = "2021"
 async-trait = "0.1.68"
 chrono = "0.4.24"
 database = { path = "../database" }
+derive_more = "0.99.17"
 jsonschema = "0.17.0"
 once_cell = "1.17.1"
 parse-display = "0.8.0"
diff --git a/packages/backend/native-utils/crates/model/src/entity/antenna.rs b/packages/backend/native-utils/crates/model/src/entity/antenna.rs
index 513d15e83..5e514576b 100644
--- a/packages/backend/native-utils/crates/model/src/entity/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/antenna.rs
@@ -1,6 +1,6 @@
 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
 
-use super::sea_orm_active_enums::AntennaSrcEnum;
+use super::{newtype, sea_orm_active_enums::AntennaSrcEnum};
 use sea_orm::entity::prelude::*;
 
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
@@ -17,7 +17,7 @@ pub struct Model {
     #[sea_orm(column_name = "userListId")]
     pub user_list_id: Option<String>,
     #[sea_orm(column_type = "JsonBinary")]
-    pub keywords: Json,
+    pub keywords: newtype::Keyword,
     #[sea_orm(column_name = "withFile")]
     pub with_file: bool,
     pub expression: Option<String>,
@@ -28,11 +28,11 @@ pub struct Model {
     pub with_replies: bool,
     #[sea_orm(column_name = "userGroupJoiningId")]
     pub user_group_joining_id: Option<String>,
-    pub users: Vec<String>,
+    pub users: newtype::StringVec,
     #[sea_orm(column_name = "excludeKeywords", column_type = "JsonBinary")]
-    pub exclude_keywords: Json,
+    pub exclude_keywords: newtype::Keyword,
     #[sea_orm(column_type = "JsonBinary")]
-    pub instances: Json,
+    pub instances: newtype::StringVec,
 }
 
 #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/mod.rs b/packages/backend/native-utils/crates/model/src/entity/mod.rs
index 077f9fa6e..6105b0555 100644
--- a/packages/backend/native-utils/crates/model/src/entity/mod.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/mod.rs
@@ -33,6 +33,7 @@ pub mod migrations;
 pub mod moderation_log;
 pub mod muted_note;
 pub mod muting;
+pub mod newtype;
 pub mod note;
 pub mod note_edit;
 pub mod note_favorite;
diff --git a/packages/backend/native-utils/crates/model/src/entity/newtype/macros.rs b/packages/backend/native-utils/crates/model/src/entity/newtype/macros.rs
new file mode 100644
index 000000000..0266e80c8
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/newtype/macros.rs
@@ -0,0 +1,51 @@
+#[macro_export]
+macro_rules! impl_json_newtype {
+    ($a:tt) => {
+        impl From<$a> for Value {
+            fn from(source: $a) -> Self {
+                Value::Json(serde_json::to_value(source).ok().map(Box::new))
+            }
+        }
+
+        impl TryGetable for $a {
+            fn try_get_by<I: sea_orm::ColIdx>(
+                res: &QueryResult,
+                idx: I,
+            ) -> Result<Self, TryGetError> {
+                let json_value: serde_json::Value =
+                    res.try_get_by(idx).map_err(TryGetError::DbErr)?;
+                serde_json::from_value(json_value)
+                    .map_err(|e| TryGetError::DbErr(DbErr::Json(e.to_string())))
+            }
+        }
+
+        impl sea_query::ValueType for $a {
+            fn try_from(v: Value) -> Result<Self, sea_query::ValueTypeErr> {
+                match v {
+                    Value::Json(Some(x)) => Ok($a(
+                        serde_json::from_value(*x).map_err(|_| sea_query::ValueTypeErr)?
+                    )),
+                    _ => Err(sea_query::ValueTypeErr),
+                }
+            }
+
+            fn type_name() -> String {
+                stringify!($a).to_owned()
+            }
+
+            fn array_type() -> sea_orm::sea_query::ArrayType {
+                sea_orm::sea_query::ArrayType::Json
+            }
+
+            fn column_type() -> sea_query::ColumnType {
+                sea_query::ColumnType::Json
+            }
+        }
+
+        impl sea_query::Nullable for $a {
+            fn null() -> Value {
+                Value::Json(None)
+            }
+        }
+    };
+}
diff --git a/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs b/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
new file mode 100644
index 000000000..714f3dafb
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
@@ -0,0 +1,17 @@
+mod macros;
+
+use derive_more::From;
+use schemars::JsonSchema;
+use sea_orm::{sea_query, DbErr, QueryResult, TryGetError, TryGetable, Value};
+use serde::{Deserialize, Serialize};
+
+use crate::impl_json_newtype;
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, From)]
+pub struct Keyword(pub Vec<Vec<String>>);
+impl_json_newtype!(Keyword);
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, From)]
+
+pub struct StringVec(pub Vec<String>);
+impl_json_newtype!(StringVec);
diff --git a/packages/backend/native-utils/crates/model/src/repository/antenna.rs b/packages/backend/native-utils/crates/model/src/repository/antenna.rs
index dc6cb5a01..9a6882b59 100644
--- a/packages/backend/native-utils/crates/model/src/repository/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/repository/antenna.rs
@@ -3,7 +3,7 @@ use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
 
 use crate::entity::{antenna, antenna_note, user_group_joining};
 use crate::error::Error;
-use crate::schema::{antenna::Antenna, json_to_keyword, json_to_string_list};
+use crate::schema::antenna::Antenna;
 
 use super::Repository;
 
@@ -30,13 +30,13 @@ impl Repository<Antenna> for antenna::Model {
             id: self.id,
             created_at: self.created_at.into(),
             name: self.name,
-            keywords: json_to_keyword(&self.keywords),
-            exclude_keywords: json_to_keyword(&self.exclude_keywords),
+            keywords: self.keywords,
+            exclude_keywords: self.exclude_keywords,
             src: self.src.try_into()?,
             user_list_id: self.user_list_id,
             user_group_id,
             users: self.users,
-            instances: json_to_string_list(&self.instances),
+            instances: self.instances,
             case_sensitive: self.case_sensitive,
             notify: self.notify,
             with_replies: self.with_replies,
diff --git a/packages/backend/native-utils/crates/model/src/schema/antenna.rs b/packages/backend/native-utils/crates/model/src/schema/antenna.rs
index d5f19a96f..035dca344 100644
--- a/packages/backend/native-utils/crates/model/src/schema/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/schema/antenna.rs
@@ -4,8 +4,8 @@ use parse_display::FromStr;
 use schemars::JsonSchema;
 use utoipa::ToSchema;
 
-use super::{Keyword, Schema, StringList};
-use crate::entity::sea_orm_active_enums::AntennaSrcEnum;
+use super::Schema;
+use crate::entity::{newtype, sea_orm_active_enums::AntennaSrcEnum};
 
 #[derive(Clone, Debug, PartialEq, Eq, JsonSchema, ToSchema)]
 #[serde(rename_all = "camelCase")]
@@ -13,14 +13,14 @@ pub struct Antenna {
     pub id: String,
     pub created_at: chrono::DateTime<chrono::Utc>,
     pub name: String,
-    pub keywords: Keyword,
-    pub exclude_keywords: Keyword,
+    pub keywords: newtype::Keyword,
+    pub exclude_keywords: newtype::Keyword,
     #[schema(inline)]
     pub src: AntennaSrc,
     pub user_list_id: Option<String>,
     pub user_group_id: Option<String>,
-    pub users: StringList,
-    pub instances: StringList,
+    pub users: newtype::StringVec,
+    pub instances: newtype::StringVec,
     #[serde(default)]
     pub case_sensitive: bool,
     #[serde(default)]
diff --git a/packages/backend/native-utils/crates/model/src/schema/mod.rs b/packages/backend/native-utils/crates/model/src/schema/mod.rs
index 2a0853814..cc495aac1 100644
--- a/packages/backend/native-utils/crates/model/src/schema/mod.rs
+++ b/packages/backend/native-utils/crates/model/src/schema/mod.rs
@@ -3,10 +3,6 @@ pub mod app;
 
 use jsonschema::JSONSchema;
 use schemars::{schema_for, JsonSchema};
-use serde_json::Value;
-
-type Keyword = Vec<Vec<String>>;
-type StringList = Vec<String>;
 
 /// Structs of schema defitions implement this trait in order to
 /// provide the JSON Schema validator [`jsonschema::JSONSchema`].
@@ -23,29 +19,3 @@ trait Schema<T: JsonSchema> {
             .expect("Unable to compile schema")
     }
 }
-
-pub(crate) fn json_to_keyword(value: &Value) -> Keyword {
-    match value.as_array() {
-        None => vec![vec![]],
-        Some(or_vec) => or_vec
-            .iter()
-            .map(|and_val| match and_val.as_array() {
-                None => vec![],
-                Some(and_vec) => and_vec
-                    .iter()
-                    .map(|word| word.as_str().unwrap_or_default().to_string())
-                    .collect(),
-            })
-            .collect(),
-    }
-}
-
-pub(crate) fn json_to_string_list(value: &Value) -> StringList {
-    match value.as_array() {
-        None => vec![],
-        Some(v) => v
-            .iter()
-            .map(|s| s.as_str().unwrap_or_default().to_string())
-            .collect(),
-    }
-}
diff --git a/packages/backend/native-utils/crates/model/tests/common.rs b/packages/backend/native-utils/crates/model/tests/common.rs
index 38c616071..6a54ca414 100644
--- a/packages/backend/native-utils/crates/model/tests/common.rs
+++ b/packages/backend/native-utils/crates/model/tests/common.rs
@@ -7,7 +7,6 @@ use model::entity::{antenna, sea_orm_active_enums::AntennaSrcEnum, user};
 use sea_orm::{
     ActiveModelTrait, ActiveValue::Set, DatabaseConnection, DbErr, EntityTrait, TransactionTrait,
 };
-use serde_json::json;
 use std::env;
 use util::{
     id::{create_id, init_id},
@@ -64,8 +63,16 @@ async fn setup_model(db: &DatabaseConnection) {
                 user_id: Set(user_id.to_owned()),
                 name: Set("Test Antenna".to_string()),
                 src: Set(AntennaSrcEnum::All),
-                keywords: Set(json!([["foo", "bar"], ["foobar"]])),
-                exclude_keywords: Set(json!([["abc"], ["def", "ghi"]])),
+                keywords: Set(vec![
+                    vec!["foo".to_string(), "bar".to_string()],
+                    vec!["foobar".to_string()],
+                ]
+                .into()),
+                exclude_keywords: Set(vec![
+                    vec!["abc".to_string()],
+                    vec!["def".to_string(), "ghi".to_string()],
+                ]
+                .into()),
                 with_file: Set(false),
                 notify: Set(true),
                 case_sensitive: Set(true),
diff --git a/packages/backend/native-utils/crates/model/tests/repository/antenna.rs b/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
index a7e490733..73ddc6505 100644
--- a/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
+++ b/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
@@ -38,16 +38,18 @@ mod it_test {
                 keywords: vec![
                     vec!["foo".to_string(), "bar".to_string()],
                     vec!["foobar".to_string()]
-                ],
+                ]
+                .into(),
                 exclude_keywords: vec![
                     vec!["abc".to_string()],
                     vec!["def".to_string(), "ghi".to_string()]
-                ],
+                ]
+                .into(),
                 src: schema::antenna::AntennaSrc::All,
                 user_list_id: None,
                 user_group_id: None,
-                users: vec![],
-                instances: vec![],
+                users: vec![].into(),
+                instances: vec![].into(),
                 case_sensitive: true,
                 notify: true,
                 with_replies: false,

From 516d5460f0009eb61890e64554b6ea6c99da3eb3 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Wed, 31 May 2023 12:24:02 -0400
Subject: [PATCH 154/198] fix varchar array

---
 .../native-utils/crates/model/src/entity/antenna.rs       | 8 ++++----
 .../native-utils/crates/model/src/entity/newtype/mod.rs   | 8 ++++----
 .../native-utils/crates/model/src/schema/antenna.rs       | 8 ++++----
 .../backend/native-utils/crates/model/tests/common.rs     | 2 +-
 .../native-utils/crates/model/tests/repository/antenna.rs | 2 +-
 packages/backend/native-utils/package.json                | 2 +-
 6 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/packages/backend/native-utils/crates/model/src/entity/antenna.rs b/packages/backend/native-utils/crates/model/src/entity/antenna.rs
index 5e514576b..ccff0aab4 100644
--- a/packages/backend/native-utils/crates/model/src/entity/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/antenna.rs
@@ -17,7 +17,7 @@ pub struct Model {
     #[sea_orm(column_name = "userListId")]
     pub user_list_id: Option<String>,
     #[sea_orm(column_type = "JsonBinary")]
-    pub keywords: newtype::Keyword,
+    pub keywords: newtype::JsonKeyword,
     #[sea_orm(column_name = "withFile")]
     pub with_file: bool,
     pub expression: Option<String>,
@@ -28,11 +28,11 @@ pub struct Model {
     pub with_replies: bool,
     #[sea_orm(column_name = "userGroupJoiningId")]
     pub user_group_joining_id: Option<String>,
-    pub users: newtype::StringVec,
+    pub users: Vec<String>,
     #[sea_orm(column_name = "excludeKeywords", column_type = "JsonBinary")]
-    pub exclude_keywords: newtype::Keyword,
+    pub exclude_keywords: newtype::JsonKeyword,
     #[sea_orm(column_type = "JsonBinary")]
-    pub instances: newtype::StringVec,
+    pub instances: newtype::JsonStringVec,
 }
 
 #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs b/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
index 714f3dafb..f7e3c3d43 100644
--- a/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
@@ -8,10 +8,10 @@ use serde::{Deserialize, Serialize};
 use crate::impl_json_newtype;
 
 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, From)]
-pub struct Keyword(pub Vec<Vec<String>>);
-impl_json_newtype!(Keyword);
+pub struct JsonKeyword(pub Vec<Vec<String>>);
+impl_json_newtype!(JsonKeyword);
 
 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, From)]
 
-pub struct StringVec(pub Vec<String>);
-impl_json_newtype!(StringVec);
+pub struct JsonStringVec(pub Vec<String>);
+impl_json_newtype!(JsonStringVec);
diff --git a/packages/backend/native-utils/crates/model/src/schema/antenna.rs b/packages/backend/native-utils/crates/model/src/schema/antenna.rs
index 035dca344..4a71982f7 100644
--- a/packages/backend/native-utils/crates/model/src/schema/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/schema/antenna.rs
@@ -13,14 +13,14 @@ pub struct Antenna {
     pub id: String,
     pub created_at: chrono::DateTime<chrono::Utc>,
     pub name: String,
-    pub keywords: newtype::Keyword,
-    pub exclude_keywords: newtype::Keyword,
+    pub keywords: newtype::JsonKeyword,
+    pub exclude_keywords: newtype::JsonKeyword,
     #[schema(inline)]
     pub src: AntennaSrc,
     pub user_list_id: Option<String>,
     pub user_group_id: Option<String>,
-    pub users: newtype::StringVec,
-    pub instances: newtype::StringVec,
+    pub users: Vec<String>,
+    pub instances: newtype::JsonStringVec,
     #[serde(default)]
     pub case_sensitive: bool,
     #[serde(default)]
diff --git a/packages/backend/native-utils/crates/model/tests/common.rs b/packages/backend/native-utils/crates/model/tests/common.rs
index 6a54ca414..d459056f3 100644
--- a/packages/backend/native-utils/crates/model/tests/common.rs
+++ b/packages/backend/native-utils/crates/model/tests/common.rs
@@ -88,7 +88,7 @@ async fn setup_model(db: &DatabaseConnection) {
     .expect("Unable to setup predefined models");
 }
 
-mod it_test {
+mod int_test {
     use super::{cleanup, prepare};
 
     #[tokio::test]
diff --git a/packages/backend/native-utils/crates/model/tests/repository/antenna.rs b/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
index 73ddc6505..e07a16c07 100644
--- a/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
+++ b/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
@@ -1,4 +1,4 @@
-mod it_test {
+mod int_test {
     use model::{
         entity::{antenna, user},
         repository::Repository,
diff --git a/packages/backend/native-utils/package.json b/packages/backend/native-utils/package.json
index 60f14260d..aa194bad3 100644
--- a/packages/backend/native-utils/package.json
+++ b/packages/backend/native-utils/package.json
@@ -41,6 +41,6 @@
     "universal": "napi universal",
     "version": "napi version",
     "cargo:unit": "cargo test --workspace unit_test",
-    "cargo:integration": "cargo test --workspace it_test -- --test-threads=1"
+    "cargo:integration": "cargo test --workspace int_test -- --test-threads=1"
   }
 }

From 128a354b83c0348b1bb5fab715eeb1ebba022cf0 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Wed, 31 May 2023 18:12:59 -0400
Subject: [PATCH 155/198] add abstraction of string array type

---
 .../native-utils/crates/database/src/lib.rs   |  6 +-
 .../native-utils/crates/migration/Cargo.toml  | 25 +++++++
 .../native-utils/crates/migration/README.md   | 41 ++++++++++
 .../native-utils/crates/migration/src/lib.rs  | 12 +++
 .../src/m20230531_180824_stringvec.rs         | 74 +++++++++++++++++++
 .../native-utils/crates/migration/src/main.rs |  6 ++
 .../native-utils/crates/model/Cargo.toml      |  6 +-
 .../crates/model/src/entity/antenna.rs        |  2 +-
 .../crates/model/src/entity/newtype/macros.rs |  6 +-
 .../crates/model/src/entity/newtype/mod.rs    | 17 +++--
 .../native-utils/crates/model/src/error.rs    |  2 +-
 .../crates/model/src/repository/antenna.rs    |  8 +-
 .../crates/model/src/schema/antenna.rs        |  8 +-
 .../native-utils/crates/model/tests/common.rs | 24 +++++-
 14 files changed, 213 insertions(+), 24 deletions(-)
 create mode 100644 packages/backend/native-utils/crates/migration/Cargo.toml
 create mode 100644 packages/backend/native-utils/crates/migration/README.md
 create mode 100644 packages/backend/native-utils/crates/migration/src/lib.rs
 create mode 100644 packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs
 create mode 100644 packages/backend/native-utils/crates/migration/src/main.rs

diff --git a/packages/backend/native-utils/crates/database/src/lib.rs b/packages/backend/native-utils/crates/database/src/lib.rs
index 6907a1281..0012d2292 100644
--- a/packages/backend/native-utils/crates/database/src/lib.rs
+++ b/packages/backend/native-utils/crates/database/src/lib.rs
@@ -1,10 +1,10 @@
 pub mod error;
 
-use sea_orm::{Database, DatabaseConnection};
+use sea_orm::{Database, DbConn};
 
 use crate::error::Error;
 
-static DB_CONN: once_cell::sync::OnceCell<DatabaseConnection> = once_cell::sync::OnceCell::new();
+static DB_CONN: once_cell::sync::OnceCell<DbConn> = once_cell::sync::OnceCell::new();
 
 pub async fn init_database(connection_uri: impl Into<String>) -> Result<(), Error> {
     let conn = Database::connect(connection_uri.into()).await?;
@@ -12,7 +12,7 @@ pub async fn init_database(connection_uri: impl Into<String>) -> Result<(), Erro
     Ok(())
 }
 
-pub fn get_database() -> Result<&'static DatabaseConnection, Error> {
+pub fn get_database() -> Result<&'static DbConn, Error> {
     DB_CONN.get().ok_or(Error::Uninitialized)
 }
 
diff --git a/packages/backend/native-utils/crates/migration/Cargo.toml b/packages/backend/native-utils/crates/migration/Cargo.toml
new file mode 100644
index 000000000..46108c68c
--- /dev/null
+++ b/packages/backend/native-utils/crates/migration/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "migration"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+name = "migration"
+path = "src/lib.rs"
+
+[dependencies]
+async-std = { version = "1", features = ["attributes", "tokio1"] }
+serde_json = "1.0.96"
+model = { path = "../model" }
+
+[dependencies.sea-orm-migration]
+version = "0.11.0"
+features = [
+  # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI.
+  # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime.
+  # e.g.
+  "runtime-tokio-rustls",  # `ASYNC_RUNTIME` feature
+  "sqlx-postgres",         # `DATABASE_DRIVER` feature
+	"sqlx-sqlite",
+]
diff --git a/packages/backend/native-utils/crates/migration/README.md b/packages/backend/native-utils/crates/migration/README.md
new file mode 100644
index 000000000..b3ea53eb4
--- /dev/null
+++ b/packages/backend/native-utils/crates/migration/README.md
@@ -0,0 +1,41 @@
+# Running Migrator CLI
+
+- Generate a new migration file
+    ```sh
+    cargo run -- migrate generate MIGRATION_NAME
+    ```
+- Apply all pending migrations
+    ```sh
+    cargo run
+    ```
+    ```sh
+    cargo run -- up
+    ```
+- Apply first 10 pending migrations
+    ```sh
+    cargo run -- up -n 10
+    ```
+- Rollback last applied migrations
+    ```sh
+    cargo run -- down
+    ```
+- Rollback last 10 applied migrations
+    ```sh
+    cargo run -- down -n 10
+    ```
+- Drop all tables from the database, then reapply all migrations
+    ```sh
+    cargo run -- fresh
+    ```
+- Rollback all applied migrations, then reapply all migrations
+    ```sh
+    cargo run -- refresh
+    ```
+- Rollback all applied migrations
+    ```sh
+    cargo run -- reset
+    ```
+- Check the status of all migrations
+    ```sh
+    cargo run -- status
+    ```
diff --git a/packages/backend/native-utils/crates/migration/src/lib.rs b/packages/backend/native-utils/crates/migration/src/lib.rs
new file mode 100644
index 000000000..a2a1b932f
--- /dev/null
+++ b/packages/backend/native-utils/crates/migration/src/lib.rs
@@ -0,0 +1,12 @@
+pub use sea_orm_migration::prelude::*;
+
+mod m20230531_180824_stringvec;
+
+pub struct Migrator;
+
+#[async_trait::async_trait]
+impl MigratorTrait for Migrator {
+    fn migrations() -> Vec<Box<dyn MigrationTrait>> {
+        vec![Box::new(m20230531_180824_stringvec::Migration)]
+    }
+}
diff --git a/packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs b/packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs
new file mode 100644
index 000000000..ba1fc257d
--- /dev/null
+++ b/packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs
@@ -0,0 +1,74 @@
+use model::entity::{antenna, newtype::StringVec};
+use sea_orm_migration::{
+    prelude::*,
+    sea_orm::{DbBackend, EntityTrait, Statement},
+};
+use serde_json::json;
+
+#[derive(DeriveMigrationName)]
+pub struct Migration;
+
+#[async_trait::async_trait]
+impl MigrationTrait for Migration {
+    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
+        if manager.get_database_backend() == DbBackend::Sqlite {
+            return Ok(());
+        }
+
+        let db = manager.get_connection();
+        let query = Query::select()
+            .column(Antenna::Id)
+            .column(Antenna::Users)
+            .from(Antenna::Table)
+            .to_string(PostgresQueryBuilder);
+        let res: Vec<(String, Vec<String>)> = db
+            .query_all(Statement::from_string(DbBackend::Postgres, query))
+            .await?
+            .iter()
+            .filter_map(|r| r.try_get_many_by_index().ok())
+            .collect();
+
+        manager
+            .alter_table(
+                Table::alter()
+                    .table(Antenna::Table)
+                    .drop_column(Antenna::Users)
+                    .add_column(
+                        ColumnDef::new(Antenna::Users)
+                            .json_binary()
+                            .not_null()
+                            .default(json!([])),
+                    )
+                    .to_owned(),
+            )
+            .await?;
+
+        let models: Vec<antenna::ActiveModel> = res
+            .iter()
+            .map(|(id, users)| antenna::ActiveModel {
+                id: sea_orm::Set(id.to_owned()),
+                users: sea_orm::Set(StringVec::from(users.to_owned())),
+                ..Default::default()
+            })
+            .collect();
+
+        for model in models {
+            antenna::Entity::update(model).exec(db).await?;
+        }
+
+        Ok(())
+    }
+
+    async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
+        // Replace the sample below with your own migration scripts
+        Ok(())
+    }
+}
+
+/// Learn more at https://docs.rs/sea-query#iden
+#[derive(Iden)]
+enum Antenna {
+    Table,
+    Id,
+    Users,
+}
diff --git a/packages/backend/native-utils/crates/migration/src/main.rs b/packages/backend/native-utils/crates/migration/src/main.rs
new file mode 100644
index 000000000..c6b6e48db
--- /dev/null
+++ b/packages/backend/native-utils/crates/migration/src/main.rs
@@ -0,0 +1,6 @@
+use sea_orm_migration::prelude::*;
+
+#[async_std::main]
+async fn main() {
+    cli::run_cli(migration::Migrator).await;
+}
diff --git a/packages/backend/native-utils/crates/model/Cargo.toml b/packages/backend/native-utils/crates/model/Cargo.toml
index 47d2e1553..4f121a1e1 100644
--- a/packages/backend/native-utils/crates/model/Cargo.toml
+++ b/packages/backend/native-utils/crates/model/Cargo.toml
@@ -4,9 +4,13 @@ version = "0.1.0"
 edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[features]
+default = ["compat"]
+compat = ["sea-orm/postgres-array"]
 
 [dependencies]
 async-trait = "0.1.68"
+cfg-if = "1.0.0"
 chrono = "0.4.24"
 database = { path = "../database" }
 derive_more = "0.99.17"
@@ -14,7 +18,7 @@ jsonschema = "0.17.0"
 once_cell = "1.17.1"
 parse-display = "0.8.0"
 schemars = { version = "0.8.12", features = ["chrono"] }
-sea-orm = { version = "0.11.3", features = ["postgres-array", "sqlx-postgres", "runtime-tokio-rustls"] }
+sea-orm = { version = "0.11.3", features = ["sqlx-postgres", "sqlx-sqlite", "runtime-tokio-rustls"] }
 serde = { version = "1.0.163", features = ["derive"] }
 serde_json = "1.0.96"
 thiserror = "1.0.40"
diff --git a/packages/backend/native-utils/crates/model/src/entity/antenna.rs b/packages/backend/native-utils/crates/model/src/entity/antenna.rs
index ccff0aab4..0ebd1cf45 100644
--- a/packages/backend/native-utils/crates/model/src/entity/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/antenna.rs
@@ -28,7 +28,7 @@ pub struct Model {
     pub with_replies: bool,
     #[sea_orm(column_name = "userGroupJoiningId")]
     pub user_group_joining_id: Option<String>,
-    pub users: Vec<String>,
+    pub users: newtype::StringVec,
     #[sea_orm(column_name = "excludeKeywords", column_type = "JsonBinary")]
     pub exclude_keywords: newtype::JsonKeyword,
     #[sea_orm(column_type = "JsonBinary")]
diff --git a/packages/backend/native-utils/crates/model/src/entity/newtype/macros.rs b/packages/backend/native-utils/crates/model/src/entity/newtype/macros.rs
index 0266e80c8..4b05c2c99 100644
--- a/packages/backend/native-utils/crates/model/src/entity/newtype/macros.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/newtype/macros.rs
@@ -33,12 +33,12 @@ macro_rules! impl_json_newtype {
                 stringify!($a).to_owned()
             }
 
-            fn array_type() -> sea_orm::sea_query::ArrayType {
-                sea_orm::sea_query::ArrayType::Json
+            fn array_type() -> sea_query::ArrayType {
+                sea_query::ArrayType::Json
             }
 
             fn column_type() -> sea_query::ColumnType {
-                sea_query::ColumnType::Json
+                sea_query::ColumnType::JsonBinary
             }
         }
 
diff --git a/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs b/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
index f7e3c3d43..2b11c5f21 100644
--- a/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
@@ -1,17 +1,24 @@
 mod macros;
 
-use derive_more::From;
-use schemars::JsonSchema;
+use cfg_if::cfg_if;
+use derive_more::{From, Into};
 use sea_orm::{sea_query, DbErr, QueryResult, TryGetError, TryGetable, Value};
 use serde::{Deserialize, Serialize};
 
 use crate::impl_json_newtype;
 
-#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, From)]
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
 pub struct JsonKeyword(pub Vec<Vec<String>>);
 impl_json_newtype!(JsonKeyword);
 
-#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, From)]
-
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
 pub struct JsonStringVec(pub Vec<String>);
 impl_json_newtype!(JsonStringVec);
+
+cfg_if! {
+    if #[cfg(feature = "compat")] {
+        pub type StringVec = Vec<String>;
+    } else {
+        pub type StringVec = JsonStringVec;
+    }
+}
diff --git a/packages/backend/native-utils/crates/model/src/error.rs b/packages/backend/native-utils/crates/model/src/error.rs
index 379ecb784..7686312ec 100644
--- a/packages/backend/native-utils/crates/model/src/error.rs
+++ b/packages/backend/native-utils/crates/model/src/error.rs
@@ -3,7 +3,7 @@ pub enum Error {
     #[error("Failed to parse string")]
     ParseError(#[from] parse_display::ParseError),
     #[error("Failed to get database connection")]
-    DatabaseConnectionError(#[from] database::error::Error),
+    DbConnError(#[from] database::error::Error),
     #[error("Database operation error: {0}")]
     DatabaseOperationError(#[from] sea_orm::DbErr),
 }
diff --git a/packages/backend/native-utils/crates/model/src/repository/antenna.rs b/packages/backend/native-utils/crates/model/src/repository/antenna.rs
index 9a6882b59..f9ef012a6 100644
--- a/packages/backend/native-utils/crates/model/src/repository/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/repository/antenna.rs
@@ -30,13 +30,13 @@ impl Repository<Antenna> for antenna::Model {
             id: self.id,
             created_at: self.created_at.into(),
             name: self.name,
-            keywords: self.keywords,
-            exclude_keywords: self.exclude_keywords,
+            keywords: self.keywords.into(),
+            exclude_keywords: self.exclude_keywords.into(),
             src: self.src.try_into()?,
             user_list_id: self.user_list_id,
             user_group_id,
-            users: self.users,
-            instances: self.instances,
+            users: self.users.into(),
+            instances: self.instances.into(),
             case_sensitive: self.case_sensitive,
             notify: self.notify,
             with_replies: self.with_replies,
diff --git a/packages/backend/native-utils/crates/model/src/schema/antenna.rs b/packages/backend/native-utils/crates/model/src/schema/antenna.rs
index 4a71982f7..fa7902b92 100644
--- a/packages/backend/native-utils/crates/model/src/schema/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/schema/antenna.rs
@@ -5,7 +5,7 @@ use schemars::JsonSchema;
 use utoipa::ToSchema;
 
 use super::Schema;
-use crate::entity::{newtype, sea_orm_active_enums::AntennaSrcEnum};
+use crate::entity::sea_orm_active_enums::AntennaSrcEnum;
 
 #[derive(Clone, Debug, PartialEq, Eq, JsonSchema, ToSchema)]
 #[serde(rename_all = "camelCase")]
@@ -13,14 +13,14 @@ pub struct Antenna {
     pub id: String,
     pub created_at: chrono::DateTime<chrono::Utc>,
     pub name: String,
-    pub keywords: newtype::JsonKeyword,
-    pub exclude_keywords: newtype::JsonKeyword,
+    pub keywords: Vec<Vec<String>>,
+    pub exclude_keywords: Vec<Vec<String>>,
     #[schema(inline)]
     pub src: AntennaSrc,
     pub user_list_id: Option<String>,
     pub user_group_id: Option<String>,
     pub users: Vec<String>,
-    pub instances: newtype::JsonStringVec,
+    pub instances: Vec<String>,
     #[serde(default)]
     pub case_sensitive: bool,
     #[serde(default)]
diff --git a/packages/backend/native-utils/crates/model/tests/common.rs b/packages/backend/native-utils/crates/model/tests/common.rs
index d459056f3..f05c4432d 100644
--- a/packages/backend/native-utils/crates/model/tests/common.rs
+++ b/packages/backend/native-utils/crates/model/tests/common.rs
@@ -5,7 +5,8 @@ mod repository;
 use chrono::Utc;
 use model::entity::{antenna, sea_orm_active_enums::AntennaSrcEnum, user};
 use sea_orm::{
-    ActiveModelTrait, ActiveValue::Set, DatabaseConnection, DbErr, EntityTrait, TransactionTrait,
+    ActiveModelTrait, ActiveValue::Set, ConnectionTrait, DbBackend, DbConn, DbErr, EntityTrait,
+    TransactionTrait,
 };
 use std::env;
 use util::{
@@ -24,6 +25,15 @@ async fn prepare() {
     setup_model(db).await;
 }
 
+async fn setup_schema(db: DbConn) {
+    // Setup Schema helper
+    let schema = sea_orm::Schema::new(DbBackend::Sqlite);
+    let stmt = schema.create_table_from_entity(antenna::Entity);
+    db.execute(db.get_database_backend().build(&stmt))
+        .await
+        .expect("Unable to initialize in-memoty sqlite");
+}
+
 /// Delete all entries in the database.
 async fn cleanup() {
     let db = database::get_database().unwrap();
@@ -39,7 +49,7 @@ async fn cleanup() {
     .expect("Unable to delete predefined models");
 }
 
-async fn setup_model(db: &DatabaseConnection) {
+async fn setup_model(db: &DbConn) {
     init_id(12);
 
     db.transaction::<_, (), DbErr>(|txn| {
@@ -89,6 +99,10 @@ async fn setup_model(db: &DatabaseConnection) {
 }
 
 mod int_test {
+    use sea_orm::Database;
+
+    use crate::setup_schema;
+
     use super::{cleanup, prepare};
 
     #[tokio::test]
@@ -96,4 +110,10 @@ mod int_test {
         prepare().await;
         cleanup().await;
     }
+
+    #[tokio::test]
+    async fn can_setup_sqlite_schema() {
+        let db = Database::connect("sqlite::memory:").await.unwrap();
+        setup_schema(db).await;
+    }
 }

From 968943c7c0c12347b24d6d17ea224e61604c9e9d Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Wed, 31 May 2023 18:21:57 -0400
Subject: [PATCH 156/198] make sqlite compat

---
 packages/backend/native-utils/crates/model/Cargo.toml         | 4 ++--
 .../native-utils/crates/model/src/entity/newtype/mod.rs       | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/packages/backend/native-utils/crates/model/Cargo.toml b/packages/backend/native-utils/crates/model/Cargo.toml
index 4f121a1e1..e99f8b561 100644
--- a/packages/backend/native-utils/crates/model/Cargo.toml
+++ b/packages/backend/native-utils/crates/model/Cargo.toml
@@ -5,8 +5,8 @@ edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 [features]
-default = ["compat"]
-compat = ["sea-orm/postgres-array"]
+default = ["legacy"]
+legacy = ["sea-orm/postgres-array"]
 
 [dependencies]
 async-trait = "0.1.68"
diff --git a/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs b/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
index 2b11c5f21..65eba72d7 100644
--- a/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
@@ -16,7 +16,7 @@ pub struct JsonStringVec(pub Vec<String>);
 impl_json_newtype!(JsonStringVec);
 
 cfg_if! {
-    if #[cfg(feature = "compat")] {
+    if #[cfg(feature = "legacy")] {
         pub type StringVec = Vec<String>;
     } else {
         pub type StringVec = JsonStringVec;

From 099f9e042afebd86dad1757f2586392df54fbe47 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Thu, 1 Jun 2023 00:18:50 -0400
Subject: [PATCH 157/198] use vec newtype

---
 .../crates/model/src/entity/access_token.rs   |  4 +++-
 .../crates/model/src/entity/app.rs            |  4 +++-
 .../crates/model/src/entity/emoji.rs          |  4 +++-
 .../crates/model/src/entity/gallery_post.rs   |  6 +++--
 .../crates/model/src/entity/hashtag.rs        | 14 +++++++-----
 .../model/src/entity/messaging_message.rs     |  4 +++-
 .../crates/model/src/entity/meta.rs           | 22 ++++++++++---------
 .../crates/model/src/entity/newtype/mod.rs    |  6 +++++
 .../crates/model/src/entity/note.rs           | 14 +++++++-----
 .../crates/model/src/entity/note_edit.rs      |  4 +++-
 .../crates/model/src/entity/page.rs           |  4 +++-
 .../crates/model/src/entity/poll.rs           |  6 +++--
 .../crates/model/src/entity/registry_item.rs  |  4 +++-
 .../crates/model/src/entity/reversi_game.rs   |  4 +++-
 .../crates/model/src/entity/user.rs           |  6 +++--
 .../crates/model/src/entity/user_profile.rs   |  4 +++-
 .../crates/model/src/entity/webhook.rs        |  4 +++-
 17 files changed, 76 insertions(+), 38 deletions(-)

diff --git a/packages/backend/native-utils/crates/model/src/entity/access_token.rs b/packages/backend/native-utils/crates/model/src/entity/access_token.rs
index fa9894414..f84971605 100644
--- a/packages/backend/native-utils/crates/model/src/entity/access_token.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/access_token.rs
@@ -2,6 +2,8 @@
 
 use sea_orm::entity::prelude::*;
 
+use super::newtype::StringVec;
+
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "access_token")]
 pub struct Model {
@@ -22,7 +24,7 @@ pub struct Model {
     pub description: Option<String>,
     #[sea_orm(column_name = "iconUrl")]
     pub icon_url: Option<String>,
-    pub permission: Vec<String>,
+    pub permission: StringVec,
     pub fetched: bool,
 }
 
diff --git a/packages/backend/native-utils/crates/model/src/entity/app.rs b/packages/backend/native-utils/crates/model/src/entity/app.rs
index 5f3d5d131..310294568 100644
--- a/packages/backend/native-utils/crates/model/src/entity/app.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/app.rs
@@ -2,6 +2,8 @@
 
 use sea_orm::entity::prelude::*;
 
+use super::newtype::StringVec;
+
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "app")]
 pub struct Model {
@@ -14,7 +16,7 @@ pub struct Model {
     pub secret: String,
     pub name: String,
     pub description: String,
-    pub permission: Vec<String>,
+    pub permission: StringVec,
     #[sea_orm(column_name = "callbackUrl")]
     pub callback_url: Option<String>,
 }
diff --git a/packages/backend/native-utils/crates/model/src/entity/emoji.rs b/packages/backend/native-utils/crates/model/src/entity/emoji.rs
index a89f0b22d..fdb99a2c3 100644
--- a/packages/backend/native-utils/crates/model/src/entity/emoji.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/emoji.rs
@@ -2,6 +2,8 @@
 
 use sea_orm::entity::prelude::*;
 
+use super::newtype::StringVec;
+
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "emoji")]
 pub struct Model {
@@ -15,7 +17,7 @@ pub struct Model {
     pub original_url: String,
     pub uri: Option<String>,
     pub r#type: Option<String>,
-    pub aliases: Vec<String>,
+    pub aliases: StringVec,
     pub category: Option<String>,
     #[sea_orm(column_name = "publicUrl")]
     pub public_url: String,
diff --git a/packages/backend/native-utils/crates/model/src/entity/gallery_post.rs b/packages/backend/native-utils/crates/model/src/entity/gallery_post.rs
index bb68eda62..875d3af58 100644
--- a/packages/backend/native-utils/crates/model/src/entity/gallery_post.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/gallery_post.rs
@@ -2,6 +2,8 @@
 
 use sea_orm::entity::prelude::*;
 
+use super::newtype::StringVec;
+
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "gallery_post")]
 pub struct Model {
@@ -16,12 +18,12 @@ pub struct Model {
     #[sea_orm(column_name = "userId")]
     pub user_id: String,
     #[sea_orm(column_name = "fileIds")]
-    pub file_ids: Vec<String>,
+    pub file_ids: StringVec,
     #[sea_orm(column_name = "isSensitive")]
     pub is_sensitive: bool,
     #[sea_orm(column_name = "likedCount")]
     pub liked_count: i32,
-    pub tags: Vec<String>,
+    pub tags: StringVec,
 }
 
 #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/hashtag.rs b/packages/backend/native-utils/crates/model/src/entity/hashtag.rs
index 9a6e44623..917f4ea1b 100644
--- a/packages/backend/native-utils/crates/model/src/entity/hashtag.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/hashtag.rs
@@ -2,6 +2,8 @@
 
 use sea_orm::entity::prelude::*;
 
+use super::newtype::StringVec;
+
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "hashtag")]
 pub struct Model {
@@ -9,27 +11,27 @@ pub struct Model {
     pub id: String,
     pub name: String,
     #[sea_orm(column_name = "mentionedUserIds")]
-    pub mentioned_user_ids: Vec<String>,
+    pub mentioned_user_ids: StringVec,
     #[sea_orm(column_name = "mentionedUsersCount")]
     pub mentioned_users_count: i32,
     #[sea_orm(column_name = "mentionedLocalUserIds")]
-    pub mentioned_local_user_ids: Vec<String>,
+    pub mentioned_local_user_ids: StringVec,
     #[sea_orm(column_name = "mentionedLocalUsersCount")]
     pub mentioned_local_users_count: i32,
     #[sea_orm(column_name = "mentionedRemoteUserIds")]
-    pub mentioned_remote_user_ids: Vec<String>,
+    pub mentioned_remote_user_ids: StringVec,
     #[sea_orm(column_name = "mentionedRemoteUsersCount")]
     pub mentioned_remote_users_count: i32,
     #[sea_orm(column_name = "attachedUserIds")]
-    pub attached_user_ids: Vec<String>,
+    pub attached_user_ids: StringVec,
     #[sea_orm(column_name = "attachedUsersCount")]
     pub attached_users_count: i32,
     #[sea_orm(column_name = "attachedLocalUserIds")]
-    pub attached_local_user_ids: Vec<String>,
+    pub attached_local_user_ids: StringVec,
     #[sea_orm(column_name = "attachedLocalUsersCount")]
     pub attached_local_users_count: i32,
     #[sea_orm(column_name = "attachedRemoteUserIds")]
-    pub attached_remote_user_ids: Vec<String>,
+    pub attached_remote_user_ids: StringVec,
     #[sea_orm(column_name = "attachedRemoteUsersCount")]
     pub attached_remote_users_count: i32,
 }
diff --git a/packages/backend/native-utils/crates/model/src/entity/messaging_message.rs b/packages/backend/native-utils/crates/model/src/entity/messaging_message.rs
index 0fbfc1ffb..b66c01fc9 100644
--- a/packages/backend/native-utils/crates/model/src/entity/messaging_message.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/messaging_message.rs
@@ -2,6 +2,8 @@
 
 use sea_orm::entity::prelude::*;
 
+use super::newtype::StringVec;
+
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "messaging_message")]
 pub struct Model {
@@ -20,7 +22,7 @@ pub struct Model {
     pub file_id: Option<String>,
     #[sea_orm(column_name = "groupId")]
     pub group_id: Option<String>,
-    pub reads: Vec<String>,
+    pub reads: StringVec,
     pub uri: Option<String>,
 }
 
diff --git a/packages/backend/native-utils/crates/model/src/entity/meta.rs b/packages/backend/native-utils/crates/model/src/entity/meta.rs
index 33ab911ba..a97cb89c4 100644
--- a/packages/backend/native-utils/crates/model/src/entity/meta.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/meta.rs
@@ -4,6 +4,8 @@ use super::sea_orm_active_enums::MetaSensitivemediadetectionEnum;
 use super::sea_orm_active_enums::MetaSensitivemediadetectionsensitivityEnum;
 use sea_orm::entity::prelude::*;
 
+use super::newtype::StringVec;
+
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "meta")]
 pub struct Model {
@@ -23,11 +25,11 @@ pub struct Model {
     pub disable_global_timeline: bool,
     #[sea_orm(column_name = "useStarForReactionFallback")]
     pub use_star_for_reaction_fallback: bool,
-    pub langs: Vec<String>,
+    pub langs: StringVec,
     #[sea_orm(column_name = "hiddenTags")]
-    pub hidden_tags: Vec<String>,
+    pub hidden_tags: StringVec,
     #[sea_orm(column_name = "blockedHosts")]
-    pub blocked_hosts: Vec<String>,
+    pub blocked_hosts: StringVec,
     #[sea_orm(column_name = "mascotImageUrl")]
     pub mascot_image_url: Option<String>,
     #[sea_orm(column_name = "bannerUrl")]
@@ -88,7 +90,7 @@ pub struct Model {
     #[sea_orm(column_name = "discordClientSecret")]
     pub discord_client_secret: Option<String>,
     #[sea_orm(column_name = "pinnedUsers")]
-    pub pinned_users: Vec<String>,
+    pub pinned_users: StringVec,
     #[sea_orm(column_name = "ToSUrl")]
     pub to_s_url: Option<String>,
     #[sea_orm(column_name = "repositoryUrl")]
@@ -128,7 +130,7 @@ pub struct Model {
     #[sea_orm(column_name = "objectStorageSetPublicRead")]
     pub object_storage_set_public_read: bool,
     #[sea_orm(column_name = "pinnedPages")]
-    pub pinned_pages: Vec<String>,
+    pub pinned_pages: StringVec,
     #[sea_orm(column_name = "backgroundImageUrl")]
     pub background_image_url: Option<String>,
     #[sea_orm(column_name = "logoImageUrl")]
@@ -138,7 +140,7 @@ pub struct Model {
     #[sea_orm(column_name = "objectStorageS3ForcePathStyle")]
     pub object_storage_s3_force_path_style: bool,
     #[sea_orm(column_name = "allowedHosts")]
-    pub allowed_hosts: Option<Vec<String>>,
+    pub allowed_hosts: Option<StringVec>,
     #[sea_orm(column_name = "secureMode")]
     pub secure_mode: Option<bool>,
     #[sea_orm(column_name = "privateMode")]
@@ -168,13 +170,13 @@ pub struct Model {
     #[sea_orm(column_name = "enableActiveEmailValidation")]
     pub enable_active_email_validation: bool,
     #[sea_orm(column_name = "customMOTD")]
-    pub custom_motd: Vec<String>,
+    pub custom_motd: StringVec,
     #[sea_orm(column_name = "customSplashIcons")]
-    pub custom_splash_icons: Vec<String>,
+    pub custom_splash_icons: StringVec,
     #[sea_orm(column_name = "disableRecommendedTimeline")]
     pub disable_recommended_timeline: bool,
     #[sea_orm(column_name = "recommendedInstances")]
-    pub recommended_instances: Vec<String>,
+    pub recommended_instances: StringVec,
     #[sea_orm(column_name = "enableGuestTimeline")]
     pub enable_guest_timeline: bool,
     #[sea_orm(column_name = "defaultReaction")]
@@ -184,7 +186,7 @@ pub struct Model {
     #[sea_orm(column_name = "libreTranslateApiKey")]
     pub libre_translate_api_key: Option<String>,
     #[sea_orm(column_name = "silencedHosts")]
-    pub silenced_hosts: Vec<String>,
+    pub silenced_hosts: StringVec,
     #[sea_orm(column_name = "experimentalFeatures", column_type = "JsonBinary")]
     pub experimental_features: Json,
 }
diff --git a/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs b/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
index 65eba72d7..bd4302441 100644
--- a/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
@@ -15,10 +15,16 @@ impl_json_newtype!(JsonKeyword);
 pub struct JsonStringVec(pub Vec<String>);
 impl_json_newtype!(JsonStringVec);
 
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
+pub struct JsonI32Vec(pub Vec<i32>);
+impl_json_newtype!(JsonI32Vec);
+
 cfg_if! {
     if #[cfg(feature = "legacy")] {
         pub type StringVec = Vec<String>;
+        pub type I32Vec = Vec<i32>;
     } else {
         pub type StringVec = JsonStringVec;
+        pub type I32Vec = JsonI32Vec;
     }
 }
diff --git a/packages/backend/native-utils/crates/model/src/entity/note.rs b/packages/backend/native-utils/crates/model/src/entity/note.rs
index 2e733edae..c2f20c11b 100644
--- a/packages/backend/native-utils/crates/model/src/entity/note.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/note.rs
@@ -3,6 +3,8 @@
 use super::sea_orm_active_enums::NoteVisibilityEnum;
 use sea_orm::entity::prelude::*;
 
+use super::newtype::StringVec;
+
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "note")]
 pub struct Model {
@@ -32,16 +34,16 @@ pub struct Model {
     pub uri: Option<String>,
     pub score: i32,
     #[sea_orm(column_name = "fileIds")]
-    pub file_ids: Vec<String>,
+    pub file_ids: StringVec,
     #[sea_orm(column_name = "attachedFileTypes")]
-    pub attached_file_types: Vec<String>,
+    pub attached_file_types: StringVec,
     #[sea_orm(column_name = "visibleUserIds")]
-    pub visible_user_ids: Vec<String>,
-    pub mentions: Vec<String>,
+    pub visible_user_ids: StringVec,
+    pub mentions: StringVec,
     #[sea_orm(column_name = "mentionedRemoteUsers", column_type = "Text")]
     pub mentioned_remote_users: String,
-    pub emojis: Vec<String>,
-    pub tags: Vec<String>,
+    pub emojis: StringVec,
+    pub tags: StringVec,
     #[sea_orm(column_name = "hasPoll")]
     pub has_poll: bool,
     #[sea_orm(column_name = "userHost")]
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_edit.rs b/packages/backend/native-utils/crates/model/src/entity/note_edit.rs
index 5e98b3da4..4e8f42083 100644
--- a/packages/backend/native-utils/crates/model/src/entity/note_edit.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/note_edit.rs
@@ -2,6 +2,8 @@
 
 use sea_orm::entity::prelude::*;
 
+use super::newtype::StringVec;
+
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "note_edit")]
 pub struct Model {
@@ -13,7 +15,7 @@ pub struct Model {
     pub text: Option<String>,
     pub cw: Option<String>,
     #[sea_orm(column_name = "fileIds")]
-    pub file_ids: Vec<String>,
+    pub file_ids: StringVec,
     #[sea_orm(column_name = "updatedAt")]
     pub updated_at: DateTimeWithTimeZone,
 }
diff --git a/packages/backend/native-utils/crates/model/src/entity/page.rs b/packages/backend/native-utils/crates/model/src/entity/page.rs
index efb794944..c3d09fa8c 100644
--- a/packages/backend/native-utils/crates/model/src/entity/page.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/page.rs
@@ -3,6 +3,8 @@
 use super::sea_orm_active_enums::PageVisibilityEnum;
 use sea_orm::entity::prelude::*;
 
+use super::newtype::StringVec;
+
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "page")]
 pub struct Model {
@@ -28,7 +30,7 @@ pub struct Model {
     pub variables: Json,
     pub visibility: PageVisibilityEnum,
     #[sea_orm(column_name = "visibleUserIds")]
-    pub visible_user_ids: Vec<String>,
+    pub visible_user_ids: StringVec,
     #[sea_orm(column_name = "likedCount")]
     pub liked_count: i32,
     #[sea_orm(column_name = "hideTitleWhenPinned")]
diff --git a/packages/backend/native-utils/crates/model/src/entity/poll.rs b/packages/backend/native-utils/crates/model/src/entity/poll.rs
index a4d9e2df1..81953cfba 100644
--- a/packages/backend/native-utils/crates/model/src/entity/poll.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/poll.rs
@@ -3,6 +3,8 @@
 use super::sea_orm_active_enums::PollNotevisibilityEnum;
 use sea_orm::entity::prelude::*;
 
+use super::newtype::{I32Vec, StringVec};
+
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "poll")]
 pub struct Model {
@@ -11,8 +13,8 @@ pub struct Model {
     #[sea_orm(column_name = "expiresAt")]
     pub expires_at: Option<DateTimeWithTimeZone>,
     pub multiple: bool,
-    pub choices: Vec<String>,
-    pub votes: Vec<i32>,
+    pub choices: StringVec,
+    pub votes: I32Vec,
     #[sea_orm(column_name = "noteVisibility")]
     pub note_visibility: PollNotevisibilityEnum,
     #[sea_orm(column_name = "userId")]
diff --git a/packages/backend/native-utils/crates/model/src/entity/registry_item.rs b/packages/backend/native-utils/crates/model/src/entity/registry_item.rs
index 6de0c740f..54d72d5d8 100644
--- a/packages/backend/native-utils/crates/model/src/entity/registry_item.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/registry_item.rs
@@ -2,6 +2,8 @@
 
 use sea_orm::entity::prelude::*;
 
+use super::newtype::StringVec;
+
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "registry_item")]
 pub struct Model {
@@ -14,7 +16,7 @@ pub struct Model {
     #[sea_orm(column_name = "userId")]
     pub user_id: String,
     pub key: String,
-    pub scope: Vec<String>,
+    pub scope: StringVec,
     pub domain: Option<String>,
     #[sea_orm(column_type = "JsonBinary", nullable)]
     pub value: Option<Json>,
diff --git a/packages/backend/native-utils/crates/model/src/entity/reversi_game.rs b/packages/backend/native-utils/crates/model/src/entity/reversi_game.rs
index 1e5359280..0c82fccf7 100644
--- a/packages/backend/native-utils/crates/model/src/entity/reversi_game.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/reversi_game.rs
@@ -2,6 +2,8 @@
 
 use sea_orm::entity::prelude::*;
 
+use super::newtype::StringVec;
+
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "reversi_game")]
 pub struct Model {
@@ -29,7 +31,7 @@ pub struct Model {
     pub surrendered: Option<String>,
     #[sea_orm(column_type = "JsonBinary")]
     pub logs: Json,
-    pub map: Vec<String>,
+    pub map: StringVec,
     pub bw: String,
     #[sea_orm(column_name = "isLlotheo")]
     pub is_llotheo: bool,
diff --git a/packages/backend/native-utils/crates/model/src/entity/user.rs b/packages/backend/native-utils/crates/model/src/entity/user.rs
index 79b9fa692..1cf6ac6b7 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user.rs
@@ -2,6 +2,8 @@
 
 use sea_orm::entity::prelude::*;
 
+use super::newtype::StringVec;
+
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "user")]
 pub struct Model {
@@ -27,7 +29,7 @@ pub struct Model {
     pub avatar_id: Option<String>,
     #[sea_orm(column_name = "bannerId", unique)]
     pub banner_id: Option<String>,
-    pub tags: Vec<String>,
+    pub tags: StringVec,
     #[sea_orm(column_name = "isSuspended")]
     pub is_suspended: bool,
     #[sea_orm(column_name = "isSilenced")]
@@ -42,7 +44,7 @@ pub struct Model {
     pub is_admin: bool,
     #[sea_orm(column_name = "isModerator")]
     pub is_moderator: bool,
-    pub emojis: Vec<String>,
+    pub emojis: StringVec,
     pub host: Option<String>,
     pub inbox: Option<String>,
     #[sea_orm(column_name = "sharedInbox")]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_profile.rs b/packages/backend/native-utils/crates/model/src/entity/user_profile.rs
index 3a12469d8..d62607f16 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_profile.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_profile.rs
@@ -3,6 +3,8 @@
 use super::sea_orm_active_enums::UserProfileFfvisibilityEnum;
 use sea_orm::entity::prelude::*;
 
+use super::newtype::StringVec;
+
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "user_profile")]
 pub struct Model {
@@ -53,7 +55,7 @@ pub struct Model {
     #[sea_orm(column_name = "mutedWords", column_type = "JsonBinary")]
     pub muted_words: Json,
     #[sea_orm(column_name = "mutingNotificationTypes")]
-    pub muting_notification_types: Vec<String>,
+    pub muting_notification_types: StringVec,
     #[sea_orm(column_name = "noCrawle")]
     pub no_crawle: bool,
     #[sea_orm(column_name = "receiveAnnouncementEmail")]
diff --git a/packages/backend/native-utils/crates/model/src/entity/webhook.rs b/packages/backend/native-utils/crates/model/src/entity/webhook.rs
index 952dcbadb..dd3c3c738 100644
--- a/packages/backend/native-utils/crates/model/src/entity/webhook.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/webhook.rs
@@ -2,6 +2,8 @@
 
 use sea_orm::entity::prelude::*;
 
+use super::newtype::StringVec;
+
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "webhook")]
 pub struct Model {
@@ -12,7 +14,7 @@ pub struct Model {
     #[sea_orm(column_name = "userId")]
     pub user_id: String,
     pub name: String,
-    pub on: Vec<String>,
+    pub on: StringVec,
     pub url: String,
     pub secret: String,
     pub active: bool,

From 239dfeec9f1333ef9e7d916759e3ba2727d60090 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Thu, 1 Jun 2023 04:01:25 -0400
Subject: [PATCH 158/198] add migration to convert array to jsonb

---
 .../src/m20230531_180824_stringvec.rs         | 395 ++++++++++++++++--
 .../native-utils/crates/model/Cargo.toml      |   6 +-
 .../crates/model/src/entity/mod.rs            |   2 -
 .../crates/model/src/entity/prelude.rs        |   2 -
 .../crates/model/src/entity/reversi_game.rs   |  69 ---
 .../model/src/entity/reversi_matching.rs      |  38 --
 .../native-utils/crates/model/tests/common.rs |   6 +-
 7 files changed, 365 insertions(+), 153 deletions(-)
 delete mode 100644 packages/backend/native-utils/crates/model/src/entity/reversi_game.rs
 delete mode 100644 packages/backend/native-utils/crates/model/src/entity/reversi_matching.rs

diff --git a/packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs b/packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs
index ba1fc257d..b7c0a70b3 100644
--- a/packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs
+++ b/packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs
@@ -1,7 +1,11 @@
-use model::entity::{antenna, newtype::StringVec};
+use model::entity::{
+    access_token, antenna, app, emoji, gallery_post, hashtag, messaging_message, meta,
+    newtype::{I32Vec, StringVec},
+    note, note_edit, poll, registry_item, user, user_profile, webhook,
+};
 use sea_orm_migration::{
     prelude::*,
-    sea_orm::{DbBackend, EntityTrait, Statement},
+    sea_orm::{DbBackend, EntityTrait, Statement, TryGetable},
 };
 use serde_json::json;
 
@@ -11,50 +15,220 @@ pub struct Migration;
 #[async_trait::async_trait]
 impl MigrationTrait for Migration {
     async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
+        manager
+            .drop_table(Table::drop().table(Alias::new("reversi_game")).to_owned())
+            .await?;
+
+        manager
+            .drop_table(
+                Table::drop()
+                    .table(Alias::new("reversi_matching"))
+                    .to_owned(),
+            )
+            .await?;
+
         if manager.get_database_backend() == DbBackend::Sqlite {
             return Ok(());
         }
 
         let db = manager.get_connection();
-        let query = Query::select()
-            .column(Antenna::Id)
-            .column(Antenna::Users)
-            .from(Antenna::Table)
-            .to_string(PostgresQueryBuilder);
-        let res: Vec<(String, Vec<String>)> = db
-            .query_all(Statement::from_string(DbBackend::Postgres, query))
-            .await?
-            .iter()
-            .filter_map(|r| r.try_get_many_by_index().ok())
-            .collect();
 
-        manager
-            .alter_table(
-                Table::alter()
-                    .table(Antenna::Table)
-                    .drop_column(Antenna::Users)
-                    .add_column(
-                        ColumnDef::new(Antenna::Users)
-                            .json_binary()
-                            .not_null()
-                            .default(json!([])),
-                    )
-                    .to_owned(),
-            )
-            .await?;
+        macro_rules! copy_data {
+            ($data:ident, $ent:ident, $col:tt) => {
+                let models: Vec<$ent::ActiveModel> = $data
+                    .iter()
+                    .map(|(id, r)| $ent::ActiveModel {
+                        id: sea_orm::Set(id.to_owned()),
+                        $col: sea_orm::Set(StringVec::from(r.to_owned())),
+                        ..Default::default()
+                    })
+                    .collect();
+                for model in models {
+                    $ent::Entity::update(model).exec(db).await?;
+                }
+            };
+        }
 
-        let models: Vec<antenna::ActiveModel> = res
+        macro_rules! convert_to_stringvec_json {
+            ($table:expr, $id:expr, $col:expr, $ent:ident, $col_name:tt) => {
+                let query = select_query($table, $id, $col);
+                let res = get_vec::<Vec<String>>(db, query).await?;
+                convert_col(manager, $table, $col).await?;
+                copy_data!(res, $ent, $col_name);
+            };
+        }
+
+        convert_to_stringvec_json!(
+            AccessToken::Table,
+            AccessToken::Id,
+            AccessToken::Permission,
+            access_token,
+            permission
+        );
+        convert_to_stringvec_json!(Antenna::Table, Antenna::Id, Antenna::Users, antenna, users);
+        convert_to_stringvec_json!(App::Table, App::Id, App::Permission, app, permission);
+        convert_to_stringvec_json!(Emoji::Table, Emoji::Id, Emoji::Aliases, emoji, aliases);
+        convert_to_stringvec_json!(
+            GalleryPost::Table,
+            GalleryPost::Id,
+            GalleryPost::FileIds,
+            gallery_post,
+            file_ids
+        );
+        convert_to_stringvec_json!(
+            GalleryPost::Table,
+            GalleryPost::Id,
+            GalleryPost::Tags,
+            gallery_post,
+            tags
+        );
+        convert_to_stringvec_json!(
+            Hashtag::Table,
+            Hashtag::Id,
+            Hashtag::MentionedUserIds,
+            hashtag,
+            mentioned_user_ids
+        );
+        convert_to_stringvec_json!(
+            Hashtag::Table,
+            Hashtag::Id,
+            Hashtag::MentionedLocalUserIds,
+            hashtag,
+            mentioned_local_user_ids
+        );
+        convert_to_stringvec_json!(
+            Hashtag::Table,
+            Hashtag::Id,
+            Hashtag::MentionedRemoteUserIds,
+            hashtag,
+            mentioned_remote_user_ids
+        );
+        convert_to_stringvec_json!(
+            Hashtag::Table,
+            Hashtag::Id,
+            Hashtag::AttachedUserIds,
+            hashtag,
+            attached_user_ids
+        );
+        convert_to_stringvec_json!(
+            Hashtag::Table,
+            Hashtag::Id,
+            Hashtag::AttachedLocalUserIds,
+            hashtag,
+            attached_local_user_ids
+        );
+        convert_to_stringvec_json!(
+            Hashtag::Table,
+            Hashtag::Id,
+            Hashtag::AttachedRemoteUserIds,
+            hashtag,
+            attached_remote_user_ids
+        );
+        convert_to_stringvec_json!(
+            MessagingMessage::Table,
+            MessagingMessage::Id,
+            MessagingMessage::Reads,
+            messaging_message,
+            reads
+        );
+        convert_to_stringvec_json!(Meta::Table, Meta::Id, Meta::Langs, meta, langs);
+        convert_to_stringvec_json!(
+            Meta::Table,
+            Meta::Id,
+            Meta::BlockedHosts,
+            meta,
+            blocked_hosts
+        );
+        convert_to_stringvec_json!(Meta::Table, Meta::Id, Meta::HiddenTags, meta, hidden_tags);
+        convert_to_stringvec_json!(Meta::Table, Meta::Id, Meta::PinnedUsers, meta, pinned_users);
+        convert_to_stringvec_json!(Meta::Table, Meta::Id, Meta::PinnedPages, meta, pinned_pages);
+        convert_to_stringvec_json!(
+            Meta::Table,
+            Meta::Id,
+            Meta::RecommendedInstances,
+            meta,
+            recommended_instances
+        );
+        convert_to_stringvec_json!(
+            Meta::Table,
+            Meta::Id,
+            Meta::SilencedHosts,
+            meta,
+            silenced_hosts
+        );
+        convert_to_stringvec_json!(Note::Table, Note::Id, Note::FileIds, note, file_ids);
+        convert_to_stringvec_json!(
+            Note::Table,
+            Note::Id,
+            Note::AttachedFileTypes,
+            note,
+            attached_file_types
+        );
+        convert_to_stringvec_json!(
+            Note::Table,
+            Note::Id,
+            Note::VisibleUserIds,
+            note,
+            visible_user_ids
+        );
+        convert_to_stringvec_json!(Note::Table, Note::Id, Note::Mentions, note, mentions);
+        convert_to_stringvec_json!(Note::Table, Note::Id, Note::Emojis, note, emojis);
+        convert_to_stringvec_json!(Note::Table, Note::Id, Note::Tags, note, tags);
+        convert_to_stringvec_json!(
+            NoteEdit::Table,
+            NoteEdit::Id,
+            NoteEdit::FileIds,
+            note_edit,
+            file_ids
+        );
+
+        // Convert poll here because its primary key is not id, but note_id.
+        let query = select_query(Poll::Table, Poll::NoteId, Poll::Choices);
+        let res = get_vec::<Vec<String>>(db, query).await?;
+        convert_col(manager, Poll::Table, Poll::Choices).await?;
+        let poll_models: Vec<poll::ActiveModel> = res
             .iter()
-            .map(|(id, users)| antenna::ActiveModel {
-                id: sea_orm::Set(id.to_owned()),
-                users: sea_orm::Set(StringVec::from(users.to_owned())),
+            .map(|(id, r)| poll::ActiveModel {
+                note_id: sea_orm::Set(id.to_owned()),
+                choices: sea_orm::Set(StringVec::from(r.to_owned())),
                 ..Default::default()
             })
             .collect();
-
-        for model in models {
-            antenna::Entity::update(model).exec(db).await?;
+        for model in poll_models {
+            poll::Entity::update(model).exec(db).await?;
         }
+        let query = select_query(Poll::Table, Poll::NoteId, Poll::Votes);
+        let res = get_vec::<Vec<i32>>(db, query).await?;
+        convert_col(manager, Poll::Table, Poll::Votes).await?;
+        let poll_models: Vec<poll::ActiveModel> = res
+            .iter()
+            .map(|(id, r)| poll::ActiveModel {
+                note_id: sea_orm::Set(id.to_owned()),
+                votes: sea_orm::Set(I32Vec::from(r.to_owned())),
+                ..Default::default()
+            })
+            .collect();
+        for model in poll_models {
+            poll::Entity::update(model).exec(db).await?;
+        }
+
+        convert_to_stringvec_json!(
+            RegistryItem::Table,
+            RegistryItem::Id,
+            RegistryItem::Scope,
+            registry_item,
+            scope
+        );
+        convert_to_stringvec_json!(User::Table, User::Id, User::Tags, user, tags);
+        convert_to_stringvec_json!(User::Table, User::Id, User::Emojis, user, emojis);
+        convert_to_stringvec_json!(
+            UserProfile::Table,
+            UserProfile::Id,
+            UserProfile::MutingNotificationTypes,
+            user_profile,
+            muting_notification_types
+        );
+        convert_to_stringvec_json!(Webhook::Table, Webhook::Id, Webhook::On, webhook, on);
 
         Ok(())
     }
@@ -66,9 +240,160 @@ impl MigrationTrait for Migration {
 }
 
 /// Learn more at https://docs.rs/sea-query#iden
-#[derive(Iden)]
+#[derive(Iden, Clone)]
 enum Antenna {
     Table,
     Id,
     Users,
 }
+#[derive(Iden, Clone)]
+enum AccessToken {
+    Table,
+    Id,
+    Permission,
+}
+#[derive(Iden, Clone)]
+enum App {
+    Table,
+    Id,
+    Permission,
+}
+#[derive(Iden, Clone)]
+enum Emoji {
+    Table,
+    Id,
+    Aliases,
+}
+#[derive(Iden, Clone)]
+enum GalleryPost {
+    Table,
+    Id,
+    FileIds,
+    Tags,
+}
+#[derive(Iden, Clone)]
+enum Hashtag {
+    Table,
+    Id,
+    MentionedUserIds,
+    MentionedLocalUserIds,
+    MentionedRemoteUserIds,
+    AttachedUserIds,
+    AttachedLocalUserIds,
+    AttachedRemoteUserIds,
+}
+#[derive(Iden, Clone)]
+enum MessagingMessage {
+    Table,
+    Id,
+    Reads,
+}
+#[derive(Iden, Clone)]
+enum Meta {
+    Table,
+    Id,
+    Langs,
+    HiddenTags,
+    BlockedHosts,
+    PinnedUsers,
+    PinnedPages,
+    RecommendedInstances,
+    SilencedHosts,
+}
+#[derive(Iden, Clone)]
+enum Note {
+    Table,
+    Id,
+    FileIds,
+    AttachedFileTypes,
+    VisibleUserIds,
+    Mentions,
+    Emojis,
+    Tags,
+}
+#[derive(Iden, Clone)]
+enum NoteEdit {
+    Table,
+    Id,
+    FileIds,
+}
+#[derive(Iden, Clone)]
+enum Page {
+    Table,
+    Id,
+    VisibleUserIds,
+}
+#[derive(Iden, Clone)]
+enum Poll {
+    Table,
+    NoteId,
+    Choices,
+    Votes, // I32Vec
+}
+#[derive(Iden, Clone)]
+enum RegistryItem {
+    Table,
+    Id,
+    Scope,
+}
+#[derive(Iden, Clone)]
+enum User {
+    Table,
+    Id,
+    Tags,
+    Emojis,
+}
+#[derive(Iden, Clone)]
+enum UserProfile {
+    Table,
+    Id,
+    MutingNotificationTypes,
+}
+#[derive(Iden, Clone)]
+enum Webhook {
+    Table,
+    Id,
+    On,
+}
+
+fn select_query<T: Iden + 'static>(table: T, id: T, col: T) -> String {
+    Query::select()
+        .column(id)
+        .column(col)
+        .from(table)
+        .to_string(PostgresQueryBuilder)
+}
+
+async fn get_vec<'a, T: TryGetable>(
+    db: &SchemaManagerConnection<'a>,
+    query: String,
+) -> Result<Vec<(String, T)>, DbErr> {
+    let res: Vec<(String, T)> = db
+        .query_all(Statement::from_string(DbBackend::Postgres, query))
+        .await?
+        .iter()
+        .filter_map(|r| r.try_get_many_by_index().ok())
+        .collect();
+    Ok(res)
+}
+
+async fn convert_col<'a, T: Iden + Clone + 'static>(
+    manager: &SchemaManager<'a>,
+    table: T,
+    col: T,
+) -> Result<(), DbErr> {
+    manager
+        .alter_table(
+            Table::alter()
+                .table(table)
+                .drop_column(col.to_owned())
+                .add_column(
+                    ColumnDef::new(col.to_owned())
+                        .json_binary()
+                        .not_null()
+                        .default(json!([])),
+                )
+                .to_owned(),
+        )
+        .await
+}
diff --git a/packages/backend/native-utils/crates/model/Cargo.toml b/packages/backend/native-utils/crates/model/Cargo.toml
index e99f8b561..a511e6e99 100644
--- a/packages/backend/native-utils/crates/model/Cargo.toml
+++ b/packages/backend/native-utils/crates/model/Cargo.toml
@@ -5,8 +5,8 @@ edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 [features]
-default = ["legacy"]
-legacy = ["sea-orm/postgres-array"]
+default = []
+legacy = []
 
 [dependencies]
 async-trait = "0.1.68"
@@ -18,7 +18,7 @@ jsonschema = "0.17.0"
 once_cell = "1.17.1"
 parse-display = "0.8.0"
 schemars = { version = "0.8.12", features = ["chrono"] }
-sea-orm = { version = "0.11.3", features = ["sqlx-postgres", "sqlx-sqlite", "runtime-tokio-rustls"] }
+sea-orm = { version = "0.11.3", features = ["sqlx-postgres", "postgres-array", "sqlx-sqlite", "runtime-tokio-rustls"] }
 serde = { version = "1.0.163", features = ["derive"] }
 serde_json = "1.0.96"
 thiserror = "1.0.40"
diff --git a/packages/backend/native-utils/crates/model/src/entity/mod.rs b/packages/backend/native-utils/crates/model/src/entity/mod.rs
index 6105b0555..d71057fde 100644
--- a/packages/backend/native-utils/crates/model/src/entity/mod.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/mod.rs
@@ -53,8 +53,6 @@ pub mod registration_ticket;
 pub mod registry_item;
 pub mod relay;
 pub mod renote_muting;
-pub mod reversi_game;
-pub mod reversi_matching;
 pub mod sea_orm_active_enums;
 pub mod signin;
 pub mod sw_subscription;
diff --git a/packages/backend/native-utils/crates/model/src/entity/prelude.rs b/packages/backend/native-utils/crates/model/src/entity/prelude.rs
index 7cab688bb..8be696cb4 100644
--- a/packages/backend/native-utils/crates/model/src/entity/prelude.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/prelude.rs
@@ -50,8 +50,6 @@ pub use super::registration_ticket::Entity as RegistrationTicket;
 pub use super::registry_item::Entity as RegistryItem;
 pub use super::relay::Entity as Relay;
 pub use super::renote_muting::Entity as RenoteMuting;
-pub use super::reversi_game::Entity as ReversiGame;
-pub use super::reversi_matching::Entity as ReversiMatching;
 pub use super::signin::Entity as Signin;
 pub use super::sw_subscription::Entity as SwSubscription;
 pub use super::used_username::Entity as UsedUsername;
diff --git a/packages/backend/native-utils/crates/model/src/entity/reversi_game.rs b/packages/backend/native-utils/crates/model/src/entity/reversi_game.rs
deleted file mode 100644
index 0c82fccf7..000000000
--- a/packages/backend/native-utils/crates/model/src/entity/reversi_game.rs
+++ /dev/null
@@ -1,69 +0,0 @@
-//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
-
-use sea_orm::entity::prelude::*;
-
-use super::newtype::StringVec;
-
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
-#[sea_orm(table_name = "reversi_game")]
-pub struct Model {
-    #[sea_orm(primary_key, auto_increment = false)]
-    pub id: String,
-    #[sea_orm(column_name = "createdAt")]
-    pub created_at: DateTimeWithTimeZone,
-    #[sea_orm(column_name = "startedAt")]
-    pub started_at: Option<DateTimeWithTimeZone>,
-    #[sea_orm(column_name = "user1Id")]
-    pub user1_id: String,
-    #[sea_orm(column_name = "user2Id")]
-    pub user2_id: String,
-    #[sea_orm(column_name = "user1Accepted")]
-    pub user1_accepted: bool,
-    #[sea_orm(column_name = "user2Accepted")]
-    pub user2_accepted: bool,
-    pub black: Option<i32>,
-    #[sea_orm(column_name = "isStarted")]
-    pub is_started: bool,
-    #[sea_orm(column_name = "isEnded")]
-    pub is_ended: bool,
-    #[sea_orm(column_name = "winnerId")]
-    pub winner_id: Option<String>,
-    pub surrendered: Option<String>,
-    #[sea_orm(column_type = "JsonBinary")]
-    pub logs: Json,
-    pub map: StringVec,
-    pub bw: String,
-    #[sea_orm(column_name = "isLlotheo")]
-    pub is_llotheo: bool,
-    #[sea_orm(column_name = "canPutEverywhere")]
-    pub can_put_everywhere: bool,
-    #[sea_orm(column_name = "loopedBoard")]
-    pub looped_board: bool,
-    #[sea_orm(column_type = "JsonBinary", nullable)]
-    pub form1: Option<Json>,
-    #[sea_orm(column_type = "JsonBinary", nullable)]
-    pub form2: Option<Json>,
-    pub crc32: Option<String>,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::user::Entity",
-        from = "Column::User2Id",
-        to = "super::user::Column::Id",
-        on_update = "NoAction",
-        on_delete = "Cascade"
-    )]
-    User2,
-    #[sea_orm(
-        belongs_to = "super::user::Entity",
-        from = "Column::User1Id",
-        to = "super::user::Column::Id",
-        on_update = "NoAction",
-        on_delete = "Cascade"
-    )]
-    User1,
-}
-
-impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/src/entity/reversi_matching.rs b/packages/backend/native-utils/crates/model/src/entity/reversi_matching.rs
deleted file mode 100644
index aafdf13f6..000000000
--- a/packages/backend/native-utils/crates/model/src/entity/reversi_matching.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
-
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
-#[sea_orm(table_name = "reversi_matching")]
-pub struct Model {
-    #[sea_orm(primary_key, auto_increment = false)]
-    pub id: String,
-    #[sea_orm(column_name = "createdAt")]
-    pub created_at: DateTimeWithTimeZone,
-    #[sea_orm(column_name = "parentId")]
-    pub parent_id: String,
-    #[sea_orm(column_name = "childId")]
-    pub child_id: String,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
-    #[sea_orm(
-        belongs_to = "super::user::Entity",
-        from = "Column::ParentId",
-        to = "super::user::Column::Id",
-        on_update = "NoAction",
-        on_delete = "Cascade"
-    )]
-    User2,
-    #[sea_orm(
-        belongs_to = "super::user::Entity",
-        from = "Column::ChildId",
-        to = "super::user::Column::Id",
-        on_update = "NoAction",
-        on_delete = "Cascade"
-    )]
-    User1,
-}
-
-impl ActiveModelBehavior for ActiveModel {}
diff --git a/packages/backend/native-utils/crates/model/tests/common.rs b/packages/backend/native-utils/crates/model/tests/common.rs
index f05c4432d..0fd32d8f9 100644
--- a/packages/backend/native-utils/crates/model/tests/common.rs
+++ b/packages/backend/native-utils/crates/model/tests/common.rs
@@ -31,7 +31,7 @@ async fn setup_schema(db: DbConn) {
     let stmt = schema.create_table_from_entity(antenna::Entity);
     db.execute(db.get_database_backend().build(&stmt))
         .await
-        .expect("Unable to initialize in-memoty sqlite");
+        .expect("Unable to setup schemas for in-memoty sqlite");
 }
 
 /// Delete all entries in the database.
@@ -101,9 +101,7 @@ async fn setup_model(db: &DbConn) {
 mod int_test {
     use sea_orm::Database;
 
-    use crate::setup_schema;
-
-    use super::{cleanup, prepare};
+    use super::{cleanup, prepare, setup_schema};
 
     #[tokio::test]
     async fn can_prepare_and_cleanup() {

From d533734575c4c5070fd5cd9b93c4fb564048e06d Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Thu, 1 Jun 2023 04:06:31 -0400
Subject: [PATCH 159/198] fix primary key specifier

---
 .../src/m20230531_180824_stringvec.rs         | 104 +++++++++++-------
 1 file changed, 64 insertions(+), 40 deletions(-)

diff --git a/packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs b/packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs
index b7c0a70b3..d9c54fd74 100644
--- a/packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs
+++ b/packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs
@@ -1,7 +1,7 @@
 use model::entity::{
     access_token, antenna, app, emoji, gallery_post, hashtag, messaging_message, meta,
     newtype::{I32Vec, StringVec},
-    note, note_edit, poll, registry_item, user, user_profile, webhook,
+    note, note_edit, page, poll, registry_item, user, user_profile, webhook,
 };
 use sea_orm_migration::{
     prelude::*,
@@ -181,37 +181,13 @@ impl MigrationTrait for Migration {
             note_edit,
             file_ids
         );
-
-        // Convert poll here because its primary key is not id, but note_id.
-        let query = select_query(Poll::Table, Poll::NoteId, Poll::Choices);
-        let res = get_vec::<Vec<String>>(db, query).await?;
-        convert_col(manager, Poll::Table, Poll::Choices).await?;
-        let poll_models: Vec<poll::ActiveModel> = res
-            .iter()
-            .map(|(id, r)| poll::ActiveModel {
-                note_id: sea_orm::Set(id.to_owned()),
-                choices: sea_orm::Set(StringVec::from(r.to_owned())),
-                ..Default::default()
-            })
-            .collect();
-        for model in poll_models {
-            poll::Entity::update(model).exec(db).await?;
-        }
-        let query = select_query(Poll::Table, Poll::NoteId, Poll::Votes);
-        let res = get_vec::<Vec<i32>>(db, query).await?;
-        convert_col(manager, Poll::Table, Poll::Votes).await?;
-        let poll_models: Vec<poll::ActiveModel> = res
-            .iter()
-            .map(|(id, r)| poll::ActiveModel {
-                note_id: sea_orm::Set(id.to_owned()),
-                votes: sea_orm::Set(I32Vec::from(r.to_owned())),
-                ..Default::default()
-            })
-            .collect();
-        for model in poll_models {
-            poll::Entity::update(model).exec(db).await?;
-        }
-
+        convert_to_stringvec_json!(
+            Page::Table,
+            Page::Id,
+            Page::VisibleUserIds,
+            page,
+            visible_user_ids
+        );
         convert_to_stringvec_json!(
             RegistryItem::Table,
             RegistryItem::Id,
@@ -221,15 +197,63 @@ impl MigrationTrait for Migration {
         );
         convert_to_stringvec_json!(User::Table, User::Id, User::Tags, user, tags);
         convert_to_stringvec_json!(User::Table, User::Id, User::Emojis, user, emojis);
-        convert_to_stringvec_json!(
-            UserProfile::Table,
-            UserProfile::Id,
-            UserProfile::MutingNotificationTypes,
-            user_profile,
-            muting_notification_types
-        );
         convert_to_stringvec_json!(Webhook::Table, Webhook::Id, Webhook::On, webhook, on);
 
+        // Convert poll here because its primary key is not id, but note_id.
+        let query = select_query(Poll::Table, Poll::NoteId, Poll::Choices);
+        let res = get_vec::<Vec<String>>(db, query).await?;
+        convert_col(manager, Poll::Table, Poll::Choices).await?;
+        let models: Vec<poll::ActiveModel> = res
+            .iter()
+            .map(|(id, r)| poll::ActiveModel {
+                note_id: sea_orm::Set(id.to_owned()),
+                choices: sea_orm::Set(StringVec::from(r.to_owned())),
+                ..Default::default()
+            })
+            .collect();
+        for model in models {
+            poll::Entity::update(model).exec(db).await?;
+        }
+        let query = select_query(Poll::Table, Poll::NoteId, Poll::Votes);
+        let res = get_vec::<Vec<i32>>(db, query).await?;
+        convert_col(manager, Poll::Table, Poll::Votes).await?;
+        let models: Vec<poll::ActiveModel> = res
+            .iter()
+            .map(|(id, r)| poll::ActiveModel {
+                note_id: sea_orm::Set(id.to_owned()),
+                votes: sea_orm::Set(I32Vec::from(r.to_owned())),
+                ..Default::default()
+            })
+            .collect();
+        for model in models {
+            poll::Entity::update(model).exec(db).await?;
+        }
+
+        // Convert user_profile here because its primary key is not id, but user_id.
+        let query = select_query(
+            UserProfile::Table,
+            UserProfile::UserId,
+            UserProfile::MutingNotificationTypes,
+        );
+        let res = get_vec::<Vec<String>>(db, query).await?;
+        convert_col(
+            manager,
+            UserProfile::Table,
+            UserProfile::MutingNotificationTypes,
+        )
+        .await?;
+        let models: Vec<user_profile::ActiveModel> = res
+            .iter()
+            .map(|(id, r)| user_profile::ActiveModel {
+                user_id: sea_orm::Set(id.to_owned()),
+                muting_notification_types: sea_orm::Set(StringVec::from(r.to_owned())),
+                ..Default::default()
+            })
+            .collect();
+        for model in models {
+            user_profile::Entity::update(model).exec(db).await?;
+        }
+
         Ok(())
     }
 
@@ -346,7 +370,7 @@ enum User {
 #[derive(Iden, Clone)]
 enum UserProfile {
     Table,
-    Id,
+    UserId,
     MutingNotificationTypes,
 }
 #[derive(Iden, Clone)]

From a98093bd61b1d5055694d3a6ad01db31c25ee302 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Thu, 1 Jun 2023 04:24:45 -0400
Subject: [PATCH 160/198] use iden enum

---
 .../src/m20230531_180824_stringvec.rs          | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs b/packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs
index d9c54fd74..e561e18d5 100644
--- a/packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs
+++ b/packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs
@@ -16,13 +16,19 @@ pub struct Migration;
 impl MigrationTrait for Migration {
     async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
         manager
-            .drop_table(Table::drop().table(Alias::new("reversi_game")).to_owned())
+            .drop_table(
+                Table::drop()
+                    .if_exists()
+                    .table(ReversiGame::Table)
+                    .to_owned(),
+            )
             .await?;
 
         manager
             .drop_table(
                 Table::drop()
-                    .table(Alias::new("reversi_matching"))
+                    .if_exists()
+                    .table(ReversiMatching::Table)
                     .to_owned(),
             )
             .await?;
@@ -379,6 +385,14 @@ enum Webhook {
     Id,
     On,
 }
+#[derive(Iden)]
+enum ReversiGame {
+    Table,
+}
+#[derive(Iden)]
+enum ReversiMatching {
+    Table,
+}
 
 fn select_query<T: Iden + 'static>(table: T, id: T, col: T) -> String {
     Query::select()

From d059dc53c0f6b83b675cef833798431916d76003 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Thu, 1 Jun 2023 12:32:15 -0400
Subject: [PATCH 161/198] use thread to copy data

---
 .../native-utils/crates/migration/Cargo.toml  |   7 +
 .../native-utils/crates/migration/src/lib.rs  |   4 +-
 .../src/m20230531_180824_drop_reversi.rs      |  49 ++
 .../src/m20230531_180824_stringvec.rs         | 437 ------------------
 .../native-utils/crates/migration/src/main.rs |   5 +
 .../crates/migration/src/vec_to_json.rs       | 279 +++++++++++
 6 files changed, 342 insertions(+), 439 deletions(-)
 create mode 100644 packages/backend/native-utils/crates/migration/src/m20230531_180824_drop_reversi.rs
 delete mode 100644 packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs
 create mode 100644 packages/backend/native-utils/crates/migration/src/vec_to_json.rs

diff --git a/packages/backend/native-utils/crates/migration/Cargo.toml b/packages/backend/native-utils/crates/migration/Cargo.toml
index 46108c68c..49accc9a2 100644
--- a/packages/backend/native-utils/crates/migration/Cargo.toml
+++ b/packages/backend/native-utils/crates/migration/Cargo.toml
@@ -8,10 +8,17 @@ publish = false
 name = "migration"
 path = "src/lib.rs"
 
+[features]
+default = []
+convert = []
+
 [dependencies]
 async-std = { version = "1", features = ["attributes", "tokio1"] }
 serde_json = "1.0.96"
 model = { path = "../model" }
+indicatif = { version = "0.17.4", features = ["tokio"] }
+tokio = { version = "1.28.2", features = ["full"] }
+futures = "0.3.28"
 
 [dependencies.sea-orm-migration]
 version = "0.11.0"
diff --git a/packages/backend/native-utils/crates/migration/src/lib.rs b/packages/backend/native-utils/crates/migration/src/lib.rs
index a2a1b932f..4835c2d3d 100644
--- a/packages/backend/native-utils/crates/migration/src/lib.rs
+++ b/packages/backend/native-utils/crates/migration/src/lib.rs
@@ -1,12 +1,12 @@
 pub use sea_orm_migration::prelude::*;
 
-mod m20230531_180824_stringvec;
+mod m20230531_180824_drop_reversi;
 
 pub struct Migrator;
 
 #[async_trait::async_trait]
 impl MigratorTrait for Migrator {
     fn migrations() -> Vec<Box<dyn MigrationTrait>> {
-        vec![Box::new(m20230531_180824_stringvec::Migration)]
+        vec![Box::new(m20230531_180824_drop_reversi::Migration)]
     }
 }
diff --git a/packages/backend/native-utils/crates/migration/src/m20230531_180824_drop_reversi.rs b/packages/backend/native-utils/crates/migration/src/m20230531_180824_drop_reversi.rs
new file mode 100644
index 000000000..c2726dd76
--- /dev/null
+++ b/packages/backend/native-utils/crates/migration/src/m20230531_180824_drop_reversi.rs
@@ -0,0 +1,49 @@
+use sea_orm_migration::{
+    prelude::*,
+    sea_orm::{DbBackend, Statement},
+};
+
+#[derive(DeriveMigrationName)]
+pub struct Migration;
+
+#[async_trait::async_trait]
+impl MigrationTrait for Migration {
+    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
+        if manager.get_database_backend() == DbBackend::Sqlite {
+            return Ok(());
+        }
+
+        let db = manager.get_connection();
+        db.query_one(Statement::from_string(
+            DbBackend::Postgres,
+            Table::drop()
+                .table(ReversiGame::Table)
+                .to_string(PostgresQueryBuilder),
+        ))
+        .await?;
+        db.query_one(Statement::from_string(
+            DbBackend::Postgres,
+            Table::drop()
+                .table(ReversiMatching::Table)
+                .to_string(PostgresQueryBuilder),
+        ))
+        .await?;
+
+        Ok(())
+    }
+
+    async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
+        // Replace the sample below with your own migration scripts
+        Ok(())
+    }
+}
+
+/// Learn more at https://docs.rs/sea-query#iden
+#[derive(Iden)]
+enum ReversiGame {
+    Table,
+}
+#[derive(Iden)]
+enum ReversiMatching {
+    Table,
+}
diff --git a/packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs b/packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs
deleted file mode 100644
index e561e18d5..000000000
--- a/packages/backend/native-utils/crates/migration/src/m20230531_180824_stringvec.rs
+++ /dev/null
@@ -1,437 +0,0 @@
-use model::entity::{
-    access_token, antenna, app, emoji, gallery_post, hashtag, messaging_message, meta,
-    newtype::{I32Vec, StringVec},
-    note, note_edit, page, poll, registry_item, user, user_profile, webhook,
-};
-use sea_orm_migration::{
-    prelude::*,
-    sea_orm::{DbBackend, EntityTrait, Statement, TryGetable},
-};
-use serde_json::json;
-
-#[derive(DeriveMigrationName)]
-pub struct Migration;
-
-#[async_trait::async_trait]
-impl MigrationTrait for Migration {
-    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
-        manager
-            .drop_table(
-                Table::drop()
-                    .if_exists()
-                    .table(ReversiGame::Table)
-                    .to_owned(),
-            )
-            .await?;
-
-        manager
-            .drop_table(
-                Table::drop()
-                    .if_exists()
-                    .table(ReversiMatching::Table)
-                    .to_owned(),
-            )
-            .await?;
-
-        if manager.get_database_backend() == DbBackend::Sqlite {
-            return Ok(());
-        }
-
-        let db = manager.get_connection();
-
-        macro_rules! copy_data {
-            ($data:ident, $ent:ident, $col:tt) => {
-                let models: Vec<$ent::ActiveModel> = $data
-                    .iter()
-                    .map(|(id, r)| $ent::ActiveModel {
-                        id: sea_orm::Set(id.to_owned()),
-                        $col: sea_orm::Set(StringVec::from(r.to_owned())),
-                        ..Default::default()
-                    })
-                    .collect();
-                for model in models {
-                    $ent::Entity::update(model).exec(db).await?;
-                }
-            };
-        }
-
-        macro_rules! convert_to_stringvec_json {
-            ($table:expr, $id:expr, $col:expr, $ent:ident, $col_name:tt) => {
-                let query = select_query($table, $id, $col);
-                let res = get_vec::<Vec<String>>(db, query).await?;
-                convert_col(manager, $table, $col).await?;
-                copy_data!(res, $ent, $col_name);
-            };
-        }
-
-        convert_to_stringvec_json!(
-            AccessToken::Table,
-            AccessToken::Id,
-            AccessToken::Permission,
-            access_token,
-            permission
-        );
-        convert_to_stringvec_json!(Antenna::Table, Antenna::Id, Antenna::Users, antenna, users);
-        convert_to_stringvec_json!(App::Table, App::Id, App::Permission, app, permission);
-        convert_to_stringvec_json!(Emoji::Table, Emoji::Id, Emoji::Aliases, emoji, aliases);
-        convert_to_stringvec_json!(
-            GalleryPost::Table,
-            GalleryPost::Id,
-            GalleryPost::FileIds,
-            gallery_post,
-            file_ids
-        );
-        convert_to_stringvec_json!(
-            GalleryPost::Table,
-            GalleryPost::Id,
-            GalleryPost::Tags,
-            gallery_post,
-            tags
-        );
-        convert_to_stringvec_json!(
-            Hashtag::Table,
-            Hashtag::Id,
-            Hashtag::MentionedUserIds,
-            hashtag,
-            mentioned_user_ids
-        );
-        convert_to_stringvec_json!(
-            Hashtag::Table,
-            Hashtag::Id,
-            Hashtag::MentionedLocalUserIds,
-            hashtag,
-            mentioned_local_user_ids
-        );
-        convert_to_stringvec_json!(
-            Hashtag::Table,
-            Hashtag::Id,
-            Hashtag::MentionedRemoteUserIds,
-            hashtag,
-            mentioned_remote_user_ids
-        );
-        convert_to_stringvec_json!(
-            Hashtag::Table,
-            Hashtag::Id,
-            Hashtag::AttachedUserIds,
-            hashtag,
-            attached_user_ids
-        );
-        convert_to_stringvec_json!(
-            Hashtag::Table,
-            Hashtag::Id,
-            Hashtag::AttachedLocalUserIds,
-            hashtag,
-            attached_local_user_ids
-        );
-        convert_to_stringvec_json!(
-            Hashtag::Table,
-            Hashtag::Id,
-            Hashtag::AttachedRemoteUserIds,
-            hashtag,
-            attached_remote_user_ids
-        );
-        convert_to_stringvec_json!(
-            MessagingMessage::Table,
-            MessagingMessage::Id,
-            MessagingMessage::Reads,
-            messaging_message,
-            reads
-        );
-        convert_to_stringvec_json!(Meta::Table, Meta::Id, Meta::Langs, meta, langs);
-        convert_to_stringvec_json!(
-            Meta::Table,
-            Meta::Id,
-            Meta::BlockedHosts,
-            meta,
-            blocked_hosts
-        );
-        convert_to_stringvec_json!(Meta::Table, Meta::Id, Meta::HiddenTags, meta, hidden_tags);
-        convert_to_stringvec_json!(Meta::Table, Meta::Id, Meta::PinnedUsers, meta, pinned_users);
-        convert_to_stringvec_json!(Meta::Table, Meta::Id, Meta::PinnedPages, meta, pinned_pages);
-        convert_to_stringvec_json!(
-            Meta::Table,
-            Meta::Id,
-            Meta::RecommendedInstances,
-            meta,
-            recommended_instances
-        );
-        convert_to_stringvec_json!(
-            Meta::Table,
-            Meta::Id,
-            Meta::SilencedHosts,
-            meta,
-            silenced_hosts
-        );
-        convert_to_stringvec_json!(Note::Table, Note::Id, Note::FileIds, note, file_ids);
-        convert_to_stringvec_json!(
-            Note::Table,
-            Note::Id,
-            Note::AttachedFileTypes,
-            note,
-            attached_file_types
-        );
-        convert_to_stringvec_json!(
-            Note::Table,
-            Note::Id,
-            Note::VisibleUserIds,
-            note,
-            visible_user_ids
-        );
-        convert_to_stringvec_json!(Note::Table, Note::Id, Note::Mentions, note, mentions);
-        convert_to_stringvec_json!(Note::Table, Note::Id, Note::Emojis, note, emojis);
-        convert_to_stringvec_json!(Note::Table, Note::Id, Note::Tags, note, tags);
-        convert_to_stringvec_json!(
-            NoteEdit::Table,
-            NoteEdit::Id,
-            NoteEdit::FileIds,
-            note_edit,
-            file_ids
-        );
-        convert_to_stringvec_json!(
-            Page::Table,
-            Page::Id,
-            Page::VisibleUserIds,
-            page,
-            visible_user_ids
-        );
-        convert_to_stringvec_json!(
-            RegistryItem::Table,
-            RegistryItem::Id,
-            RegistryItem::Scope,
-            registry_item,
-            scope
-        );
-        convert_to_stringvec_json!(User::Table, User::Id, User::Tags, user, tags);
-        convert_to_stringvec_json!(User::Table, User::Id, User::Emojis, user, emojis);
-        convert_to_stringvec_json!(Webhook::Table, Webhook::Id, Webhook::On, webhook, on);
-
-        // Convert poll here because its primary key is not id, but note_id.
-        let query = select_query(Poll::Table, Poll::NoteId, Poll::Choices);
-        let res = get_vec::<Vec<String>>(db, query).await?;
-        convert_col(manager, Poll::Table, Poll::Choices).await?;
-        let models: Vec<poll::ActiveModel> = res
-            .iter()
-            .map(|(id, r)| poll::ActiveModel {
-                note_id: sea_orm::Set(id.to_owned()),
-                choices: sea_orm::Set(StringVec::from(r.to_owned())),
-                ..Default::default()
-            })
-            .collect();
-        for model in models {
-            poll::Entity::update(model).exec(db).await?;
-        }
-        let query = select_query(Poll::Table, Poll::NoteId, Poll::Votes);
-        let res = get_vec::<Vec<i32>>(db, query).await?;
-        convert_col(manager, Poll::Table, Poll::Votes).await?;
-        let models: Vec<poll::ActiveModel> = res
-            .iter()
-            .map(|(id, r)| poll::ActiveModel {
-                note_id: sea_orm::Set(id.to_owned()),
-                votes: sea_orm::Set(I32Vec::from(r.to_owned())),
-                ..Default::default()
-            })
-            .collect();
-        for model in models {
-            poll::Entity::update(model).exec(db).await?;
-        }
-
-        // Convert user_profile here because its primary key is not id, but user_id.
-        let query = select_query(
-            UserProfile::Table,
-            UserProfile::UserId,
-            UserProfile::MutingNotificationTypes,
-        );
-        let res = get_vec::<Vec<String>>(db, query).await?;
-        convert_col(
-            manager,
-            UserProfile::Table,
-            UserProfile::MutingNotificationTypes,
-        )
-        .await?;
-        let models: Vec<user_profile::ActiveModel> = res
-            .iter()
-            .map(|(id, r)| user_profile::ActiveModel {
-                user_id: sea_orm::Set(id.to_owned()),
-                muting_notification_types: sea_orm::Set(StringVec::from(r.to_owned())),
-                ..Default::default()
-            })
-            .collect();
-        for model in models {
-            user_profile::Entity::update(model).exec(db).await?;
-        }
-
-        Ok(())
-    }
-
-    async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
-        // Replace the sample below with your own migration scripts
-        Ok(())
-    }
-}
-
-/// Learn more at https://docs.rs/sea-query#iden
-#[derive(Iden, Clone)]
-enum Antenna {
-    Table,
-    Id,
-    Users,
-}
-#[derive(Iden, Clone)]
-enum AccessToken {
-    Table,
-    Id,
-    Permission,
-}
-#[derive(Iden, Clone)]
-enum App {
-    Table,
-    Id,
-    Permission,
-}
-#[derive(Iden, Clone)]
-enum Emoji {
-    Table,
-    Id,
-    Aliases,
-}
-#[derive(Iden, Clone)]
-enum GalleryPost {
-    Table,
-    Id,
-    FileIds,
-    Tags,
-}
-#[derive(Iden, Clone)]
-enum Hashtag {
-    Table,
-    Id,
-    MentionedUserIds,
-    MentionedLocalUserIds,
-    MentionedRemoteUserIds,
-    AttachedUserIds,
-    AttachedLocalUserIds,
-    AttachedRemoteUserIds,
-}
-#[derive(Iden, Clone)]
-enum MessagingMessage {
-    Table,
-    Id,
-    Reads,
-}
-#[derive(Iden, Clone)]
-enum Meta {
-    Table,
-    Id,
-    Langs,
-    HiddenTags,
-    BlockedHosts,
-    PinnedUsers,
-    PinnedPages,
-    RecommendedInstances,
-    SilencedHosts,
-}
-#[derive(Iden, Clone)]
-enum Note {
-    Table,
-    Id,
-    FileIds,
-    AttachedFileTypes,
-    VisibleUserIds,
-    Mentions,
-    Emojis,
-    Tags,
-}
-#[derive(Iden, Clone)]
-enum NoteEdit {
-    Table,
-    Id,
-    FileIds,
-}
-#[derive(Iden, Clone)]
-enum Page {
-    Table,
-    Id,
-    VisibleUserIds,
-}
-#[derive(Iden, Clone)]
-enum Poll {
-    Table,
-    NoteId,
-    Choices,
-    Votes, // I32Vec
-}
-#[derive(Iden, Clone)]
-enum RegistryItem {
-    Table,
-    Id,
-    Scope,
-}
-#[derive(Iden, Clone)]
-enum User {
-    Table,
-    Id,
-    Tags,
-    Emojis,
-}
-#[derive(Iden, Clone)]
-enum UserProfile {
-    Table,
-    UserId,
-    MutingNotificationTypes,
-}
-#[derive(Iden, Clone)]
-enum Webhook {
-    Table,
-    Id,
-    On,
-}
-#[derive(Iden)]
-enum ReversiGame {
-    Table,
-}
-#[derive(Iden)]
-enum ReversiMatching {
-    Table,
-}
-
-fn select_query<T: Iden + 'static>(table: T, id: T, col: T) -> String {
-    Query::select()
-        .column(id)
-        .column(col)
-        .from(table)
-        .to_string(PostgresQueryBuilder)
-}
-
-async fn get_vec<'a, T: TryGetable>(
-    db: &SchemaManagerConnection<'a>,
-    query: String,
-) -> Result<Vec<(String, T)>, DbErr> {
-    let res: Vec<(String, T)> = db
-        .query_all(Statement::from_string(DbBackend::Postgres, query))
-        .await?
-        .iter()
-        .filter_map(|r| r.try_get_many_by_index().ok())
-        .collect();
-    Ok(res)
-}
-
-async fn convert_col<'a, T: Iden + Clone + 'static>(
-    manager: &SchemaManager<'a>,
-    table: T,
-    col: T,
-) -> Result<(), DbErr> {
-    manager
-        .alter_table(
-            Table::alter()
-                .table(table)
-                .drop_column(col.to_owned())
-                .add_column(
-                    ColumnDef::new(col.to_owned())
-                        .json_binary()
-                        .not_null()
-                        .default(json!([])),
-                )
-                .to_owned(),
-        )
-        .await
-}
diff --git a/packages/backend/native-utils/crates/migration/src/main.rs b/packages/backend/native-utils/crates/migration/src/main.rs
index c6b6e48db..31a72026e 100644
--- a/packages/backend/native-utils/crates/migration/src/main.rs
+++ b/packages/backend/native-utils/crates/migration/src/main.rs
@@ -1,6 +1,11 @@
 use sea_orm_migration::prelude::*;
 
+mod vec_to_json;
+
 #[async_std::main]
 async fn main() {
     cli::run_cli(migration::Migrator).await;
+
+    #[cfg(feature = "convert")]
+    vec_to_json::convert().await;
 }
diff --git a/packages/backend/native-utils/crates/migration/src/vec_to_json.rs b/packages/backend/native-utils/crates/migration/src/vec_to_json.rs
new file mode 100644
index 000000000..eccd80114
--- /dev/null
+++ b/packages/backend/native-utils/crates/migration/src/vec_to_json.rs
@@ -0,0 +1,279 @@
+#![allow(dead_code)]
+
+use sea_orm_migration::{prelude::*, sea_orm::{DbConn, DbBackend, Statement, TryGetable, Database}};
+use serde_json::json;
+use std::time::Duration;
+use indicatif::{ProgressBar, ProgressStyle, MultiProgress};
+use model::entity::newtype::{I32Vec, StringVec};
+use std::env;
+
+pub async fn convert() {
+    let uri = env::var("DATABASE_URL").expect("Environment variable 'DATABASE_URL' not set");
+
+    let db = Database::connect(uri).await.expect("Unable to connect");
+    let mp = MultiProgress::new();
+
+    let handlers = vec![
+        tokio::spawn(to_json::<AccessToken, Vec<String>, StringVec>(db.clone(), mp.clone(), AccessToken::Table, AccessToken::Id, AccessToken::Permission)),
+        tokio::spawn(to_json::<Antenna, Vec<String>, StringVec>(db.clone(), mp.clone(), Antenna::Table, Antenna::Id, Antenna::Users)),
+        tokio::spawn(to_json::<App, Vec<String>, StringVec>(db.clone(), mp.clone(), App::Table, App::Id, App::Permission)),
+        tokio::spawn(to_json::<Emoji, Vec<String>, StringVec>(db.clone(), mp.clone(), Emoji::Table, Emoji::Id, Emoji::Aliases)),
+        tokio::spawn(to_json::<GalleryPost, Vec<String>, StringVec>(db.clone(), mp.clone(), GalleryPost::Table, GalleryPost::Id, GalleryPost::FileIds)),
+        tokio::spawn(to_json::<GalleryPost, Vec<String>, StringVec>(db.clone(), mp.clone(), GalleryPost::Table, GalleryPost::Id, GalleryPost::Tags)),
+        tokio::spawn(to_json::<Hashtag, Vec<String>, StringVec>(db.clone(), mp.clone(), Hashtag::Table, Hashtag::Id, Hashtag::MentionedUserIds)),
+        tokio::spawn(to_json::<Hashtag, Vec<String>, StringVec>(db.clone(), mp.clone(), Hashtag::Table, Hashtag::Id, Hashtag::MentionedLocalUserIds)),
+        tokio::spawn(to_json::<Hashtag, Vec<String>, StringVec>(db.clone(), mp.clone(), Hashtag::Table, Hashtag::Id, Hashtag::MentionedRemoteUserIds)),
+        tokio::spawn(to_json::<Hashtag, Vec<String>, StringVec>(db.clone(), mp.clone(), Hashtag::Table, Hashtag::Id, Hashtag::AttachedUserIds)),
+        tokio::spawn(to_json::<Hashtag, Vec<String>, StringVec>(db.clone(), mp.clone(), Hashtag::Table, Hashtag::Id, Hashtag::AttachedLocalUserIds)),
+        tokio::spawn(to_json::<Hashtag, Vec<String>, StringVec>(db.clone(), mp.clone(), Hashtag::Table, Hashtag::Id, Hashtag::AttachedRemoteUserIds)),
+        tokio::spawn(to_json::<MessagingMessage, Vec<String>, StringVec>(db.clone(), mp.clone(), MessagingMessage::Table, MessagingMessage::Id, MessagingMessage::Reads)),
+        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(db.clone(), mp.clone(), Meta::Table, Meta::Id, Meta::Langs)),
+        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(db.clone(), mp.clone(), Meta::Table, Meta::Id, Meta::BlockedHosts)),
+        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(db.clone(), mp.clone(), Meta::Table, Meta::Id, Meta::HiddenTags)),
+        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(db.clone(), mp.clone(), Meta::Table, Meta::Id, Meta::PinnedUsers)),
+        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(db.clone(), mp.clone(), Meta::Table, Meta::Id, Meta::PinnedPages)),
+        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(db.clone(), mp.clone(), Meta::Table, Meta::Id, Meta::RecommendedInstances)),
+        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(db.clone(), mp.clone(), Meta::Table, Meta::Id, Meta::SilencedHosts)),
+        tokio::spawn(to_json::<Note, Vec<String>, StringVec>(db.clone(), mp.clone(), Note::Table, Note::Id, Note::FileIds)),
+        tokio::spawn(to_json::<Note, Vec<String>, StringVec>(db.clone(), mp.clone(), Note::Table, Note::Id, Note::AttachedFileTypes)),
+        tokio::spawn(to_json::<Note, Vec<String>, StringVec>(db.clone(), mp.clone(), Note::Table, Note::Id, Note::VisibleUserIds)),
+        tokio::spawn(to_json::<Note, Vec<String>, StringVec>(db.clone(), mp.clone(), Note::Table, Note::Id, Note::Mentions)),
+        tokio::spawn(to_json::<Note, Vec<String>, StringVec>(db.clone(), mp.clone(), Note::Table, Note::Id, Note::Emojis)),
+        tokio::spawn(to_json::<Note, Vec<String>, StringVec>(db.clone(), mp.clone(), Note::Table, Note::Id, Note::Tags)),
+        tokio::spawn(to_json::<NoteEdit, Vec<String>, StringVec>(db.clone(), mp.clone(), NoteEdit::Table, NoteEdit::Id, NoteEdit::FileIds)),
+        tokio::spawn(to_json::<Page, Vec<String>, StringVec>(db.clone(), mp.clone(), Page::Table, Page::Id, Page::VisibleUserIds)),
+        tokio::spawn(to_json::<RegistryItem, Vec<String>, StringVec>(db.clone(), mp.clone(), RegistryItem::Table, RegistryItem::Id, RegistryItem::Scope)),
+        tokio::spawn(to_json::<User, Vec<String>, StringVec>(db.clone(), mp.clone(), User::Table, User::Id, User::Tags)),
+        tokio::spawn(to_json::<User, Vec<String>, StringVec>(db.clone(), mp.clone(), User::Table, User::Id, User::Emojis)),
+        tokio::spawn(to_json::<Webhook, Vec<String>, StringVec>(db.clone(), mp.clone(), Webhook::Table, Webhook::Id, Webhook::On)),
+        tokio::spawn(to_json::<Poll, Vec<String>, StringVec>(db.clone(), mp.clone(), Poll::Table, Poll::NoteId, Poll::Choices)),
+        tokio::spawn(to_json::<Poll, Vec<i32>, I32Vec>(db.clone(), mp.clone(), Poll::Table, Poll::NoteId, Poll::Votes)),
+        tokio::spawn(to_json::<UserProfile, Vec<String>, StringVec>(db.clone(), mp.clone(), UserProfile::Table, UserProfile::UserId, UserProfile::MutingNotificationTypes)),
+    ];
+
+    futures::future::join_all(handlers).await;
+}
+
+fn select_query<T: Iden + 'static>(table: T, id: T, col: T) -> String {
+    Query::select()
+        .column(id)
+        .column(col)
+        .from(table)
+        .to_string(PostgresQueryBuilder)
+}
+
+async fn get_vec<T: TryGetable>(
+    db: &DbConn,
+    query: String,
+) -> Result<Vec<(String, T)>, DbErr> {
+    let res: Vec<(String, T)> = db
+        .query_all(Statement::from_string(DbBackend::Postgres, query))
+        .await?
+        .iter()
+        .filter_map(|r| r.try_get_many_by_index().ok())
+        .collect();
+    Ok(res)
+}
+
+async fn convert_col<T: Iden + Clone + 'static>(
+    db: &DbConn,
+    table: T,
+    col: T,
+) -> Result<(), DbErr> {
+    let stmt = Table::alter()
+                .table(table)
+                .drop_column(col.to_owned())
+                .add_column(
+                    ColumnDef::new(col.to_owned())
+                        .json_binary()
+                        .not_null()
+                        .default(json!([])),
+                ).to_string(PostgresQueryBuilder);
+    db.query_one(Statement::from_string(DbBackend::Postgres, stmt)).await?;
+    Ok(())
+}
+
+async fn to_json<T, U, V>(
+    db: DbConn,
+    mp: MultiProgress,
+    table: T,
+    id: T,
+    col: T,
+) -> Result<(), DbErr>
+where
+    T: Iden + Clone + 'static,
+    U: TryGetable + Clone,
+    V: From<U> + Into<SimpleExpr>,
+{
+    let query = select_query(table.clone(), id.clone(), col.clone());
+    let loading = ProgressBar::new_spinner()
+        .with_style(ProgressStyle::with_template("{prefix} {msg} {spinner}").unwrap())
+        .with_prefix("[-]")
+        .with_message(format!("Loading data from {}.{}", table.to_string(), col.to_string()));
+    loading.enable_steady_tick(Duration::from_millis(100));
+    let loading = mp.add(loading);
+    let res = get_vec::<U>(&db, query).await?;
+    let models: Vec<(String, V)> = res
+        .iter()
+        .map(|(id, r)| (id.clone(), <V>::from(r.clone())))
+        .collect();
+    loading.finish_and_clear();
+    convert_col(&db, table.clone(), col.clone()).await?;
+
+    let progress = ProgressBar::new(models.len() as u64)
+        .with_style(ProgressStyle::with_template("{prefix} {msg} {wide_bar} {pos}/{len}").unwrap().progress_chars("##-"))
+        .with_prefix("[*]")
+        .with_message(format!("Copying {}.{}", table.to_string(), col.to_string()));
+    let progress = mp.add(progress);
+
+    for model in models {
+        progress.inc(1);
+        let q = Query::update()
+            .table(table.clone())
+            .values([(col.clone(), model.1.into())])
+            .and_where(Expr::col(id.clone()).eq(model.0))
+            .to_string(PostgresQueryBuilder);
+        db.query_one(Statement::from_string(DbBackend::Postgres, q))
+            .await?;
+    }
+    progress.finish_with_message(format!("Complete {}.{}", table.to_string(), col.to_string()));
+
+    Ok(())
+}
+
+#[derive(Iden, Clone)]
+enum AccessToken {
+    Table,
+    Id,
+    Permission,
+}
+#[derive(Iden, Clone)]
+enum Antenna {
+    Table,
+    Id,
+    Users,
+}
+#[derive(Iden, Clone)]
+enum App {
+    Table,
+    Id,
+    Permission,
+}
+#[derive(Iden, Clone)]
+enum Emoji {
+    Table,
+    Id,
+    Aliases,
+}
+#[derive(Iden, Clone)]
+enum GalleryPost {
+    Table,
+    Id,
+    #[iden = "fileIds"]
+    FileIds,
+    Tags,
+}
+#[derive(Iden, Clone)]
+enum Hashtag {
+    Table,
+    Id,
+    #[iden = "mentionedUserIds"]
+    MentionedUserIds,
+    #[iden = "mentionedLocalUserIds"]
+    MentionedLocalUserIds,
+    #[iden = "mentionedRemoteUserIds"]
+    MentionedRemoteUserIds,
+    #[iden = "attachedUserIds"]
+    AttachedUserIds,
+    #[iden = "attachedLocalUserIds"]
+    AttachedLocalUserIds,
+    #[iden = "attachedRemoteUserIds"]
+    AttachedRemoteUserIds,
+}
+#[derive(Iden, Clone)]
+enum MessagingMessage {
+    Table,
+    Id,
+    Reads,
+}
+#[derive(Iden, Clone)]
+enum Meta {
+    Table,
+    Id,
+    Langs,
+    #[iden = "hiddenTags"]
+    HiddenTags,
+    #[iden = "blockedHosts"]
+    BlockedHosts,
+    #[iden = "pinnedUsers"]
+    PinnedUsers,
+    #[iden = "pinnedPages"]
+    PinnedPages,
+    #[iden = "recommendedInstances"]
+    RecommendedInstances,
+    #[iden = "silencedHosts"]
+    SilencedHosts,
+}
+#[derive(Iden, Clone)]
+enum Note {
+    Table,
+    Id,
+    #[iden = "fileIds"]
+    FileIds,
+    #[iden = "attachedFileTypes"]
+    AttachedFileTypes,
+    #[iden = "visibleUserIds"]
+    VisibleUserIds,
+    Mentions,
+    Emojis,
+    Tags,
+}
+#[derive(Iden, Clone)]
+enum NoteEdit {
+    Table,
+    Id,
+    #[iden = "fileIds"]
+    FileIds,
+}
+#[derive(Iden, Clone)]
+enum Page {
+    Table,
+    Id,
+    #[iden = "visibleUserIds"]
+    VisibleUserIds,
+}
+#[derive(Iden, Clone)]
+enum Poll {
+    Table,
+    #[iden = "noteId"]
+    NoteId,
+    Choices,
+    Votes, // I32Vec
+}
+#[derive(Iden, Clone)]
+enum RegistryItem {
+    Table,
+    Id,
+    Scope,
+}
+#[derive(Iden, Clone)]
+enum User {
+    Table,
+    Id,
+    Tags,
+    Emojis,
+}
+#[derive(Iden, Clone)]
+enum UserProfile {
+    Table,
+    #[iden = "userId"]
+    UserId,
+    #[iden = "mutingNotificationTypes"]
+    MutingNotificationTypes,
+}
+#[derive(Iden, Clone)]
+enum Webhook {
+    Table,
+    Id,
+    On,
+}

From ed8122ea8c7b70d450565b838b9b083b61ee38a2 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Thu, 1 Jun 2023 12:56:23 -0400
Subject: [PATCH 162/198] skip empty array

---
 .../crates/migration/src/vec_to_json.rs       | 335 +++++++++++++++---
 1 file changed, 278 insertions(+), 57 deletions(-)

diff --git a/packages/backend/native-utils/crates/migration/src/vec_to_json.rs b/packages/backend/native-utils/crates/migration/src/vec_to_json.rs
index eccd80114..c762dcf1c 100644
--- a/packages/backend/native-utils/crates/migration/src/vec_to_json.rs
+++ b/packages/backend/native-utils/crates/migration/src/vec_to_json.rs
@@ -1,11 +1,14 @@
 #![allow(dead_code)]
 
-use sea_orm_migration::{prelude::*, sea_orm::{DbConn, DbBackend, Statement, TryGetable, Database}};
-use serde_json::json;
-use std::time::Duration;
-use indicatif::{ProgressBar, ProgressStyle, MultiProgress};
+use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
 use model::entity::newtype::{I32Vec, StringVec};
+use sea_orm_migration::{
+    prelude::*,
+    sea_orm::{Database, DbBackend, DbConn, Statement, TryGetable},
+};
+use serde_json::json;
 use std::env;
+use std::time::Duration;
 
 pub async fn convert() {
     let uri = env::var("DATABASE_URL").expect("Environment variable 'DATABASE_URL' not set");
@@ -14,41 +17,251 @@ pub async fn convert() {
     let mp = MultiProgress::new();
 
     let handlers = vec![
-        tokio::spawn(to_json::<AccessToken, Vec<String>, StringVec>(db.clone(), mp.clone(), AccessToken::Table, AccessToken::Id, AccessToken::Permission)),
-        tokio::spawn(to_json::<Antenna, Vec<String>, StringVec>(db.clone(), mp.clone(), Antenna::Table, Antenna::Id, Antenna::Users)),
-        tokio::spawn(to_json::<App, Vec<String>, StringVec>(db.clone(), mp.clone(), App::Table, App::Id, App::Permission)),
-        tokio::spawn(to_json::<Emoji, Vec<String>, StringVec>(db.clone(), mp.clone(), Emoji::Table, Emoji::Id, Emoji::Aliases)),
-        tokio::spawn(to_json::<GalleryPost, Vec<String>, StringVec>(db.clone(), mp.clone(), GalleryPost::Table, GalleryPost::Id, GalleryPost::FileIds)),
-        tokio::spawn(to_json::<GalleryPost, Vec<String>, StringVec>(db.clone(), mp.clone(), GalleryPost::Table, GalleryPost::Id, GalleryPost::Tags)),
-        tokio::spawn(to_json::<Hashtag, Vec<String>, StringVec>(db.clone(), mp.clone(), Hashtag::Table, Hashtag::Id, Hashtag::MentionedUserIds)),
-        tokio::spawn(to_json::<Hashtag, Vec<String>, StringVec>(db.clone(), mp.clone(), Hashtag::Table, Hashtag::Id, Hashtag::MentionedLocalUserIds)),
-        tokio::spawn(to_json::<Hashtag, Vec<String>, StringVec>(db.clone(), mp.clone(), Hashtag::Table, Hashtag::Id, Hashtag::MentionedRemoteUserIds)),
-        tokio::spawn(to_json::<Hashtag, Vec<String>, StringVec>(db.clone(), mp.clone(), Hashtag::Table, Hashtag::Id, Hashtag::AttachedUserIds)),
-        tokio::spawn(to_json::<Hashtag, Vec<String>, StringVec>(db.clone(), mp.clone(), Hashtag::Table, Hashtag::Id, Hashtag::AttachedLocalUserIds)),
-        tokio::spawn(to_json::<Hashtag, Vec<String>, StringVec>(db.clone(), mp.clone(), Hashtag::Table, Hashtag::Id, Hashtag::AttachedRemoteUserIds)),
-        tokio::spawn(to_json::<MessagingMessage, Vec<String>, StringVec>(db.clone(), mp.clone(), MessagingMessage::Table, MessagingMessage::Id, MessagingMessage::Reads)),
-        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(db.clone(), mp.clone(), Meta::Table, Meta::Id, Meta::Langs)),
-        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(db.clone(), mp.clone(), Meta::Table, Meta::Id, Meta::BlockedHosts)),
-        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(db.clone(), mp.clone(), Meta::Table, Meta::Id, Meta::HiddenTags)),
-        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(db.clone(), mp.clone(), Meta::Table, Meta::Id, Meta::PinnedUsers)),
-        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(db.clone(), mp.clone(), Meta::Table, Meta::Id, Meta::PinnedPages)),
-        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(db.clone(), mp.clone(), Meta::Table, Meta::Id, Meta::RecommendedInstances)),
-        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(db.clone(), mp.clone(), Meta::Table, Meta::Id, Meta::SilencedHosts)),
-        tokio::spawn(to_json::<Note, Vec<String>, StringVec>(db.clone(), mp.clone(), Note::Table, Note::Id, Note::FileIds)),
-        tokio::spawn(to_json::<Note, Vec<String>, StringVec>(db.clone(), mp.clone(), Note::Table, Note::Id, Note::AttachedFileTypes)),
-        tokio::spawn(to_json::<Note, Vec<String>, StringVec>(db.clone(), mp.clone(), Note::Table, Note::Id, Note::VisibleUserIds)),
-        tokio::spawn(to_json::<Note, Vec<String>, StringVec>(db.clone(), mp.clone(), Note::Table, Note::Id, Note::Mentions)),
-        tokio::spawn(to_json::<Note, Vec<String>, StringVec>(db.clone(), mp.clone(), Note::Table, Note::Id, Note::Emojis)),
-        tokio::spawn(to_json::<Note, Vec<String>, StringVec>(db.clone(), mp.clone(), Note::Table, Note::Id, Note::Tags)),
-        tokio::spawn(to_json::<NoteEdit, Vec<String>, StringVec>(db.clone(), mp.clone(), NoteEdit::Table, NoteEdit::Id, NoteEdit::FileIds)),
-        tokio::spawn(to_json::<Page, Vec<String>, StringVec>(db.clone(), mp.clone(), Page::Table, Page::Id, Page::VisibleUserIds)),
-        tokio::spawn(to_json::<RegistryItem, Vec<String>, StringVec>(db.clone(), mp.clone(), RegistryItem::Table, RegistryItem::Id, RegistryItem::Scope)),
-        tokio::spawn(to_json::<User, Vec<String>, StringVec>(db.clone(), mp.clone(), User::Table, User::Id, User::Tags)),
-        tokio::spawn(to_json::<User, Vec<String>, StringVec>(db.clone(), mp.clone(), User::Table, User::Id, User::Emojis)),
-        tokio::spawn(to_json::<Webhook, Vec<String>, StringVec>(db.clone(), mp.clone(), Webhook::Table, Webhook::Id, Webhook::On)),
-        tokio::spawn(to_json::<Poll, Vec<String>, StringVec>(db.clone(), mp.clone(), Poll::Table, Poll::NoteId, Poll::Choices)),
-        tokio::spawn(to_json::<Poll, Vec<i32>, I32Vec>(db.clone(), mp.clone(), Poll::Table, Poll::NoteId, Poll::Votes)),
-        tokio::spawn(to_json::<UserProfile, Vec<String>, StringVec>(db.clone(), mp.clone(), UserProfile::Table, UserProfile::UserId, UserProfile::MutingNotificationTypes)),
+        tokio::spawn(to_json::<AccessToken, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            AccessToken::Table,
+            AccessToken::Id,
+            AccessToken::Permission,
+        )),
+        tokio::spawn(to_json::<Antenna, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Antenna::Table,
+            Antenna::Id,
+            Antenna::Users,
+        )),
+        tokio::spawn(to_json::<App, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            App::Table,
+            App::Id,
+            App::Permission,
+        )),
+        tokio::spawn(to_json::<Emoji, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Emoji::Table,
+            Emoji::Id,
+            Emoji::Aliases,
+        )),
+        tokio::spawn(to_json::<GalleryPost, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            GalleryPost::Table,
+            GalleryPost::Id,
+            GalleryPost::FileIds,
+        )),
+        tokio::spawn(to_json::<GalleryPost, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            GalleryPost::Table,
+            GalleryPost::Id,
+            GalleryPost::Tags,
+        )),
+        tokio::spawn(to_json::<Hashtag, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Hashtag::Table,
+            Hashtag::Id,
+            Hashtag::MentionedUserIds,
+        )),
+        tokio::spawn(to_json::<Hashtag, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Hashtag::Table,
+            Hashtag::Id,
+            Hashtag::MentionedLocalUserIds,
+        )),
+        tokio::spawn(to_json::<Hashtag, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Hashtag::Table,
+            Hashtag::Id,
+            Hashtag::MentionedRemoteUserIds,
+        )),
+        tokio::spawn(to_json::<Hashtag, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Hashtag::Table,
+            Hashtag::Id,
+            Hashtag::AttachedUserIds,
+        )),
+        tokio::spawn(to_json::<Hashtag, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Hashtag::Table,
+            Hashtag::Id,
+            Hashtag::AttachedLocalUserIds,
+        )),
+        tokio::spawn(to_json::<Hashtag, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Hashtag::Table,
+            Hashtag::Id,
+            Hashtag::AttachedRemoteUserIds,
+        )),
+        tokio::spawn(to_json::<MessagingMessage, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            MessagingMessage::Table,
+            MessagingMessage::Id,
+            MessagingMessage::Reads,
+        )),
+        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Meta::Table,
+            Meta::Id,
+            Meta::Langs,
+        )),
+        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Meta::Table,
+            Meta::Id,
+            Meta::BlockedHosts,
+        )),
+        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Meta::Table,
+            Meta::Id,
+            Meta::HiddenTags,
+        )),
+        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Meta::Table,
+            Meta::Id,
+            Meta::PinnedUsers,
+        )),
+        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Meta::Table,
+            Meta::Id,
+            Meta::PinnedPages,
+        )),
+        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Meta::Table,
+            Meta::Id,
+            Meta::RecommendedInstances,
+        )),
+        tokio::spawn(to_json::<Meta, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Meta::Table,
+            Meta::Id,
+            Meta::SilencedHosts,
+        )),
+        tokio::spawn(to_json::<Note, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Note::Table,
+            Note::Id,
+            Note::FileIds,
+        )),
+        tokio::spawn(to_json::<Note, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Note::Table,
+            Note::Id,
+            Note::AttachedFileTypes,
+        )),
+        tokio::spawn(to_json::<Note, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Note::Table,
+            Note::Id,
+            Note::VisibleUserIds,
+        )),
+        tokio::spawn(to_json::<Note, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Note::Table,
+            Note::Id,
+            Note::Mentions,
+        )),
+        tokio::spawn(to_json::<Note, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Note::Table,
+            Note::Id,
+            Note::Emojis,
+        )),
+        tokio::spawn(to_json::<Note, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Note::Table,
+            Note::Id,
+            Note::Tags,
+        )),
+        tokio::spawn(to_json::<NoteEdit, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            NoteEdit::Table,
+            NoteEdit::Id,
+            NoteEdit::FileIds,
+        )),
+        tokio::spawn(to_json::<Page, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Page::Table,
+            Page::Id,
+            Page::VisibleUserIds,
+        )),
+        tokio::spawn(to_json::<RegistryItem, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            RegistryItem::Table,
+            RegistryItem::Id,
+            RegistryItem::Scope,
+        )),
+        tokio::spawn(to_json::<User, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            User::Table,
+            User::Id,
+            User::Tags,
+        )),
+        tokio::spawn(to_json::<User, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            User::Table,
+            User::Id,
+            User::Emojis,
+        )),
+        tokio::spawn(to_json::<Webhook, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Webhook::Table,
+            Webhook::Id,
+            Webhook::On,
+        )),
+        tokio::spawn(to_json::<Poll, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            Poll::Table,
+            Poll::NoteId,
+            Poll::Choices,
+        )),
+        tokio::spawn(to_json::<Poll, Vec<i32>, I32Vec>(
+            db.clone(),
+            mp.clone(),
+            Poll::Table,
+            Poll::NoteId,
+            Poll::Votes,
+        )),
+        tokio::spawn(to_json::<UserProfile, Vec<String>, StringVec>(
+            db.clone(),
+            mp.clone(),
+            UserProfile::Table,
+            UserProfile::UserId,
+            UserProfile::MutingNotificationTypes,
+        )),
     ];
 
     futures::future::join_all(handlers).await;
@@ -62,10 +275,7 @@ fn select_query<T: Iden + 'static>(table: T, id: T, col: T) -> String {
         .to_string(PostgresQueryBuilder)
 }
 
-async fn get_vec<T: TryGetable>(
-    db: &DbConn,
-    query: String,
-) -> Result<Vec<(String, T)>, DbErr> {
+async fn get_vec<T: TryGetable>(db: &DbConn, query: String) -> Result<Vec<(String, T)>, DbErr> {
     let res: Vec<(String, T)> = db
         .query_all(Statement::from_string(DbBackend::Postgres, query))
         .await?
@@ -81,15 +291,17 @@ async fn convert_col<T: Iden + Clone + 'static>(
     col: T,
 ) -> Result<(), DbErr> {
     let stmt = Table::alter()
-                .table(table)
-                .drop_column(col.to_owned())
-                .add_column(
-                    ColumnDef::new(col.to_owned())
-                        .json_binary()
-                        .not_null()
-                        .default(json!([])),
-                ).to_string(PostgresQueryBuilder);
-    db.query_one(Statement::from_string(DbBackend::Postgres, stmt)).await?;
+        .table(table)
+        .drop_column(col.to_owned())
+        .add_column(
+            ColumnDef::new(col.to_owned())
+                .json_binary()
+                .not_null()
+                .default(json!([])),
+        )
+        .to_string(PostgresQueryBuilder);
+    db.query_one(Statement::from_string(DbBackend::Postgres, stmt))
+        .await?;
     Ok(())
 }
 
@@ -102,26 +314,35 @@ async fn to_json<T, U, V>(
 ) -> Result<(), DbErr>
 where
     T: Iden + Clone + 'static,
-    U: TryGetable + Clone,
+    U: TryGetable + IntoIterator + Clone,
     V: From<U> + Into<SimpleExpr>,
 {
     let query = select_query(table.clone(), id.clone(), col.clone());
     let loading = ProgressBar::new_spinner()
         .with_style(ProgressStyle::with_template("{prefix} {msg} {spinner}").unwrap())
         .with_prefix("[-]")
-        .with_message(format!("Loading data from {}.{}", table.to_string(), col.to_string()));
-    loading.enable_steady_tick(Duration::from_millis(100));
+        .with_message(format!(
+            "Loading data from {}.{}",
+            table.to_string(),
+            col.to_string()
+        ));
     let loading = mp.add(loading);
+    loading.enable_steady_tick(Duration::from_millis(100));
     let res = get_vec::<U>(&db, query).await?;
     let models: Vec<(String, V)> = res
         .iter()
+        .filter(|(_, r)| r.clone().into_iter().count() > 0)
         .map(|(id, r)| (id.clone(), <V>::from(r.clone())))
         .collect();
     loading.finish_and_clear();
     convert_col(&db, table.clone(), col.clone()).await?;
 
     let progress = ProgressBar::new(models.len() as u64)
-        .with_style(ProgressStyle::with_template("{prefix} {msg} {wide_bar} {pos}/{len}").unwrap().progress_chars("##-"))
+        .with_style(
+            ProgressStyle::with_template("{prefix} {msg} {wide_bar} {pos}/{len}")
+                .unwrap()
+                .progress_chars("##-"),
+        )
         .with_prefix("[*]")
         .with_message(format!("Copying {}.{}", table.to_string(), col.to_string()));
     let progress = mp.add(progress);
@@ -136,7 +357,7 @@ where
         db.query_one(Statement::from_string(DbBackend::Postgres, q))
             .await?;
     }
-    progress.finish_with_message(format!("Complete {}.{}", table.to_string(), col.to_string()));
+    progress.finish_with_message(format!("Done {}.{}", table.to_string(), col.to_string()));
 
     Ok(())
 }

From a2e6c78f090be79fbc0c8d29c7ad63f5feb28197 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 01:10:23 -0400
Subject: [PATCH 163/198] rename feature

---
 packages/backend/native-utils/crates/migration/Cargo.toml | 2 +-
 packages/backend/native-utils/crates/model/Cargo.toml     | 2 +-
 .../native-utils/crates/model/src/entity/newtype/mod.rs   | 8 ++++----
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/packages/backend/native-utils/crates/migration/Cargo.toml b/packages/backend/native-utils/crates/migration/Cargo.toml
index 49accc9a2..dbfd41be6 100644
--- a/packages/backend/native-utils/crates/migration/Cargo.toml
+++ b/packages/backend/native-utils/crates/migration/Cargo.toml
@@ -10,7 +10,7 @@ path = "src/lib.rs"
 
 [features]
 default = []
-convert = []
+convert = ["model/noarray"]
 
 [dependencies]
 async-std = { version = "1", features = ["attributes", "tokio1"] }
diff --git a/packages/backend/native-utils/crates/model/Cargo.toml b/packages/backend/native-utils/crates/model/Cargo.toml
index a511e6e99..e9a3260c4 100644
--- a/packages/backend/native-utils/crates/model/Cargo.toml
+++ b/packages/backend/native-utils/crates/model/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 [features]
 default = []
-legacy = []
+noarray = []
 
 [dependencies]
 async-trait = "0.1.68"
diff --git a/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs b/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
index bd4302441..45aa17540 100644
--- a/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
@@ -20,11 +20,11 @@ pub struct JsonI32Vec(pub Vec<i32>);
 impl_json_newtype!(JsonI32Vec);
 
 cfg_if! {
-    if #[cfg(feature = "legacy")] {
-        pub type StringVec = Vec<String>;
-        pub type I32Vec = Vec<i32>;
-    } else {
+    if #[cfg(feature = "noarray")] {
         pub type StringVec = JsonStringVec;
         pub type I32Vec = JsonI32Vec;
+    } else {
+        pub type StringVec = Vec<String>;
+        pub type I32Vec = Vec<i32>;
     }
 }

From e79d34e1edf4372502b3eb00d2b7715c5dbb8c37 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 03:39:52 -0400
Subject: [PATCH 164/198] add default values

---
 .../model/src/entity/abuse_user_report.rs     |   2 +-
 .../crates/model/src/entity/access_token.rs   |   2 +-
 .../crates/model/src/entity/ad.rs             |   2 +-
 .../crates/model/src/entity/announcement.rs   |   2 +-
 .../model/src/entity/announcement_read.rs     |   2 +-
 .../crates/model/src/entity/antenna.rs        |   2 +-
 .../crates/model/src/entity/antenna_note.rs   |   2 +-
 .../crates/model/src/entity/app.rs            |   2 +-
 .../model/src/entity/attestation_challenge.rs |   2 +-
 .../crates/model/src/entity/auth_session.rs   |   2 +-
 .../crates/model/src/entity/blocking.rs       |   2 +-
 .../crates/model/src/entity/channel.rs        |   2 +-
 .../model/src/entity/channel_following.rs     |   2 +-
 .../model/src/entity/channel_note_pining.rs   |   2 +-
 .../crates/model/src/entity/clip.rs           |   2 +-
 .../crates/model/src/entity/clip_note.rs      |   2 +-
 .../crates/model/src/entity/drive_file.rs     |   2 +-
 .../crates/model/src/entity/drive_folder.rs   |   2 +-
 .../crates/model/src/entity/emoji.rs          |   2 +-
 .../crates/model/src/entity/follow_request.rs |   2 +-
 .../crates/model/src/entity/following.rs      |   2 +-
 .../crates/model/src/entity/gallery_like.rs   |   2 +-
 .../crates/model/src/entity/gallery_post.rs   |   2 +-
 .../crates/model/src/entity/hashtag.rs        |   2 +-
 .../crates/model/src/entity/instance.rs       |   2 +-
 .../model/src/entity/messaging_message.rs     |   2 +-
 .../crates/model/src/entity/meta.rs           |   2 +-
 .../crates/model/src/entity/migrations.rs     |   2 +-
 .../crates/model/src/entity/moderation_log.rs |   2 +-
 .../crates/model/src/entity/muted_note.rs     |   2 +-
 .../crates/model/src/entity/muting.rs         |   2 +-
 .../crates/model/src/entity/newtype/mod.rs    |   6 +-
 .../crates/model/src/entity/note.rs           |   2 +-
 .../crates/model/src/entity/note_edit.rs      |   2 +-
 .../crates/model/src/entity/note_favorite.rs  |   2 +-
 .../crates/model/src/entity/note_reaction.rs  |   2 +-
 .../model/src/entity/note_thread_muting.rs    |   2 +-
 .../crates/model/src/entity/note_unread.rs    |   2 +-
 .../crates/model/src/entity/note_watching.rs  |   2 +-
 .../crates/model/src/entity/notification.rs   |   2 +-
 .../crates/model/src/entity/page.rs           |   2 +-
 .../crates/model/src/entity/page_like.rs      |   2 +-
 .../src/entity/password_reset_request.rs      |   2 +-
 .../crates/model/src/entity/poll.rs           |   2 +-
 .../crates/model/src/entity/poll_vote.rs      |   2 +-
 .../crates/model/src/entity/promo_note.rs     |   2 +-
 .../crates/model/src/entity/promo_read.rs     |   2 +-
 .../model/src/entity/registration_ticket.rs   |   2 +-
 .../crates/model/src/entity/registry_item.rs  |   2 +-
 .../crates/model/src/entity/relay.rs          |   2 +-
 .../crates/model/src/entity/renote_muting.rs  |   2 +-
 .../model/src/entity/sea_orm_active_enums.rs  |  30 ++-
 .../crates/model/src/entity/signin.rs         |   2 +-
 .../model/src/entity/sw_subscription.rs       |   2 +-
 .../crates/model/src/entity/used_username.rs  |   2 +-
 .../crates/model/src/entity/user.rs           |   2 +-
 .../crates/model/src/entity/user_group.rs     |   2 +-
 .../model/src/entity/user_group_invitation.rs |   2 +-
 .../model/src/entity/user_group_invite.rs     |   2 +-
 .../model/src/entity/user_group_joining.rs    |   2 +-
 .../crates/model/src/entity/user_ip.rs        |   2 +-
 .../crates/model/src/entity/user_keypair.rs   |   2 +-
 .../crates/model/src/entity/user_list.rs      |   2 +-
 .../model/src/entity/user_list_joining.rs     |   2 +-
 .../model/src/entity/user_note_pining.rs      |   2 +-
 .../crates/model/src/entity/user_pending.rs   |   2 +-
 .../crates/model/src/entity/user_profile.rs   |   2 +-
 .../crates/model/src/entity/user_publickey.rs |   2 +-
 .../model/src/entity/user_security_key.rs     |   2 +-
 .../crates/model/src/entity/webhook.rs        |   2 +-
 .../native-utils/crates/model/tests/common.rs | 184 +++++++++++++-----
 71 files changed, 226 insertions(+), 130 deletions(-)

diff --git a/packages/backend/native-utils/crates/model/src/entity/abuse_user_report.rs b/packages/backend/native-utils/crates/model/src/entity/abuse_user_report.rs
index 270837973..24230b394 100644
--- a/packages/backend/native-utils/crates/model/src/entity/abuse_user_report.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/abuse_user_report.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "abuse_user_report")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/access_token.rs b/packages/backend/native-utils/crates/model/src/entity/access_token.rs
index f84971605..dd9289224 100644
--- a/packages/backend/native-utils/crates/model/src/entity/access_token.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/access_token.rs
@@ -4,7 +4,7 @@ use sea_orm::entity::prelude::*;
 
 use super::newtype::StringVec;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "access_token")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/ad.rs b/packages/backend/native-utils/crates/model/src/entity/ad.rs
index 708ed69ce..2cf7a6fc8 100644
--- a/packages/backend/native-utils/crates/model/src/entity/ad.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/ad.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "ad")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/announcement.rs b/packages/backend/native-utils/crates/model/src/entity/announcement.rs
index 3e9b91687..e8a2a28aa 100644
--- a/packages/backend/native-utils/crates/model/src/entity/announcement.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/announcement.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "announcement")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/announcement_read.rs b/packages/backend/native-utils/crates/model/src/entity/announcement_read.rs
index 7fc51d475..53ff8d6ce 100644
--- a/packages/backend/native-utils/crates/model/src/entity/announcement_read.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/announcement_read.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "announcement_read")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/antenna.rs b/packages/backend/native-utils/crates/model/src/entity/antenna.rs
index 0ebd1cf45..85bdfbfea 100644
--- a/packages/backend/native-utils/crates/model/src/entity/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/antenna.rs
@@ -3,7 +3,7 @@
 use super::{newtype, sea_orm_active_enums::AntennaSrcEnum};
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "antenna")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/antenna_note.rs b/packages/backend/native-utils/crates/model/src/entity/antenna_note.rs
index d4c850bfc..c86fb349d 100644
--- a/packages/backend/native-utils/crates/model/src/entity/antenna_note.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/antenna_note.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "antenna_note")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/app.rs b/packages/backend/native-utils/crates/model/src/entity/app.rs
index 310294568..6400d0b24 100644
--- a/packages/backend/native-utils/crates/model/src/entity/app.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/app.rs
@@ -4,7 +4,7 @@ use sea_orm::entity::prelude::*;
 
 use super::newtype::StringVec;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "app")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/attestation_challenge.rs b/packages/backend/native-utils/crates/model/src/entity/attestation_challenge.rs
index 135a4f1fb..5217b2796 100644
--- a/packages/backend/native-utils/crates/model/src/entity/attestation_challenge.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/attestation_challenge.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "attestation_challenge")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/auth_session.rs b/packages/backend/native-utils/crates/model/src/entity/auth_session.rs
index 83aecbaa6..8ced191c3 100644
--- a/packages/backend/native-utils/crates/model/src/entity/auth_session.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/auth_session.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "auth_session")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/blocking.rs b/packages/backend/native-utils/crates/model/src/entity/blocking.rs
index 4667e60c6..4f326f6fa 100644
--- a/packages/backend/native-utils/crates/model/src/entity/blocking.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/blocking.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "blocking")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/channel.rs b/packages/backend/native-utils/crates/model/src/entity/channel.rs
index 9132d9d4e..abc79b4f5 100644
--- a/packages/backend/native-utils/crates/model/src/entity/channel.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/channel.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "channel")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/channel_following.rs b/packages/backend/native-utils/crates/model/src/entity/channel_following.rs
index bd1b16dce..93739459a 100644
--- a/packages/backend/native-utils/crates/model/src/entity/channel_following.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/channel_following.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "channel_following")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/channel_note_pining.rs b/packages/backend/native-utils/crates/model/src/entity/channel_note_pining.rs
index 2c9089ac4..50ec1ecef 100644
--- a/packages/backend/native-utils/crates/model/src/entity/channel_note_pining.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/channel_note_pining.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "channel_note_pining")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/clip.rs b/packages/backend/native-utils/crates/model/src/entity/clip.rs
index 209bd047e..a51ef720e 100644
--- a/packages/backend/native-utils/crates/model/src/entity/clip.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/clip.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "clip")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/clip_note.rs b/packages/backend/native-utils/crates/model/src/entity/clip_note.rs
index 953c5511c..a8bfd4564 100644
--- a/packages/backend/native-utils/crates/model/src/entity/clip_note.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/clip_note.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "clip_note")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/drive_file.rs b/packages/backend/native-utils/crates/model/src/entity/drive_file.rs
index abc191ba3..7c42b9881 100644
--- a/packages/backend/native-utils/crates/model/src/entity/drive_file.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/drive_file.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "drive_file")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/drive_folder.rs b/packages/backend/native-utils/crates/model/src/entity/drive_folder.rs
index f0b716283..98a9f8901 100644
--- a/packages/backend/native-utils/crates/model/src/entity/drive_folder.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/drive_folder.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "drive_folder")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/emoji.rs b/packages/backend/native-utils/crates/model/src/entity/emoji.rs
index fdb99a2c3..00fc6184a 100644
--- a/packages/backend/native-utils/crates/model/src/entity/emoji.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/emoji.rs
@@ -4,7 +4,7 @@ use sea_orm::entity::prelude::*;
 
 use super::newtype::StringVec;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "emoji")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/follow_request.rs b/packages/backend/native-utils/crates/model/src/entity/follow_request.rs
index af763baa6..6f8b00b79 100644
--- a/packages/backend/native-utils/crates/model/src/entity/follow_request.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/follow_request.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "follow_request")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/following.rs b/packages/backend/native-utils/crates/model/src/entity/following.rs
index 087ca270b..641e41530 100644
--- a/packages/backend/native-utils/crates/model/src/entity/following.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/following.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "following")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/gallery_like.rs b/packages/backend/native-utils/crates/model/src/entity/gallery_like.rs
index 186c92703..e90dfedb3 100644
--- a/packages/backend/native-utils/crates/model/src/entity/gallery_like.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/gallery_like.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "gallery_like")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/gallery_post.rs b/packages/backend/native-utils/crates/model/src/entity/gallery_post.rs
index 875d3af58..7e53e6bf3 100644
--- a/packages/backend/native-utils/crates/model/src/entity/gallery_post.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/gallery_post.rs
@@ -4,7 +4,7 @@ use sea_orm::entity::prelude::*;
 
 use super::newtype::StringVec;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "gallery_post")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/hashtag.rs b/packages/backend/native-utils/crates/model/src/entity/hashtag.rs
index 917f4ea1b..7a8722a5f 100644
--- a/packages/backend/native-utils/crates/model/src/entity/hashtag.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/hashtag.rs
@@ -4,7 +4,7 @@ use sea_orm::entity::prelude::*;
 
 use super::newtype::StringVec;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "hashtag")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/instance.rs b/packages/backend/native-utils/crates/model/src/entity/instance.rs
index 3f3af2a5d..fc9c5bf8b 100644
--- a/packages/backend/native-utils/crates/model/src/entity/instance.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/instance.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "instance")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/messaging_message.rs b/packages/backend/native-utils/crates/model/src/entity/messaging_message.rs
index b66c01fc9..8d7c7b8cc 100644
--- a/packages/backend/native-utils/crates/model/src/entity/messaging_message.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/messaging_message.rs
@@ -4,7 +4,7 @@ use sea_orm::entity::prelude::*;
 
 use super::newtype::StringVec;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "messaging_message")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/meta.rs b/packages/backend/native-utils/crates/model/src/entity/meta.rs
index a97cb89c4..2c0dc315c 100644
--- a/packages/backend/native-utils/crates/model/src/entity/meta.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/meta.rs
@@ -6,7 +6,7 @@ use sea_orm::entity::prelude::*;
 
 use super::newtype::StringVec;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "meta")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/migrations.rs b/packages/backend/native-utils/crates/model/src/entity/migrations.rs
index c03df1180..54e44f2fd 100644
--- a/packages/backend/native-utils/crates/model/src/entity/migrations.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/migrations.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "migrations")]
 pub struct Model {
     #[sea_orm(primary_key)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/moderation_log.rs b/packages/backend/native-utils/crates/model/src/entity/moderation_log.rs
index 330685392..eb882b896 100644
--- a/packages/backend/native-utils/crates/model/src/entity/moderation_log.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/moderation_log.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "moderation_log")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/muted_note.rs b/packages/backend/native-utils/crates/model/src/entity/muted_note.rs
index 1740e9078..238898549 100644
--- a/packages/backend/native-utils/crates/model/src/entity/muted_note.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/muted_note.rs
@@ -3,7 +3,7 @@
 use super::sea_orm_active_enums::MutedNoteReasonEnum;
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "muted_note")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/muting.rs b/packages/backend/native-utils/crates/model/src/entity/muting.rs
index 83885034c..7b46a0b24 100644
--- a/packages/backend/native-utils/crates/model/src/entity/muting.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/muting.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "muting")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs b/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
index 45aa17540..3dc2d7553 100644
--- a/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
@@ -7,15 +7,15 @@ use serde::{Deserialize, Serialize};
 
 use crate::impl_json_newtype;
 
-#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into, Default)]
 pub struct JsonKeyword(pub Vec<Vec<String>>);
 impl_json_newtype!(JsonKeyword);
 
-#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into, Default)]
 pub struct JsonStringVec(pub Vec<String>);
 impl_json_newtype!(JsonStringVec);
 
-#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into, Default)]
 pub struct JsonI32Vec(pub Vec<i32>);
 impl_json_newtype!(JsonI32Vec);
 
diff --git a/packages/backend/native-utils/crates/model/src/entity/note.rs b/packages/backend/native-utils/crates/model/src/entity/note.rs
index c2f20c11b..077841e48 100644
--- a/packages/backend/native-utils/crates/model/src/entity/note.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/note.rs
@@ -5,7 +5,7 @@ use sea_orm::entity::prelude::*;
 
 use super::newtype::StringVec;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "note")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_edit.rs b/packages/backend/native-utils/crates/model/src/entity/note_edit.rs
index 4e8f42083..ea9b9eabd 100644
--- a/packages/backend/native-utils/crates/model/src/entity/note_edit.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/note_edit.rs
@@ -4,7 +4,7 @@ use sea_orm::entity::prelude::*;
 
 use super::newtype::StringVec;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "note_edit")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_favorite.rs b/packages/backend/native-utils/crates/model/src/entity/note_favorite.rs
index 42f3c400f..470ad55d2 100644
--- a/packages/backend/native-utils/crates/model/src/entity/note_favorite.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/note_favorite.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "note_favorite")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_reaction.rs b/packages/backend/native-utils/crates/model/src/entity/note_reaction.rs
index c740d994f..a4e9f490d 100644
--- a/packages/backend/native-utils/crates/model/src/entity/note_reaction.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/note_reaction.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "note_reaction")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_thread_muting.rs b/packages/backend/native-utils/crates/model/src/entity/note_thread_muting.rs
index f1dbfb598..51688a088 100644
--- a/packages/backend/native-utils/crates/model/src/entity/note_thread_muting.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/note_thread_muting.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "note_thread_muting")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_unread.rs b/packages/backend/native-utils/crates/model/src/entity/note_unread.rs
index 746815611..a444eb35d 100644
--- a/packages/backend/native-utils/crates/model/src/entity/note_unread.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/note_unread.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "note_unread")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_watching.rs b/packages/backend/native-utils/crates/model/src/entity/note_watching.rs
index 4a87a4495..962ef081e 100644
--- a/packages/backend/native-utils/crates/model/src/entity/note_watching.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/note_watching.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "note_watching")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/notification.rs b/packages/backend/native-utils/crates/model/src/entity/notification.rs
index 4500e59d9..896b6c2da 100644
--- a/packages/backend/native-utils/crates/model/src/entity/notification.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/notification.rs
@@ -3,7 +3,7 @@
 use super::sea_orm_active_enums::NotificationTypeEnum;
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "notification")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/page.rs b/packages/backend/native-utils/crates/model/src/entity/page.rs
index c3d09fa8c..dabb5c9f0 100644
--- a/packages/backend/native-utils/crates/model/src/entity/page.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/page.rs
@@ -5,7 +5,7 @@ use sea_orm::entity::prelude::*;
 
 use super::newtype::StringVec;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "page")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/page_like.rs b/packages/backend/native-utils/crates/model/src/entity/page_like.rs
index 3d3d2f3ac..108b6b929 100644
--- a/packages/backend/native-utils/crates/model/src/entity/page_like.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/page_like.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "page_like")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/password_reset_request.rs b/packages/backend/native-utils/crates/model/src/entity/password_reset_request.rs
index 3b24d70d9..45cc3de10 100644
--- a/packages/backend/native-utils/crates/model/src/entity/password_reset_request.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/password_reset_request.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "password_reset_request")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/poll.rs b/packages/backend/native-utils/crates/model/src/entity/poll.rs
index 81953cfba..4d64594c7 100644
--- a/packages/backend/native-utils/crates/model/src/entity/poll.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/poll.rs
@@ -5,7 +5,7 @@ use sea_orm::entity::prelude::*;
 
 use super::newtype::{I32Vec, StringVec};
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "poll")]
 pub struct Model {
     #[sea_orm(column_name = "noteId", primary_key, auto_increment = false, unique)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/poll_vote.rs b/packages/backend/native-utils/crates/model/src/entity/poll_vote.rs
index 1b8b3ba1c..bf26bf5dd 100644
--- a/packages/backend/native-utils/crates/model/src/entity/poll_vote.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/poll_vote.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "poll_vote")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/promo_note.rs b/packages/backend/native-utils/crates/model/src/entity/promo_note.rs
index aa5eb2f3d..288a0ea81 100644
--- a/packages/backend/native-utils/crates/model/src/entity/promo_note.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/promo_note.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "promo_note")]
 pub struct Model {
     #[sea_orm(column_name = "noteId", primary_key, auto_increment = false, unique)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/promo_read.rs b/packages/backend/native-utils/crates/model/src/entity/promo_read.rs
index d7dcacfb8..4e6224cf2 100644
--- a/packages/backend/native-utils/crates/model/src/entity/promo_read.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/promo_read.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "promo_read")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/registration_ticket.rs b/packages/backend/native-utils/crates/model/src/entity/registration_ticket.rs
index f71c87327..798f19586 100644
--- a/packages/backend/native-utils/crates/model/src/entity/registration_ticket.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/registration_ticket.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "registration_ticket")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/registry_item.rs b/packages/backend/native-utils/crates/model/src/entity/registry_item.rs
index 54d72d5d8..904c43abf 100644
--- a/packages/backend/native-utils/crates/model/src/entity/registry_item.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/registry_item.rs
@@ -4,7 +4,7 @@ use sea_orm::entity::prelude::*;
 
 use super::newtype::StringVec;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "registry_item")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/relay.rs b/packages/backend/native-utils/crates/model/src/entity/relay.rs
index 736b48b78..bed89c849 100644
--- a/packages/backend/native-utils/crates/model/src/entity/relay.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/relay.rs
@@ -3,7 +3,7 @@
 use super::sea_orm_active_enums::RelayStatusEnum;
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "relay")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/renote_muting.rs b/packages/backend/native-utils/crates/model/src/entity/renote_muting.rs
index b5e7d38f2..44751c14c 100644
--- a/packages/backend/native-utils/crates/model/src/entity/renote_muting.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/renote_muting.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "renote_muting")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/sea_orm_active_enums.rs b/packages/backend/native-utils/crates/model/src/entity/sea_orm_active_enums.rs
index 14ef7002a..f26995224 100644
--- a/packages/backend/native-utils/crates/model/src/entity/sea_orm_active_enums.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/sea_orm_active_enums.rs
@@ -2,9 +2,10 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Default)]
 #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "antenna_src_enum")]
 pub enum AntennaSrcEnum {
+    #[default]
     #[sea_orm(string_value = "all")]
     All,
     #[sea_orm(string_value = "group")]
@@ -18,13 +19,14 @@ pub enum AntennaSrcEnum {
     #[sea_orm(string_value = "users")]
     Users,
 }
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Default)]
 #[sea_orm(
     rs_type = "String",
     db_type = "Enum",
     enum_name = "meta_sensitivemediadetection_enum"
 )]
 pub enum MetaSensitivemediadetectionEnum {
+    #[default]
     #[sea_orm(string_value = "all")]
     All,
     #[sea_orm(string_value = "local")]
@@ -34,7 +36,7 @@ pub enum MetaSensitivemediadetectionEnum {
     #[sea_orm(string_value = "remote")]
     Remote,
 }
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Default)]
 #[sea_orm(
     rs_type = "String",
     db_type = "Enum",
@@ -45,6 +47,7 @@ pub enum MetaSensitivemediadetectionsensitivityEnum {
     High,
     #[sea_orm(string_value = "low")]
     Low,
+    #[default]
     #[sea_orm(string_value = "medium")]
     Medium,
     #[sea_orm(string_value = "veryHigh")]
@@ -52,13 +55,14 @@ pub enum MetaSensitivemediadetectionsensitivityEnum {
     #[sea_orm(string_value = "veryLow")]
     VeryLow,
 }
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Default)]
 #[sea_orm(
     rs_type = "String",
     db_type = "Enum",
     enum_name = "muted_note_reason_enum"
 )]
 pub enum MutedNoteReasonEnum {
+    #[default]
     #[sea_orm(string_value = "manual")]
     Manual,
     #[sea_orm(string_value = "other")]
@@ -68,7 +72,7 @@ pub enum MutedNoteReasonEnum {
     #[sea_orm(string_value = "word")]
     Word,
 }
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Default)]
 #[sea_orm(
     rs_type = "String",
     db_type = "Enum",
@@ -81,12 +85,13 @@ pub enum NoteVisibilityEnum {
     Hidden,
     #[sea_orm(string_value = "home")]
     Home,
+    #[default]
     #[sea_orm(string_value = "public")]
     Public,
     #[sea_orm(string_value = "specified")]
     Specified,
 }
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Default)]
 #[sea_orm(
     rs_type = "String",
     db_type = "Enum",
@@ -115,10 +120,11 @@ pub enum NotificationTypeEnum {
     ReceiveFollowRequest,
     #[sea_orm(string_value = "renote")]
     Renote,
+    #[default]
     #[sea_orm(string_value = "reply")]
     Reply,
 }
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Default)]
 #[sea_orm(
     rs_type = "String",
     db_type = "Enum",
@@ -127,12 +133,13 @@ pub enum NotificationTypeEnum {
 pub enum PageVisibilityEnum {
     #[sea_orm(string_value = "followers")]
     Followers,
+    #[default]
     #[sea_orm(string_value = "public")]
     Public,
     #[sea_orm(string_value = "specified")]
     Specified,
 }
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Default)]
 #[sea_orm(
     rs_type = "String",
     db_type = "Enum",
@@ -143,22 +150,24 @@ pub enum PollNotevisibilityEnum {
     Followers,
     #[sea_orm(string_value = "home")]
     Home,
+    #[default]
     #[sea_orm(string_value = "public")]
     Public,
     #[sea_orm(string_value = "specified")]
     Specified,
 }
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Default)]
 #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "relay_status_enum")]
 pub enum RelayStatusEnum {
     #[sea_orm(string_value = "accepted")]
     Accepted,
     #[sea_orm(string_value = "rejected")]
     Rejected,
+    #[default]
     #[sea_orm(string_value = "requesting")]
     Requesting,
 }
-#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
+#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Default)]
 #[sea_orm(
     rs_type = "String",
     db_type = "Enum",
@@ -169,6 +178,7 @@ pub enum UserProfileFfvisibilityEnum {
     Followers,
     #[sea_orm(string_value = "private")]
     Private,
+    #[default]
     #[sea_orm(string_value = "public")]
     Public,
 }
diff --git a/packages/backend/native-utils/crates/model/src/entity/signin.rs b/packages/backend/native-utils/crates/model/src/entity/signin.rs
index 6220973c1..60bbc33d2 100644
--- a/packages/backend/native-utils/crates/model/src/entity/signin.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/signin.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "signin")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/sw_subscription.rs b/packages/backend/native-utils/crates/model/src/entity/sw_subscription.rs
index eaa332d8c..1be9e046a 100644
--- a/packages/backend/native-utils/crates/model/src/entity/sw_subscription.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/sw_subscription.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "sw_subscription")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/used_username.rs b/packages/backend/native-utils/crates/model/src/entity/used_username.rs
index e9e8eb097..620950b64 100644
--- a/packages/backend/native-utils/crates/model/src/entity/used_username.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/used_username.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "used_username")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user.rs b/packages/backend/native-utils/crates/model/src/entity/user.rs
index 1cf6ac6b7..f30fd8ace 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user.rs
@@ -4,7 +4,7 @@ use sea_orm::entity::prelude::*;
 
 use super::newtype::StringVec;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "user")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_group.rs b/packages/backend/native-utils/crates/model/src/entity/user_group.rs
index 680f78b9e..74ee4f22f 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_group.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_group.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "user_group")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_group_invitation.rs b/packages/backend/native-utils/crates/model/src/entity/user_group_invitation.rs
index 5a6f6f4a1..baa6fea83 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_group_invitation.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_group_invitation.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "user_group_invitation")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_group_invite.rs b/packages/backend/native-utils/crates/model/src/entity/user_group_invite.rs
index 786dd1f31..dbbc055f0 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_group_invite.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_group_invite.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "user_group_invite")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_group_joining.rs b/packages/backend/native-utils/crates/model/src/entity/user_group_joining.rs
index 2baa0b9a7..e7741520c 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_group_joining.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_group_joining.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "user_group_joining")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_ip.rs b/packages/backend/native-utils/crates/model/src/entity/user_ip.rs
index 872cfd860..ce0af264d 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_ip.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_ip.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "user_ip")]
 pub struct Model {
     #[sea_orm(primary_key)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_keypair.rs b/packages/backend/native-utils/crates/model/src/entity/user_keypair.rs
index df23b506b..0382d5d76 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_keypair.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_keypair.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "user_keypair")]
 pub struct Model {
     #[sea_orm(column_name = "userId", primary_key, auto_increment = false, unique)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_list.rs b/packages/backend/native-utils/crates/model/src/entity/user_list.rs
index ff05f2c44..7cc972133 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_list.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_list.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "user_list")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_list_joining.rs b/packages/backend/native-utils/crates/model/src/entity/user_list_joining.rs
index 27899a8c5..4f28a21db 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_list_joining.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_list_joining.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "user_list_joining")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_note_pining.rs b/packages/backend/native-utils/crates/model/src/entity/user_note_pining.rs
index bcb3ec8b0..e657fcb53 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_note_pining.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_note_pining.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "user_note_pining")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_pending.rs b/packages/backend/native-utils/crates/model/src/entity/user_pending.rs
index 1fb3b4fdc..297fe553c 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_pending.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_pending.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "user_pending")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_profile.rs b/packages/backend/native-utils/crates/model/src/entity/user_profile.rs
index d62607f16..4c2f903d4 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_profile.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_profile.rs
@@ -5,7 +5,7 @@ use sea_orm::entity::prelude::*;
 
 use super::newtype::StringVec;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "user_profile")]
 pub struct Model {
     #[sea_orm(column_name = "userId", primary_key, auto_increment = false, unique)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_publickey.rs b/packages/backend/native-utils/crates/model/src/entity/user_publickey.rs
index c3c6dbf1f..b1f426c5b 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_publickey.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_publickey.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "user_publickey")]
 pub struct Model {
     #[sea_orm(column_name = "userId", primary_key, auto_increment = false, unique)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_security_key.rs b/packages/backend/native-utils/crates/model/src/entity/user_security_key.rs
index cbb31e1b0..4bc976336 100644
--- a/packages/backend/native-utils/crates/model/src/entity/user_security_key.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/user_security_key.rs
@@ -2,7 +2,7 @@
 
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "user_security_key")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/src/entity/webhook.rs b/packages/backend/native-utils/crates/model/src/entity/webhook.rs
index dd3c3c738..06ea1516b 100644
--- a/packages/backend/native-utils/crates/model/src/entity/webhook.rs
+++ b/packages/backend/native-utils/crates/model/src/entity/webhook.rs
@@ -4,7 +4,7 @@ use sea_orm::entity::prelude::*;
 
 use super::newtype::StringVec;
 
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
 #[sea_orm(table_name = "webhook")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
diff --git a/packages/backend/native-utils/crates/model/tests/common.rs b/packages/backend/native-utils/crates/model/tests/common.rs
index 0fd32d8f9..add8c12b2 100644
--- a/packages/backend/native-utils/crates/model/tests/common.rs
+++ b/packages/backend/native-utils/crates/model/tests/common.rs
@@ -3,12 +3,12 @@ extern crate model;
 mod repository;
 
 use chrono::Utc;
-use model::entity::{antenna, sea_orm_active_enums::AntennaSrcEnum, user};
+use model::entity;
+use model::entity::sea_orm_active_enums::AntennaSrcEnum;
 use sea_orm::{
-    ActiveModelTrait, ActiveValue::Set, ConnectionTrait, DbBackend, DbConn, DbErr, EntityTrait,
-    TransactionTrait,
+    sea_query::TableCreateStatement, ActiveModelTrait, ConnectionTrait, DbBackend, DbConn, DbErr,
+    EntityTrait, IntoActiveModel, TransactionTrait,
 };
-use std::env;
 use util::{
     id::{create_id, init_id},
     random::gen_string,
@@ -16,31 +16,119 @@ use util::{
 
 /// Insert predefined entries in the database.
 async fn prepare() {
-    let conn_uri = env::var("DATABASE_URL")
-        .unwrap_or("postgres://calckey:calckey@localhost/calckey".to_string());
-    database::init_database(conn_uri)
+    database::init_database("sqlite::memory:")
         .await
         .expect("Unable to initialize database connection");
     let db = database::get_database().expect("Unable to get database connection from pool");
+    setup_schema(db).await;
     setup_model(db).await;
 }
 
-async fn setup_schema(db: DbConn) {
-    // Setup Schema helper
+/// Setup schemas in the database.
+async fn setup_schema(db: &DbConn) {
     let schema = sea_orm::Schema::new(DbBackend::Sqlite);
-    let stmt = schema.create_table_from_entity(antenna::Entity);
-    db.execute(db.get_database_backend().build(&stmt))
-        .await
-        .expect("Unable to setup schemas for in-memoty sqlite");
+    let mut stmts: Vec<TableCreateStatement> = Vec::new();
+    macro_rules! create_table_statement {
+        ($a:tt) => {
+            stmts.push(schema.create_table_from_entity(entity::$a::Entity).if_not_exists().to_owned());
+        };
+        ($a:tt, $($b:tt),+) => {
+            create_table_statement!($a);
+            create_table_statement!($($b),+);
+        };
+    }
+    create_table_statement!(
+        abuse_user_report,
+        access_token,
+        ad,
+        announcement_read,
+        announcement,
+        antenna_note,
+        antenna,
+        app,
+        attestation_challenge,
+        auth_session,
+        blocking,
+        channel_following,
+        channel_note_pining,
+        channel,
+        clip_note,
+        clip,
+        drive_file,
+        drive_folder,
+        emoji,
+        following,
+        follow_request,
+        gallery_like,
+        gallery_post,
+        hashtag,
+        instance,
+        messaging_message,
+        meta,
+        migrations,
+        moderation_log,
+        muted_note,
+        muting,
+        note_edit,
+        note_favorite,
+        note_reaction,
+        note,
+        note_thread_muting,
+        note_unread,
+        note_watching,
+        notification,
+        page_like,
+        page,
+        password_reset_request,
+        poll,
+        poll_vote,
+        promo_note,
+        promo_read,
+        registration_ticket,
+        registry_item,
+        relay,
+        renote_muting,
+        signin,
+        sw_subscription,
+        used_username,
+        user_group_invitation,
+        user_group_invite,
+        user_group_joining,
+        user_group,
+        user_ip,
+        user_keypair,
+        user_list_joining,
+        user_list,
+        user_note_pining,
+        user_pending,
+        user_profile,
+        user_publickey,
+        user,
+        user_security_key,
+        webhook
+    );
+    db.transaction::<_, (), DbErr>(|txn| {
+        Box::pin(async move {
+            for stmt in stmts {
+                txn.execute(txn.get_database_backend().build(&stmt)).await?;
+            }
+            Ok(())
+        })
+    })
+    .await
+    .expect("Unable to setup schemas");
 }
 
 /// Delete all entries in the database.
 async fn cleanup() {
-    let db = database::get_database().unwrap();
+    let db = database::get_database().expect("Unable to get database connection from pool");
     db.transaction::<_, (), DbErr>(|txn| {
         Box::pin(async move {
-            user::Entity::delete_many().exec(txn).await.unwrap();
-            antenna::Entity::delete_many().exec(txn).await.unwrap();
+            entity::user::Entity::delete_many().exec(txn).await.unwrap();
+            entity::antenna::Entity::delete_many()
+                .exec(txn)
+                .await
+                .unwrap();
 
             Ok(())
         })
@@ -56,40 +144,46 @@ async fn setup_model(db: &DbConn) {
         Box::pin(async move {
             let user_id = create_id().unwrap();
             let name = "Alice";
-            let user_model = user::ActiveModel {
-                id: Set(user_id.to_owned()),
-                created_at: Set(Utc::now().into()),
-                username: Set(name.to_lowercase().to_string()),
-                username_lower: Set(name.to_lowercase().to_string()),
-                name: Set(Some(name.to_string())),
-                token: Set(Some(gen_string(16))),
-                is_admin: Set(true),
+            let user_model = entity::user::Model {
+                id: user_id.to_owned(),
+                created_at: Utc::now().into(),
+                username: name.to_lowercase().to_string(),
+                username_lower: name.to_lowercase().to_string(),
+                name: Some(name.to_string()),
+                token: Some(gen_string(16)),
+                is_admin: true,
                 ..Default::default()
             };
-            user_model.insert(txn).await?;
-            let antenna_model = antenna::ActiveModel {
-                id: Set(create_id().unwrap()),
-                created_at: Set(Utc::now().into()),
-                user_id: Set(user_id.to_owned()),
-                name: Set("Test Antenna".to_string()),
-                src: Set(AntennaSrcEnum::All),
-                keywords: Set(vec![
+            user_model
+                .into_active_model()
+                .reset_all()
+                .insert(txn)
+                .await?;
+            let antenna_model = entity::antenna::Model {
+                id: create_id().unwrap(),
+                created_at: Utc::now().into(),
+                user_id: user_id.to_owned(),
+                name: "Test Antenna".to_string(),
+                src: AntennaSrcEnum::All,
+                keywords: vec![
                     vec!["foo".to_string(), "bar".to_string()],
                     vec!["foobar".to_string()],
                 ]
-                .into()),
-                exclude_keywords: Set(vec![
+                .into(),
+                exclude_keywords: vec![
                     vec!["abc".to_string()],
                     vec!["def".to_string(), "ghi".to_string()],
                 ]
-                .into()),
-                with_file: Set(false),
-                notify: Set(true),
-                case_sensitive: Set(true),
-                with_replies: Set(false),
+                .into(),
+                notify: true,
+                case_sensitive: true,
                 ..Default::default()
             };
-            antenna_model.insert(txn).await?;
+            antenna_model
+                .into_active_model()
+                .reset_all()
+                .insert(txn)
+                .await?;
 
             Ok(())
         })
@@ -99,19 +193,11 @@ async fn setup_model(db: &DbConn) {
 }
 
 mod int_test {
-    use sea_orm::Database;
-
-    use super::{cleanup, prepare, setup_schema};
+    use super::{cleanup, prepare};
 
     #[tokio::test]
     async fn can_prepare_and_cleanup() {
         prepare().await;
         cleanup().await;
     }
-
-    #[tokio::test]
-    async fn can_setup_sqlite_schema() {
-        let db = Database::connect("sqlite::memory:").await.unwrap();
-        setup_schema(db).await;
-    }
 }

From 3dd44d146f3f66e22311b0924bcb5e76130aec60 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 04:34:49 -0400
Subject: [PATCH 165/198] add pack_by_id

---
 .../native-utils/crates/model/src/error.rs    |  4 +-
 .../crates/model/src/repository/antenna.rs    |  5 ++
 .../crates/model/src/repository/mod.rs        | 14 +++++
 .../crates/model/src/schema/mod.rs            |  2 +-
 .../crates/model/tests/repository/antenna.rs  | 60 ++++++++++---------
 5 files changed, 55 insertions(+), 30 deletions(-)

diff --git a/packages/backend/native-utils/crates/model/src/error.rs b/packages/backend/native-utils/crates/model/src/error.rs
index 7686312ec..c90f3f5a3 100644
--- a/packages/backend/native-utils/crates/model/src/error.rs
+++ b/packages/backend/native-utils/crates/model/src/error.rs
@@ -5,5 +5,7 @@ pub enum Error {
     #[error("Failed to get database connection")]
     DbConnError(#[from] database::error::Error),
     #[error("Database operation error: {0}")]
-    DatabaseOperationError(#[from] sea_orm::DbErr),
+    DbOperationError(#[from] sea_orm::DbErr),
+    #[error("Requested entity not found")]
+    NotFound,
 }
diff --git a/packages/backend/native-utils/crates/model/src/repository/antenna.rs b/packages/backend/native-utils/crates/model/src/repository/antenna.rs
index f9ef012a6..d3ad6ecec 100644
--- a/packages/backend/native-utils/crates/model/src/repository/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/repository/antenna.rs
@@ -5,6 +5,7 @@ use crate::entity::{antenna, antenna_note, user_group_joining};
 use crate::error::Error;
 use crate::schema::antenna::Antenna;
 
+use super::macros::impl_pack_by_id;
 use super::Repository;
 
 #[async_trait]
@@ -44,4 +45,8 @@ impl Repository<Antenna> for antenna::Model {
             has_unread_note,
         })
     }
+
+    async fn pack_by_id(id: String) -> Result<Antenna, Error> {
+        impl_pack_by_id!(antenna::Entity, id)
+    }
 }
diff --git a/packages/backend/native-utils/crates/model/src/repository/mod.rs b/packages/backend/native-utils/crates/model/src/repository/mod.rs
index 01e11412d..83573ab16 100644
--- a/packages/backend/native-utils/crates/model/src/repository/mod.rs
+++ b/packages/backend/native-utils/crates/model/src/repository/mod.rs
@@ -8,4 +8,18 @@ use crate::error::Error;
 #[async_trait]
 pub trait Repository<T: JsonSchema> {
     async fn pack(self) -> Result<T, Error>;
+    async fn pack_by_id(id: String) -> Result<T, Error>;
+}
+
+mod macros {
+    macro_rules! impl_pack_by_id {
+        ($a:ty, $b:ident) => {
+            match <$a>::find_by_id($b).one(database::get_database()?).await? {
+                None => Err(Error::NotFound),
+                Some(m) => m.pack().await,
+            }
+        };
+    }
+
+    pub(crate) use impl_pack_by_id;
 }
diff --git a/packages/backend/native-utils/crates/model/src/schema/mod.rs b/packages/backend/native-utils/crates/model/src/schema/mod.rs
index cc495aac1..a64751020 100644
--- a/packages/backend/native-utils/crates/model/src/schema/mod.rs
+++ b/packages/backend/native-utils/crates/model/src/schema/mod.rs
@@ -6,7 +6,7 @@ use schemars::{schema_for, JsonSchema};
 
 /// Structs of schema defitions implement this trait in order to
 /// provide the JSON Schema validator [`jsonschema::JSONSchema`].
-trait Schema<T: JsonSchema> {
+pub trait Schema<T: JsonSchema> {
     /// Returns the validator of [JSON Schema Draft
     /// 7](https://json-schema.org/specification-links.html#draft-7) with the
     /// default settings of [`schemars::gen::SchemaSettings`].
diff --git a/packages/backend/native-utils/crates/model/tests/repository/antenna.rs b/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
index e07a16c07..193efcba2 100644
--- a/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
+++ b/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
@@ -29,34 +29,38 @@ mod int_test {
             .await
             .expect("Unable to pack");
 
-        assert_eq!(
-            packed,
-            schema::antenna::Antenna {
-                id: alice_antenna.id,
-                created_at: alice_antenna.created_at.into(),
-                name: "Test Antenna".to_string(),
-                keywords: vec![
-                    vec!["foo".to_string(), "bar".to_string()],
-                    vec!["foobar".to_string()]
-                ]
-                .into(),
-                exclude_keywords: vec![
-                    vec!["abc".to_string()],
-                    vec!["def".to_string(), "ghi".to_string()]
-                ]
-                .into(),
-                src: schema::antenna::AntennaSrc::All,
-                user_list_id: None,
-                user_group_id: None,
-                users: vec![].into(),
-                instances: vec![].into(),
-                case_sensitive: true,
-                notify: true,
-                with_replies: false,
-                with_file: false,
-                has_unread_note: false,
-            }
-        );
+        let packed_by_id = antenna::Model::pack_by_id(alice_antenna.id.to_owned())
+            .await
+            .expect("Unable to pack");
+
+        let result = schema::antenna::Antenna {
+            id: alice_antenna.id,
+            created_at: alice_antenna.created_at.into(),
+            name: "Test Antenna".to_string(),
+            keywords: vec![
+                vec!["foo".to_string(), "bar".to_string()],
+                vec!["foobar".to_string()],
+            ]
+            .into(),
+            exclude_keywords: vec![
+                vec!["abc".to_string()],
+                vec!["def".to_string(), "ghi".to_string()],
+            ]
+            .into(),
+            src: schema::antenna::AntennaSrc::All,
+            user_list_id: None,
+            user_group_id: None,
+            users: vec![].into(),
+            instances: vec![].into(),
+            case_sensitive: true,
+            notify: true,
+            with_replies: false,
+            with_file: false,
+            has_unread_note: false,
+        };
+
+        assert_eq!(packed, result);
+        assert_eq!(packed_by_id, result);
 
         cleanup().await;
     }

From 993ece47555641c99c37e6ea7d9851e3203bf2e7 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 06:22:09 -0400
Subject: [PATCH 166/198] add napi schema

---
 .../native-utils/crates/model/Cargo.toml      |  5 ++
 .../model/src/{entity/mod.rs => entity.rs}    |  0
 .../src/{repository/mod.rs => repository.rs}  |  0
 .../crates/model/src/repository/antenna.rs    | 13 ++++-
 .../model/src/{schema/mod.rs => schema.rs}    | 12 +++++
 .../crates/model/src/schema/antenna.rs        |  6 +--
 .../crates/model/src/schema/napi.rs           |  1 +
 .../crates/model/src/schema/napi/antenna.rs   | 47 +++++++++++++++++++
 .../native-utils/crates/model/tests/common.rs |  2 +
 .../crates/model/tests/repository/antenna.rs  |  4 +-
 10 files changed, 83 insertions(+), 7 deletions(-)
 rename packages/backend/native-utils/crates/model/src/{entity/mod.rs => entity.rs} (100%)
 rename packages/backend/native-utils/crates/model/src/{repository/mod.rs => repository.rs} (100%)
 rename packages/backend/native-utils/crates/model/src/{schema/mod.rs => schema.rs} (75%)
 create mode 100644 packages/backend/native-utils/crates/model/src/schema/napi.rs
 create mode 100644 packages/backend/native-utils/crates/model/src/schema/napi/antenna.rs

diff --git a/packages/backend/native-utils/crates/model/Cargo.toml b/packages/backend/native-utils/crates/model/Cargo.toml
index e9a3260c4..c2002443d 100644
--- a/packages/backend/native-utils/crates/model/Cargo.toml
+++ b/packages/backend/native-utils/crates/model/Cargo.toml
@@ -7,6 +7,7 @@ edition = "2021"
 [features]
 default = []
 noarray = []
+napi = ["dep:napi", "dep:napi-derive"]
 
 [dependencies]
 async-trait = "0.1.68"
@@ -25,3 +26,7 @@ thiserror = "1.0.40"
 tokio = { version = "1.28.1", features = ["sync"] }
 util = { path = "../util" }
 utoipa = "3.3.0"
+
+# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
+napi = { version = "2.12.0", default-features = false, features = ["napi4"], optional = true }
+napi-derive = { version = "2.12.0", optional = true }
diff --git a/packages/backend/native-utils/crates/model/src/entity/mod.rs b/packages/backend/native-utils/crates/model/src/entity.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/mod.rs
rename to packages/backend/native-utils/crates/model/src/entity.rs
diff --git a/packages/backend/native-utils/crates/model/src/repository/mod.rs b/packages/backend/native-utils/crates/model/src/repository.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/repository/mod.rs
rename to packages/backend/native-utils/crates/model/src/repository.rs
diff --git a/packages/backend/native-utils/crates/model/src/repository/antenna.rs b/packages/backend/native-utils/crates/model/src/repository/antenna.rs
index d3ad6ecec..c8324edd3 100644
--- a/packages/backend/native-utils/crates/model/src/repository/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/repository/antenna.rs
@@ -1,9 +1,10 @@
 use async_trait::async_trait;
+use cfg_if::cfg_if;
 use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
 
 use crate::entity::{antenna, antenna_note, user_group_joining};
 use crate::error::Error;
-use crate::schema::antenna::Antenna;
+use crate::schema::Antenna;
 
 use super::macros::impl_pack_by_id;
 use super::Repository;
@@ -27,9 +28,17 @@ impl Repository<Antenna> for antenna::Model {
             Some(m) => Some(m.user_group_id),
         };
 
+        cfg_if! {
+            if #[cfg(feature = "napi")] {
+                let created_at: String = self.created_at.to_rfc3339();
+            } else {
+                let created_at: chrono::DateTime<chrono::Utc> = self.created_at.into();
+            }
+        }
+
         Ok(Antenna {
             id: self.id,
-            created_at: self.created_at.into(),
+            created_at,
             name: self.name,
             keywords: self.keywords.into(),
             exclude_keywords: self.exclude_keywords.into(),
diff --git a/packages/backend/native-utils/crates/model/src/schema/mod.rs b/packages/backend/native-utils/crates/model/src/schema.rs
similarity index 75%
rename from packages/backend/native-utils/crates/model/src/schema/mod.rs
rename to packages/backend/native-utils/crates/model/src/schema.rs
index a64751020..78a0887fe 100644
--- a/packages/backend/native-utils/crates/model/src/schema/mod.rs
+++ b/packages/backend/native-utils/crates/model/src/schema.rs
@@ -1,9 +1,21 @@
 pub mod antenna;
 pub mod app;
 
+use cfg_if::cfg_if;
 use jsonschema::JSONSchema;
 use schemars::{schema_for, JsonSchema};
 
+cfg_if! {
+    if #[cfg(feature = "napi")] {
+        mod napi;
+        pub use napi::antenna::Antenna;
+        pub use napi::antenna::AntennaSrc;
+    } else {
+        pub use antenna::Antenna;
+        pub use antenna::AntennaSrc;
+    }
+}
+
 /// Structs of schema defitions implement this trait in order to
 /// provide the JSON Schema validator [`jsonschema::JSONSchema`].
 pub trait Schema<T: JsonSchema> {
diff --git a/packages/backend/native-utils/crates/model/src/schema/antenna.rs b/packages/backend/native-utils/crates/model/src/schema/antenna.rs
index fa7902b92..2f13a563d 100644
--- a/packages/backend/native-utils/crates/model/src/schema/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/schema/antenna.rs
@@ -47,10 +47,10 @@ pub enum AntennaSrc {
 }
 
 impl TryFrom<AntennaSrcEnum> for AntennaSrc {
-    type Error = parse_display::ParseError;
+    type Error = crate::error::Error;
 
     fn try_from(value: AntennaSrcEnum) -> Result<Self, Self::Error> {
-        value.to_string().parse()
+        value.to_string().parse().map_err(crate::error::Error::from)
     }
 }
 
@@ -63,7 +63,7 @@ pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| Antenna::validator());
 mod unit_test {
     use serde_json::json;
 
-    use crate::{entity::sea_orm_active_enums::AntennaSrcEnum, schema::antenna::AntennaSrc};
+    use crate::{entity::sea_orm_active_enums::AntennaSrcEnum, schema::AntennaSrc};
 
     use super::VALIDATOR;
 
diff --git a/packages/backend/native-utils/crates/model/src/schema/napi.rs b/packages/backend/native-utils/crates/model/src/schema/napi.rs
new file mode 100644
index 000000000..16130e4cc
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/schema/napi.rs
@@ -0,0 +1 @@
+pub mod antenna;
diff --git a/packages/backend/native-utils/crates/model/src/schema/napi/antenna.rs b/packages/backend/native-utils/crates/model/src/schema/napi/antenna.rs
new file mode 100644
index 000000000..cfba50d7f
--- /dev/null
+++ b/packages/backend/native-utils/crates/model/src/schema/napi/antenna.rs
@@ -0,0 +1,47 @@
+use parse_display::FromStr;
+use schemars::JsonSchema;
+use utoipa::ToSchema;
+
+use napi::bindgen_prelude::{FromNapiValue, ToNapiValue};
+use napi_derive::napi;
+
+#[napi]
+#[derive(Clone, Debug, PartialEq, Eq, JsonSchema, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct Antenna {
+    pub id: String,
+    pub created_at: String,
+    pub name: String,
+    pub keywords: Vec<Vec<String>>,
+    pub exclude_keywords: Vec<Vec<String>>,
+    #[schema(inline)]
+    pub src: AntennaSrc,
+    pub user_list_id: Option<String>,
+    pub user_group_id: Option<String>,
+    pub users: Vec<String>,
+    pub instances: Vec<String>,
+    #[serde(default)]
+    pub case_sensitive: bool,
+    #[serde(default)]
+    pub notify: bool,
+    #[serde(default)]
+    pub with_replies: bool,
+    #[serde(default)]
+    pub with_file: bool,
+    #[serde(default)]
+    pub has_unread_note: bool,
+}
+
+#[napi]
+#[derive(Debug, FromStr, PartialEq, Eq, JsonSchema, ToSchema)]
+#[serde(rename_all = "camelCase")]
+#[display(style = "camelCase")]
+#[display("'{}'")]
+pub enum AntennaSrc {
+    Home,
+    All,
+    Users,
+    List,
+    Group,
+    Instances,
+}
diff --git a/packages/backend/native-utils/crates/model/tests/common.rs b/packages/backend/native-utils/crates/model/tests/common.rs
index add8c12b2..554a8ca73 100644
--- a/packages/backend/native-utils/crates/model/tests/common.rs
+++ b/packages/backend/native-utils/crates/model/tests/common.rs
@@ -1,3 +1,5 @@
+#![cfg(not(feature = "napi"))]
+
 extern crate model;
 
 mod repository;
diff --git a/packages/backend/native-utils/crates/model/tests/repository/antenna.rs b/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
index 193efcba2..90732130a 100644
--- a/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
+++ b/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
@@ -33,7 +33,7 @@ mod int_test {
             .await
             .expect("Unable to pack");
 
-        let result = schema::antenna::Antenna {
+        let result = schema::Antenna {
             id: alice_antenna.id,
             created_at: alice_antenna.created_at.into(),
             name: "Test Antenna".to_string(),
@@ -47,7 +47,7 @@ mod int_test {
                 vec!["def".to_string(), "ghi".to_string()],
             ]
             .into(),
-            src: schema::antenna::AntennaSrc::All,
+            src: schema::AntennaSrc::All,
             user_list_id: None,
             user_group_id: None,
             users: vec![].into(),

From 1c34915018e81874185bf04579686fa667300702 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 07:08:58 -0400
Subject: [PATCH 167/198] fix unit test

---
 .../native-utils/crates/model/Cargo.toml      |  5 +++-
 .../native-utils/crates/model/src/schema.rs   |  6 ++--
 .../crates/model/src/schema/antenna.rs        | 30 +++++++++++--------
 .../crates/model/src/schema/app.rs            |  4 +--
 .../crates/model/src/schema/napi/antenna.rs   |  5 ++--
 .../crates/model/tests/repository/antenna.rs  |  1 +
 .../native-utils/crates/util/Cargo.toml       |  3 ++
 .../native-utils/crates/util/src/id.rs        |  1 +
 .../native-utils/crates/util/src/random.rs    |  1 +
 9 files changed, 35 insertions(+), 21 deletions(-)

diff --git a/packages/backend/native-utils/crates/model/Cargo.toml b/packages/backend/native-utils/crates/model/Cargo.toml
index c2002443d..4925fb702 100644
--- a/packages/backend/native-utils/crates/model/Cargo.toml
+++ b/packages/backend/native-utils/crates/model/Cargo.toml
@@ -5,7 +5,7 @@ edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 [features]
-default = []
+default = ["napi"]
 noarray = []
 napi = ["dep:napi", "dep:napi-derive"]
 
@@ -30,3 +30,6 @@ utoipa = "3.3.0"
 # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
 napi = { version = "2.12.0", default-features = false, features = ["napi4"], optional = true }
 napi-derive = { version = "2.12.0", optional = true }
+
+[dev-dependencies]
+pretty_assertions = "1.3.0"
diff --git a/packages/backend/native-utils/crates/model/src/schema.rs b/packages/backend/native-utils/crates/model/src/schema.rs
index 78a0887fe..71f9348b3 100644
--- a/packages/backend/native-utils/crates/model/src/schema.rs
+++ b/packages/backend/native-utils/crates/model/src/schema.rs
@@ -8,11 +8,13 @@ use schemars::{schema_for, JsonSchema};
 cfg_if! {
     if #[cfg(feature = "napi")] {
         mod napi;
-        pub use napi::antenna::Antenna;
-        pub use napi::antenna::AntennaSrc;
+        pub use self::napi::antenna::Antenna;
+        pub use self::napi::antenna::AntennaSrc;
     } else {
         pub use antenna::Antenna;
         pub use antenna::AntennaSrc;
+        pub use app::App;
+        pub use app::AppPermission;
     }
 }
 
diff --git a/packages/backend/native-utils/crates/model/src/schema/antenna.rs b/packages/backend/native-utils/crates/model/src/schema/antenna.rs
index 2f13a563d..338998019 100644
--- a/packages/backend/native-utils/crates/model/src/schema/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/schema/antenna.rs
@@ -46,7 +46,7 @@ pub enum AntennaSrc {
     Instances,
 }
 
-impl TryFrom<AntennaSrcEnum> for AntennaSrc {
+impl TryFrom<AntennaSrcEnum> for super::AntennaSrc {
     type Error = crate::error::Error;
 
     fn try_from(value: AntennaSrcEnum) -> Result<Self, Self::Error> {
@@ -55,12 +55,13 @@ impl TryFrom<AntennaSrcEnum> for AntennaSrc {
 }
 
 // ---- TODO: could be macro
-impl Schema<Self> for Antenna {}
-pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| Antenna::validator());
+impl Schema<Self> for super::Antenna {}
+pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| super::Antenna::validator());
 // ----
 
 #[cfg(test)]
 mod unit_test {
+    use pretty_assertions::assert_eq;
     use serde_json::json;
 
     use crate::{entity::sea_orm_active_enums::AntennaSrcEnum, schema::AntennaSrc};
@@ -128,20 +129,23 @@ mod unit_test {
         let result = VALIDATOR
             .validate(&instance)
             .expect_err("validation must fail");
-        let mut paths: Vec<String> = result.map(|e| e.schema_path.to_string()).collect();
+        let mut paths: Vec<String> = result
+            .map(|e| e.instance_path.to_string())
+            .filter(|e| !e.is_empty())
+            .collect();
         paths.sort();
         assert_eq!(
             paths,
             vec![
-                "/properties/caseSensitive/type",
-                "/properties/createdAt/format",
-                "/properties/excludeKeywords/type",
-                "/properties/id/type",
-                "/properties/keywords/type",
-                "/properties/src/enum",
-                "/properties/userListId/type",
-                "/properties/users/items/type",
-                "/required"
+                "/caseSensitive",
+                #[cfg(not(feature = "napi"))]
+                "/createdAt",
+                "/excludeKeywords",
+                "/id",
+                "/keywords",
+                "/src",
+                "/userListId",
+                "/users/0"
             ]
         );
     }
diff --git a/packages/backend/native-utils/crates/model/src/schema/app.rs b/packages/backend/native-utils/crates/model/src/schema/app.rs
index b45cad6d6..f24c1387b 100644
--- a/packages/backend/native-utils/crates/model/src/schema/app.rs
+++ b/packages/backend/native-utils/crates/model/src/schema/app.rs
@@ -13,14 +13,14 @@ pub struct App {
     #[schemars(url)]
     pub callback_url: Option<String>,
     #[schema(inline)]
-    pub permission: Vec<Permission>,
+    pub permission: Vec<AppPermission>,
     pub secret: Option<String>,
     pub is_authorized: Option<bool>,
 }
 
 /// This represents `permissions` in `packages/calckey-js/src/consts.ts`.
 #[derive(Clone, Debug, PartialEq, Eq, JsonSchema, ToSchema)]
-pub enum Permission {
+pub enum AppPermission {
     #[serde(rename = "read:account")]
     ReadAccount,
     #[serde(rename = "write:account")]
diff --git a/packages/backend/native-utils/crates/model/src/schema/napi/antenna.rs b/packages/backend/native-utils/crates/model/src/schema/napi/antenna.rs
index cfba50d7f..ffb817c12 100644
--- a/packages/backend/native-utils/crates/model/src/schema/napi/antenna.rs
+++ b/packages/backend/native-utils/crates/model/src/schema/napi/antenna.rs
@@ -1,10 +1,9 @@
+use napi::bindgen_prelude::{FromNapiValue, ToNapiValue};
+use napi_derive::napi;
 use parse_display::FromStr;
 use schemars::JsonSchema;
 use utoipa::ToSchema;
 
-use napi::bindgen_prelude::{FromNapiValue, ToNapiValue};
-use napi_derive::napi;
-
 #[napi]
 #[derive(Clone, Debug, PartialEq, Eq, JsonSchema, ToSchema)]
 #[serde(rename_all = "camelCase")]
diff --git a/packages/backend/native-utils/crates/model/tests/repository/antenna.rs b/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
index 90732130a..2da1e59d4 100644
--- a/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
+++ b/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
@@ -4,6 +4,7 @@ mod int_test {
         repository::Repository,
         schema,
     };
+    use pretty_assertions::assert_eq;
     use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
 
     use crate::{cleanup, prepare};
diff --git a/packages/backend/native-utils/crates/util/Cargo.toml b/packages/backend/native-utils/crates/util/Cargo.toml
index ad5486b4b..6050342dd 100644
--- a/packages/backend/native-utils/crates/util/Cargo.toml
+++ b/packages/backend/native-utils/crates/util/Cargo.toml
@@ -10,3 +10,6 @@ cuid2 = "0.1.0"
 once_cell = "1.17.1"
 rand = "0.8.5"
 thiserror = "1.0.40"
+
+[dev-dependencies]
+pretty_assertions = "1.3.0"
diff --git a/packages/backend/native-utils/crates/util/src/id.rs b/packages/backend/native-utils/crates/util/src/id.rs
index 2831ef79d..d3c5809ea 100644
--- a/packages/backend/native-utils/crates/util/src/id.rs
+++ b/packages/backend/native-utils/crates/util/src/id.rs
@@ -22,6 +22,7 @@ pub fn create_id() -> Result<String, ErrorUninitialized> {
 
 #[cfg(test)]
 mod unit_test {
+    use pretty_assertions::{assert_eq, assert_ne};
     use std::thread;
 
     use crate::id;
diff --git a/packages/backend/native-utils/crates/util/src/random.rs b/packages/backend/native-utils/crates/util/src/random.rs
index 1c4a20d50..5ee06d128 100644
--- a/packages/backend/native-utils/crates/util/src/random.rs
+++ b/packages/backend/native-utils/crates/util/src/random.rs
@@ -10,6 +10,7 @@ pub fn gen_string(length: u16) -> String {
 
 #[cfg(test)]
 mod unit_test {
+    use pretty_assertions::{assert_eq, assert_ne};
     use std::thread;
 
     use super::gen_string;

From 5f849e417ec0f3ba9755e1b1ebbfc562c4eefa4e Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 08:48:12 -0400
Subject: [PATCH 168/198] move files out from crate

---
 packages/backend/native-utils/Cargo.toml      | 33 ++++++++++++++---
 .../native-utils/crates/database/Cargo.toml   | 12 -------
 .../native-utils/crates/model/Cargo.toml      | 35 -------------------
 .../native-utils/crates/util/Cargo.toml       | 15 --------
 .../{crates => }/migration/Cargo.toml         |  0
 .../{crates => }/migration/README.md          |  0
 .../{crates => }/migration/src/lib.rs         |  0
 .../src/m20230531_180824_drop_reversi.rs      |  0
 .../{crates => }/migration/src/main.rs        |  0
 .../{crates => }/migration/src/vec_to_json.rs |  0
 .../database/src => src/database}/error.rs    |  0
 .../src/lib.rs => src/database/mod.rs}        |  6 ++--
 packages/backend/native-utils/src/lib.rs      |  5 +++
 .../{crates/model/src => src/model}/entity.rs |  0
 .../model}/entity/abuse_user_report.rs        |  0
 .../src => src/model}/entity/access_token.rs  |  0
 .../model/src => src/model}/entity/ad.rs      |  0
 .../src => src/model}/entity/announcement.rs  |  0
 .../model}/entity/announcement_read.rs        |  0
 .../model/src => src/model}/entity/antenna.rs |  0
 .../src => src/model}/entity/antenna_note.rs  |  0
 .../model/src => src/model}/entity/app.rs     |  0
 .../model}/entity/attestation_challenge.rs    |  0
 .../src => src/model}/entity/auth_session.rs  |  0
 .../src => src/model}/entity/blocking.rs      |  0
 .../model/src => src/model}/entity/channel.rs |  0
 .../model}/entity/channel_following.rs        |  0
 .../model}/entity/channel_note_pining.rs      |  0
 .../model/src => src/model}/entity/clip.rs    |  0
 .../src => src/model}/entity/clip_note.rs     |  0
 .../src => src/model}/entity/drive_file.rs    |  0
 .../src => src/model}/entity/drive_folder.rs  |  0
 .../model/src => src/model}/entity/emoji.rs   |  0
 .../model}/entity/follow_request.rs           |  0
 .../src => src/model}/entity/following.rs     |  0
 .../src => src/model}/entity/gallery_like.rs  |  0
 .../src => src/model}/entity/gallery_post.rs  |  0
 .../model/src => src/model}/entity/hashtag.rs |  0
 .../src => src/model}/entity/instance.rs      |  0
 .../model}/entity/messaging_message.rs        |  0
 .../model/src => src/model}/entity/meta.rs    |  0
 .../src => src/model}/entity/migrations.rs    |  0
 .../model}/entity/moderation_log.rs           |  0
 .../src => src/model}/entity/muted_note.rs    |  0
 .../model/src => src/model}/entity/muting.rs  |  0
 .../model}/entity/newtype/macros.rs           |  0
 .../src => src/model}/entity/newtype/mod.rs   |  0
 .../model/src => src/model}/entity/note.rs    |  0
 .../src => src/model}/entity/note_edit.rs     |  0
 .../src => src/model}/entity/note_favorite.rs |  0
 .../src => src/model}/entity/note_reaction.rs |  0
 .../model}/entity/note_thread_muting.rs       |  0
 .../src => src/model}/entity/note_unread.rs   |  0
 .../src => src/model}/entity/note_watching.rs |  0
 .../src => src/model}/entity/notification.rs  |  0
 .../model/src => src/model}/entity/page.rs    |  0
 .../src => src/model}/entity/page_like.rs     |  0
 .../model}/entity/password_reset_request.rs   |  0
 .../model/src => src/model}/entity/poll.rs    |  0
 .../src => src/model}/entity/poll_vote.rs     |  0
 .../model/src => src/model}/entity/prelude.rs |  0
 .../src => src/model}/entity/promo_note.rs    |  0
 .../src => src/model}/entity/promo_read.rs    |  0
 .../model}/entity/registration_ticket.rs      |  0
 .../src => src/model}/entity/registry_item.rs |  0
 .../model/src => src/model}/entity/relay.rs   |  0
 .../src => src/model}/entity/renote_muting.rs |  0
 .../model}/entity/sea_orm_active_enums.rs     |  0
 .../model/src => src/model}/entity/signin.rs  |  0
 .../model}/entity/sw_subscription.rs          |  0
 .../src => src/model}/entity/used_username.rs |  0
 .../model/src => src/model}/entity/user.rs    |  0
 .../src => src/model}/entity/user_group.rs    |  0
 .../model}/entity/user_group_invitation.rs    |  0
 .../model}/entity/user_group_invite.rs        |  0
 .../model}/entity/user_group_joining.rs       |  0
 .../model/src => src/model}/entity/user_ip.rs |  0
 .../src => src/model}/entity/user_keypair.rs  |  0
 .../src => src/model}/entity/user_list.rs     |  0
 .../model}/entity/user_list_joining.rs        |  0
 .../model}/entity/user_note_pining.rs         |  0
 .../src => src/model}/entity/user_pending.rs  |  0
 .../src => src/model}/entity/user_profile.rs  |  0
 .../model}/entity/user_publickey.rs           |  0
 .../model}/entity/user_security_key.rs        |  0
 .../model/src => src/model}/entity/webhook.rs |  0
 .../{crates/model/src => src/model}/error.rs  |  9 ++++-
 .../model/src/lib.rs => src/model/mod.rs}     |  0
 .../model/src => src/model}/repository.rs     |  7 ++--
 .../src => src/model}/repository/antenna.rs   |  7 ++--
 .../{crates/model/src => src/model}/schema.rs |  0
 .../model/src => src/model}/schema/antenna.rs |  9 ++---
 .../model/src => src/model}/schema/app.rs     |  0
 .../model/src => src/model}/schema/napi.rs    |  0
 .../src => src/model}/schema/napi/antenna.rs  | 13 ++++++-
 .../{crates/util/src => src/util}/id.rs       |  2 +-
 .../util/src/lib.rs => src/util/mod.rs}       |  0
 .../{crates/util/src => src/util}/random.rs   |  0
 .../{crates/model => }/tests/common.rs        | 17 +++++----
 .../backend/native-utils/tests/model/mod.rs   |  1 +
 .../mod.rs => tests/model/repository.rs}      |  0
 .../model}/repository/antenna.rs              |  2 ++
 102 files changed, 82 insertions(+), 91 deletions(-)
 delete mode 100644 packages/backend/native-utils/crates/database/Cargo.toml
 delete mode 100644 packages/backend/native-utils/crates/model/Cargo.toml
 delete mode 100644 packages/backend/native-utils/crates/util/Cargo.toml
 rename packages/backend/native-utils/{crates => }/migration/Cargo.toml (100%)
 rename packages/backend/native-utils/{crates => }/migration/README.md (100%)
 rename packages/backend/native-utils/{crates => }/migration/src/lib.rs (100%)
 rename packages/backend/native-utils/{crates => }/migration/src/m20230531_180824_drop_reversi.rs (100%)
 rename packages/backend/native-utils/{crates => }/migration/src/main.rs (100%)
 rename packages/backend/native-utils/{crates => }/migration/src/vec_to_json.rs (100%)
 rename packages/backend/native-utils/{crates/database/src => src/database}/error.rs (100%)
 rename packages/backend/native-utils/{crates/database/src/lib.rs => src/database/mod.rs} (87%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/abuse_user_report.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/access_token.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/ad.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/announcement.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/announcement_read.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/antenna.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/antenna_note.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/app.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/attestation_challenge.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/auth_session.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/blocking.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/channel.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/channel_following.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/channel_note_pining.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/clip.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/clip_note.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/drive_file.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/drive_folder.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/emoji.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/follow_request.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/following.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/gallery_like.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/gallery_post.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/hashtag.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/instance.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/messaging_message.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/meta.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/migrations.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/moderation_log.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/muted_note.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/muting.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/newtype/macros.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/newtype/mod.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/note.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/note_edit.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/note_favorite.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/note_reaction.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/note_thread_muting.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/note_unread.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/note_watching.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/notification.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/page.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/page_like.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/password_reset_request.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/poll.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/poll_vote.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/prelude.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/promo_note.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/promo_read.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/registration_ticket.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/registry_item.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/relay.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/renote_muting.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/sea_orm_active_enums.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/signin.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/sw_subscription.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/used_username.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/user.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/user_group.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/user_group_invitation.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/user_group_invite.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/user_group_joining.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/user_ip.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/user_keypair.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/user_list.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/user_list_joining.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/user_note_pining.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/user_pending.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/user_profile.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/user_publickey.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/user_security_key.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/entity/webhook.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/error.rs (62%)
 rename packages/backend/native-utils/{crates/model/src/lib.rs => src/model/mod.rs} (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/repository.rs (75%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/repository/antenna.rs (92%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/schema.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/schema/antenna.rs (93%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/schema/app.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/schema/napi.rs (100%)
 rename packages/backend/native-utils/{crates/model/src => src/model}/schema/napi/antenna.rs (78%)
 rename packages/backend/native-utils/{crates/util/src => src/util}/id.rs (97%)
 rename packages/backend/native-utils/{crates/util/src/lib.rs => src/util/mod.rs} (100%)
 rename packages/backend/native-utils/{crates/util/src => src/util}/random.rs (100%)
 rename packages/backend/native-utils/{crates/model => }/tests/common.rs (96%)
 create mode 100644 packages/backend/native-utils/tests/model/mod.rs
 rename packages/backend/native-utils/{crates/model/tests/repository/mod.rs => tests/model/repository.rs} (100%)
 rename packages/backend/native-utils/{crates/model/tests => tests/model}/repository/antenna.rs (97%)

diff --git a/packages/backend/native-utils/Cargo.toml b/packages/backend/native-utils/Cargo.toml
index 6f3c0e23f..3c70a0209 100644
--- a/packages/backend/native-utils/Cargo.toml
+++ b/packages/backend/native-utils/Cargo.toml
@@ -4,15 +4,40 @@ name = "native-utils"
 version = "0.0.0"
 
 [workspace]
-members = ["crates/*"]
+members = ["migration/Cargo.toml"]
+
+[features]
+default = ["napi"]
+noarray = []
+napi = ["dep:napi", "dep:napi-derive"]
 
 [lib]
-crate-type = ["cdylib"]
+crate-type = ["cdylib", "lib"]
 
 [dependencies]
+async-trait = "0.1.68"
+cfg-if = "1.0.0"
+chrono = "0.4.24"
+cuid2 = "0.1.0"
+derive_more = "0.99.17"
+jsonschema = "0.17.0"
+once_cell = "1.17.1"
+parse-display = "0.8.0"
+rand = "0.8.5"
+schemars = { version = "0.8.12", features = ["chrono"] }
+sea-orm = { version = "0.11.3", features = ["sqlx-postgres", "postgres-array", "sqlx-sqlite", "runtime-tokio-rustls"] }
+serde = { version = "1.0.163", features = ["derive"] }
+serde_json = "1.0.96"
+thiserror = "1.0.40"
+tokio = { version = "1.28.1", features = ["full"] }
+utoipa = "3.3.0"
+
 # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
-napi = { version = "2.12.0", default-features = false, features = ["napi4"] }
-napi-derive = "2.12.0"
+napi = { version = "2.12.0", default-features = false, features = ["napi4", "tokio_rt"], optional = true }
+napi-derive = { version = "2.12.0", optional = true }
+
+[dev-dependencies]
+pretty_assertions = "1.3.0"
 
 [build-dependencies]
 napi-build = "2.0.1"
diff --git a/packages/backend/native-utils/crates/database/Cargo.toml b/packages/backend/native-utils/crates/database/Cargo.toml
deleted file mode 100644
index 440a76cf5..000000000
--- a/packages/backend/native-utils/crates/database/Cargo.toml
+++ /dev/null
@@ -1,12 +0,0 @@
-[package]
-name = "database"
-version = "0.1.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-once_cell = "1.17.1"
-sea-orm = { version = "0.11.3", features = ["sqlx-postgres", "runtime-tokio-rustls"] }
-thiserror = "1.0.40"
-tokio = { version = "1.28.1", features = ["macros"] }
diff --git a/packages/backend/native-utils/crates/model/Cargo.toml b/packages/backend/native-utils/crates/model/Cargo.toml
deleted file mode 100644
index 4925fb702..000000000
--- a/packages/backend/native-utils/crates/model/Cargo.toml
+++ /dev/null
@@ -1,35 +0,0 @@
-[package]
-name = "model"
-version = "0.1.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-[features]
-default = ["napi"]
-noarray = []
-napi = ["dep:napi", "dep:napi-derive"]
-
-[dependencies]
-async-trait = "0.1.68"
-cfg-if = "1.0.0"
-chrono = "0.4.24"
-database = { path = "../database" }
-derive_more = "0.99.17"
-jsonschema = "0.17.0"
-once_cell = "1.17.1"
-parse-display = "0.8.0"
-schemars = { version = "0.8.12", features = ["chrono"] }
-sea-orm = { version = "0.11.3", features = ["sqlx-postgres", "postgres-array", "sqlx-sqlite", "runtime-tokio-rustls"] }
-serde = { version = "1.0.163", features = ["derive"] }
-serde_json = "1.0.96"
-thiserror = "1.0.40"
-tokio = { version = "1.28.1", features = ["sync"] }
-util = { path = "../util" }
-utoipa = "3.3.0"
-
-# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
-napi = { version = "2.12.0", default-features = false, features = ["napi4"], optional = true }
-napi-derive = { version = "2.12.0", optional = true }
-
-[dev-dependencies]
-pretty_assertions = "1.3.0"
diff --git a/packages/backend/native-utils/crates/util/Cargo.toml b/packages/backend/native-utils/crates/util/Cargo.toml
deleted file mode 100644
index 6050342dd..000000000
--- a/packages/backend/native-utils/crates/util/Cargo.toml
+++ /dev/null
@@ -1,15 +0,0 @@
-[package]
-name = "util"
-version = "0.1.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-cuid2 = "0.1.0"
-once_cell = "1.17.1"
-rand = "0.8.5"
-thiserror = "1.0.40"
-
-[dev-dependencies]
-pretty_assertions = "1.3.0"
diff --git a/packages/backend/native-utils/crates/migration/Cargo.toml b/packages/backend/native-utils/migration/Cargo.toml
similarity index 100%
rename from packages/backend/native-utils/crates/migration/Cargo.toml
rename to packages/backend/native-utils/migration/Cargo.toml
diff --git a/packages/backend/native-utils/crates/migration/README.md b/packages/backend/native-utils/migration/README.md
similarity index 100%
rename from packages/backend/native-utils/crates/migration/README.md
rename to packages/backend/native-utils/migration/README.md
diff --git a/packages/backend/native-utils/crates/migration/src/lib.rs b/packages/backend/native-utils/migration/src/lib.rs
similarity index 100%
rename from packages/backend/native-utils/crates/migration/src/lib.rs
rename to packages/backend/native-utils/migration/src/lib.rs
diff --git a/packages/backend/native-utils/crates/migration/src/m20230531_180824_drop_reversi.rs b/packages/backend/native-utils/migration/src/m20230531_180824_drop_reversi.rs
similarity index 100%
rename from packages/backend/native-utils/crates/migration/src/m20230531_180824_drop_reversi.rs
rename to packages/backend/native-utils/migration/src/m20230531_180824_drop_reversi.rs
diff --git a/packages/backend/native-utils/crates/migration/src/main.rs b/packages/backend/native-utils/migration/src/main.rs
similarity index 100%
rename from packages/backend/native-utils/crates/migration/src/main.rs
rename to packages/backend/native-utils/migration/src/main.rs
diff --git a/packages/backend/native-utils/crates/migration/src/vec_to_json.rs b/packages/backend/native-utils/migration/src/vec_to_json.rs
similarity index 100%
rename from packages/backend/native-utils/crates/migration/src/vec_to_json.rs
rename to packages/backend/native-utils/migration/src/vec_to_json.rs
diff --git a/packages/backend/native-utils/crates/database/src/error.rs b/packages/backend/native-utils/src/database/error.rs
similarity index 100%
rename from packages/backend/native-utils/crates/database/src/error.rs
rename to packages/backend/native-utils/src/database/error.rs
diff --git a/packages/backend/native-utils/crates/database/src/lib.rs b/packages/backend/native-utils/src/database/mod.rs
similarity index 87%
rename from packages/backend/native-utils/crates/database/src/lib.rs
rename to packages/backend/native-utils/src/database/mod.rs
index 0012d2292..c3cdea727 100644
--- a/packages/backend/native-utils/crates/database/src/lib.rs
+++ b/packages/backend/native-utils/src/database/mod.rs
@@ -1,9 +1,8 @@
 pub mod error;
 
+use error::Error;
 use sea_orm::{Database, DbConn};
 
-use crate::error::Error;
-
 static DB_CONN: once_cell::sync::OnceCell<DbConn> = once_cell::sync::OnceCell::new();
 
 pub async fn init_database(connection_uri: impl Into<String>) -> Result<(), Error> {
@@ -18,8 +17,7 @@ pub fn get_database() -> Result<&'static DbConn, Error> {
 
 #[cfg(test)]
 mod unit_test {
-    use super::get_database;
-    use crate::error::Error;
+    use super::{error::Error, get_database};
 
     #[test]
     fn error_uninitialized() {
diff --git a/packages/backend/native-utils/src/lib.rs b/packages/backend/native-utils/src/lib.rs
index e13190140..6a5e3f7f2 100644
--- a/packages/backend/native-utils/src/lib.rs
+++ b/packages/backend/native-utils/src/lib.rs
@@ -1 +1,6 @@
+pub mod database;
+pub mod model;
+pub mod util;
+
+#[cfg(feature = "napi")]
 pub mod mastodon_api;
diff --git a/packages/backend/native-utils/crates/model/src/entity.rs b/packages/backend/native-utils/src/model/entity.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity.rs
rename to packages/backend/native-utils/src/model/entity.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/abuse_user_report.rs b/packages/backend/native-utils/src/model/entity/abuse_user_report.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/abuse_user_report.rs
rename to packages/backend/native-utils/src/model/entity/abuse_user_report.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/access_token.rs b/packages/backend/native-utils/src/model/entity/access_token.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/access_token.rs
rename to packages/backend/native-utils/src/model/entity/access_token.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/ad.rs b/packages/backend/native-utils/src/model/entity/ad.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/ad.rs
rename to packages/backend/native-utils/src/model/entity/ad.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/announcement.rs b/packages/backend/native-utils/src/model/entity/announcement.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/announcement.rs
rename to packages/backend/native-utils/src/model/entity/announcement.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/announcement_read.rs b/packages/backend/native-utils/src/model/entity/announcement_read.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/announcement_read.rs
rename to packages/backend/native-utils/src/model/entity/announcement_read.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/antenna.rs b/packages/backend/native-utils/src/model/entity/antenna.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/antenna.rs
rename to packages/backend/native-utils/src/model/entity/antenna.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/antenna_note.rs b/packages/backend/native-utils/src/model/entity/antenna_note.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/antenna_note.rs
rename to packages/backend/native-utils/src/model/entity/antenna_note.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/app.rs b/packages/backend/native-utils/src/model/entity/app.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/app.rs
rename to packages/backend/native-utils/src/model/entity/app.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/attestation_challenge.rs b/packages/backend/native-utils/src/model/entity/attestation_challenge.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/attestation_challenge.rs
rename to packages/backend/native-utils/src/model/entity/attestation_challenge.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/auth_session.rs b/packages/backend/native-utils/src/model/entity/auth_session.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/auth_session.rs
rename to packages/backend/native-utils/src/model/entity/auth_session.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/blocking.rs b/packages/backend/native-utils/src/model/entity/blocking.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/blocking.rs
rename to packages/backend/native-utils/src/model/entity/blocking.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/channel.rs b/packages/backend/native-utils/src/model/entity/channel.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/channel.rs
rename to packages/backend/native-utils/src/model/entity/channel.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/channel_following.rs b/packages/backend/native-utils/src/model/entity/channel_following.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/channel_following.rs
rename to packages/backend/native-utils/src/model/entity/channel_following.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/channel_note_pining.rs b/packages/backend/native-utils/src/model/entity/channel_note_pining.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/channel_note_pining.rs
rename to packages/backend/native-utils/src/model/entity/channel_note_pining.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/clip.rs b/packages/backend/native-utils/src/model/entity/clip.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/clip.rs
rename to packages/backend/native-utils/src/model/entity/clip.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/clip_note.rs b/packages/backend/native-utils/src/model/entity/clip_note.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/clip_note.rs
rename to packages/backend/native-utils/src/model/entity/clip_note.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/drive_file.rs b/packages/backend/native-utils/src/model/entity/drive_file.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/drive_file.rs
rename to packages/backend/native-utils/src/model/entity/drive_file.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/drive_folder.rs b/packages/backend/native-utils/src/model/entity/drive_folder.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/drive_folder.rs
rename to packages/backend/native-utils/src/model/entity/drive_folder.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/emoji.rs b/packages/backend/native-utils/src/model/entity/emoji.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/emoji.rs
rename to packages/backend/native-utils/src/model/entity/emoji.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/follow_request.rs b/packages/backend/native-utils/src/model/entity/follow_request.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/follow_request.rs
rename to packages/backend/native-utils/src/model/entity/follow_request.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/following.rs b/packages/backend/native-utils/src/model/entity/following.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/following.rs
rename to packages/backend/native-utils/src/model/entity/following.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/gallery_like.rs b/packages/backend/native-utils/src/model/entity/gallery_like.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/gallery_like.rs
rename to packages/backend/native-utils/src/model/entity/gallery_like.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/gallery_post.rs b/packages/backend/native-utils/src/model/entity/gallery_post.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/gallery_post.rs
rename to packages/backend/native-utils/src/model/entity/gallery_post.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/hashtag.rs b/packages/backend/native-utils/src/model/entity/hashtag.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/hashtag.rs
rename to packages/backend/native-utils/src/model/entity/hashtag.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/instance.rs b/packages/backend/native-utils/src/model/entity/instance.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/instance.rs
rename to packages/backend/native-utils/src/model/entity/instance.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/messaging_message.rs b/packages/backend/native-utils/src/model/entity/messaging_message.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/messaging_message.rs
rename to packages/backend/native-utils/src/model/entity/messaging_message.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/meta.rs b/packages/backend/native-utils/src/model/entity/meta.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/meta.rs
rename to packages/backend/native-utils/src/model/entity/meta.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/migrations.rs b/packages/backend/native-utils/src/model/entity/migrations.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/migrations.rs
rename to packages/backend/native-utils/src/model/entity/migrations.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/moderation_log.rs b/packages/backend/native-utils/src/model/entity/moderation_log.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/moderation_log.rs
rename to packages/backend/native-utils/src/model/entity/moderation_log.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/muted_note.rs b/packages/backend/native-utils/src/model/entity/muted_note.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/muted_note.rs
rename to packages/backend/native-utils/src/model/entity/muted_note.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/muting.rs b/packages/backend/native-utils/src/model/entity/muting.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/muting.rs
rename to packages/backend/native-utils/src/model/entity/muting.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/newtype/macros.rs b/packages/backend/native-utils/src/model/entity/newtype/macros.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/newtype/macros.rs
rename to packages/backend/native-utils/src/model/entity/newtype/macros.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs b/packages/backend/native-utils/src/model/entity/newtype/mod.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/newtype/mod.rs
rename to packages/backend/native-utils/src/model/entity/newtype/mod.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/note.rs b/packages/backend/native-utils/src/model/entity/note.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/note.rs
rename to packages/backend/native-utils/src/model/entity/note.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_edit.rs b/packages/backend/native-utils/src/model/entity/note_edit.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/note_edit.rs
rename to packages/backend/native-utils/src/model/entity/note_edit.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_favorite.rs b/packages/backend/native-utils/src/model/entity/note_favorite.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/note_favorite.rs
rename to packages/backend/native-utils/src/model/entity/note_favorite.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_reaction.rs b/packages/backend/native-utils/src/model/entity/note_reaction.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/note_reaction.rs
rename to packages/backend/native-utils/src/model/entity/note_reaction.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_thread_muting.rs b/packages/backend/native-utils/src/model/entity/note_thread_muting.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/note_thread_muting.rs
rename to packages/backend/native-utils/src/model/entity/note_thread_muting.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_unread.rs b/packages/backend/native-utils/src/model/entity/note_unread.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/note_unread.rs
rename to packages/backend/native-utils/src/model/entity/note_unread.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/note_watching.rs b/packages/backend/native-utils/src/model/entity/note_watching.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/note_watching.rs
rename to packages/backend/native-utils/src/model/entity/note_watching.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/notification.rs b/packages/backend/native-utils/src/model/entity/notification.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/notification.rs
rename to packages/backend/native-utils/src/model/entity/notification.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/page.rs b/packages/backend/native-utils/src/model/entity/page.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/page.rs
rename to packages/backend/native-utils/src/model/entity/page.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/page_like.rs b/packages/backend/native-utils/src/model/entity/page_like.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/page_like.rs
rename to packages/backend/native-utils/src/model/entity/page_like.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/password_reset_request.rs b/packages/backend/native-utils/src/model/entity/password_reset_request.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/password_reset_request.rs
rename to packages/backend/native-utils/src/model/entity/password_reset_request.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/poll.rs b/packages/backend/native-utils/src/model/entity/poll.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/poll.rs
rename to packages/backend/native-utils/src/model/entity/poll.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/poll_vote.rs b/packages/backend/native-utils/src/model/entity/poll_vote.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/poll_vote.rs
rename to packages/backend/native-utils/src/model/entity/poll_vote.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/prelude.rs b/packages/backend/native-utils/src/model/entity/prelude.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/prelude.rs
rename to packages/backend/native-utils/src/model/entity/prelude.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/promo_note.rs b/packages/backend/native-utils/src/model/entity/promo_note.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/promo_note.rs
rename to packages/backend/native-utils/src/model/entity/promo_note.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/promo_read.rs b/packages/backend/native-utils/src/model/entity/promo_read.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/promo_read.rs
rename to packages/backend/native-utils/src/model/entity/promo_read.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/registration_ticket.rs b/packages/backend/native-utils/src/model/entity/registration_ticket.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/registration_ticket.rs
rename to packages/backend/native-utils/src/model/entity/registration_ticket.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/registry_item.rs b/packages/backend/native-utils/src/model/entity/registry_item.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/registry_item.rs
rename to packages/backend/native-utils/src/model/entity/registry_item.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/relay.rs b/packages/backend/native-utils/src/model/entity/relay.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/relay.rs
rename to packages/backend/native-utils/src/model/entity/relay.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/renote_muting.rs b/packages/backend/native-utils/src/model/entity/renote_muting.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/renote_muting.rs
rename to packages/backend/native-utils/src/model/entity/renote_muting.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/sea_orm_active_enums.rs b/packages/backend/native-utils/src/model/entity/sea_orm_active_enums.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/sea_orm_active_enums.rs
rename to packages/backend/native-utils/src/model/entity/sea_orm_active_enums.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/signin.rs b/packages/backend/native-utils/src/model/entity/signin.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/signin.rs
rename to packages/backend/native-utils/src/model/entity/signin.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/sw_subscription.rs b/packages/backend/native-utils/src/model/entity/sw_subscription.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/sw_subscription.rs
rename to packages/backend/native-utils/src/model/entity/sw_subscription.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/used_username.rs b/packages/backend/native-utils/src/model/entity/used_username.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/used_username.rs
rename to packages/backend/native-utils/src/model/entity/used_username.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/user.rs b/packages/backend/native-utils/src/model/entity/user.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/user.rs
rename to packages/backend/native-utils/src/model/entity/user.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_group.rs b/packages/backend/native-utils/src/model/entity/user_group.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/user_group.rs
rename to packages/backend/native-utils/src/model/entity/user_group.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_group_invitation.rs b/packages/backend/native-utils/src/model/entity/user_group_invitation.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/user_group_invitation.rs
rename to packages/backend/native-utils/src/model/entity/user_group_invitation.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_group_invite.rs b/packages/backend/native-utils/src/model/entity/user_group_invite.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/user_group_invite.rs
rename to packages/backend/native-utils/src/model/entity/user_group_invite.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_group_joining.rs b/packages/backend/native-utils/src/model/entity/user_group_joining.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/user_group_joining.rs
rename to packages/backend/native-utils/src/model/entity/user_group_joining.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_ip.rs b/packages/backend/native-utils/src/model/entity/user_ip.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/user_ip.rs
rename to packages/backend/native-utils/src/model/entity/user_ip.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_keypair.rs b/packages/backend/native-utils/src/model/entity/user_keypair.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/user_keypair.rs
rename to packages/backend/native-utils/src/model/entity/user_keypair.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_list.rs b/packages/backend/native-utils/src/model/entity/user_list.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/user_list.rs
rename to packages/backend/native-utils/src/model/entity/user_list.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_list_joining.rs b/packages/backend/native-utils/src/model/entity/user_list_joining.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/user_list_joining.rs
rename to packages/backend/native-utils/src/model/entity/user_list_joining.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_note_pining.rs b/packages/backend/native-utils/src/model/entity/user_note_pining.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/user_note_pining.rs
rename to packages/backend/native-utils/src/model/entity/user_note_pining.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_pending.rs b/packages/backend/native-utils/src/model/entity/user_pending.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/user_pending.rs
rename to packages/backend/native-utils/src/model/entity/user_pending.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_profile.rs b/packages/backend/native-utils/src/model/entity/user_profile.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/user_profile.rs
rename to packages/backend/native-utils/src/model/entity/user_profile.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_publickey.rs b/packages/backend/native-utils/src/model/entity/user_publickey.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/user_publickey.rs
rename to packages/backend/native-utils/src/model/entity/user_publickey.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/user_security_key.rs b/packages/backend/native-utils/src/model/entity/user_security_key.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/user_security_key.rs
rename to packages/backend/native-utils/src/model/entity/user_security_key.rs
diff --git a/packages/backend/native-utils/crates/model/src/entity/webhook.rs b/packages/backend/native-utils/src/model/entity/webhook.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/entity/webhook.rs
rename to packages/backend/native-utils/src/model/entity/webhook.rs
diff --git a/packages/backend/native-utils/crates/model/src/error.rs b/packages/backend/native-utils/src/model/error.rs
similarity index 62%
rename from packages/backend/native-utils/crates/model/src/error.rs
rename to packages/backend/native-utils/src/model/error.rs
index c90f3f5a3..2292246ae 100644
--- a/packages/backend/native-utils/crates/model/src/error.rs
+++ b/packages/backend/native-utils/src/model/error.rs
@@ -3,9 +3,16 @@ pub enum Error {
     #[error("Failed to parse string")]
     ParseError(#[from] parse_display::ParseError),
     #[error("Failed to get database connection")]
-    DbConnError(#[from] database::error::Error),
+    DbConnError(#[from] crate::database::error::Error),
     #[error("Database operation error: {0}")]
     DbOperationError(#[from] sea_orm::DbErr),
     #[error("Requested entity not found")]
     NotFound,
 }
+
+#[cfg(feature = "napi")]
+impl Into<napi::Error> for Error {
+    fn into(self) -> napi::Error {
+        napi::Error::from_reason(self.to_string())
+    }
+}
diff --git a/packages/backend/native-utils/crates/model/src/lib.rs b/packages/backend/native-utils/src/model/mod.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/lib.rs
rename to packages/backend/native-utils/src/model/mod.rs
diff --git a/packages/backend/native-utils/crates/model/src/repository.rs b/packages/backend/native-utils/src/model/repository.rs
similarity index 75%
rename from packages/backend/native-utils/crates/model/src/repository.rs
rename to packages/backend/native-utils/src/model/repository.rs
index 83573ab16..0f9f7de32 100644
--- a/packages/backend/native-utils/crates/model/src/repository.rs
+++ b/packages/backend/native-utils/src/model/repository.rs
@@ -3,7 +3,7 @@ pub mod antenna;
 use async_trait::async_trait;
 use schemars::JsonSchema;
 
-use crate::error::Error;
+use super::error::Error;
 
 #[async_trait]
 pub trait Repository<T: JsonSchema> {
@@ -14,7 +14,10 @@ pub trait Repository<T: JsonSchema> {
 mod macros {
     macro_rules! impl_pack_by_id {
         ($a:ty, $b:ident) => {
-            match <$a>::find_by_id($b).one(database::get_database()?).await? {
+            match <$a>::find_by_id($b)
+                .one(crate::database::get_database()?)
+                .await?
+            {
                 None => Err(Error::NotFound),
                 Some(m) => m.pack().await,
             }
diff --git a/packages/backend/native-utils/crates/model/src/repository/antenna.rs b/packages/backend/native-utils/src/model/repository/antenna.rs
similarity index 92%
rename from packages/backend/native-utils/crates/model/src/repository/antenna.rs
rename to packages/backend/native-utils/src/model/repository/antenna.rs
index c8324edd3..7c614b954 100644
--- a/packages/backend/native-utils/crates/model/src/repository/antenna.rs
+++ b/packages/backend/native-utils/src/model/repository/antenna.rs
@@ -2,9 +2,10 @@ use async_trait::async_trait;
 use cfg_if::cfg_if;
 use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
 
-use crate::entity::{antenna, antenna_note, user_group_joining};
-use crate::error::Error;
-use crate::schema::Antenna;
+use crate::database;
+use crate::model::entity::{antenna, antenna_note, user_group_joining};
+use crate::model::error::Error;
+use crate::model::schema::Antenna;
 
 use super::macros::impl_pack_by_id;
 use super::Repository;
diff --git a/packages/backend/native-utils/crates/model/src/schema.rs b/packages/backend/native-utils/src/model/schema.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/schema.rs
rename to packages/backend/native-utils/src/model/schema.rs
diff --git a/packages/backend/native-utils/crates/model/src/schema/antenna.rs b/packages/backend/native-utils/src/model/schema/antenna.rs
similarity index 93%
rename from packages/backend/native-utils/crates/model/src/schema/antenna.rs
rename to packages/backend/native-utils/src/model/schema/antenna.rs
index 338998019..6376b3bc7 100644
--- a/packages/backend/native-utils/crates/model/src/schema/antenna.rs
+++ b/packages/backend/native-utils/src/model/schema/antenna.rs
@@ -5,7 +5,8 @@ use schemars::JsonSchema;
 use utoipa::ToSchema;
 
 use super::Schema;
-use crate::entity::sea_orm_active_enums::AntennaSrcEnum;
+use crate::model;
+use crate::model::entity::sea_orm_active_enums::AntennaSrcEnum;
 
 #[derive(Clone, Debug, PartialEq, Eq, JsonSchema, ToSchema)]
 #[serde(rename_all = "camelCase")]
@@ -47,10 +48,10 @@ pub enum AntennaSrc {
 }
 
 impl TryFrom<AntennaSrcEnum> for super::AntennaSrc {
-    type Error = crate::error::Error;
+    type Error = model::error::Error;
 
     fn try_from(value: AntennaSrcEnum) -> Result<Self, Self::Error> {
-        value.to_string().parse().map_err(crate::error::Error::from)
+        value.to_string().parse().map_err(model::error::Error::from)
     }
 }
 
@@ -64,7 +65,7 @@ mod unit_test {
     use pretty_assertions::assert_eq;
     use serde_json::json;
 
-    use crate::{entity::sea_orm_active_enums::AntennaSrcEnum, schema::AntennaSrc};
+    use crate::model::{entity::sea_orm_active_enums::AntennaSrcEnum, schema::AntennaSrc};
 
     use super::VALIDATOR;
 
diff --git a/packages/backend/native-utils/crates/model/src/schema/app.rs b/packages/backend/native-utils/src/model/schema/app.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/schema/app.rs
rename to packages/backend/native-utils/src/model/schema/app.rs
diff --git a/packages/backend/native-utils/crates/model/src/schema/napi.rs b/packages/backend/native-utils/src/model/schema/napi.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/src/schema/napi.rs
rename to packages/backend/native-utils/src/model/schema/napi.rs
diff --git a/packages/backend/native-utils/crates/model/src/schema/napi/antenna.rs b/packages/backend/native-utils/src/model/schema/napi/antenna.rs
similarity index 78%
rename from packages/backend/native-utils/crates/model/src/schema/napi/antenna.rs
rename to packages/backend/native-utils/src/model/schema/napi/antenna.rs
index ffb817c12..e5c156efa 100644
--- a/packages/backend/native-utils/crates/model/src/schema/napi/antenna.rs
+++ b/packages/backend/native-utils/src/model/schema/napi/antenna.rs
@@ -1,9 +1,12 @@
-use napi::bindgen_prelude::{FromNapiValue, ToNapiValue};
+use napi::bindgen_prelude::*;
 use napi_derive::napi;
 use parse_display::FromStr;
 use schemars::JsonSchema;
 use utoipa::ToSchema;
 
+use crate::model::entity::antenna::Model;
+use crate::model::repository::Repository;
+
 #[napi]
 #[derive(Clone, Debug, PartialEq, Eq, JsonSchema, ToSchema)]
 #[serde(rename_all = "camelCase")]
@@ -44,3 +47,11 @@ pub enum AntennaSrc {
     Group,
     Instances,
 }
+
+#[napi]
+impl Antenna {
+    #[napi]
+    pub async fn pack_by_id(id: String) -> napi::Result<Antenna> {
+        Model::pack_by_id(id).await.map_err(Into::into)
+    }
+}
diff --git a/packages/backend/native-utils/crates/util/src/id.rs b/packages/backend/native-utils/src/util/id.rs
similarity index 97%
rename from packages/backend/native-utils/crates/util/src/id.rs
rename to packages/backend/native-utils/src/util/id.rs
index d3c5809ea..98dd63c4e 100644
--- a/packages/backend/native-utils/crates/util/src/id.rs
+++ b/packages/backend/native-utils/src/util/id.rs
@@ -25,7 +25,7 @@ mod unit_test {
     use pretty_assertions::{assert_eq, assert_ne};
     use std::thread;
 
-    use crate::id;
+    use crate::util::id;
 
     #[test]
     fn can_generate_unique_ids() {
diff --git a/packages/backend/native-utils/crates/util/src/lib.rs b/packages/backend/native-utils/src/util/mod.rs
similarity index 100%
rename from packages/backend/native-utils/crates/util/src/lib.rs
rename to packages/backend/native-utils/src/util/mod.rs
diff --git a/packages/backend/native-utils/crates/util/src/random.rs b/packages/backend/native-utils/src/util/random.rs
similarity index 100%
rename from packages/backend/native-utils/crates/util/src/random.rs
rename to packages/backend/native-utils/src/util/random.rs
diff --git a/packages/backend/native-utils/crates/model/tests/common.rs b/packages/backend/native-utils/tests/common.rs
similarity index 96%
rename from packages/backend/native-utils/crates/model/tests/common.rs
rename to packages/backend/native-utils/tests/common.rs
index 554a8ca73..775b37212 100644
--- a/packages/backend/native-utils/crates/model/tests/common.rs
+++ b/packages/backend/native-utils/tests/common.rs
@@ -1,20 +1,19 @@
 #![cfg(not(feature = "napi"))]
 
-extern crate model;
-
-mod repository;
+mod model;
 
 use chrono::Utc;
-use model::entity;
-use model::entity::sea_orm_active_enums::AntennaSrcEnum;
+use native_utils::database;
+use native_utils::model::entity;
+use native_utils::model::entity::sea_orm_active_enums::AntennaSrcEnum;
+use native_utils::util::{
+    id::{create_id, init_id},
+    random::gen_string,
+};
 use sea_orm::{
     sea_query::TableCreateStatement, ActiveModelTrait, ConnectionTrait, DbBackend, DbConn, DbErr,
     EntityTrait, IntoActiveModel, TransactionTrait,
 };
-use util::{
-    id::{create_id, init_id},
-    random::gen_string,
-};
 
 /// Insert predefined entries in the database.
 async fn prepare() {
diff --git a/packages/backend/native-utils/tests/model/mod.rs b/packages/backend/native-utils/tests/model/mod.rs
new file mode 100644
index 000000000..a35bac056
--- /dev/null
+++ b/packages/backend/native-utils/tests/model/mod.rs
@@ -0,0 +1 @@
+mod repository;
diff --git a/packages/backend/native-utils/crates/model/tests/repository/mod.rs b/packages/backend/native-utils/tests/model/repository.rs
similarity index 100%
rename from packages/backend/native-utils/crates/model/tests/repository/mod.rs
rename to packages/backend/native-utils/tests/model/repository.rs
diff --git a/packages/backend/native-utils/crates/model/tests/repository/antenna.rs b/packages/backend/native-utils/tests/model/repository/antenna.rs
similarity index 97%
rename from packages/backend/native-utils/crates/model/tests/repository/antenna.rs
rename to packages/backend/native-utils/tests/model/repository/antenna.rs
index 2da1e59d4..a37007398 100644
--- a/packages/backend/native-utils/crates/model/tests/repository/antenna.rs
+++ b/packages/backend/native-utils/tests/model/repository/antenna.rs
@@ -1,4 +1,6 @@
 mod int_test {
+    use native_utils::{database, model};
+
     use model::{
         entity::{antenna, user},
         repository::Repository,

From bc209143e67b7991a98e0735337b1ea57b0aee84 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 09:29:52 -0400
Subject: [PATCH 169/198] adjust module structure

---
 .../backend/native-utils/src/model/schema.rs  | 25 ++++----
 .../native-utils/src/model/schema/antenna.rs  | 60 +++++++++++++++++++
 .../native-utils/src/model/schema/napi.rs     |  1 -
 .../src/model/schema/napi/antenna.rs          | 57 ------------------
 4 files changed, 72 insertions(+), 71 deletions(-)
 delete mode 100644 packages/backend/native-utils/src/model/schema/napi.rs
 delete mode 100644 packages/backend/native-utils/src/model/schema/napi/antenna.rs

diff --git a/packages/backend/native-utils/src/model/schema.rs b/packages/backend/native-utils/src/model/schema.rs
index 71f9348b3..ef4368dda 100644
--- a/packages/backend/native-utils/src/model/schema.rs
+++ b/packages/backend/native-utils/src/model/schema.rs
@@ -5,19 +5,6 @@ use cfg_if::cfg_if;
 use jsonschema::JSONSchema;
 use schemars::{schema_for, JsonSchema};
 
-cfg_if! {
-    if #[cfg(feature = "napi")] {
-        mod napi;
-        pub use self::napi::antenna::Antenna;
-        pub use self::napi::antenna::AntennaSrc;
-    } else {
-        pub use antenna::Antenna;
-        pub use antenna::AntennaSrc;
-        pub use app::App;
-        pub use app::AppPermission;
-    }
-}
-
 /// Structs of schema defitions implement this trait in order to
 /// provide the JSON Schema validator [`jsonschema::JSONSchema`].
 pub trait Schema<T: JsonSchema> {
@@ -33,3 +20,15 @@ pub trait Schema<T: JsonSchema> {
             .expect("Unable to compile schema")
     }
 }
+
+cfg_if! {
+    if #[cfg(feature = "napi")] {
+        pub use antenna::napi::AntennaSchema as Antenna;
+        pub use antenna::napi::AntennaSrc;
+    } else {
+        pub use antenna::Antenna;
+        pub use antenna::AntennaSrc;
+        pub use app::App;
+        pub use app::AppPermission;
+    }
+}
diff --git a/packages/backend/native-utils/src/model/schema/antenna.rs b/packages/backend/native-utils/src/model/schema/antenna.rs
index 6376b3bc7..8b553c082 100644
--- a/packages/backend/native-utils/src/model/schema/antenna.rs
+++ b/packages/backend/native-utils/src/model/schema/antenna.rs
@@ -60,6 +60,66 @@ impl Schema<Self> for super::Antenna {}
 pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| super::Antenna::validator());
 // ----
 
+#[cfg(feature = "napi")]
+pub mod napi {
+    use napi::bindgen_prelude::*;
+    use napi_derive::napi;
+    use parse_display::FromStr;
+    use schemars::JsonSchema;
+    use utoipa::ToSchema;
+
+    use crate::model::{entity::antenna, repository::Repository};
+
+    #[napi]
+    #[derive(Clone, Debug, PartialEq, Eq, JsonSchema, ToSchema)]
+    #[serde(rename_all = "camelCase")]
+    pub struct AntennaSchema {
+        pub id: String,
+        pub created_at: String,
+        pub name: String,
+        pub keywords: Vec<Vec<String>>,
+        pub exclude_keywords: Vec<Vec<String>>,
+        #[schema(inline)]
+        pub src: AntennaSrc,
+        pub user_list_id: Option<String>,
+        pub user_group_id: Option<String>,
+        pub users: Vec<String>,
+        pub instances: Vec<String>,
+        #[serde(default)]
+        pub case_sensitive: bool,
+        #[serde(default)]
+        pub notify: bool,
+        #[serde(default)]
+        pub with_replies: bool,
+        #[serde(default)]
+        pub with_file: bool,
+        #[serde(default)]
+        pub has_unread_note: bool,
+    }
+
+    #[napi]
+    #[derive(Debug, FromStr, PartialEq, Eq, JsonSchema, ToSchema)]
+    #[serde(rename_all = "camelCase")]
+    #[display(style = "camelCase")]
+    #[display("'{}'")]
+    pub enum AntennaSrc {
+        Home,
+        All,
+        Users,
+        List,
+        Group,
+        Instances,
+    }
+
+    #[napi]
+    impl AntennaSchema {
+        #[napi]
+        pub async fn pack_by_id(id: String) -> napi::Result<AntennaSchema> {
+            antenna::Model::pack_by_id(id).await.map_err(Into::into)
+        }
+    }
+}
+
 #[cfg(test)]
 mod unit_test {
     use pretty_assertions::assert_eq;
diff --git a/packages/backend/native-utils/src/model/schema/napi.rs b/packages/backend/native-utils/src/model/schema/napi.rs
deleted file mode 100644
index 16130e4cc..000000000
--- a/packages/backend/native-utils/src/model/schema/napi.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub mod antenna;
diff --git a/packages/backend/native-utils/src/model/schema/napi/antenna.rs b/packages/backend/native-utils/src/model/schema/napi/antenna.rs
deleted file mode 100644
index e5c156efa..000000000
--- a/packages/backend/native-utils/src/model/schema/napi/antenna.rs
+++ /dev/null
@@ -1,57 +0,0 @@
-use napi::bindgen_prelude::*;
-use napi_derive::napi;
-use parse_display::FromStr;
-use schemars::JsonSchema;
-use utoipa::ToSchema;
-
-use crate::model::entity::antenna::Model;
-use crate::model::repository::Repository;
-
-#[napi]
-#[derive(Clone, Debug, PartialEq, Eq, JsonSchema, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct Antenna {
-    pub id: String,
-    pub created_at: String,
-    pub name: String,
-    pub keywords: Vec<Vec<String>>,
-    pub exclude_keywords: Vec<Vec<String>>,
-    #[schema(inline)]
-    pub src: AntennaSrc,
-    pub user_list_id: Option<String>,
-    pub user_group_id: Option<String>,
-    pub users: Vec<String>,
-    pub instances: Vec<String>,
-    #[serde(default)]
-    pub case_sensitive: bool,
-    #[serde(default)]
-    pub notify: bool,
-    #[serde(default)]
-    pub with_replies: bool,
-    #[serde(default)]
-    pub with_file: bool,
-    #[serde(default)]
-    pub has_unread_note: bool,
-}
-
-#[napi]
-#[derive(Debug, FromStr, PartialEq, Eq, JsonSchema, ToSchema)]
-#[serde(rename_all = "camelCase")]
-#[display(style = "camelCase")]
-#[display("'{}'")]
-pub enum AntennaSrc {
-    Home,
-    All,
-    Users,
-    List,
-    Group,
-    Instances,
-}
-
-#[napi]
-impl Antenna {
-    #[napi]
-    pub async fn pack_by_id(id: String) -> napi::Result<Antenna> {
-        Model::pack_by_id(id).await.map_err(Into::into)
-    }
-}

From 3af4a86254ccf67b7a0c2060bf55013776500c77 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 09:55:57 -0400
Subject: [PATCH 170/198] use schema in native-utils

---
 packages/backend/native-utils/package.json    |  8 ++---
 packages/backend/package.json                 |  2 +-
 .../src/models/repositories/antenna.ts        | 34 +++----------------
 pnpm-workspace.yaml                           |  1 +
 4 files changed, 11 insertions(+), 34 deletions(-)

diff --git a/packages/backend/native-utils/package.json b/packages/backend/native-utils/package.json
index aa194bad3..f1ea2c9b4 100644
--- a/packages/backend/native-utils/package.json
+++ b/packages/backend/native-utils/package.json
@@ -23,8 +23,8 @@
   },
   "license": "MIT",
   "devDependencies": {
-    "@napi-rs/cli": "^2.15.0",
-    "ava": "^5.1.1"
+    "@napi-rs/cli": "2.15.0",
+    "ava": "5.1.1"
   },
   "ava": {
     "timeout": "3m"
@@ -40,7 +40,7 @@
     "test": "ava",
     "universal": "napi universal",
     "version": "napi version",
-    "cargo:unit": "cargo test --workspace unit_test",
-    "cargo:integration": "cargo test --workspace int_test -- --test-threads=1"
+    "cargo:unit": "cargo test unit_test",
+    "cargo:integration": "cargo test --no-default-features int_test -- --test-threads=1"
   }
 }
diff --git a/packages/backend/package.json b/packages/backend/package.json
index c084d67bc..0b0315a06 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -9,7 +9,7 @@
 		"migrate": "typeorm migration:run -d ormconfig.js",
 		"revertmigration": "typeorm migration:revert -d ormconfig.js",
 		"check:connect": "node ./check_connect.js",
-		"build": "napi build --platform --release --cargo-cwd native-utils ./native-utils/built/ && pnpm swc src -d built -D",
+		"build": "pnpm swc src -d built -D",
 		"watch": "pnpm swc src -d built -D -w",
 		"lint": "pnpm rome check \"src/**/*.ts\"",
 		"mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
diff --git a/packages/backend/src/models/repositories/antenna.ts b/packages/backend/src/models/repositories/antenna.ts
index c325e2589..d9405f4a9 100644
--- a/packages/backend/src/models/repositories/antenna.ts
+++ b/packages/backend/src/models/repositories/antenna.ts
@@ -1,36 +1,12 @@
 import { db } from "@/db/postgre.js";
 import { Antenna } from "@/models/entities/antenna.js";
-import type { Packed } from "@/misc/schema.js";
-import { AntennaNotes, UserGroupJoinings } from "../index.js";
+import { AntennaSchema } from "native-utils/built/index.js";
 
 export const AntennaRepository = db.getRepository(Antenna).extend({
-	async pack(src: Antenna["id"] | Antenna): Promise<Packed<"Antenna">> {
-		const antenna =
-			typeof src === "object" ? src : await this.findOneByOrFail({ id: src });
+	async pack(src: Antenna["id"] | Antenna): Promise<AntennaSchema> {
+		const id =
+			typeof src === "object" ? src.id : src;
 
-		const hasUnreadNote =
-			(await AntennaNotes.findOneBy({ antennaId: antenna.id, read: false })) !=
-			null;
-		const userGroupJoining = antenna.userGroupJoiningId
-			? await UserGroupJoinings.findOneBy({ id: antenna.userGroupJoiningId })
-			: null;
-
-		return {
-			id: antenna.id,
-			createdAt: antenna.createdAt.toISOString(),
-			name: antenna.name,
-			keywords: antenna.keywords,
-			excludeKeywords: antenna.excludeKeywords,
-			src: antenna.src,
-			userListId: antenna.userListId,
-			userGroupId: userGroupJoining ? userGroupJoining.userGroupId : null,
-			users: antenna.users,
-			instances: antenna.instances,
-			caseSensitive: antenna.caseSensitive,
-			notify: antenna.notify,
-			withReplies: antenna.withReplies,
-			withFile: antenna.withFile,
-			hasUnreadNote,
-		};
+		return await AntennaSchema.packById(id);
 	},
 });
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index a2ebb0465..2bf4474f0 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,5 +1,6 @@
 packages:
  - 'packages/backend'
+ - 'packages/backend/native-utils'
  - 'packages/client'
  - 'packages/sw'
  - 'packages/calckey-js'

From af853045784f59ddaef867e481e4a1f9036b02a3 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 11:55:14 -0400
Subject: [PATCH 171/198] add native calls

---
 packages/backend/native-utils/Cargo.toml      |   5 +-
 packages/backend/native-utils/package.json    |   4 +-
 .../native-utils/src/database/error.rs        |   4 +
 .../backend/native-utils/src/database/mod.rs  |  16 ++-
 packages/backend/native-utils/src/lib.rs      |   1 +
 packages/backend/native-utils/src/macros.rs   |  11 ++
 .../backend/native-utils/src/model/error.rs   |  13 +--
 .../native-utils/src/model/repository.rs      |   5 +
 .../backend/native-utils/src/model/schema.rs  |   5 +-
 .../native-utils/src/model/schema/antenna.rs  | 107 +++++++++---------
 packages/backend/native-utils/src/util/id.rs  |  47 +++++++-
 .../backend/native-utils/src/util/random.rs   |   7 ++
 12 files changed, 151 insertions(+), 74 deletions(-)
 create mode 100644 packages/backend/native-utils/src/macros.rs

diff --git a/packages/backend/native-utils/Cargo.toml b/packages/backend/native-utils/Cargo.toml
index 3c70a0209..ba7a48b8d 100644
--- a/packages/backend/native-utils/Cargo.toml
+++ b/packages/backend/native-utils/Cargo.toml
@@ -9,7 +9,7 @@ members = ["migration/Cargo.toml"]
 [features]
 default = ["napi"]
 noarray = []
-napi = ["dep:napi", "dep:napi-derive"]
+napi = ["dep:napi", "dep:napi-derive", "dep:radix_fmt"]
 
 [lib]
 crate-type = ["cdylib", "lib"]
@@ -33,8 +33,9 @@ tokio = { version = "1.28.1", features = ["full"] }
 utoipa = "3.3.0"
 
 # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
-napi = { version = "2.12.0", default-features = false, features = ["napi4", "tokio_rt"], optional = true }
+napi = { version = "2.12.0", default-features = false, features = ["napi6", "tokio_rt"], optional = true }
 napi-derive = { version = "2.12.0", optional = true }
+radix_fmt = { version = "1.0.0", optional = true }
 
 [dev-dependencies]
 pretty_assertions = "1.3.0"
diff --git a/packages/backend/native-utils/package.json b/packages/backend/native-utils/package.json
index f1ea2c9b4..8423f569b 100644
--- a/packages/backend/native-utils/package.json
+++ b/packages/backend/native-utils/package.json
@@ -34,13 +34,13 @@
   },
   "scripts": {
     "artifacts": "napi artifacts",
-    "build": "napi build --platform --release ./built/",
+    "build": "napi build --features napi --platform --release ./built/",
     "build:debug": "napi build --platform",
     "prepublishOnly": "napi prepublish -t npm",
     "test": "ava",
     "universal": "napi universal",
     "version": "napi version",
     "cargo:unit": "cargo test unit_test",
-    "cargo:integration": "cargo test --no-default-features int_test -- --test-threads=1"
+    "cargo:integration": "cargo test --no-default-features -F noarray int_test -- --test-threads=1"
   }
 }
diff --git a/packages/backend/native-utils/src/database/error.rs b/packages/backend/native-utils/src/database/error.rs
index babdd6831..68e959e0a 100644
--- a/packages/backend/native-utils/src/database/error.rs
+++ b/packages/backend/native-utils/src/database/error.rs
@@ -1,5 +1,7 @@
 use sea_orm::error::DbErr;
 
+use crate::impl_into_napi_error;
+
 #[derive(thiserror::Error, Debug, PartialEq, Eq)]
 pub enum Error {
     #[error("The database connections have not been initialized yet")]
@@ -7,3 +9,5 @@ pub enum Error {
     #[error("ORM error: {0}")]
     OrmError(#[from] DbErr),
 }
+
+impl_into_napi_error!(Error);
diff --git a/packages/backend/native-utils/src/database/mod.rs b/packages/backend/native-utils/src/database/mod.rs
index c3cdea727..80189a813 100644
--- a/packages/backend/native-utils/src/database/mod.rs
+++ b/packages/backend/native-utils/src/database/mod.rs
@@ -1,12 +1,13 @@
 pub mod error;
 
+use cfg_if::cfg_if;
 use error::Error;
 use sea_orm::{Database, DbConn};
 
 static DB_CONN: once_cell::sync::OnceCell<DbConn> = once_cell::sync::OnceCell::new();
 
-pub async fn init_database(connection_uri: impl Into<String>) -> Result<(), Error> {
-    let conn = Database::connect(connection_uri.into()).await?;
+pub async fn init_database(conn_uri: impl Into<String>) -> Result<(), Error> {
+    let conn = Database::connect(conn_uri.into()).await?;
     DB_CONN.get_or_init(move || conn);
     Ok(())
 }
@@ -15,6 +16,17 @@ pub fn get_database() -> Result<&'static DbConn, Error> {
     DB_CONN.get().ok_or(Error::Uninitialized)
 }
 
+cfg_if! {
+    if #[cfg(feature = "napi")] {
+        use napi_derive::napi;
+
+        #[napi]
+        pub async fn native_init_database(conn_uri: String) -> napi::Result<()> {
+            init_database(conn_uri).await.map_err(Into::into)
+        }
+    }
+}
+
 #[cfg(test)]
 mod unit_test {
     use super::{error::Error, get_database};
diff --git a/packages/backend/native-utils/src/lib.rs b/packages/backend/native-utils/src/lib.rs
index 6a5e3f7f2..f18e69a48 100644
--- a/packages/backend/native-utils/src/lib.rs
+++ b/packages/backend/native-utils/src/lib.rs
@@ -1,4 +1,5 @@
 pub mod database;
+pub mod macros;
 pub mod model;
 pub mod util;
 
diff --git a/packages/backend/native-utils/src/macros.rs b/packages/backend/native-utils/src/macros.rs
new file mode 100644
index 000000000..49ab82632
--- /dev/null
+++ b/packages/backend/native-utils/src/macros.rs
@@ -0,0 +1,11 @@
+#[macro_export]
+macro_rules! impl_into_napi_error {
+    ($a:ty) => {
+        #[cfg(feature = "napi")]
+        impl Into<napi::Error> for $a {
+            fn into(self) -> napi::Error {
+                napi::Error::from_reason(self.to_string())
+            }
+        }
+    };
+}
diff --git a/packages/backend/native-utils/src/model/error.rs b/packages/backend/native-utils/src/model/error.rs
index 2292246ae..8e9213066 100644
--- a/packages/backend/native-utils/src/model/error.rs
+++ b/packages/backend/native-utils/src/model/error.rs
@@ -1,8 +1,10 @@
+use crate::impl_into_napi_error;
+
 #[derive(thiserror::Error, Debug, PartialEq, Eq)]
 pub enum Error {
-    #[error("Failed to parse string")]
+    #[error("Failed to parse string: {0}")]
     ParseError(#[from] parse_display::ParseError),
-    #[error("Failed to get database connection")]
+    #[error("Failed to get database connection: {0}")]
     DbConnError(#[from] crate::database::error::Error),
     #[error("Database operation error: {0}")]
     DbOperationError(#[from] sea_orm::DbErr),
@@ -10,9 +12,4 @@ pub enum Error {
     NotFound,
 }
 
-#[cfg(feature = "napi")]
-impl Into<napi::Error> for Error {
-    fn into(self) -> napi::Error {
-        napi::Error::from_reason(self.to_string())
-    }
-}
+impl_into_napi_error!(Error);
diff --git a/packages/backend/native-utils/src/model/repository.rs b/packages/backend/native-utils/src/model/repository.rs
index 0f9f7de32..5abf7907f 100644
--- a/packages/backend/native-utils/src/model/repository.rs
+++ b/packages/backend/native-utils/src/model/repository.rs
@@ -5,13 +5,18 @@ use schemars::JsonSchema;
 
 use super::error::Error;
 
+/// Repositories have a packer that converts a database model to its
+/// corresponding API schema.
 #[async_trait]
 pub trait Repository<T: JsonSchema> {
     async fn pack(self) -> Result<T, Error>;
+    /// Retrieves one model by its id and pack it.
     async fn pack_by_id(id: String) -> Result<T, Error>;
 }
 
 mod macros {
+    /// Provides the default implementation of
+    /// [crate::model::repository::Repository::pack_by_id].
     macro_rules! impl_pack_by_id {
         ($a:ty, $b:ident) => {
             match <$a>::find_by_id($b)
diff --git a/packages/backend/native-utils/src/model/schema.rs b/packages/backend/native-utils/src/model/schema.rs
index ef4368dda..4c0ca7941 100644
--- a/packages/backend/native-utils/src/model/schema.rs
+++ b/packages/backend/native-utils/src/model/schema.rs
@@ -23,8 +23,9 @@ pub trait Schema<T: JsonSchema> {
 
 cfg_if! {
     if #[cfg(feature = "napi")] {
-        pub use antenna::napi::AntennaSchema as Antenna;
-        pub use antenna::napi::AntennaSrc;
+        // Will be disabled once we completely migrate to rust
+        pub use antenna::NativeAntennaSchema as Antenna;
+        pub use antenna::NativeAntennaSrc as AntennaSrc;
     } else {
         pub use antenna::Antenna;
         pub use antenna::AntennaSrc;
diff --git a/packages/backend/native-utils/src/model/schema/antenna.rs b/packages/backend/native-utils/src/model/schema/antenna.rs
index 8b553c082..99521a98b 100644
--- a/packages/backend/native-utils/src/model/schema/antenna.rs
+++ b/packages/backend/native-utils/src/model/schema/antenna.rs
@@ -1,3 +1,4 @@
+use cfg_if::cfg_if;
 use jsonschema::JSONSchema;
 use once_cell::sync::Lazy;
 use parse_display::FromStr;
@@ -60,62 +61,62 @@ impl Schema<Self> for super::Antenna {}
 pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| super::Antenna::validator());
 // ----
 
-#[cfg(feature = "napi")]
-pub mod napi {
-    use napi::bindgen_prelude::*;
-    use napi_derive::napi;
-    use parse_display::FromStr;
-    use schemars::JsonSchema;
-    use utoipa::ToSchema;
+cfg_if! {
+    if #[cfg(feature = "napi")] {
+        use napi::bindgen_prelude::{FromNapiValue, ToNapiValue};
+        use napi_derive::napi;
 
-    use crate::model::{entity::antenna, repository::Repository};
+        use crate::model::entity::antenna;
+        use crate::model::repository::Repository;
 
-    #[napi]
-    #[derive(Clone, Debug, PartialEq, Eq, JsonSchema, ToSchema)]
-    #[serde(rename_all = "camelCase")]
-    pub struct AntennaSchema {
-        pub id: String,
-        pub created_at: String,
-        pub name: String,
-        pub keywords: Vec<Vec<String>>,
-        pub exclude_keywords: Vec<Vec<String>>,
-        #[schema(inline)]
-        pub src: AntennaSrc,
-        pub user_list_id: Option<String>,
-        pub user_group_id: Option<String>,
-        pub users: Vec<String>,
-        pub instances: Vec<String>,
-        #[serde(default)]
-        pub case_sensitive: bool,
-        #[serde(default)]
-        pub notify: bool,
-        #[serde(default)]
-        pub with_replies: bool,
-        #[serde(default)]
-        pub with_file: bool,
-        #[serde(default)]
-        pub has_unread_note: bool,
-    }
-
-    #[napi]
-    #[derive(Debug, FromStr, PartialEq, Eq, JsonSchema, ToSchema)]
-    #[serde(rename_all = "camelCase")]
-    #[display(style = "camelCase")]
-    #[display("'{}'")]
-    pub enum AntennaSrc {
-        Home,
-        All,
-        Users,
-        List,
-        Group,
-        Instances,
-    }
-
-    #[napi]
-    impl AntennaSchema {
+        /// For NAPI because [chrono] is not supported.
         #[napi]
-        pub async fn pack_by_id(id: String) -> napi::Result<AntennaSchema> {
-            antenna::Model::pack_by_id(id).await.map_err(Into::into)
+        #[derive(Clone, Debug, PartialEq, Eq, JsonSchema, ToSchema)]
+        #[serde(rename_all = "camelCase")]
+        pub struct NativeAntennaSchema {
+            pub id: String,
+            pub created_at: String,
+            pub name: String,
+            pub keywords: Vec<Vec<String>>,
+            pub exclude_keywords: Vec<Vec<String>>,
+            #[schema(inline)]
+            pub src: NativeAntennaSrc,
+            pub user_list_id: Option<String>,
+            pub user_group_id: Option<String>,
+            pub users: Vec<String>,
+            pub instances: Vec<String>,
+            #[serde(default)]
+            pub case_sensitive: bool,
+            #[serde(default)]
+            pub notify: bool,
+            #[serde(default)]
+            pub with_replies: bool,
+            #[serde(default)]
+            pub with_file: bool,
+            #[serde(default)]
+            pub has_unread_note: bool,
+        }
+
+        #[napi]
+        #[derive(Debug, FromStr, PartialEq, Eq, JsonSchema, ToSchema)]
+        #[serde(rename_all = "camelCase")]
+        #[display(style = "camelCase")]
+        #[display("'{}'")]
+        pub enum NativeAntennaSrc {
+            Home,
+            All,
+            Users,
+            List,
+            Group,
+            Instances,
+        }
+
+        #[napi]
+        impl NativeAntennaSchema {
+            #[napi]
+            pub async fn pack_by_id(id: String) -> napi::Result<NativeAntennaSchema> {
+                antenna::Model::pack_by_id(id).await.map_err(Into::into)
+            }
         }
     }
 }
diff --git a/packages/backend/native-utils/src/util/id.rs b/packages/backend/native-utils/src/util/id.rs
index 98dd63c4e..1d28b80c0 100644
--- a/packages/backend/native-utils/src/util/id.rs
+++ b/packages/backend/native-utils/src/util/id.rs
@@ -1,18 +1,31 @@
 //! ID generation utility based on [cuid2]
 
-use cuid2::CuidConstructor;
+use cfg_if::cfg_if;
 use once_cell::sync::OnceCell;
 
+use crate::impl_into_napi_error;
+
 #[derive(thiserror::Error, Debug, PartialEq, Eq)]
 #[error("ID generator has not been initialized yet")]
 pub struct ErrorUninitialized;
 
-static GENERATOR: OnceCell<CuidConstructor> = OnceCell::new();
+impl_into_napi_error!(ErrorUninitialized);
 
-pub fn init_id(length: u16) {
-    GENERATOR.get_or_init(move || CuidConstructor::new().with_length(length));
+static FINGERPRINT: OnceCell<String> = OnceCell::new();
+static GENERATOR: OnceCell<cuid2::CuidConstructor> = OnceCell::new();
+
+/// Initializes Cuid2 generator. Must be called before any [create_id].
+pub fn init_id(length: u16, fingerprint: impl Into<String>) {
+    FINGERPRINT.get_or_init(move || format!("{}{}", fingerprint.into(), cuid2::create_id()));
+    GENERATOR.get_or_init(move || {
+        cuid2::CuidConstructor::new()
+            .with_length(length)
+            .with_fingerprinter(|| FINGERPRINT.get().unwrap().clone())
+    });
 }
 
+/// Returns Cuid2 with the length specified by [init_id]. Must be called after
+/// [init_id], otherwise returns [ErrorUninitialized].
 pub fn create_id() -> Result<String, ErrorUninitialized> {
     match GENERATOR.get() {
         None => Err(ErrorUninitialized),
@@ -20,6 +33,30 @@ pub fn create_id() -> Result<String, ErrorUninitialized> {
     }
 }
 
+cfg_if! {
+    if #[cfg(feature = "napi")] {
+        use radix_fmt::radix_36;
+        use std::cmp;
+        use napi::bindgen_prelude::BigInt;
+        use napi_derive::napi;
+
+        const TIME_2000: u64 = 946_684_800_000;
+
+        /// Calls [init_id] inside. Must be called before [native_create_id].
+        #[napi]
+        pub fn native_init_id_generator(length: u16, fingerprint: String) {
+            init_id(length, fingerprint);
+        }
+
+        /// Generates
+        #[napi]
+        pub fn native_create_id(date_num: BigInt) -> String {
+            let time = cmp::max(date_num.get_u64().1 - TIME_2000, 0);
+            format!("{:0>8}{}", radix_36(time).to_string(), create_id().unwrap())
+        }
+    }
+}
+
 #[cfg(test)]
 mod unit_test {
     use pretty_assertions::{assert_eq, assert_ne};
@@ -30,7 +67,7 @@ mod unit_test {
     #[test]
     fn can_generate_unique_ids() {
         assert_eq!(id::create_id(), Err(id::ErrorUninitialized));
-        id::init_id(12);
+        id::init_id(12, "");
         assert_eq!(id::create_id().unwrap().len(), 12);
         assert_ne!(id::create_id().unwrap(), id::create_id().unwrap());
         let id1 = thread::spawn(|| id::create_id().unwrap());
diff --git a/packages/backend/native-utils/src/util/random.rs b/packages/backend/native-utils/src/util/random.rs
index 5ee06d128..ffcbca980 100644
--- a/packages/backend/native-utils/src/util/random.rs
+++ b/packages/backend/native-utils/src/util/random.rs
@@ -1,5 +1,6 @@
 use rand::{distributions::Alphanumeric, thread_rng, Rng};
 
+/// Generate random string based on [thread_rng] and [Alphanumeric].
 pub fn gen_string(length: u16) -> String {
     thread_rng()
         .sample_iter(Alphanumeric)
@@ -8,6 +9,12 @@ pub fn gen_string(length: u16) -> String {
         .collect()
 }
 
+#[cfg(feature = "napi")]
+#[napi_derive::napi]
+pub fn native_random_str(length: u16) -> String {
+    gen_string(length)
+}
+
 #[cfg(test)]
 mod unit_test {
     use pretty_assertions::{assert_eq, assert_ne};

From d8f95d7d99224991607ab0090bfd96bf09e39bac Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 12:19:36 -0400
Subject: [PATCH 172/198] fix unit test

---
 packages/backend/native-utils/Cargo.toml      |  2 +-
 packages/backend/native-utils/src/util/id.rs  | 43 ++++++++++++++-----
 packages/backend/native-utils/tests/common.rs |  2 +-
 3 files changed, 34 insertions(+), 13 deletions(-)

diff --git a/packages/backend/native-utils/Cargo.toml b/packages/backend/native-utils/Cargo.toml
index ba7a48b8d..071d0f331 100644
--- a/packages/backend/native-utils/Cargo.toml
+++ b/packages/backend/native-utils/Cargo.toml
@@ -7,7 +7,7 @@ version = "0.0.0"
 members = ["migration/Cargo.toml"]
 
 [features]
-default = ["napi"]
+default = []
 noarray = []
 napi = ["dep:napi", "dep:napi-derive", "dep:radix_fmt"]
 
diff --git a/packages/backend/native-utils/src/util/id.rs b/packages/backend/native-utils/src/util/id.rs
index 1d28b80c0..539b0415e 100644
--- a/packages/backend/native-utils/src/util/id.rs
+++ b/packages/backend/native-utils/src/util/id.rs
@@ -41,11 +41,13 @@ cfg_if! {
         use napi_derive::napi;
 
         const TIME_2000: u64 = 946_684_800_000;
+        const TIMESTAMP_LENGTH: u16 = 8;
 
         /// Calls [init_id] inside. Must be called before [native_create_id].
         #[napi]
         pub fn native_init_id_generator(length: u16, fingerprint: String) {
-            init_id(length, fingerprint);
+            // length to pass init_id shoule be greater than or equal to 8.
+            init_id(cmp::max(length - TIMESTAMP_LENGTH, 8), fingerprint);
         }
 
         /// Generates
@@ -59,19 +61,38 @@ cfg_if! {
 
 #[cfg(test)]
 mod unit_test {
+    use cfg_if::cfg_if;
     use pretty_assertions::{assert_eq, assert_ne};
     use std::thread;
-
     use crate::util::id;
 
-    #[test]
-    fn can_generate_unique_ids() {
-        assert_eq!(id::create_id(), Err(id::ErrorUninitialized));
-        id::init_id(12, "");
-        assert_eq!(id::create_id().unwrap().len(), 12);
-        assert_ne!(id::create_id().unwrap(), id::create_id().unwrap());
-        let id1 = thread::spawn(|| id::create_id().unwrap());
-        let id2 = thread::spawn(|| id::create_id().unwrap());
-        assert_ne!(id1.join().unwrap(), id2.join().unwrap())
+    cfg_if! {
+        if #[cfg(feature = "napi")] {
+            use chrono::Utc;
+
+            #[test]
+            fn can_generate_aid_compat_ids() {
+                id::native_init_id_generator(16, "".to_string());
+                let id1 = id::native_create_id(Utc::now().timestamp_millis().into());
+                assert_eq!(id1.len(), 16);
+                let id1 = id::native_create_id(Utc::now().timestamp_millis().into());
+                let id2 = id::native_create_id(Utc::now().timestamp_millis().into());
+                assert_ne!(id1, id2);
+                let id1 = thread::spawn(|| id::native_create_id(Utc::now().timestamp_millis().into()));
+                let id2 = thread::spawn(|| id::native_create_id(Utc::now().timestamp_millis().into()));
+                assert_ne!(id1.join().unwrap(), id2.join().unwrap());
+            }
+        } else {
+            #[test]
+            fn can_generate_unique_ids() {
+                assert_eq!(id::create_id(), Err(id::ErrorUninitialized));
+                id::init_id(12, "");
+                assert_eq!(id::create_id().unwrap().len(), 12);
+                assert_ne!(id::create_id().unwrap(), id::create_id().unwrap());
+                let id1 = thread::spawn(|| id::create_id().unwrap());
+                let id2 = thread::spawn(|| id::create_id().unwrap());
+                assert_ne!(id1.join().unwrap(), id2.join().unwrap())
+            }
+        }
     }
 }
diff --git a/packages/backend/native-utils/tests/common.rs b/packages/backend/native-utils/tests/common.rs
index 775b37212..61dd20aac 100644
--- a/packages/backend/native-utils/tests/common.rs
+++ b/packages/backend/native-utils/tests/common.rs
@@ -139,7 +139,7 @@ async fn cleanup() {
 }
 
 async fn setup_model(db: &DbConn) {
-    init_id(12);
+    init_id(12, "");
 
     db.transaction::<_, (), DbErr>(|txn| {
         Box::pin(async move {

From 7a25a9ac2fcbdd51a8248352e152277692cc02bd Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 15:00:48 -0400
Subject: [PATCH 173/198] add test

---
 .../native-utils/__test__/index.spec.mjs      | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/packages/backend/native-utils/__test__/index.spec.mjs b/packages/backend/native-utils/__test__/index.spec.mjs
index 1788dbb06..6e6a91858 100644
--- a/packages/backend/native-utils/__test__/index.spec.mjs
+++ b/packages/backend/native-utils/__test__/index.spec.mjs
@@ -1,6 +1,12 @@
 import test from "ava";
 
-import { convertId, IdConvertType } from "../built/index.js";
+import {
+	convertId,
+	IdConvertType,
+	nativeInitIdGenerator,
+	nativeCreateId,
+	nativeRandomStr,
+} from "../built/index.js";
 
 test("convert to mastodon id", (t) => {
 	t.is(convertId("9gf61ehcxv", IdConvertType.MastodonId), "960365976481219");
@@ -13,3 +19,14 @@ test("convert to mastodon id", (t) => {
 		"3494513243013053824",
 	);
 });
+
+test("create cuid2 with timestamp prefix", (t) => {
+	nativeInitIdGenerator(16, "");
+	t.not(nativeCreateId(BigInt(Date.now())), nativeCreateId(BigInt(Date.now())));
+	t.is(nativeCreateId(BigInt(Date.now())).length, 16);
+});
+
+test("create random string", (t) => {
+	t.not(nativeRandomStr(16), nativeRandomStr(16));
+	t.is(nativeRandomStr(24).length, 24);
+});

From bd5fbb1e3a1ba45876738bfc97484e7f55708af9 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 15:01:03 -0400
Subject: [PATCH 174/198] use native generators

---
 packages/backend/src/misc/gen-id.ts        | 19 +++++-----------
 packages/backend/src/misc/secure-rndstr.ts | 25 +++-------------------
 2 files changed, 8 insertions(+), 36 deletions(-)

diff --git a/packages/backend/src/misc/gen-id.ts b/packages/backend/src/misc/gen-id.ts
index fb92dd808..c9e52c829 100644
--- a/packages/backend/src/misc/gen-id.ts
+++ b/packages/backend/src/misc/gen-id.ts
@@ -1,14 +1,9 @@
-import { init, createId } from "@paralleldrive/cuid2";
 import config from "@/config/index.js";
+import { nativeCreateId, nativeInitIdGenerator } from "native-utils/built";
 
-const TIME2000 = 946684800000;
-const TIMESTAMP_LENGTH = 8;
-
-const length =
-	Math.min(Math.max(config.cuid?.length ?? 16, 16), 24) - TIMESTAMP_LENGTH;
-const fingerprint = `${config.cuid?.fingerprint ?? ""}${createId()}`;
-
-const genCuid2 = init({ length, fingerprint });
+const length = Math.min(Math.max(config.cuid?.length ?? 16, 16), 24);
+const fingerprint = config.cuid?.fingerprint ?? "";
+nativeInitIdGenerator(length, fingerprint);
 
 /**
  * The generated ID results in the form of `[8 chars timestamp] + [cuid2]`.
@@ -19,9 +14,5 @@ const genCuid2 = init({ length, fingerprint });
  * Ref: https://github.com/paralleldrive/cuid2#parameterized-length
  */
 export function genId(date?: Date): string {
-	const now = (date ?? new Date()).getTime();
-	const time = Math.max(now - TIME2000, 0);
-	const timestamp = time.toString(36).padStart(TIMESTAMP_LENGTH, "0");
-
-	return `${timestamp}${genCuid2()}`;
+	return nativeCreateId(BigInt((date ?? new Date()).getTime()));
 }
diff --git a/packages/backend/src/misc/secure-rndstr.ts b/packages/backend/src/misc/secure-rndstr.ts
index 7f5754e1c..4f4c9884a 100644
--- a/packages/backend/src/misc/secure-rndstr.ts
+++ b/packages/backend/src/misc/secure-rndstr.ts
@@ -1,24 +1,5 @@
-import * as crypto from "node:crypto";
+import { nativeRandomStr } from "native-utils/built";
 
-const L_CHARS = "0123456789abcdefghijklmnopqrstuvwxyz";
-const LU_CHARS =
-	"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
-
-export function secureRndstr(length = 32, useLU = true): string {
-	const chars = useLU ? LU_CHARS : L_CHARS;
-	const chars_len = chars.length;
-
-	let str = "";
-
-	for (let i = 0; i < length; i++) {
-		let rand = Math.floor(
-			(crypto.randomBytes(1).readUInt8(0) / 0xff) * chars_len,
-		);
-		if (rand === chars_len) {
-			rand = chars_len - 1;
-		}
-		str += chars.charAt(rand);
-	}
-
-	return str;
+export function secureRndstr(length = 32, _ = true): string {
+	return nativeRandomStr(length);
 }

From 77fd2ab0ad3365dad9b6175922954cdc81f49cba Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 15:01:29 -0400
Subject: [PATCH 175/198] initialize native database on boot

---
 packages/backend/src/db/postgre.ts | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts
index dd202b3de..9ffaf596c 100644
--- a/packages/backend/src/db/postgre.ts
+++ b/packages/backend/src/db/postgre.ts
@@ -78,6 +78,7 @@ import { entities as charts } from "@/services/chart/entities.js";
 import { envOption } from "../env.js";
 import { dbLogger } from "./logger.js";
 import { redisClient } from "./redis.js";
+import { nativeInitDatabase } from "native-utils/built/index.js";
 
 const sqlLogger = dbLogger.createSubLogger("sql", "gray", false);
 
@@ -220,6 +221,9 @@ export const db = new DataSource({
 });
 
 export async function initDb(force = false) {
+	await nativeInitDatabase(
+		`postgres://${config.db.user}:${config.db.pass}@${config.db.host}:${config.db.port}/${config.db.db}`,
+	);
 	if (force) {
 		if (db.isInitialized) {
 			await db.destroy();

From 34d2efa59835972cb7c49dd5fb8be3bbdf293170 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 15:04:25 -0400
Subject: [PATCH 176/198] remove node cuid2

---
 packages/backend/package.json | 1 -
 1 file changed, 1 deletion(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 0b0315a06..020257dd8 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -32,7 +32,6 @@
 		"@koa/cors": "3.4.3",
 		"@koa/multer": "3.0.0",
 		"@koa/router": "9.0.1",
-		"@paralleldrive/cuid2": "2.2.0",
 		"@peertube/http-signature": "1.7.0",
 		"@redocly/openapi-core": "1.0.0-beta.120",
 		"@sinonjs/fake-timers": "9.1.2",

From b0a7970ac7c3069f5131a3cc2d6ad13278278ae5 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 15:50:40 -0400
Subject: [PATCH 177/198] fix native import

---
 .../native-utils/src/model/schema/antenna.rs  | 26 ++++++++-----------
 packages/backend/src/misc/gen-id.ts           |  2 +-
 packages/backend/src/misc/secure-rndstr.ts    |  2 +-
 .../src/models/repositories/antenna.ts        | 12 +++++----
 4 files changed, 20 insertions(+), 22 deletions(-)

diff --git a/packages/backend/native-utils/src/model/schema/antenna.rs b/packages/backend/native-utils/src/model/schema/antenna.rs
index 99521a98b..198230e9a 100644
--- a/packages/backend/native-utils/src/model/schema/antenna.rs
+++ b/packages/backend/native-utils/src/model/schema/antenna.rs
@@ -70,7 +70,7 @@ cfg_if! {
         use crate::model::repository::Repository;
 
         /// For NAPI because [chrono] is not supported.
-        #[napi]
+        #[napi(object)]
         #[derive(Clone, Debug, PartialEq, Eq, JsonSchema, ToSchema)]
         #[serde(rename_all = "camelCase")]
         pub struct NativeAntennaSchema {
@@ -97,26 +97,22 @@ cfg_if! {
             pub has_unread_note: bool,
         }
 
-        #[napi]
+        #[napi(string_enum)]
         #[derive(Debug, FromStr, PartialEq, Eq, JsonSchema, ToSchema)]
-        #[serde(rename_all = "camelCase")]
-        #[display(style = "camelCase")]
         #[display("'{}'")]
+        #[allow(non_camel_case_types)]
         pub enum NativeAntennaSrc {
-            Home,
-            All,
-            Users,
-            List,
-            Group,
-            Instances,
+            home,
+            all,
+            users,
+            list,
+            group,
+            instances,
         }
 
         #[napi]
-        impl NativeAntennaSchema {
-            #[napi]
-            pub async fn pack_by_id(id: String) -> napi::Result<NativeAntennaSchema> {
-                antenna::Model::pack_by_id(id).await.map_err(Into::into)
-            }
+        pub async fn native_pack_antenna_by_id(id: String) -> napi::Result<NativeAntennaSchema> {
+            antenna::Model::pack_by_id(id).await.map_err(Into::into)
         }
     }
 }
diff --git a/packages/backend/src/misc/gen-id.ts b/packages/backend/src/misc/gen-id.ts
index c9e52c829..a016111ca 100644
--- a/packages/backend/src/misc/gen-id.ts
+++ b/packages/backend/src/misc/gen-id.ts
@@ -1,5 +1,5 @@
 import config from "@/config/index.js";
-import { nativeCreateId, nativeInitIdGenerator } from "native-utils/built";
+import { nativeCreateId, nativeInitIdGenerator } from "native-utils/built/index.js";
 
 const length = Math.min(Math.max(config.cuid?.length ?? 16, 16), 24);
 const fingerprint = config.cuid?.fingerprint ?? "";
diff --git a/packages/backend/src/misc/secure-rndstr.ts b/packages/backend/src/misc/secure-rndstr.ts
index 4f4c9884a..3d69a4d4a 100644
--- a/packages/backend/src/misc/secure-rndstr.ts
+++ b/packages/backend/src/misc/secure-rndstr.ts
@@ -1,4 +1,4 @@
-import { nativeRandomStr } from "native-utils/built";
+import { nativeRandomStr } from "native-utils/built/index.js";
 
 export function secureRndstr(length = 32, _ = true): string {
 	return nativeRandomStr(length);
diff --git a/packages/backend/src/models/repositories/antenna.ts b/packages/backend/src/models/repositories/antenna.ts
index d9405f4a9..d66aab4ee 100644
--- a/packages/backend/src/models/repositories/antenna.ts
+++ b/packages/backend/src/models/repositories/antenna.ts
@@ -1,12 +1,14 @@
 import { db } from "@/db/postgre.js";
 import { Antenna } from "@/models/entities/antenna.js";
-import { AntennaSchema } from "native-utils/built/index.js";
+import {
+	NativeAntennaSchema,
+	nativePackAntennaById,
+} from "native-utils/built/index.js";
 
 export const AntennaRepository = db.getRepository(Antenna).extend({
-	async pack(src: Antenna["id"] | Antenna): Promise<AntennaSchema> {
-		const id =
-			typeof src === "object" ? src.id : src;
+	async pack(src: Antenna["id"] | Antenna): Promise<NativeAntennaSchema> {
+		const id = typeof src === "object" ? src.id : src;
 
-		return await AntennaSchema.packById(id);
+		return await nativePackAntennaById(id);
 	},
 });

From 6679f992e72218e40dc37032a4fdc5773ba3568d Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 16:05:29 -0400
Subject: [PATCH 178/198] pnpm install

---
 pnpm-lock.yaml | 499 +++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 480 insertions(+), 19 deletions(-)

diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1c06f87dd..0d65da71e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -108,9 +108,6 @@ importers:
       '@koa/router':
         specifier: 9.0.1
         version: 9.0.1
-      '@paralleldrive/cuid2':
-        specifier: 2.2.0
-        version: 2.2.0
       '@peertube/http-signature':
         specifier: 1.7.0
         version: 1.7.0
@@ -612,6 +609,15 @@ importers:
         specifier: 8.11.0
         version: 8.11.0
 
+  packages/backend/native-utils:
+    devDependencies:
+      '@napi-rs/cli':
+        specifier: 2.15.0
+        version: 2.15.0
+      ava:
+        specifier: 5.1.1
+        version: 5.1.1
+
   packages/calckey-js:
     dependencies:
       autobind-decorator:
@@ -2288,11 +2294,6 @@ packages:
     resolution: {integrity: sha512-RDDr7ZF0cgbd37+NBGeQOjP7Tm/iNM+y3FmrT5bVQBXLePOTuKVC/dBsdN5UZv3Sl2XAwEvBfaGR90E0d8AA6g==}
     engines: {node: '>= 10'}
     hasBin: true
-    dev: false
-
-  /@noble/hashes@1.3.0:
-    resolution: {integrity: sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==}
-    dev: false
 
   /@nodelib/fs.scandir@2.1.5:
     resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
@@ -2347,12 +2348,6 @@ packages:
       through: 2.3.4
     dev: false
 
-  /@paralleldrive/cuid2@2.2.0:
-    resolution: {integrity: sha512-CVQDpPIUHrUGGLdrMGz1NmqZvqmsB2j2rCIQEu1EvxWjlFh4fhvEGmgR409cY20/67/WlJsggenq0no3p3kYsw==}
-    dependencies:
-      '@noble/hashes': 1.3.0
-    dev: false
-
   /@peertube/http-signature@1.7.0:
     resolution: {integrity: sha512-aGQIwo6/sWtyyqhVK4e1MtxYz4N1X8CNt6SOtCc+Wnczs5S5ONaLHDDR8LYaGn0MgOwvGgXyuZ5sJIfd7iyoUw==}
     engines: {node: '>=0.10'}
@@ -4170,6 +4165,14 @@ packages:
       clean-stack: 2.2.0
       indent-string: 4.0.0
 
+  /aggregate-error@4.0.1:
+    resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==}
+    engines: {node: '>=12'}
+    dependencies:
+      clean-stack: 4.2.0
+      indent-string: 5.0.0
+    dev: true
+
   /ajv-keywords@3.5.2(ajv@6.12.6):
     resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
     peerDependencies:
@@ -4239,6 +4242,11 @@ packages:
     resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
     engines: {node: '>=8'}
 
+  /ansi-regex@6.0.1:
+    resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
+    engines: {node: '>=12'}
+    dev: true
+
   /ansi-styles@2.2.1:
     resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==}
     engines: {node: '>=0.10.0'}
@@ -4261,6 +4269,11 @@ packages:
     engines: {node: '>=10'}
     dev: true
 
+  /ansi-styles@6.2.1:
+    resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
+    engines: {node: '>=12'}
+    dev: true
+
   /ansi-wrap@0.1.0:
     resolution: {integrity: sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==}
     engines: {node: '>=0.10.0'}
@@ -4420,6 +4433,11 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /array-find-index@1.0.2:
+    resolution: {integrity: sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
   /array-initial@1.1.0:
     resolution: {integrity: sha512-BC4Yl89vneCYfpLrs5JU2aAu9/a+xWbeKhvISg9PT7eWFB9UlRvI+rKEtk6mgxWr3dSkk9gQ8hCrdqt06NXPdw==}
     engines: {node: '>=0.10.0'}
@@ -4459,11 +4477,21 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /arrgv@1.0.2:
+    resolution: {integrity: sha512-a4eg4yhp7mmruZDQFqVMlxNRFGi/i1r87pt8SDHy0/I8PqSXoUTlWZRdAZo0VXgvEARcujbtTk8kiZRi1uDGRw==}
+    engines: {node: '>=8.0.0'}
+    dev: true
+
   /arrify@1.0.1:
     resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==}
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /arrify@3.0.0:
+    resolution: {integrity: sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==}
+    engines: {node: '>=12'}
+    dev: true
+
   /asap@2.0.6:
     resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
 
@@ -4569,6 +4597,65 @@ packages:
       oauth: 0.9.15
     dev: false
 
+  /ava@5.1.1:
+    resolution: {integrity: sha512-od1CWgWVIKZSdEc1dhQWhbsd6KBs0EYjek7eqZNGPvy+NyC9Q1bXixcadlgOXwDG9aM0zLMQZwRXfe9gMb1LQQ==}
+    engines: {node: '>=14.19 <15 || >=16.15 <17 || >=18'}
+    hasBin: true
+    peerDependencies:
+      '@ava/typescript': '*'
+    peerDependenciesMeta:
+      '@ava/typescript':
+        optional: true
+    dependencies:
+      acorn: 8.8.1
+      acorn-walk: 8.2.0
+      ansi-styles: 6.2.1
+      arrgv: 1.0.2
+      arrify: 3.0.0
+      callsites: 4.0.0
+      cbor: 8.1.0
+      chalk: 5.2.0
+      chokidar: 3.5.3
+      chunkd: 2.0.1
+      ci-info: 3.7.1
+      ci-parallel-vars: 1.0.1
+      clean-yaml-object: 0.1.0
+      cli-truncate: 3.1.0
+      code-excerpt: 4.0.0
+      common-path-prefix: 3.0.0
+      concordance: 5.0.4
+      currently-unhandled: 0.4.1
+      debug: 4.3.4(supports-color@8.1.1)
+      del: 7.0.0
+      emittery: 1.0.1
+      figures: 5.0.0
+      globby: 13.1.4
+      ignore-by-default: 2.1.0
+      indent-string: 5.0.0
+      is-error: 2.2.2
+      is-plain-object: 5.0.0
+      is-promise: 4.0.0
+      matcher: 5.0.0
+      mem: 9.0.2
+      ms: 2.1.3
+      p-event: 5.0.1
+      p-map: 5.5.0
+      picomatch: 2.3.1
+      pkg-conf: 4.0.0
+      plur: 5.1.0
+      pretty-ms: 8.0.0
+      resolve-cwd: 3.0.0
+      slash: 3.0.0
+      stack-utils: 2.0.6
+      strip-ansi: 7.1.0
+      supertap: 3.0.1
+      temp-dir: 3.0.0
+      write-file-atomic: 5.0.1
+      yargs: 17.6.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /available-typed-arrays@1.0.5:
     resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==}
     engines: {node: '>= 0.4'}
@@ -4833,6 +4920,10 @@ packages:
   /bluebird@3.7.2:
     resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
 
+  /blueimp-md5@2.19.0:
+    resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==}
+    dev: true
+
   /blurhash@1.1.5:
     resolution: {integrity: sha512-a+LO3A2DfxTaTztsmkbLYmUzUeApi0LZuKalwbNmqAHR6HhJGMt1qSV/R3wc+w4DL28holjqO3Bg74aUGavGjg==}
 
@@ -5109,6 +5200,11 @@ packages:
     resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
     engines: {node: '>=6'}
 
+  /callsites@4.0.0:
+    resolution: {integrity: sha512-y3jRROutgpKdz5vzEhWM34TidDU8vkJppF8dszITeb1PQmSqV3DTxyV8G/lyO/DNvtE1YTedehmw9MPZsCBHxQ==}
+    engines: {node: '>=12.20'}
+    dev: true
+
   /camelcase-keys@6.2.2:
     resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==}
     engines: {node: '>=8'}
@@ -5205,7 +5301,6 @@ packages:
   /chalk@5.2.0:
     resolution: {integrity: sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==}
     engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
-    dev: false
 
   /char-regex@1.0.2:
     resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
@@ -5315,11 +5410,19 @@ packages:
     engines: {node: '>=6.0'}
     dev: true
 
+  /chunkd@2.0.1:
+    resolution: {integrity: sha512-7d58XsFmOq0j6el67Ug9mHf9ELUXsQXYJBkyxhH/k+6Ke0qXRnv0kbemx+Twc6fRJ07C49lcbdgm9FL1Ei/6SQ==}
+    dev: true
+
   /ci-info@3.7.1:
     resolution: {integrity: sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==}
     engines: {node: '>=8'}
     dev: true
 
+  /ci-parallel-vars@1.0.1:
+    resolution: {integrity: sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg==}
+    dev: true
+
   /city-timezones@1.2.1:
     resolution: {integrity: sha512-hruuB611QFoUFMsan7xd9B2VPMrA8XC716O/999WW34kmaJUT1hxKF2W8TSXAWkhSqgvbu70DjcDv7/wpM6vow==}
     dependencies:
@@ -5351,6 +5454,18 @@ packages:
     resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
     engines: {node: '>=6'}
 
+  /clean-stack@4.2.0:
+    resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==}
+    engines: {node: '>=12'}
+    dependencies:
+      escape-string-regexp: 5.0.0
+    dev: true
+
+  /clean-yaml-object@0.1.0:
+    resolution: {integrity: sha512-3yONmlN9CSAkzNwnRCiJQ7Q2xK5mWuEfL3PuTZcAUzhObbXsfsnMptJzXwz93nc5zn9V9TwCVMmV7w4xsm43dw==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
   /cli-cursor@3.1.0:
     resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
     engines: {node: '>=8'}
@@ -5388,6 +5503,14 @@ packages:
       string-width: 4.2.3
     dev: true
 
+  /cli-truncate@3.1.0:
+    resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      slice-ansi: 5.0.0
+      string-width: 5.1.2
+    dev: true
+
   /cliui@3.2.0:
     resolution: {integrity: sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==}
     dependencies:
@@ -5418,7 +5541,6 @@ packages:
       string-width: 4.2.3
       strip-ansi: 6.0.1
       wrap-ansi: 7.0.0
-    dev: false
 
   /clone-buffer@1.0.0:
     resolution: {integrity: sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==}
@@ -5499,6 +5621,13 @@ packages:
       q: 1.5.1
     dev: true
 
+  /code-excerpt@4.0.0:
+    resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      convert-to-spaces: 2.0.1
+    dev: true
+
   /code-point-at@1.1.0:
     resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==}
     engines: {node: '>=0.10.0'}
@@ -5630,6 +5759,10 @@ packages:
     engines: {node: ^12.20.0 || >=14}
     dev: true
 
+  /common-path-prefix@3.0.0:
+    resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==}
+    dev: true
+
   /common-tags@1.8.2:
     resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==}
     engines: {node: '>=4.0.0'}
@@ -5665,6 +5798,20 @@ packages:
       readable-stream: 2.3.7
       typedarray: 0.0.6
 
+  /concordance@5.0.4:
+    resolution: {integrity: sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==}
+    engines: {node: '>=10.18.0 <11 || >=12.14.0 <13 || >=14'}
+    dependencies:
+      date-time: 3.1.0
+      esutils: 2.0.3
+      fast-diff: 1.3.0
+      js-string-escape: 1.0.1
+      lodash: 4.17.21
+      md5-hex: 3.0.1
+      semver: 7.3.8
+      well-known-symbols: 2.0.0
+    dev: true
+
   /condense-newlines@0.2.1:
     resolution: {integrity: sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg==}
     engines: {node: '>=0.10.0'}
@@ -5873,6 +6020,11 @@ packages:
     resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
     dev: true
 
+  /convert-to-spaces@2.0.1:
+    resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dev: true
+
   /cookies@0.8.0:
     resolution: {integrity: sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==}
     engines: {node: '>= 0.8'}
@@ -6075,6 +6227,13 @@ packages:
     resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
     dev: true
 
+  /currently-unhandled@0.4.1:
+    resolution: {integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      array-find-index: 1.0.2
+    dev: true
+
   /custom-event-polyfill@1.0.7:
     resolution: {integrity: sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==}
     dev: true
@@ -6177,6 +6336,13 @@ packages:
     resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==}
     engines: {node: '>=0.11'}
 
+  /date-time@3.1.0:
+    resolution: {integrity: sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==}
+    engines: {node: '>=6'}
+    dependencies:
+      time-zone: 1.0.0
+    dev: true
+
   /dayjs@1.11.7:
     resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==}
 
@@ -6341,6 +6507,20 @@ packages:
     resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==}
     dev: true
 
+  /del@7.0.0:
+    resolution: {integrity: sha512-tQbV/4u5WVB8HMJr08pgw0b6nG4RGt/tj+7Numvq+zqcvUFeMaIWWOUFltiU+6go8BSO2/ogsB4EasDaj0y68Q==}
+    engines: {node: '>=14.16'}
+    dependencies:
+      globby: 13.1.4
+      graceful-fs: 4.2.11
+      is-glob: 4.0.3
+      is-path-cwd: 3.0.0
+      is-path-inside: 4.0.0
+      p-map: 5.5.0
+      rimraf: 3.0.2
+      slash: 4.0.0
+    dev: true
+
   /delayed-stream@1.0.0:
     resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
     engines: {node: '>=0.4.0'}
@@ -6520,6 +6700,10 @@ packages:
       object.defaults: 1.1.0
     dev: true
 
+  /eastasianwidth@0.2.0:
+    resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+    dev: true
+
   /ecc-jsbn@0.1.2:
     resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==}
     dependencies:
@@ -6561,9 +6745,18 @@ packages:
     engines: {node: '>=10'}
     dev: true
 
+  /emittery@1.0.1:
+    resolution: {integrity: sha512-2ID6FdrMD9KDLldGesP6317G78K7km/kMcwItRtVFva7I/cSEOIaLpewaUb+YLXVwdAp3Ctfxh/V5zIl1sj7dQ==}
+    engines: {node: '>=14.16'}
+    dev: true
+
   /emoji-regex@8.0.0:
     resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
 
+  /emoji-regex@9.2.2:
+    resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+    dev: true
+
   /emojis-list@3.0.0:
     resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
     engines: {node: '>= 4'}
@@ -7198,6 +7391,10 @@ packages:
   /fast-deep-equal@3.1.3:
     resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
 
+  /fast-diff@1.3.0:
+    resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
+    dev: true
+
   /fast-glob@3.2.12:
     resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==}
     engines: {node: '>=8.6.0'}
@@ -7269,6 +7466,14 @@ packages:
       escape-string-regexp: 1.0.5
     dev: true
 
+  /figures@5.0.0:
+    resolution: {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==}
+    engines: {node: '>=14'}
+    dependencies:
+      escape-string-regexp: 5.0.0
+      is-unicode-supported: 1.3.0
+    dev: true
+
   /file-entry-cache@6.0.1:
     resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
     engines: {node: ^10.12.0 || >=12.0.0}
@@ -7345,6 +7550,14 @@ packages:
       locate-path: 6.0.0
       path-exists: 4.0.0
 
+  /find-up@6.3.0:
+    resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      locate-path: 7.2.0
+      path-exists: 5.0.0
+    dev: true
+
   /find-versions@5.1.0:
     resolution: {integrity: sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==}
     engines: {node: '>=12'}
@@ -7571,7 +7784,7 @@ packages:
     engines: {node: '>=10'}
     dependencies:
       at-least-node: 1.0.0
-      graceful-fs: 4.2.10
+      graceful-fs: 4.2.11
       jsonfile: 6.1.0
       universalify: 2.0.0
     dev: true
@@ -7882,6 +8095,17 @@ packages:
       slash: 3.0.0
     dev: true
 
+  /globby@13.1.4:
+    resolution: {integrity: sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      dir-glob: 3.0.1
+      fast-glob: 3.2.12
+      ignore: 5.2.4
+      merge2: 1.4.1
+      slash: 4.0.0
+    dev: true
+
   /glogg@1.0.2:
     resolution: {integrity: sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==}
     engines: {node: '>= 0.10'}
@@ -8393,6 +8617,11 @@ packages:
   /ieee754@1.2.1:
     resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
 
+  /ignore-by-default@2.1.0:
+    resolution: {integrity: sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==}
+    engines: {node: '>=10 <11 || >=12 <13 || >=14'}
+    dev: true
+
   /ignore@4.0.6:
     resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==}
     engines: {node: '>= 4'}
@@ -8435,6 +8664,11 @@ packages:
     resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
     engines: {node: '>=8'}
 
+  /indent-string@5.0.0:
+    resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==}
+    engines: {node: '>=12'}
+    dev: true
+
   /indexes-of@1.0.1:
     resolution: {integrity: sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA==}
     dev: true
@@ -8659,6 +8893,10 @@ packages:
     resolution: {integrity: sha512-r8EEQQsqT+Gn0aXFx7lTFygYQhILLCB+wn0WCDL5LZRINeLH/Rvw1j2oKodELLXYNImQ3CRlVsY8wW4cGOsyuw==}
     dev: false
 
+  /is-error@2.2.2:
+    resolution: {integrity: sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg==}
+    dev: true
+
   /is-expression@4.0.0:
     resolution: {integrity: sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==}
     dependencies:
@@ -8691,6 +8929,11 @@ packages:
     resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
     engines: {node: '>=8'}
 
+  /is-fullwidth-code-point@4.0.0:
+    resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==}
+    engines: {node: '>=12'}
+    dev: true
+
   /is-generator-fn@2.1.0:
     resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==}
     engines: {node: '>=6'}
@@ -8756,10 +8999,20 @@ packages:
     resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
     engines: {node: '>=0.12.0'}
 
+  /is-path-cwd@3.0.0:
+    resolution: {integrity: sha512-kyiNFFLU0Ampr6SDZitD/DwUo4Zs1nSdnygUBqsu3LooL00Qvb5j+UnvApUn/TTj1J3OuE6BTdQ5rudKmU2ZaA==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dev: true
+
   /is-path-inside@3.0.3:
     resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
     engines: {node: '>=8'}
 
+  /is-path-inside@4.0.0:
+    resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==}
+    engines: {node: '>=12'}
+    dev: true
+
   /is-plain-obj@1.1.0:
     resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==}
     engines: {node: '>=0.10.0'}
@@ -8787,6 +9040,10 @@ packages:
   /is-promise@2.2.2:
     resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==}
 
+  /is-promise@4.0.0:
+    resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
+    dev: true
+
   /is-regex@1.1.4:
     resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
     engines: {node: '>= 0.4'}
@@ -8856,6 +9113,11 @@ packages:
     engines: {node: '>=10'}
     dev: true
 
+  /is-unicode-supported@1.3.0:
+    resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==}
+    engines: {node: '>=12'}
+    dev: true
+
   /is-url@1.2.4:
     resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==}
     dev: false
@@ -9501,6 +9763,11 @@ packages:
   /js-sdsl@4.2.0:
     resolution: {integrity: sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==}
 
+  /js-string-escape@1.0.1:
+    resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==}
+    engines: {node: '>= 0.8'}
+    dev: true
+
   /js-stringify@1.0.2:
     resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==}
 
@@ -10133,6 +10400,11 @@ packages:
       strip-bom: 2.0.0
     dev: true
 
+  /load-json-file@7.0.1:
+    resolution: {integrity: sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dev: true
+
   /loader-runner@4.3.0:
     resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
     engines: {node: '>=6.11.5'}
@@ -10163,6 +10435,13 @@ packages:
     dependencies:
       p-locate: 5.0.0
 
+  /locate-path@7.2.0:
+    resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      p-locate: 6.0.0
+    dev: true
+
   /lodash.assignin@4.2.0:
     resolution: {integrity: sha512-yX/rx6d/UTVh7sSVWVSIMjfnz95evAgDFdb1ZozC35I9mSFCkmzptOzevxjgbQUsc78NR44LVHWjsoMQXy9FDg==}
     dev: false
@@ -10365,6 +10644,13 @@ packages:
       tmpl: 1.0.5
     dev: true
 
+  /map-age-cleaner@0.1.3:
+    resolution: {integrity: sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==}
+    engines: {node: '>=6'}
+    dependencies:
+      p-defer: 1.0.0
+    dev: true
+
   /map-cache@0.2.2:
     resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==}
     engines: {node: '>=0.10.0'}
@@ -10403,6 +10689,13 @@ packages:
       - supports-color
     dev: true
 
+  /matcher@5.0.0:
+    resolution: {integrity: sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      escape-string-regexp: 5.0.0
+    dev: true
+
   /math-expression-evaluator@1.4.0:
     resolution: {integrity: sha512-4vRUvPyxdO8cWULGTh9dZWL2tZK6LDBvj+OGHBER7poH9Qdt7kXEoj20wiz4lQUbUXQZFjPbe5mVDo9nutizCw==}
     dev: true
@@ -10411,6 +10704,13 @@ packages:
     resolution: {integrity: sha512-/ZVem4WygUnbmo/iE4oHZpZS97btfBtYy5Iwn1396vUZU7YhgVEN8J4UWwfZwY1ZqoTYlPgjvSw9WXauuXL0mg==}
     dev: true
 
+  /md5-hex@3.0.1:
+    resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==}
+    engines: {node: '>=8'}
+    dependencies:
+      blueimp-md5: 2.19.0
+    dev: true
+
   /media-typer@0.3.0:
     resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
     engines: {node: '>= 0.6'}
@@ -10424,6 +10724,14 @@ packages:
       - encoding
     dev: false
 
+  /mem@9.0.2:
+    resolution: {integrity: sha512-F2t4YIv9XQUBHt6AOJ0y7lSmP1+cY7Fm1DRh9GClTGzKST7UWLMx6ly9WZdLH/G/ppM5RL4MlQfRT71ri9t19A==}
+    engines: {node: '>=12.20'}
+    dependencies:
+      map-age-cleaner: 0.1.3
+      mimic-fn: 4.0.0
+    dev: true
+
   /meow@9.0.0:
     resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==}
     engines: {node: '>=10'}
@@ -11322,6 +11630,18 @@ packages:
     engines: {node: '>=12.20'}
     dev: false
 
+  /p-defer@1.0.0:
+    resolution: {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==}
+    engines: {node: '>=4'}
+    dev: true
+
+  /p-event@5.0.1:
+    resolution: {integrity: sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      p-timeout: 5.1.0
+    dev: true
+
   /p-finally@1.0.0:
     resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==}
     engines: {node: '>=4'}
@@ -11339,6 +11659,13 @@ packages:
     dependencies:
       yocto-queue: 0.1.0
 
+  /p-limit@4.0.0:
+    resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      yocto-queue: 1.0.0
+    dev: true
+
   /p-locate@4.1.0:
     resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
     engines: {node: '>=8'}
@@ -11351,12 +11678,26 @@ packages:
     dependencies:
       p-limit: 3.1.0
 
+  /p-locate@6.0.0:
+    resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      p-limit: 4.0.0
+    dev: true
+
   /p-map@4.0.0:
     resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
     engines: {node: '>=10'}
     dependencies:
       aggregate-error: 3.1.0
 
+  /p-map@5.5.0:
+    resolution: {integrity: sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==}
+    engines: {node: '>=12'}
+    dependencies:
+      aggregate-error: 4.0.1
+    dev: true
+
   /p-queue@6.6.2:
     resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==}
     engines: {node: '>=8'}
@@ -11372,6 +11713,11 @@ packages:
       p-finally: 1.0.0
     dev: true
 
+  /p-timeout@5.1.0:
+    resolution: {integrity: sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==}
+    engines: {node: '>=12'}
+    dev: true
+
   /p-try@2.2.0:
     resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
     engines: {node: '>=6'}
@@ -11424,6 +11770,11 @@ packages:
       xtend: 4.0.2
     dev: false
 
+  /parse-ms@3.0.0:
+    resolution: {integrity: sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==}
+    engines: {node: '>=12'}
+    dev: true
+
   /parse-node-version@1.0.1:
     resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
     engines: {node: '>= 0.10'}
@@ -11485,6 +11836,11 @@ packages:
     resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
     engines: {node: '>=8'}
 
+  /path-exists@5.0.0:
+    resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dev: true
+
   /path-is-absolute@1.0.1:
     resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
     engines: {node: '>=0.10.0'}
@@ -11647,6 +12003,14 @@ packages:
     engines: {node: '>= 6'}
     dev: true
 
+  /pkg-conf@4.0.0:
+    resolution: {integrity: sha512-7dmgi4UY4qk+4mj5Cd8v/GExPo0K+SlY+hulOSdfZ/T6jVH6//y7NtzZo5WrfhDBxuQ0jCa7fLZmNaNh7EWL/w==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      find-up: 6.3.0
+      load-json-file: 7.0.1
+    dev: true
+
   /pkg-dir@4.2.0:
     resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
     engines: {node: '>=8'}
@@ -11677,6 +12041,13 @@ packages:
       irregular-plurals: 3.5.0
     dev: true
 
+  /plur@5.1.0:
+    resolution: {integrity: sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      irregular-plurals: 3.5.0
+    dev: true
+
   /pluralize@8.0.0:
     resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
     engines: {node: '>=4'}
@@ -12015,6 +12386,13 @@ packages:
     engines: {node: '>= 0.8'}
     dev: true
 
+  /pretty-ms@8.0.0:
+    resolution: {integrity: sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==}
+    engines: {node: '>=14.16'}
+    dependencies:
+      parse-ms: 3.0.0
+    dev: true
+
   /pretty@2.0.0:
     resolution: {integrity: sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w==}
     engines: {node: '>=0.10.0'}
@@ -12985,6 +13363,13 @@ packages:
     dependencies:
       lru-cache: 6.0.0
 
+  /serialize-error@7.0.1:
+    resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==}
+    engines: {node: '>=10'}
+    dependencies:
+      type-fest: 0.13.1
+    dev: true
+
   /serialize-javascript@6.0.0:
     resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==}
     dependencies:
@@ -13086,6 +13471,11 @@ packages:
   /signal-exit@3.0.7:
     resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
 
+  /signal-exit@4.0.2:
+    resolution: {integrity: sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==}
+    engines: {node: '>=14'}
+    dev: true
+
   /simple-concat@1.0.1:
     resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
     dev: false
@@ -13113,6 +13503,11 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
+  /slash@4.0.0:
+    resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==}
+    engines: {node: '>=12'}
+    dev: true
+
   /slice-ansi@3.0.0:
     resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==}
     engines: {node: '>=8'}
@@ -13131,6 +13526,14 @@ packages:
       is-fullwidth-code-point: 3.0.0
     dev: true
 
+  /slice-ansi@5.0.0:
+    resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
+    engines: {node: '>=12'}
+    dependencies:
+      ansi-styles: 6.2.1
+      is-fullwidth-code-point: 4.0.0
+    dev: true
+
   /smart-buffer@4.2.0:
     resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
     engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
@@ -13454,6 +13857,15 @@ packages:
       is-fullwidth-code-point: 3.0.0
       strip-ansi: 6.0.1
 
+  /string-width@5.1.2:
+    resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
+    engines: {node: '>=12'}
+    dependencies:
+      eastasianwidth: 0.2.0
+      emoji-regex: 9.2.2
+      strip-ansi: 7.1.0
+    dev: true
+
   /string_decoder@0.10.31:
     resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==}
     dev: false
@@ -13486,6 +13898,13 @@ packages:
     dependencies:
       ansi-regex: 5.0.1
 
+  /strip-ansi@7.1.0:
+    resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
+    engines: {node: '>=12'}
+    dependencies:
+      ansi-regex: 6.0.1
+    dev: true
+
   /strip-bom@2.0.0:
     resolution: {integrity: sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==}
     engines: {node: '>=0.10.0'}
@@ -13568,6 +13987,16 @@ packages:
       - supports-color
     dev: false
 
+  /supertap@3.0.1:
+    resolution: {integrity: sha512-u1ZpIBCawJnO+0QePsEiOknOfCRq0yERxiAchT0i4li0WHNUJbf0evXXSXOcCAR4M8iMDoajXYmstm/qO81Isw==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      indent-string: 5.0.0
+      js-yaml: 3.14.1
+      serialize-error: 7.0.1
+      strip-ansi: 7.1.0
+    dev: true
+
   /supports-color@2.0.0:
     resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==}
     engines: {node: '>=0.8.0'}
@@ -13725,6 +14154,11 @@ packages:
       yallist: 4.0.0
     dev: false
 
+  /temp-dir@3.0.0:
+    resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==}
+    engines: {node: '>=14.16'}
+    dev: true
+
   /terminal-link@2.1.1:
     resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==}
     engines: {node: '>=8'}
@@ -13877,6 +14311,11 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /time-zone@1.0.0:
+    resolution: {integrity: sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==}
+    engines: {node: '>=4'}
+    dev: true
+
   /timsort@0.3.0:
     resolution: {integrity: sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==}
     dev: true
@@ -14201,6 +14640,11 @@ packages:
     resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
     engines: {node: '>=4'}
 
+  /type-fest@0.13.1:
+    resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
+    engines: {node: '>=10'}
+    dev: true
+
   /type-fest@0.18.1:
     resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==}
     engines: {node: '>=10'}
@@ -14609,7 +15053,7 @@ packages:
     dependencies:
       fs-mkdirp-stream: 1.0.0
       glob-stream: 6.1.0
-      graceful-fs: 4.2.10
+      graceful-fs: 4.2.11
       is-valid-glob: 1.0.0
       lazystream: 1.0.1
       lead: 1.0.0
@@ -14945,6 +15389,11 @@ packages:
       - supports-color
     dev: false
 
+  /well-known-symbols@2.0.0:
+    resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==}
+    engines: {node: '>=6'}
+    dev: true
+
   /whatwg-encoding@1.0.5:
     resolution: {integrity: sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==}
     dependencies:
@@ -15091,6 +15540,14 @@ packages:
       typedarray-to-buffer: 3.1.5
     dev: true
 
+  /write-file-atomic@5.0.1:
+    resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==}
+    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+    dependencies:
+      imurmurhash: 0.1.4
+      signal-exit: 4.0.2
+    dev: true
+
   /ws@7.5.9:
     resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==}
     engines: {node: '>=8.3.0'}
@@ -15289,7 +15746,6 @@ packages:
       string-width: 4.2.3
       y18n: 5.0.8
       yargs-parser: 21.1.1
-    dev: false
 
   /yargs@7.1.2:
     resolution: {integrity: sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==}
@@ -15329,6 +15785,11 @@ packages:
     resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
     engines: {node: '>=10'}
 
+  /yocto-queue@1.0.0:
+    resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
+    engines: {node: '>=12.20'}
+    dev: true
+
   /z-schema@5.0.5:
     resolution: {integrity: sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==}
     engines: {node: '>=8.0.0'}

From 3bfb1a0cf023cf55b6c4965f29fff96c1239802a Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 16:10:01 -0400
Subject: [PATCH 179/198] add format script

---
 package.json                                 | 2 +-
 packages/backend/native-utils/package.json   | 1 +
 packages/backend/native-utils/src/util/id.rs | 2 +-
 packages/backend/src/misc/gen-id.ts          | 5 ++++-
 4 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/package.json b/package.json
index 5c57c79cf..41fc1e666 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,7 @@
 		"e2e": "start-server-and-test start:test http://localhost:61812 cy:run",
 		"mocha": "pnpm --filter backend run mocha",
 		"test": "pnpm run mocha",
-		"format": "pnpm rome format packages/**/* --write && pnpm --filter client run format",
+		"format": "pnpm rome format packages/**/* --write && pnpm -r run format",
 		"clean": "pnpm node ./scripts/clean.js",
 		"clean-all": "pnpm node ./scripts/clean-all.js",
 		"cleanall": "pnpm run clean-all"
diff --git a/packages/backend/native-utils/package.json b/packages/backend/native-utils/package.json
index 8423f569b..1503bca19 100644
--- a/packages/backend/native-utils/package.json
+++ b/packages/backend/native-utils/package.json
@@ -40,6 +40,7 @@
     "test": "ava",
     "universal": "napi universal",
     "version": "napi version",
+		"format": "cargo fmt",
     "cargo:unit": "cargo test unit_test",
     "cargo:integration": "cargo test --no-default-features -F noarray int_test -- --test-threads=1"
   }
diff --git a/packages/backend/native-utils/src/util/id.rs b/packages/backend/native-utils/src/util/id.rs
index 539b0415e..b8bf54747 100644
--- a/packages/backend/native-utils/src/util/id.rs
+++ b/packages/backend/native-utils/src/util/id.rs
@@ -61,10 +61,10 @@ cfg_if! {
 
 #[cfg(test)]
 mod unit_test {
+    use crate::util::id;
     use cfg_if::cfg_if;
     use pretty_assertions::{assert_eq, assert_ne};
     use std::thread;
-    use crate::util::id;
 
     cfg_if! {
         if #[cfg(feature = "napi")] {
diff --git a/packages/backend/src/misc/gen-id.ts b/packages/backend/src/misc/gen-id.ts
index a016111ca..ea0d414e7 100644
--- a/packages/backend/src/misc/gen-id.ts
+++ b/packages/backend/src/misc/gen-id.ts
@@ -1,5 +1,8 @@
 import config from "@/config/index.js";
-import { nativeCreateId, nativeInitIdGenerator } from "native-utils/built/index.js";
+import {
+	nativeCreateId,
+	nativeInitIdGenerator,
+} from "native-utils/built/index.js";
 
 const length = Math.min(Math.max(config.cuid?.length ?? 16, 16), 24);
 const fingerprint = config.cuid?.fingerprint ?? "";

From 8b021f828ff2c5bc9e202d46b8d3db6d4e66cd8d Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 16:34:45 -0400
Subject: [PATCH 180/198] fix unit tests

---
 .../backend/native-utils/src/model/schema/antenna.rs     | 9 ++++++++-
 packages/backend/native-utils/src/util/id.rs             | 2 +-
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/packages/backend/native-utils/src/model/schema/antenna.rs b/packages/backend/native-utils/src/model/schema/antenna.rs
index 198230e9a..00f1216ac 100644
--- a/packages/backend/native-utils/src/model/schema/antenna.rs
+++ b/packages/backend/native-utils/src/model/schema/antenna.rs
@@ -119,6 +119,7 @@ cfg_if! {
 
 #[cfg(test)]
 mod unit_test {
+    use cfg_if::cfg_if;
     use pretty_assertions::assert_eq;
     use serde_json::json;
 
@@ -129,7 +130,13 @@ mod unit_test {
     #[test]
     fn src_from_active_enum() {
         let src = AntennaSrc::try_from(AntennaSrcEnum::All).unwrap();
-        assert_eq!(src, AntennaSrc::All);
+        cfg_if! {
+            if #[cfg(feature = "napi")] {
+                assert_eq!(src, AntennaSrc::all);
+            } else {
+                assert_eq!(src, AntennaSrc::All);
+            }
+        }
     }
 
     #[test]
diff --git a/packages/backend/native-utils/src/util/id.rs b/packages/backend/native-utils/src/util/id.rs
index b8bf54747..b17b112d7 100644
--- a/packages/backend/native-utils/src/util/id.rs
+++ b/packages/backend/native-utils/src/util/id.rs
@@ -91,7 +91,7 @@ mod unit_test {
                 assert_ne!(id::create_id().unwrap(), id::create_id().unwrap());
                 let id1 = thread::spawn(|| id::create_id().unwrap());
                 let id2 = thread::spawn(|| id::create_id().unwrap());
-                assert_ne!(id1.join().unwrap(), id2.join().unwrap())
+                assert_ne!(id1.join().unwrap(), id2.join().unwrap());
             }
         }
     }

From 1279f396a6359943fe7857d96304090f60f07e2b Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 23:01:53 -0400
Subject: [PATCH 181/198] add unit test

---
 packages/backend/native-utils/package.json    |  4 +-
 .../native-utils/src/model/schema/antenna.rs  | 10 ++--
 .../native-utils/src/model/schema/app.rs      | 48 +++++++++++++++++--
 packages/backend/native-utils/src/util/id.rs  |  4 +-
 4 files changed, 53 insertions(+), 13 deletions(-)

diff --git a/packages/backend/native-utils/package.json b/packages/backend/native-utils/package.json
index 1503bca19..029372548 100644
--- a/packages/backend/native-utils/package.json
+++ b/packages/backend/native-utils/package.json
@@ -41,7 +41,7 @@
     "universal": "napi universal",
     "version": "napi version",
 		"format": "cargo fmt",
-    "cargo:unit": "cargo test unit_test",
-    "cargo:integration": "cargo test --no-default-features -F noarray int_test -- --test-threads=1"
+    "cargo:unit": "cargo test unit_test && cargo test -F napi unit_test",
+    "cargo:integration": "cargo test -F noarray int_test -- --test-threads=1"
   }
 }
diff --git a/packages/backend/native-utils/src/model/schema/antenna.rs b/packages/backend/native-utils/src/model/schema/antenna.rs
index 00f1216ac..4ec1e0794 100644
--- a/packages/backend/native-utils/src/model/schema/antenna.rs
+++ b/packages/backend/native-utils/src/model/schema/antenna.rs
@@ -142,7 +142,7 @@ mod unit_test {
     #[test]
     fn antenna_valid() {
         let instance = json!({
-            "id": "9f4x0bkx1u",
+            "id": "9fil64s6g7cskdrb",
             "createdAt": "2023-05-24T06:56:14.323Z",
             "name": "Valid Antenna",
             "keywords": [["first", "keyword"], ["second"]],
@@ -150,7 +150,7 @@ mod unit_test {
             "src": "users",
             // "userListId" and "userGroupId" can be null or be omitted
             "userListId": null,
-            "users": ["9f4yjw6m13", "9f4yk2cp6d"],
+            "users": ["9fil64s6g7cskdrb", "9fil66brl1udxau2"],
             "instances": [],
             // "caseSensitive", "notify", "withReplies", "withFile", and
             // "hasUnreadNote" are false if ommited
@@ -175,14 +175,14 @@ mod unit_test {
             "keywords": "invalid keyword",
             // "excludeKeywords" is required
             "excludeKeywords": null,
-            // "src" should be one of "home", "all", "users", "list", "group", and
+            // "src" must be one of "home", "all", "users", "list", "group", and
             // "instances"
             "src": "invalid_src",
             // "userListId" is string
             "userListId": ["9f4ziiqfxw"],
             // "users" must be an array of strings
-            "users": [1, "9f4ykyuza6"],
-            "instances": ["9f4ykyuybo"],
+            "users": [1, "9fil64s6g7cskdrb"],
+            "instances": ["9fil65jzhtjpi3xn"],
             // "caseSensitive" is boolean
             "caseSensitive": 0,
             "notify": true,
diff --git a/packages/backend/native-utils/src/model/schema/app.rs b/packages/backend/native-utils/src/model/schema/app.rs
index f24c1387b..682b82ec0 100644
--- a/packages/backend/native-utils/src/model/schema/app.rs
+++ b/packages/backend/native-utils/src/model/schema/app.rs
@@ -95,13 +95,53 @@ pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| App::validator());
 
 #[cfg(test)]
 mod unit_test {
+    use pretty_assertions::assert_eq;
+    use serde_json::json;
+
+    use crate::util::id::{create_id, init_id};
+    use crate::util::random::gen_string;
+
+    use super::VALIDATOR;
+
     #[test]
-    fn valid() {
-        todo!();
+    fn app_valid() {
+        init_id(12, "");
+        let instance = json!({
+            "id": create_id().unwrap(),
+            "name": "Test App",
+            "secret": gen_string(24),
+            "callbackUrl": "urn:ietf:wg:oauth:2.0:oob",
+            "permission": ["read:account", "write:account", "read:notes"],
+        });
+
+        assert!(VALIDATOR.is_valid(&instance));
     }
 
     #[test]
-    fn invalid() {
-        todo!();
+    fn app_invalid() {
+        init_id(12, "");
+        let instance = json!({
+            "id": create_id().unwrap(),
+            // "name" is required
+            "name": null,
+            // "permission" must be one of the app permissions
+            "permission": ["write:invalid_perm", "write:notes"],
+            // "secret" is a nullable string
+            "secret": 123,
+            // "is_authorized" is a nullable boolean
+            "isAuthorized": "true-ish",
+        });
+        let result = VALIDATOR
+            .validate(&instance)
+            .expect_err("validation must fail");
+        let mut paths: Vec<String> = result
+            .map(|e| e.instance_path.to_string())
+            .filter(|e| !e.is_empty())
+            .collect();
+        paths.sort();
+        assert_eq!(
+            paths,
+            vec!["/isAuthorized", "/name", "/permission/0", "/secret"]
+        );
     }
 }
diff --git a/packages/backend/native-utils/src/util/id.rs b/packages/backend/native-utils/src/util/id.rs
index b17b112d7..d922518f9 100644
--- a/packages/backend/native-utils/src/util/id.rs
+++ b/packages/backend/native-utils/src/util/id.rs
@@ -72,9 +72,9 @@ mod unit_test {
 
             #[test]
             fn can_generate_aid_compat_ids() {
-                id::native_init_id_generator(16, "".to_string());
+                id::native_init_id_generator(20, "".to_string());
                 let id1 = id::native_create_id(Utc::now().timestamp_millis().into());
-                assert_eq!(id1.len(), 16);
+                assert_eq!(id1.len(), 20);
                 let id1 = id::native_create_id(Utc::now().timestamp_millis().into());
                 let id2 = id::native_create_id(Utc::now().timestamp_millis().into());
                 assert_ne!(id1, id2);

From 31ac7fb34f47bf6ca7eb3938728a65609dcd032b Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 23:29:48 -0400
Subject: [PATCH 182/198] add integration test of antenna

---
 packages/backend/native-utils/tests/common.rs | 14 ++++-
 .../tests/model/repository/antenna.rs         | 53 +++++++++++++++++--
 2 files changed, 61 insertions(+), 6 deletions(-)

diff --git a/packages/backend/native-utils/tests/common.rs b/packages/backend/native-utils/tests/common.rs
index 61dd20aac..186e862bd 100644
--- a/packages/backend/native-utils/tests/common.rs
+++ b/packages/backend/native-utils/tests/common.rs
@@ -164,7 +164,7 @@ async fn setup_model(db: &DbConn) {
                 id: create_id().unwrap(),
                 created_at: Utc::now().into(),
                 user_id: user_id.to_owned(),
-                name: "Test Antenna".to_string(),
+                name: "Alice Antenna".to_string(),
                 src: AntennaSrcEnum::All,
                 keywords: vec![
                     vec!["foo".to_string(), "bar".to_string()],
@@ -185,6 +185,18 @@ async fn setup_model(db: &DbConn) {
                 .reset_all()
                 .insert(txn)
                 .await?;
+            let note_model = entity::note::Model {
+                id: create_id().unwrap(),
+                created_at: Utc::now().into(),
+                text: Some("Testing 123".to_string()),
+                user_id: user_id.to_owned(),
+                ..Default::default()
+            };
+            note_model
+                .into_active_model()
+                .reset_all()
+                .insert(txn)
+                .await?;
 
             Ok(())
         })
diff --git a/packages/backend/native-utils/tests/model/repository/antenna.rs b/packages/backend/native-utils/tests/model/repository/antenna.rs
index a37007398..3bda2ca18 100644
--- a/packages/backend/native-utils/tests/model/repository/antenna.rs
+++ b/packages/backend/native-utils/tests/model/repository/antenna.rs
@@ -1,13 +1,13 @@
 mod int_test {
-    use native_utils::{database, model};
+    use native_utils::{database, model, util};
 
     use model::{
-        entity::{antenna, user},
+        entity::{antenna, antenna_note, note, user},
         repository::Repository,
         schema,
     };
     use pretty_assertions::assert_eq;
-    use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
+    use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter};
 
     use crate::{cleanup, prepare};
 
@@ -39,7 +39,7 @@ mod int_test {
         let result = schema::Antenna {
             id: alice_antenna.id,
             created_at: alice_antenna.created_at.into(),
-            name: "Test Antenna".to_string(),
+            name: "Alice Antenna".to_string(),
             keywords: vec![
                 vec!["foo".to_string(), "bar".to_string()],
                 vec!["foobar".to_string()],
@@ -70,6 +70,49 @@ mod int_test {
 
     #[tokio::test]
     async fn unread_note() {
-        todo!();
+        prepare().await;
+        let db = database::get_database().unwrap();
+
+        let (alice, alice_antenna) = user::Entity::find()
+            .filter(user::Column::Username.eq("alice"))
+            .find_also_related(antenna::Entity)
+            .one(db)
+            .await
+            .unwrap()
+            .expect("alice not found");
+        let alice_antenna = alice_antenna.expect("alice's antenna not found");
+        let packed = alice_antenna
+            .to_owned()
+            .pack()
+            .await
+            .expect("Unable to pack");
+        assert_eq!(packed.has_unread_note, false);
+
+        let note_model = note::Entity::find()
+            .filter(note::Column::UserId.eq(alice.id))
+            .one(db)
+            .await
+            .unwrap()
+            .expect("note not found");
+        let antenna_note = antenna_note::Model {
+            id: util::id::create_id().unwrap(),
+            antenna_id: alice_antenna.id.to_owned(),
+            note_id: note_model.id.to_owned(),
+            read: false,
+        };
+        antenna_note
+            .into_active_model()
+            .reset_all()
+            .insert(db)
+            .await
+            .unwrap();
+        let packed = alice_antenna
+            .to_owned()
+            .pack()
+            .await
+            .expect("Unable to pack");
+        assert_eq!(packed.has_unread_note, true);
+
+        cleanup().await;
     }
 }

From dc8ab49a783f5d29a52ee311b1eef8f8d9d3c131 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Fri, 2 Jun 2023 23:37:36 -0400
Subject: [PATCH 183/198] fix migration

---
 packages/backend/native-utils/Cargo.toml                      | 2 +-
 packages/backend/native-utils/migration/Cargo.toml            | 4 ++--
 .../migration/src/m20230531_180824_drop_reversi.rs            | 2 ++
 packages/backend/native-utils/migration/src/vec_to_json.rs    | 4 ++--
 4 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/packages/backend/native-utils/Cargo.toml b/packages/backend/native-utils/Cargo.toml
index 071d0f331..9b030e37d 100644
--- a/packages/backend/native-utils/Cargo.toml
+++ b/packages/backend/native-utils/Cargo.toml
@@ -4,7 +4,7 @@ name = "native-utils"
 version = "0.0.0"
 
 [workspace]
-members = ["migration/Cargo.toml"]
+members = ["migration"]
 
 [features]
 default = []
diff --git a/packages/backend/native-utils/migration/Cargo.toml b/packages/backend/native-utils/migration/Cargo.toml
index dbfd41be6..0b4b2b2e7 100644
--- a/packages/backend/native-utils/migration/Cargo.toml
+++ b/packages/backend/native-utils/migration/Cargo.toml
@@ -10,12 +10,12 @@ path = "src/lib.rs"
 
 [features]
 default = []
-convert = ["model/noarray"]
+convert = ["dep:native-utils"]
 
 [dependencies]
 async-std = { version = "1", features = ["attributes", "tokio1"] }
 serde_json = "1.0.96"
-model = { path = "../model" }
+native-utils = { path = "../", optional = true }
 indicatif = { version = "0.17.4", features = ["tokio"] }
 tokio = { version = "1.28.2", features = ["full"] }
 futures = "0.3.28"
diff --git a/packages/backend/native-utils/migration/src/m20230531_180824_drop_reversi.rs b/packages/backend/native-utils/migration/src/m20230531_180824_drop_reversi.rs
index c2726dd76..32b8dae22 100644
--- a/packages/backend/native-utils/migration/src/m20230531_180824_drop_reversi.rs
+++ b/packages/backend/native-utils/migration/src/m20230531_180824_drop_reversi.rs
@@ -18,6 +18,7 @@ impl MigrationTrait for Migration {
             DbBackend::Postgres,
             Table::drop()
                 .table(ReversiGame::Table)
+                .if_exists()
                 .to_string(PostgresQueryBuilder),
         ))
         .await?;
@@ -25,6 +26,7 @@ impl MigrationTrait for Migration {
             DbBackend::Postgres,
             Table::drop()
                 .table(ReversiMatching::Table)
+                .if_exists()
                 .to_string(PostgresQueryBuilder),
         ))
         .await?;
diff --git a/packages/backend/native-utils/migration/src/vec_to_json.rs b/packages/backend/native-utils/migration/src/vec_to_json.rs
index c762dcf1c..c4a6d9b07 100644
--- a/packages/backend/native-utils/migration/src/vec_to_json.rs
+++ b/packages/backend/native-utils/migration/src/vec_to_json.rs
@@ -1,7 +1,7 @@
-#![allow(dead_code)]
+#![cfg(feature = "convert")]
 
 use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
-use model::entity::newtype::{I32Vec, StringVec};
+use native_utils::model::entity::newtype::{I32Vec, StringVec};
 use sea_orm_migration::{
     prelude::*,
     sea_orm::{Database, DbBackend, DbConn, Statement, TryGetable},

From 9e4b3c6a1fdeda247ce2792bcdcd8a433aae0ac4 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Sat, 3 Jun 2023 00:27:00 -0400
Subject: [PATCH 184/198] call seaorm migrations after typeorm

---
 .../backend/native-utils/migration/Cargo.toml |  2 ++
 .../native-utils/migration/src/main.rs        | 34 +++++++++++++++++++
 .../native-utils/migration/src/vec_to_json.rs |  2 --
 packages/backend/package.json                 |  8 +++--
 4 files changed, 42 insertions(+), 4 deletions(-)

diff --git a/packages/backend/native-utils/migration/Cargo.toml b/packages/backend/native-utils/migration/Cargo.toml
index 0b4b2b2e7..3813eccd8 100644
--- a/packages/backend/native-utils/migration/Cargo.toml
+++ b/packages/backend/native-utils/migration/Cargo.toml
@@ -19,6 +19,8 @@ native-utils = { path = "../", optional = true }
 indicatif = { version = "0.17.4", features = ["tokio"] }
 tokio = { version = "1.28.2", features = ["full"] }
 futures = "0.3.28"
+serde_yaml = "0.9.21"
+serde = { version = "1.0.163", features = ["derive"] }
 
 [dependencies.sea-orm-migration]
 version = "0.11.0"
diff --git a/packages/backend/native-utils/migration/src/main.rs b/packages/backend/native-utils/migration/src/main.rs
index 31a72026e..a845a41ff 100644
--- a/packages/backend/native-utils/migration/src/main.rs
+++ b/packages/backend/native-utils/migration/src/main.rs
@@ -1,11 +1,45 @@
+use serde::Deserialize;
+use std::env;
+use std::fs;
+
 use sea_orm_migration::prelude::*;
 
+#[cfg(feature = "convert")]
 mod vec_to_json;
 
 #[async_std::main]
 async fn main() {
+    let cwd = env::current_dir().unwrap();
+    let yml = fs::File::open(cwd.join("../../.config/default.yml"))
+        .expect("Unable to read '.config/default.yml'");
+    let config: Config = serde_yaml::from_reader(yml).expect("Unable to parse");
+
+    env::set_var(
+        "DATABASE_URL",
+        format!(
+            "postgres://{}:{}@{}:{}/{}",
+            config.db.user, config.db.pass, config.db.host, config.db.port, config.db.db
+        ),
+    );
+
     cli::run_cli(migration::Migrator).await;
 
     #[cfg(feature = "convert")]
     vec_to_json::convert().await;
 }
+
+#[derive(Debug, PartialEq, Deserialize)]
+#[serde(rename = "camelCase")]
+pub struct Config {
+    pub db: DbConfig,
+}
+
+#[derive(Debug, PartialEq, Deserialize)]
+#[serde(rename = "camelCase")]
+pub struct DbConfig {
+    pub host: String,
+    pub port: u32,
+    pub db: String,
+    pub user: String,
+    pub pass: String,
+}
diff --git a/packages/backend/native-utils/migration/src/vec_to_json.rs b/packages/backend/native-utils/migration/src/vec_to_json.rs
index c4a6d9b07..104357a49 100644
--- a/packages/backend/native-utils/migration/src/vec_to_json.rs
+++ b/packages/backend/native-utils/migration/src/vec_to_json.rs
@@ -1,5 +1,3 @@
-#![cfg(feature = "convert")]
-
 use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
 use native_utils::model::entity::newtype::{I32Vec, StringVec};
 use sea_orm_migration::{
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 020257dd8..ca916576b 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -6,8 +6,12 @@
 	"scripts": {
 		"start": "pnpm node ./built/index.js",
 		"start:test": "NODE_ENV=test pnpm node ./built/index.js",
-		"migrate": "typeorm migration:run -d ormconfig.js",
-		"revertmigration": "typeorm migration:revert -d ormconfig.js",
+		"migrate": "pnpm run migrate:typeorm && pnpm run migrate:cargo",
+		"migrate:typeorm": "typeorm migration:run -d ormconfig.js",
+		"migrate:cargo": "cargo run --manifest-path native-utils/migration/Cargo.toml -- up",
+		"revertmigration": "pnpm run revertmigration:cargo && pnpm run revertmigration:typeorm",
+		"revertmigration:typeorm": "typeorm migration:revert -d ormconfig.js",
+		"revertmigration:cargo": "cargo run --manifest-path native-utils/migration/Cargo.toml -- down",
 		"check:connect": "node ./check_connect.js",
 		"build": "pnpm swc src -d built -D",
 		"watch": "pnpm swc src -d built -D -w",

From 2f0629b42bdd0c9632313eb8f04beab758c7e436 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Sat, 3 Jun 2023 00:36:51 -0400
Subject: [PATCH 185/198] cargo formats all modules

---
 packages/backend/native-utils/package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend/native-utils/package.json b/packages/backend/native-utils/package.json
index 029372548..691914171 100644
--- a/packages/backend/native-utils/package.json
+++ b/packages/backend/native-utils/package.json
@@ -40,7 +40,7 @@
     "test": "ava",
     "universal": "napi universal",
     "version": "napi version",
-		"format": "cargo fmt",
+		"format": "cargo fmt --all",
     "cargo:unit": "cargo test unit_test && cargo test -F napi unit_test",
     "cargo:integration": "cargo test -F noarray int_test -- --test-threads=1"
   }

From e3e007333465d44be7cfa737e69e063e45a20ed1 Mon Sep 17 00:00:00 2001
From: Namekuji <nmkj@wahh.foo>
Date: Sat, 3 Jun 2023 00:40:51 -0400
Subject: [PATCH 186/198] add cargo test script

---
 packages/backend/native-utils/package.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/packages/backend/native-utils/package.json b/packages/backend/native-utils/package.json
index 691914171..64f9e0e50 100644
--- a/packages/backend/native-utils/package.json
+++ b/packages/backend/native-utils/package.json
@@ -37,10 +37,11 @@
     "build": "napi build --features napi --platform --release ./built/",
     "build:debug": "napi build --platform",
     "prepublishOnly": "napi prepublish -t npm",
-    "test": "ava",
+    "test": "pnpm run cargo:test && pnpm run build && ava",
     "universal": "napi universal",
     "version": "napi version",
 		"format": "cargo fmt --all",
+		"cargo:test": "pnpm run cargo:unit && pnpm run cargo:integration",
     "cargo:unit": "cargo test unit_test && cargo test -F napi unit_test",
     "cargo:integration": "cargo test -F noarray int_test -- --test-threads=1"
   }

From d33cee63bd4f7acb9510e7858644eec35502bf65 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Fri, 2 Jun 2023 21:45:11 -0700
Subject: [PATCH 187/198] refactor: :recycle: ssr views

Correct og:type for users, format docs, deprecate _info_card_
---
 packages/backend/src/server/web/index.ts      | 18 -------
 .../backend/src/server/web/views/channel.pug  |  2 +-
 .../backend/src/server/web/views/clip.pug     |  2 +-
 .../src/server/web/views/gallery-post.pug     |  2 +-
 .../src/server/web/views/info-card.pug        | 50 -------------------
 .../backend/src/server/web/views/note.pug     |  4 +-
 .../backend/src/server/web/views/page.pug     |  2 +-
 .../backend/src/server/web/views/user.pug     | 11 ++--
 8 files changed, 12 insertions(+), 79 deletions(-)
 delete mode 100644 packages/backend/src/server/web/views/info-card.pug

diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts
index f1e0ed692..0d4034f55 100644
--- a/packages/backend/src/server/web/index.ts
+++ b/packages/backend/src/server/web/index.ts
@@ -590,24 +590,6 @@ router.get("/channels/:channel", async (ctx, next) => {
 });
 //#endregion
 
-router.get("/_info_card_", async (ctx) => {
-	const meta = await fetchMeta(true);
-	if (meta.privateMode) {
-		ctx.status = 403;
-		return;
-	}
-
-	ctx.remove("X-Frame-Options");
-
-	await ctx.render("info-card", {
-		version: config.version,
-		host: config.host,
-		meta: meta,
-		originalUsersCount: await Users.countBy({ host: IsNull() }),
-		originalNotesCount: await Notes.countBy({ userHost: IsNull() }),
-	});
-});
-
 router.get("/bios", async (ctx) => {
 	await ctx.render("bios", {
 		version: config.version,
diff --git a/packages/backend/src/server/web/views/channel.pug b/packages/backend/src/server/web/views/channel.pug
index c4594b766..2427144fb 100644
--- a/packages/backend/src/server/web/views/channel.pug
+++ b/packages/backend/src/server/web/views/channel.pug
@@ -13,7 +13,7 @@ block desc
 
 block og
 	unless privateMode
-		meta(property='og:type'        content='article')
+		meta(property='og:type' content='article')
 		meta(property='og:title'       content= title)
 		meta(property='og:description' content= channel.description)
 		meta(property='og:url'         content= url)
diff --git a/packages/backend/src/server/web/views/clip.pug b/packages/backend/src/server/web/views/clip.pug
index 4c29d64d1..79d629380 100644
--- a/packages/backend/src/server/web/views/clip.pug
+++ b/packages/backend/src/server/web/views/clip.pug
@@ -14,7 +14,7 @@ block desc
 
 block og
 	unless privateMode
-		meta(property='og:type'        content='article')
+		meta(property='og:type' content='article')
 		meta(property='og:title'       content= title)
 		meta(property='og:description' content= clip.description)
 		meta(property='og:url'         content= url)
diff --git a/packages/backend/src/server/web/views/gallery-post.pug b/packages/backend/src/server/web/views/gallery-post.pug
index 86bbb4769..561a19b25 100644
--- a/packages/backend/src/server/web/views/gallery-post.pug
+++ b/packages/backend/src/server/web/views/gallery-post.pug
@@ -14,7 +14,7 @@ block desc
 
 block og
 	unless privateMode
-		meta(property='og:type'        content='article')
+		meta(property='og:type' content='article')
 		meta(property='og:title'       content= title)
 		meta(property='og:description' content= post.description)
 		meta(property='og:url'         content= url)
diff --git a/packages/backend/src/server/web/views/info-card.pug b/packages/backend/src/server/web/views/info-card.pug
deleted file mode 100644
index be52d0c39..000000000
--- a/packages/backend/src/server/web/views/info-card.pug
+++ /dev/null
@@ -1,50 +0,0 @@
-doctype html
-
-html
-
-	head
-		meta(charset='utf-8')
-		meta(name='application-name' content='Calckey')
-		title= meta.name || host
-		style.
-			html, body {
-				margin: 0;
-				padding: 0;
-				min-height: 100vh;
-				background: #fff;
-			}
-
-			#a {
-				display: block;
-			}
-
-			#banner {
-				background-size: cover;
-				background-position: center center;
-			}
-
-			#title {
-				display: inline-block;
-				margin: 24px;
-				padding: 0.5em 0.8em;
-				color: #fff;
-				background: rgba(0, 0, 0, 0.5);
-				font-weight: bold;
-				font-size: 1.3em;
-			}
-
-			#content {
-				overflow: auto;
-				color: #353c3e;
-			}
-
-			#description {
-				margin: 24px;
-			}
-
-	body
-		a#a(href=`https://${host}` target="_blank")
-			header#banner(style=`background-image: url(${meta.bannerUrl})`)
-				div#title= meta.name || host
-		div#content
-			div#description= meta.description
diff --git a/packages/backend/src/server/web/views/note.pug b/packages/backend/src/server/web/views/note.pug
index ff2a77165..dae0a82d5 100644
--- a/packages/backend/src/server/web/views/note.pug
+++ b/packages/backend/src/server/web/views/note.pug
@@ -18,7 +18,7 @@ block desc
 
 block og
 	unless privateMode
-		meta(property='og:type'        content='article')
+		meta(property='og:type' content='article')
 		meta(property='og:title'       content= title)
 		meta(property='og:description' content= summary)
 		meta(property='og:url'         content= url)
@@ -27,7 +27,7 @@ block og
 			meta(property='og:image:width'     content=note.files[0].properties.width)
 			meta(property='og:image:height'    content=note.files[0].properties.height)
 			meta(property='og:image:type'      content=note.files[0].type)
-			meta(property='twitter:card'       content="summary_large_image")
+			meta(property='twitter:card' content="summary_large_image")
 		if isVideo
 			meta(property='og:video:type'      content=note.files[0].type)
 			meta(property='og:video'           content=note.files[0].url)
diff --git a/packages/backend/src/server/web/views/page.pug b/packages/backend/src/server/web/views/page.pug
index eacaaab98..bfc7ebf79 100644
--- a/packages/backend/src/server/web/views/page.pug
+++ b/packages/backend/src/server/web/views/page.pug
@@ -14,7 +14,7 @@ block desc
 
 block og
 	unless privateMode
-		meta(property='og:type'        content='article')
+		meta(property='og:type' content='article')
 		meta(property='og:title'       content= title)
 		meta(property='og:description' content= page.summary)
 		meta(property='og:url'         content= url)
diff --git a/packages/backend/src/server/web/views/user.pug b/packages/backend/src/server/web/views/user.pug
index 1cc429156..f5820f55f 100644
--- a/packages/backend/src/server/web/views/user.pug
+++ b/packages/backend/src/server/web/views/user.pug
@@ -13,11 +13,12 @@ block desc
 
 block og
 	unless privateMode
-		meta(property='og:type'        content='blog')
-		meta(property='og:title'       content= title)
-		meta(property='og:description' content= profile.description)
-		meta(property='og:url'         content= url)
-		meta(property='og:image'       content= avatarUrl)
+		meta(property='og:type' content='profile')
+		meta(property='og:title'         content= title)
+		meta(property='og:description'   content= profile.description)
+		meta(property='og:url'           content= url)
+		meta(property='og:image'         content= avatarUrl)
+		meta(property='profile:username' content= user.username)
 
 block meta
 	unless privateMode

From 117f95799ba7683fd565b976df233bdd14465f94 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Fri, 2 Jun 2023 22:11:34 -0700
Subject: [PATCH 188/198] refactor: :recycle: sync note summaries

---
 locales/ar-SA.yml                               | 1 -
 locales/bn-BD.yml                               | 1 -
 locales/ca-ES.yml                               | 1 -
 locales/cs-CZ.yml                               | 1 -
 locales/da-DK.yml                               | 1 -
 locales/de-DE.yml                               | 1 -
 locales/el-GR.yml                               | 1 -
 locales/en-US.yml                               | 1 -
 locales/es-ES.yml                               | 1 -
 locales/fi.yml                                  | 1 -
 locales/fr-FR.yml                               | 1 -
 locales/id-ID.yml                               | 1 -
 locales/it-IT.yml                               | 1 -
 locales/ja-JP.yml                               | 1 -
 locales/ja-KS.yml                               | 1 -
 locales/ko-KR.yml                               | 1 -
 locales/nl-NL.yml                               | 1 -
 locales/pl-PL.yml                               | 1 -
 locales/pt-PT.yml                               | 1 -
 locales/ro-RO.yml                               | 1 -
 locales/ru-RU.yml                               | 1 -
 locales/sk-SK.yml                               | 1 -
 locales/sv-SE.yml                               | 1 -
 locales/th-TH.yml                               | 1 -
 locales/uk-UA.yml                               | 1 -
 locales/vi-VN.yml                               | 1 -
 locales/zh-CN.yml                               | 1 -
 locales/zh-TW.yml                               | 1 -
 packages/backend/src/misc/get-note-summary.ts   | 5 +++--
 packages/client/src/scripts/get-note-summary.ts | 5 +++--
 30 files changed, 6 insertions(+), 32 deletions(-)

diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml
index 148cd32a0..3ce83ebcc 100644
--- a/locales/ar-SA.yml
+++ b/locales/ar-SA.yml
@@ -176,7 +176,6 @@ operations: "الإجراءات"
 software: "البرمجية"
 version: "الإصدار"
 metadata: "البيانات الوصفية"
-withNFiles: "{n} ملف (ملفات)"
 monitor: "شاشة التحكم"
 jobQueue: "قائمة الانتظار"
 cpuAndMemory: "وحدة المعالجة المركزية والذاكرة"
diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml
index cde425d7f..f08e4bfc2 100644
--- a/locales/bn-BD.yml
+++ b/locales/bn-BD.yml
@@ -177,7 +177,6 @@ operations: "ক্রিয়াকলাপ"
 software: "সফটওয়্যার"
 version: "সংস্করণ"
 metadata: "মেটাডাটা"
-withNFiles: "{n} টি ফাইল"
 monitor: "মনিটর"
 jobQueue: "জব কিউ"
 cpuAndMemory: "সিপিউ এবং মেমরি"
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index f438b4f9a..857caac1f 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -791,7 +791,6 @@ selectUser: Selecciona un usuari
 latestStatus: Últim estat
 storageUsage: Ús del emmagatzematge
 metadata: Metadades
-withNFiles: '{n} fitxer(s)'
 monitor: Seguiment
 software: Programari
 version: Versió
diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml
index 1d12e69ab..8b502762f 100644
--- a/locales/cs-CZ.yml
+++ b/locales/cs-CZ.yml
@@ -189,7 +189,6 @@ operations: "Operace"
 software: "Software"
 version: "Verze"
 metadata: "Metadata"
-withNFiles: "{n} soubor(ů)"
 monitor: "Monitorovat"
 jobQueue: "Fronta úloh"
 cpuAndMemory: "CPU a paměť"
diff --git a/locales/da-DK.yml b/locales/da-DK.yml
index c6d339a2e..878273c65 100644
--- a/locales/da-DK.yml
+++ b/locales/da-DK.yml
@@ -117,7 +117,6 @@ operations: Operationer
 software: Software
 metadata: Metadata
 version: Version
-withNFiles: '{n} fil(er)'
 monitor: Vagt
 jobQueue: Jobkø
 statistics: Statistik
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index 2a039af4c..45edbe045 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -200,7 +200,6 @@ operations: "Tätigkeiten"
 software: "Software"
 version: "Version"
 metadata: "Metadaten"
-withNFiles: "{n} Datei(en)"
 monitor: "Überwachung"
 jobQueue: "Auftragswarteschlange"
 cpuAndMemory: "CPU und Speicher"
diff --git a/locales/el-GR.yml b/locales/el-GR.yml
index 57270f7aa..d444882b4 100644
--- a/locales/el-GR.yml
+++ b/locales/el-GR.yml
@@ -435,7 +435,6 @@ nothing: Δεν υπάρχει τίποτα να δείτε εδώ
 newNoteRecived: Υπάρχουν νέες δημοσιεύσεις
 passwordMatched: Ταιριάζει
 unmarkAsSensitive: Αναίρεση επισήμανσης ως Ευαίσθητο Περιεχόμενο (NSFW)
-withNFiles: '{n} αρχείο(-α)'
 blockedUsers: Μπλοκαρισμένα μέλη
 noteDeleteConfirm: Θέλετε σίγουρα να διαγράψετε αυτή τη δημοσίευση;
 preview: Προεπισκόπηση
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 5b6abcc35..127b2d0fe 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -211,7 +211,6 @@ operations: "Operations"
 software: "Software"
 version: "Version"
 metadata: "Metadata"
-withNFiles: "{n} file(s)"
 monitor: "Monitor"
 jobQueue: "Job Queue"
 cpuAndMemory: "CPU and Memory"
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index 49f51bff6..2dc40f359 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -195,7 +195,6 @@ operations: "Operaciones"
 software: "Software"
 version: "Versión"
 metadata: "Metadatos"
-withNFiles: "{n} archivos"
 monitor: "Monitor"
 jobQueue: "Cola de trabajos"
 cpuAndMemory: "CPU y Memoria"
diff --git a/locales/fi.yml b/locales/fi.yml
index ec7438096..aa9699cca 100644
--- a/locales/fi.yml
+++ b/locales/fi.yml
@@ -182,7 +182,6 @@ followsYou: Seuraa sinua
 pageLoadErrorDescription: Tämä yleensä johtuu verkkovirheistä tai selaimen välimuistista.
   Kokeile tyhjentämällä välimuisti ja yritä sitten hetken kuluttua uudelleen.
 enterListName: Anna listalle nimi
-withNFiles: '{n} tiedosto(t)'
 instanceInfo: Instanssin tiedot
 clearQueue: Tyhjennä jono
 suspendConfirm: Oletko varma, että haluat keskeyttää tämän tilin?
diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml
index e7416f577..dad095688 100644
--- a/locales/fr-FR.yml
+++ b/locales/fr-FR.yml
@@ -201,7 +201,6 @@ operations: "Opérations"
 software: "Logiciel"
 version: "Version"
 metadata: "Métadonnées"
-withNFiles: "{n} fichier(s)"
 monitor: "Contrôle"
 jobQueue: "File d’attente"
 cpuAndMemory: "Processeur et mémoire"
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index 4caca4439..f9859c2c7 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -197,7 +197,6 @@ operations: "Tindakan"
 software: "Perangkat lunak"
 version: "Versi"
 metadata: "Metadata"
-withNFiles: "{n} berkas"
 monitor: "Pantau"
 jobQueue: "Antrian kerja"
 cpuAndMemory: "CPU dan Memori"
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 6dc4b68b5..ed3632ec6 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -175,7 +175,6 @@ operations: "Operazioni"
 software: "Software"
 version: "Versione"
 metadata: "Metadato"
-withNFiles: "{n} file in allegato"
 monitor: "Monitorare"
 jobQueue: "Coda di lavoro"
 cpuAndMemory: "CPU e Memoria"
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 94454f3f9..1bbc38c49 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -188,7 +188,6 @@ operations: "操作"
 software: "ソフトウェア"
 version: "バージョン"
 metadata: "メタデータ"
-withNFiles: "{n}つのファイル"
 monitor: "モニター"
 jobQueue: "ジョブキュー"
 cpuAndMemory: "CPUとメモリ"
diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml
index d5c48276f..8a9b91486 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -177,7 +177,6 @@ operations: "操作"
 software: "ソフトウェア"
 version: "バージョン"
 metadata: "メタデータ"
-withNFiles: "{n}個のファイル"
 monitor: "モニター"
 jobQueue: "ジョブキュー"
 cpuAndMemory: "CPUとメモリ"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index 15e776faf..7143cf2f9 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -177,7 +177,6 @@ operations: "작업"
 software: "소프트웨어"
 version: "버전"
 metadata: "메타데이터"
-withNFiles: "{n}개의 파일"
 monitor: "모니터"
 jobQueue: "작업 대기열"
 cpuAndMemory: "CPU와 메모리"
diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml
index 49b737e91..115c003e3 100644
--- a/locales/nl-NL.yml
+++ b/locales/nl-NL.yml
@@ -179,7 +179,6 @@ operations: "Verwerkingen"
 software: "Software"
 version: "Versie"
 metadata: "Metadata"
-withNFiles: "{n} bestand(en)"
 monitor: "Monitor"
 jobQueue: "Job Queue"
 cpuAndMemory: "CPU en geheugen"
diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml
index 659e204af..a1b0f0c15 100644
--- a/locales/pl-PL.yml
+++ b/locales/pl-PL.yml
@@ -187,7 +187,6 @@ operations: "Działania"
 software: "Oprogramowanie"
 version: "Wersja"
 metadata: "Metadane"
-withNFiles: "{n} plik(i/ów)"
 monitor: "Monitor"
 jobQueue: "Kolejka zadań"
 cpuAndMemory: "CPU i pamięć"
diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml
index 2803df928..3e15beebb 100644
--- a/locales/pt-PT.yml
+++ b/locales/pt-PT.yml
@@ -177,7 +177,6 @@ operations: "operar"
 software: "Programas"
 version: "versão"
 metadata: "Metadados"
-withNFiles: "{n} Um arquivo"
 monitor: "monitor"
 jobQueue: "Fila de trabalhos"
 cpuAndMemory: "CPU e memória"
diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml
index 92a35ffa8..ba51950e3 100644
--- a/locales/ro-RO.yml
+++ b/locales/ro-RO.yml
@@ -177,7 +177,6 @@ operations: "Operațiuni"
 software: "Software"
 version: "Versiune"
 metadata: "Metadata"
-withNFiles: "{n} fișier(e)"
 monitor: "Monitor"
 jobQueue: "coada de job-uri"
 cpuAndMemory: "CPU și memorie"
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index ad47d7d37..01b21b0fc 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -192,7 +192,6 @@ operations: "Операции"
 software: "Программы"
 version: "Версия"
 metadata: "Метаданные"
-withNFiles: "Файлы, {n} шт"
 monitor: "Монитор"
 jobQueue: "Очередь заданий"
 cpuAndMemory: "Процессор и память"
diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml
index a36caf1fd..462f34ed2 100644
--- a/locales/sk-SK.yml
+++ b/locales/sk-SK.yml
@@ -177,7 +177,6 @@ operations: "Operácie"
 software: "Softvér"
 version: "Verzia"
 metadata: "Metadáta"
-withNFiles: "{n} súbor(ov)"
 monitor: "Monitor"
 jobQueue: "Fronta úloh"
 cpuAndMemory: "CPU a pamäť"
diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml
index 993af2a23..6523ce068 100644
--- a/locales/sv-SE.yml
+++ b/locales/sv-SE.yml
@@ -176,7 +176,6 @@ operations: "Operationer"
 software: "Mjukvara"
 version: "Version"
 metadata: "Metadata"
-withNFiles: "{n} fil(er)"
 monitor: "Övervakning"
 jobQueue: "Jobbkö"
 cpuAndMemory: "CPU och minne"
diff --git a/locales/th-TH.yml b/locales/th-TH.yml
index b6d81d8f5..9666737ee 100644
--- a/locales/th-TH.yml
+++ b/locales/th-TH.yml
@@ -177,7 +177,6 @@ operations: "ดำเนินการ"
 software: "ซอฟต์แวร์"
 version: "เวอร์ชั่น"
 metadata: "ข้อมูลเมตา"
-withNFiles: "{n} ไฟล์(s)"
 monitor: "มอนิเตอร์"
 jobQueue: "คิวงาน"
 cpuAndMemory: "ซีพียู และ หน่วยความจำ"
diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml
index 0368a0897..549dce666 100644
--- a/locales/uk-UA.yml
+++ b/locales/uk-UA.yml
@@ -177,7 +177,6 @@ operations: "Операції"
 software: "Програмне забезпечення"
 version: "Версія"
 metadata: "Метадані"
-withNFiles: "файли: {n}"
 monitor: "Монітор"
 jobQueue: "Черга завдань"
 cpuAndMemory: "ЦП та пам'ять"
diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml
index 7662af409..4b254fe94 100644
--- a/locales/vi-VN.yml
+++ b/locales/vi-VN.yml
@@ -177,7 +177,6 @@ operations: "Vận hành"
 software: "Phần mềm"
 version: "Phiên bản"
 metadata: "Metadata"
-withNFiles: "{n} tập tin"
 monitor: "Giám sát"
 jobQueue: "Công việc chờ xử lý"
 cpuAndMemory: "CPU và Dung lượng"
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index 8ca19e596..5359cb6ef 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -181,7 +181,6 @@ operations: "操作"
 software: "软件"
 version: "版本"
 metadata: "元数据"
-withNFiles: "{n}个文件"
 monitor: "服务器状态"
 jobQueue: "作业队列"
 cpuAndMemory: "CPU和内存"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index fd5117f48..61b5afbe6 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -180,7 +180,6 @@ operations: "操作"
 software: "軟體"
 version: "版本"
 metadata: "元資料"
-withNFiles: "{n}個檔案"
 monitor: "監視器"
 jobQueue: "佇列"
 cpuAndMemory: "CPU及記憶體用量"
diff --git a/packages/backend/src/misc/get-note-summary.ts b/packages/backend/src/misc/get-note-summary.ts
index 446e3fc14..285dcffd3 100644
--- a/packages/backend/src/misc/get-note-summary.ts
+++ b/packages/backend/src/misc/get-note-summary.ts
@@ -20,12 +20,13 @@ export const getNoteSummary = (note: Packed<"Note">): string => {
 
 	// ファイルが添付されているとき
 	if ((note.files || []).length !== 0) {
-		summary += ` (📎${note.files!.length})`;
+		const len = note.files?.length;
+		summary += ` 📎 (${len !== 1 ? len : ''})`;
 	}
 
 	// 投票が添付されているとき
 	if (note.poll) {
-		summary += " (📊)";
+		summary += " 📊";
 	}
 
 	/*
diff --git a/packages/client/src/scripts/get-note-summary.ts b/packages/client/src/scripts/get-note-summary.ts
index 43bd1d3b9..4333bd6f0 100644
--- a/packages/client/src/scripts/get-note-summary.ts
+++ b/packages/client/src/scripts/get-note-summary.ts
@@ -23,12 +23,13 @@ export const getNoteSummary = (note: misskey.entities.Note): string => {
 
 	// ファイルが添付されているとき
 	if ((note.files || []).length !== 0) {
-		summary += ` (${i18n.t("withNFiles", { n: note.files.length })})`;
+		const len = note.files?.length;
+		summary += ` 📎 (${len !== 1 ? len : ''})`;
 	}
 
 	// 投票が添付されているとき
 	if (note.poll) {
-		summary += ` (${i18n.ts.poll})`;
+		summary += " 📊";
 	}
 
 	/*

From 30b33d2c20b92e01c76a95a0be5aeae817a8efcc Mon Sep 17 00:00:00 2001
From: Kainoa Kanter <kainoa@t1c.dev>
Date: Sat, 3 Jun 2023 06:02:47 +0000
Subject: [PATCH 189/198] dev39

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 41fc1e666..703fef632 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "calckey",
-	"version": "14.0.0-dev35",
+	"version": "14.0.0-dev39",
 	"codename": "aqua",
 	"repository": {
 		"type": "git",

From 792f28a367b6864d65bcc0d22039df58cb2ef244 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Fri, 2 Jun 2023 23:20:34 -0700
Subject: [PATCH 190/198] fix: remove unessicary extra line in note menu

---
 packages/client/src/scripts/get-note-menu.ts | 2 --
 1 file changed, 2 deletions(-)

diff --git a/packages/client/src/scripts/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts
index 6eeecf4a6..aaec3099d 100644
--- a/packages/client/src/scripts/get-note-menu.ts
+++ b/packages/client/src/scripts/get-note-menu.ts
@@ -399,8 +399,6 @@ export function getNoteMenu(props: {
 				  ]
 				: []),
 
-			null,
-
 			isAppearAuthor || isModerator
 				? {
 						icon: "ph-trash ph-bold ph-lg",

From decdb6490ce76699b8cae471cd771993ba19f866 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Fri, 2 Jun 2023 23:21:06 -0700
Subject: [PATCH 191/198] chore: formatting

---
 packages/backend/src/misc/get-note-summary.ts   | 2 +-
 packages/client/src/scripts/get-note-summary.ts | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/backend/src/misc/get-note-summary.ts b/packages/backend/src/misc/get-note-summary.ts
index 285dcffd3..63521c1c6 100644
--- a/packages/backend/src/misc/get-note-summary.ts
+++ b/packages/backend/src/misc/get-note-summary.ts
@@ -21,7 +21,7 @@ export const getNoteSummary = (note: Packed<"Note">): string => {
 	// ファイルが添付されているとき
 	if ((note.files || []).length !== 0) {
 		const len = note.files?.length;
-		summary += ` 📎 (${len !== 1 ? len : ''})`;
+		summary += ` 📎 (${len !== 1 ? len : ""})`;
 	}
 
 	// 投票が添付されているとき
diff --git a/packages/client/src/scripts/get-note-summary.ts b/packages/client/src/scripts/get-note-summary.ts
index 4333bd6f0..4a1530162 100644
--- a/packages/client/src/scripts/get-note-summary.ts
+++ b/packages/client/src/scripts/get-note-summary.ts
@@ -24,7 +24,7 @@ export const getNoteSummary = (note: misskey.entities.Note): string => {
 	// ファイルが添付されているとき
 	if ((note.files || []).length !== 0) {
 		const len = note.files?.length;
-		summary += ` 📎 (${len !== 1 ? len : ''})`;
+		summary += ` 📎 (${len !== 1 ? len : ""})`;
 	}
 
 	// 投票が添付されているとき

From ed5a4395dfbb666cf8e79eb1c5d0336e4d4aefb9 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Sat, 3 Jun 2023 15:24:16 +0900
Subject: [PATCH 192/198] fix tutorial

---
 .../client/src/components/MkTutorialDialog.vue     | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/packages/client/src/components/MkTutorialDialog.vue b/packages/client/src/components/MkTutorialDialog.vue
index 6927e961c..b4d8eee0a 100644
--- a/packages/client/src/components/MkTutorialDialog.vue
+++ b/packages/client/src/components/MkTutorialDialog.vue
@@ -140,26 +140,26 @@
 										</template>
 									</I18n>
 								</li>
-								<li v-if="timelines.includes('recommended')">
+								<li v-if="timelines.includes('social')">
 									<I18n
 										:src="i18n.ts._tutorial.step5_5"
 										tag="div"
 									>
 										<template #icon>
 											<i
-												class="ph-thumbs-up ph-bold ph-lg"
+												class="ph-handshake ph-bold ph-lg"
 											/>
 										</template>
 									</I18n>
 								</li>
-								<li v-if="timelines.includes('social')">
+								<li v-if="timelines.includes('recommended')">
 									<I18n
 										:src="i18n.ts._tutorial.step5_6"
 										tag="div"
 									>
 										<template #icon>
 											<i
-												class="ph-handshake ph-bold ph-lg"
+												class="ph-thumbs-up ph-bold ph-lg"
 											/>
 										</template>
 									</I18n>
@@ -235,12 +235,12 @@ let timelines = ["home"];
 if (isLocalTimelineAvailable) {
 	timelines.push("local");
 }
-if (isRecommendedTimelineAvailable) {
-	timelines.push("recommended");
-}
 if (isLocalTimelineAvailable) {
 	timelines.push("social");
 }
+if (isRecommendedTimelineAvailable) {
+	timelines.push("recommended");
+}
 if (isGlobalTimelineAvailable) {
 	timelines.push("global");
 }

From c0dd5aa9646a18cfef857adfe79a34714f1dd533 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Fri, 2 Jun 2023 23:28:21 -0700
Subject: [PATCH 193/198] =?UTF-8?q?perf:=20=E3=82=A4=E3=83=B3=E3=82=B9?=
 =?UTF-8?q?=E3=82=BF=E3=83=B3=E3=82=B9=E3=83=87=E3=83=95=E3=82=A9=E3=83=AB?=
 =?UTF-8?q?=E3=83=88=E3=83=86=E3=83=BC=E3=83=9E=E3=82=92=E4=BA=88=E3=82=81?=
 =?UTF-8?q?json5=20->=20json=E3=81=AB=E5=A4=89=E6=8F=9B=E3=81=97=E3=81=A6?=
 =?UTF-8?q?=E3=81=8A=E3=81=8F=E3=81=93=E3=81=A8=E3=81=A7json5=E3=82=92?=
 =?UTF-8?q?=E5=88=9D=E6=9C=9F=E3=83=90=E3=83=B3=E3=83=89=E3=83=AB=E3=81=AB?=
 =?UTF-8?q?=E5=90=AB=E3=82=81=E3=81=9A=E3=81=AB=E6=B8=88=E3=82=80=E3=82=88?=
 =?UTF-8?q?=E3=81=86=E3=81=AB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Syuilo <syuilotan@yahoo.co.jp>
---
 packages/backend/src/server/api/endpoints/meta.ts | 10 ++++++++--
 packages/client/src/init.ts                       |  5 ++---
 2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index 139afc88b..23cfe4018 100644
--- a/packages/backend/src/server/api/endpoints/meta.ts
+++ b/packages/backend/src/server/api/endpoints/meta.ts
@@ -1,3 +1,4 @@
+import * as JSON5 from "json5";
 import { IsNull, MoreThan } from "typeorm";
 import config from "@/config/index.js";
 import { fetchMeta } from "@/misc/fetch-meta.js";
@@ -462,8 +463,13 @@ export default define(meta, paramDef, async (ps, me) => {
 		maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため
 		maxCaptionTextLength: MAX_CAPTION_TEXT_LENGTH,
 		emojis: instance.privateMode && !me ? [] : await Emojis.packMany(emojis),
-		defaultLightTheme: instance.defaultLightTheme,
-		defaultDarkTheme: instance.defaultDarkTheme,
+		// クライアントの手間を減らすためあらかじめJSONに変換しておく
+		defaultLightTheme: instance.defaultLightTheme
+			? JSON.stringify(JSON5.parse(instance.defaultLightTheme))
+			: null,
+		defaultDarkTheme: instance.defaultDarkTheme
+			? JSON.stringify(JSON5.parse(instance.defaultDarkTheme))
+			: null,
 		ads:
 			instance.privateMode && !me
 				? []
diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts
index 7a004688d..5d9ae12f3 100644
--- a/packages/client/src/init.ts
+++ b/packages/client/src/init.ts
@@ -27,7 +27,6 @@ import {
 	defineAsyncComponent,
 } from "vue";
 import { compareVersions } from "compare-versions";
-import JSON5 from "json5";
 
 import widgets from "@/widgets";
 import directives from "@/directives";
@@ -312,12 +311,12 @@ import { getAccountFromId } from "@/scripts/get-account-from-id";
 			if (instance.defaultLightTheme != null)
 				ColdDeviceStorage.set(
 					"lightTheme",
-					JSON5.parse(instance.defaultLightTheme),
+					JSON.parse(instance.defaultLightTheme),
 				);
 			if (instance.defaultDarkTheme != null)
 				ColdDeviceStorage.set(
 					"darkTheme",
-					JSON5.parse(instance.defaultDarkTheme),
+					JSON.parse(instance.defaultDarkTheme),
 				);
 			defaultStore.set("themeInitial", false);
 		}

From 0b9ef6bc52323f8944c853bb142b30713e85019b Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Fri, 2 Jun 2023 23:30:51 -0700
Subject: [PATCH 194/198] refactor: remove mk remnants

---
 packages/backend/migration/1000000000000-Init.js        | 2 +-
 packages/backend/src/models/entities/meta.ts            | 4 ++--
 packages/backend/src/server/api/endpoints/admin/meta.ts | 4 ++--
 packages/backend/src/server/api/endpoints/meta.ts       | 4 ++--
 4 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/packages/backend/migration/1000000000000-Init.js b/packages/backend/migration/1000000000000-Init.js
index c07500e35..d32a6e0d2 100644
--- a/packages/backend/migration/1000000000000-Init.js
+++ b/packages/backend/migration/1000000000000-Init.js
@@ -220,7 +220,7 @@ export class Init1000000000000 {
 			`CREATE INDEX "IDX_3c601b70a1066d2c8b517094cb" ON "notification" ("notifieeId") `,
 		);
 		await queryRunner.query(
-			`CREATE TABLE "meta" ("id" character varying(32) NOT NULL, "name" character varying(128), "description" character varying(1024), "maintainerName" character varying(128), "maintainerEmail" character varying(128), "announcements" jsonb NOT NULL DEFAULT '[]', "disableRegistration" boolean NOT NULL DEFAULT false, "disableLocalTimeline" boolean NOT NULL DEFAULT false, "disableGlobalTimeline" boolean NOT NULL DEFAULT false, "enableEmojiReaction" boolean NOT NULL DEFAULT true, "useStarForReactionFallback" boolean NOT NULL DEFAULT false, "langs" character varying(64) array NOT NULL DEFAULT '{}'::varchar[], "hiddenTags" character varying(256) array NOT NULL DEFAULT '{}'::varchar[], "blockedHosts" character varying(256) array NOT NULL DEFAULT '{}'::varchar[], "mascotImageUrl" character varying(512) DEFAULT '/assets/ai.png', "bannerUrl" character varying(512), "errorImageUrl" character varying(512) DEFAULT 'https://xn--931a.moe/aiart/yubitun.png', "iconUrl" character varying(512), "cacheRemoteFiles" boolean NOT NULL DEFAULT true, "proxyAccount" character varying(128), "enableRecaptcha" boolean NOT NULL DEFAULT false, "recaptchaSiteKey" character varying(64), "recaptchaSecretKey" character varying(64), "localDriveCapacityMb" integer NOT NULL DEFAULT 1024, "remoteDriveCapacityMb" integer NOT NULL DEFAULT 32, "maxNoteTextLength" integer NOT NULL DEFAULT 500, "summalyProxy" character varying(128), "enableEmail" boolean NOT NULL DEFAULT false, "email" character varying(128), "smtpSecure" boolean NOT NULL DEFAULT false, "smtpHost" character varying(128), "smtpPort" integer, "smtpUser" character varying(128), "smtpPass" character varying(128), "enableServiceWorker" boolean NOT NULL DEFAULT false, "swPublicKey" character varying(128), "swPrivateKey" character varying(128), "enableTwitterIntegration" boolean NOT NULL DEFAULT false, "twitterConsumerKey" character varying(128), "twitterConsumerSecret" character varying(128), "enableGithubIntegration" boolean NOT NULL DEFAULT false, "githubClientId" character varying(128), "githubClientSecret" character varying(128), "enableDiscordIntegration" boolean NOT NULL DEFAULT false, "discordClientId" character varying(128), "discordClientSecret" character varying(128), CONSTRAINT "PK_c4c17a6c2bd7651338b60fc590b" PRIMARY KEY ("id"))`,
+			`CREATE TABLE "meta" ("id" character varying(32) NOT NULL, "name" character varying(128), "description" character varying(1024), "maintainerName" character varying(128), "maintainerEmail" character varying(128), "announcements" jsonb NOT NULL DEFAULT '[]', "disableRegistration" boolean NOT NULL DEFAULT false, "disableLocalTimeline" boolean NOT NULL DEFAULT false, "disableGlobalTimeline" boolean NOT NULL DEFAULT false, "enableEmojiReaction" boolean NOT NULL DEFAULT true, "useStarForReactionFallback" boolean NOT NULL DEFAULT false, "langs" character varying(64) array NOT NULL DEFAULT '{}'::varchar[], "hiddenTags" character varying(256) array NOT NULL DEFAULT '{}'::varchar[], "blockedHosts" character varying(256) array NOT NULL DEFAULT '{}'::varchar[], "mascotImageUrl" character varying(512) DEFAULT '/static-assets/badges/info.png', "bannerUrl" character varying(512), "errorImageUrl" character varying(512) DEFAULT '/static-assets/badges/error.png', "iconUrl" character varying(512), "cacheRemoteFiles" boolean NOT NULL DEFAULT true, "proxyAccount" character varying(128), "enableRecaptcha" boolean NOT NULL DEFAULT false, "recaptchaSiteKey" character varying(64), "recaptchaSecretKey" character varying(64), "localDriveCapacityMb" integer NOT NULL DEFAULT 1024, "remoteDriveCapacityMb" integer NOT NULL DEFAULT 32, "maxNoteTextLength" integer NOT NULL DEFAULT 500, "summalyProxy" character varying(128), "enableEmail" boolean NOT NULL DEFAULT false, "email" character varying(128), "smtpSecure" boolean NOT NULL DEFAULT false, "smtpHost" character varying(128), "smtpPort" integer, "smtpUser" character varying(128), "smtpPass" character varying(128), "enableServiceWorker" boolean NOT NULL DEFAULT false, "swPublicKey" character varying(128), "swPrivateKey" character varying(128), "enableTwitterIntegration" boolean NOT NULL DEFAULT false, "twitterConsumerKey" character varying(128), "twitterConsumerSecret" character varying(128), "enableGithubIntegration" boolean NOT NULL DEFAULT false, "githubClientId" character varying(128), "githubClientSecret" character varying(128), "enableDiscordIntegration" boolean NOT NULL DEFAULT false, "discordClientId" character varying(128), "discordClientSecret" character varying(128), CONSTRAINT "PK_c4c17a6c2bd7651338b60fc590b" PRIMARY KEY ("id"))`,
 		);
 		await queryRunner.query(
 			`CREATE TABLE "following" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "followeeId" character varying(32) NOT NULL, "followerId" character varying(32) NOT NULL, "followerHost" character varying(128), "followerInbox" character varying(512), "followerSharedInbox" character varying(512), "followeeHost" character varying(128), "followeeInbox" character varying(512), "followeeSharedInbox" character varying(512), CONSTRAINT "PK_c76c6e044bdf76ecf8bfb82a645" PRIMARY KEY ("id"))`,
diff --git a/packages/backend/src/models/entities/meta.ts b/packages/backend/src/models/entities/meta.ts
index 433ad0db5..b22c6510f 100644
--- a/packages/backend/src/models/entities/meta.ts
+++ b/packages/backend/src/models/entities/meta.ts
@@ -162,7 +162,7 @@ export class Meta {
 	@Column("varchar", {
 		length: 512,
 		nullable: true,
-		default: "/assets/ai.png",
+		default: "/static-assets/badges/info.png",
 	})
 	public mascotImageUrl: string | null;
 
@@ -187,7 +187,7 @@ export class Meta {
 	@Column("varchar", {
 		length: 512,
 		nullable: true,
-		default: "https://xn--931a.moe/aiart/yubitun.png",
+		default: "/static-assets/badges/error.png",
 	})
 	public errorImageUrl: string | null;
 
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index 24c895cbb..ad7031063 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -64,7 +64,7 @@ export const meta = {
 				type: "string",
 				optional: false,
 				nullable: false,
-				default: "/assets/ai.png",
+				default: "/static-assets/badges/info.png",
 			},
 			bannerUrl: {
 				type: "string",
@@ -75,7 +75,7 @@ export const meta = {
 				type: "string",
 				optional: false,
 				nullable: false,
-				default: "https://xn--931a.moe/aiart/yubitun.png",
+				default: "/static-assets/badges/error.png",
 			},
 			iconUrl: {
 				type: "string",
diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index 23cfe4018..c603cb9ac 100644
--- a/packages/backend/src/server/api/endpoints/meta.ts
+++ b/packages/backend/src/server/api/endpoints/meta.ts
@@ -155,7 +155,7 @@ export const meta = {
 				type: "string",
 				optional: false,
 				nullable: false,
-				default: "/assets/ai.png",
+				default: "/static-assets/badges/info.png",
 			},
 			bannerUrl: {
 				type: "string",
@@ -166,7 +166,7 @@ export const meta = {
 				type: "string",
 				optional: false,
 				nullable: false,
-				default: "https://xn--931a.moe/aiart/yubitun.png",
+				default: "/static-assets/badges/error.png",
 			},
 			iconUrl: {
 				type: "string",

From 141fbc7b67c838956ced2babc25063c9bf65bbd5 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Fri, 2 Jun 2023 23:38:05 -0700
Subject: [PATCH 195/198] regress: no metrics in dashboard for now

---
 packages/client/src/pages/admin/overview.vue | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/packages/client/src/pages/admin/overview.vue b/packages/client/src/pages/admin/overview.vue
index 67da86746..d88022595 100644
--- a/packages/client/src/pages/admin/overview.vue
+++ b/packages/client/src/pages/admin/overview.vue
@@ -51,10 +51,10 @@
 				<XQueue domain="inbox" />
 			</MkFolder>
 
-			<MkFolder class="item">
+			<!-- <MkFolder class="item">
 				<template #header>Server metrics</template>
 				<XMetrics domain="inbox" />
-			</MkFolder>
+			</MkFolder> -->
 		</div>
 	</MkSpacer>
 </template>
@@ -76,7 +76,7 @@ import XActiveUsers from "./overview.active-users.vue";
 import XStats from "./overview.stats.vue";
 import XModerators from "./overview.moderators.vue";
 import XHeatmap from "./overview.heatmap.vue";
-import XMetrics from "./overview.metrics.vue";
+// import XMetrics from "./overview.metrics.vue";
 import MkTagCloud from "@/components/MkTagCloud.vue";
 import { version, url } from "@/config";
 import * as os from "@/os";

From 04d4d21f74063e0ef5221f2136689fde4c3d6ebc Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Fri, 2 Jun 2023 23:55:31 -0700
Subject: [PATCH 196/198] fix: summary if 1 attachment

---
 packages/backend/src/misc/get-note-summary.ts   | 2 +-
 packages/client/src/scripts/get-note-summary.ts | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/backend/src/misc/get-note-summary.ts b/packages/backend/src/misc/get-note-summary.ts
index 63521c1c6..0a662e434 100644
--- a/packages/backend/src/misc/get-note-summary.ts
+++ b/packages/backend/src/misc/get-note-summary.ts
@@ -21,7 +21,7 @@ export const getNoteSummary = (note: Packed<"Note">): string => {
 	// ファイルが添付されているとき
 	if ((note.files || []).length !== 0) {
 		const len = note.files?.length;
-		summary += ` 📎 (${len !== 1 ? len : ""})`;
+		summary += ` 📎${len !== 1 ? ` (${len})` : ""}`;
 	}
 
 	// 投票が添付されているとき
diff --git a/packages/client/src/scripts/get-note-summary.ts b/packages/client/src/scripts/get-note-summary.ts
index 4a1530162..bbbe4afb8 100644
--- a/packages/client/src/scripts/get-note-summary.ts
+++ b/packages/client/src/scripts/get-note-summary.ts
@@ -24,7 +24,7 @@ export const getNoteSummary = (note: misskey.entities.Note): string => {
 	// ファイルが添付されているとき
 	if ((note.files || []).length !== 0) {
 		const len = note.files?.length;
-		summary += ` 📎 (${len !== 1 ? len : ""})`;
+		summary += ` 📎${len !== 1 ? ` (${len})` : ""}`;
 	}
 
 	// 投票が添付されているとき

From d0ff53ad70114c2e839f2d2a2bc3aaeac91724f1 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Sat, 3 Jun 2023 00:41:30 -0700
Subject: [PATCH 197/198] chore: :arrow_up: up various deps

---
 package.json                               |  4 +-
 packages/backend/native-utils/Cargo.toml   |  2 +-
 packages/backend/native-utils/package.json |  2 +-
 packages/backend/package.json              |  2 +-
 packages/client/.vscode/settings.json      | 11 ++-
 packages/client/package.json               | 10 +--
 pnpm-lock.yaml                             | 84 ++++++++++++----------
 7 files changed, 67 insertions(+), 48 deletions(-)

diff --git a/package.json b/package.json
index 703fef632..6b6531fc6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "calckey",
-	"version": "14.0.0-dev39",
+	"version": "14.0.0-dev40",
 	"codename": "aqua",
 	"repository": {
 		"type": "git",
@@ -38,7 +38,7 @@
 	"dependencies": {
 		"@bull-board/api": "5.2.0",
 		"@bull-board/ui": "5.2.0",
-		"@napi-rs/cli": "^2.15.0",
+		"@napi-rs/cli": "^2.16.1",
 		"@tensorflow/tfjs": "^3.21.0",
 		"focus-trap": "^7.2.0",
 		"focus-trap-vue": "^4.0.1",
diff --git a/packages/backend/native-utils/Cargo.toml b/packages/backend/native-utils/Cargo.toml
index 9b030e37d..f93180fe4 100644
--- a/packages/backend/native-utils/Cargo.toml
+++ b/packages/backend/native-utils/Cargo.toml
@@ -33,7 +33,7 @@ tokio = { version = "1.28.1", features = ["full"] }
 utoipa = "3.3.0"
 
 # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
-napi = { version = "2.12.0", default-features = false, features = ["napi6", "tokio_rt"], optional = true }
+napi = { version = "2.13.1", default-features = false, features = ["napi6", "tokio_rt"], optional = true }
 napi-derive = { version = "2.12.0", optional = true }
 radix_fmt = { version = "1.0.0", optional = true }
 
diff --git a/packages/backend/native-utils/package.json b/packages/backend/native-utils/package.json
index 64f9e0e50..2e6a721f4 100644
--- a/packages/backend/native-utils/package.json
+++ b/packages/backend/native-utils/package.json
@@ -23,7 +23,7 @@
   },
   "license": "MIT",
   "devDependencies": {
-    "@napi-rs/cli": "2.15.0",
+    "@napi-rs/cli": "2.16.1",
     "ava": "5.1.1"
   },
   "ava": {
diff --git a/packages/backend/package.json b/packages/backend/package.json
index ca916576b..47e9c415c 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -63,7 +63,7 @@
 		"cli-highlight": "2.1.11",
 		"color-convert": "2.0.1",
 		"content-disposition": "0.5.4",
-		"date-fns": "2.29.3",
+		"date-fns": "2.30.0",
 		"deep-email-validator": "0.1.21",
 		"escape-regexp": "0.0.1",
 		"feed": "4.2.2",
diff --git a/packages/client/.vscode/settings.json b/packages/client/.vscode/settings.json
index 1950a66b9..d654bb166 100644
--- a/packages/client/.vscode/settings.json
+++ b/packages/client/.vscode/settings.json
@@ -1,6 +1,15 @@
 {
-	"typescript.tsdk": "node_modules\\typescript\\lib",
+	"typescript.tsdk": "node_modules/typescript/lib",
 	"path-intellisense.mappings": {
 		"@": "${workspaceRoot}/packages/client/src/"
 	},
+	"files.exclude": {
+		"**/.git": true,
+		"**/.svn": true,
+		"**/.hg": true,
+		"**/CVS": true,
+		"**/.DS_Store": true,
+		"**/Thumbs.db": true,
+		"**/_client_dist_": true
+	}
 }
diff --git a/packages/client/package.json b/packages/client/package.json
index ccddda6b9..83ea429c4 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -33,17 +33,17 @@
 		"broadcast-channel": "4.19.1",
 		"browser-image-resizer": "github:misskey-dev/browser-image-resizer",
 		"calckey-js": "workspace:*",
-		"chart.js": "4.1.1",
-		"chartjs-adapter-date-fns": "2.0.1",
+		"chart.js": "4.3.0",
+		"chartjs-adapter-date-fns": "3.0.0",
 		"chartjs-chart-matrix": "^2.0.1",
-		"chartjs-plugin-gradient": "0.5.1",
-		"chartjs-plugin-zoom": "1.2.1",
+		"chartjs-plugin-gradient": "0.6.1",
+		"chartjs-plugin-zoom": "2.0.1",
 		"city-timezones": "^1.2.1",
 		"compare-versions": "5.0.3",
 		"cropperjs": "2.0.0-beta.2",
 		"cross-env": "7.0.3",
 		"cypress": "10.11.0",
-		"date-fns": "2.29.3",
+		"date-fns": "2.30.0",
 		"escape-regexp": "0.0.1",
 		"eventemitter3": "4.0.7",
 		"gsap": "^3.11.4",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0d65da71e..f001fec77 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -18,8 +18,8 @@ importers:
         specifier: 5.2.0
         version: 5.2.0
       '@napi-rs/cli':
-        specifier: ^2.15.0
-        version: 2.15.0
+        specifier: ^2.16.1
+        version: 2.16.1
       '@tensorflow/tfjs':
         specifier: ^3.21.0
         version: 3.21.0(seedrandom@3.0.5)
@@ -190,8 +190,8 @@ importers:
         specifier: 0.5.4
         version: 0.5.4
       date-fns:
-        specifier: 2.29.3
-        version: 2.29.3
+        specifier: 2.30.0
+        version: 2.30.0
       deep-email-validator:
         specifier: 0.1.21
         version: 0.1.21
@@ -612,8 +612,8 @@ importers:
   packages/backend/native-utils:
     devDependencies:
       '@napi-rs/cli':
-        specifier: 2.15.0
-        version: 2.15.0
+        specifier: 2.16.1
+        version: 2.16.1
       ava:
         specifier: 5.1.1
         version: 5.1.1
@@ -754,20 +754,20 @@ importers:
         specifier: workspace:*
         version: link:../calckey-js
       chart.js:
-        specifier: 4.1.1
-        version: 4.1.1
+        specifier: 4.3.0
+        version: 4.3.0
       chartjs-adapter-date-fns:
-        specifier: 2.0.1
-        version: 2.0.1(chart.js@4.1.1)
+        specifier: 3.0.0
+        version: 3.0.0(chart.js@4.3.0)(date-fns@2.30.0)
       chartjs-chart-matrix:
         specifier: ^2.0.1
-        version: 2.0.1(chart.js@4.1.1)
+        version: 2.0.1(chart.js@4.3.0)
       chartjs-plugin-gradient:
-        specifier: 0.5.1
-        version: 0.5.1(chart.js@4.1.1)
+        specifier: 0.6.1
+        version: 0.6.1(chart.js@4.3.0)
       chartjs-plugin-zoom:
-        specifier: 1.2.1
-        version: 1.2.1(chart.js@4.1.1)
+        specifier: 2.0.1
+        version: 2.0.1(chart.js@4.3.0)
       city-timezones:
         specifier: ^1.2.1
         version: 1.2.1
@@ -784,8 +784,8 @@ importers:
         specifier: 10.11.0
         version: 10.11.0
       date-fns:
-        specifier: 2.29.3
-        version: 2.29.3
+        specifier: 2.30.0
+        version: 2.30.0
       escape-regexp:
         specifier: 0.0.1
         version: 0.0.1
@@ -1262,6 +1262,12 @@ packages:
       regenerator-runtime: 0.13.11
     dev: true
 
+  /@babel/runtime@7.22.3:
+    resolution: {integrity: sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      regenerator-runtime: 0.13.11
+
   /@babel/template@7.20.7:
     resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==}
     engines: {node: '>=6.9.0'}
@@ -2290,8 +2296,8 @@ packages:
     dev: false
     optional: true
 
-  /@napi-rs/cli@2.15.0:
-    resolution: {integrity: sha512-RDDr7ZF0cgbd37+NBGeQOjP7Tm/iNM+y3FmrT5bVQBXLePOTuKVC/dBsdN5UZv3Sl2XAwEvBfaGR90E0d8AA6g==}
+  /@napi-rs/cli@2.16.1:
+    resolution: {integrity: sha512-L0Gr5iEQIDEbvWdDr1HUaBOxBSHL1VZhWSk1oryawoT8qJIY+KGfLFelU+Qma64ivCPbxYpkfPoKYVG3rcoGIA==}
     engines: {node: '>= 10'}
     hasBin: true
 
@@ -5315,43 +5321,45 @@ packages:
     resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==}
     dev: true
 
-  /chart.js@4.1.1:
-    resolution: {integrity: sha512-P0pCosNXp+LR8zO/QTkZKT6Hb7p0DPFtypEeVOf+6x06hX13NIb75R0DXUA4Ksx/+48chDQKtCCmRCviQRTqsA==}
-    engines: {pnpm: ^7.0.0}
+  /chart.js@4.3.0:
+    resolution: {integrity: sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==}
+    engines: {pnpm: '>=7'}
     dependencies:
       '@kurkle/color': 0.3.2
     dev: true
 
-  /chartjs-adapter-date-fns@2.0.1(chart.js@4.1.1):
-    resolution: {integrity: sha512-v3WV9rdnQ05ce3A0ZCjzUekJCAbfm6+3HqSoeY2BIkdMYZoYr/4T+ril1tZyDl869lz6xdNVMXejUFT9YKpw4A==}
+  /chartjs-adapter-date-fns@3.0.0(chart.js@4.3.0)(date-fns@2.30.0):
+    resolution: {integrity: sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==}
     peerDependencies:
       chart.js: '>=2.8.0'
+      date-fns: '>=2.0.0'
     dependencies:
-      chart.js: 4.1.1
+      chart.js: 4.3.0
+      date-fns: 2.30.0
     dev: true
 
-  /chartjs-chart-matrix@2.0.1(chart.js@4.1.1):
+  /chartjs-chart-matrix@2.0.1(chart.js@4.3.0):
     resolution: {integrity: sha512-BGfeY+/PHnITyDlc7WfnKJ1RyOfgOzIqWp/gxzzl7pUjyoGzHDcw51qd2xJF9gdT9Def7ZwOnOMm8GJUXDxI0w==}
     peerDependencies:
       chart.js: '>=3.0.0'
     dependencies:
-      chart.js: 4.1.1
+      chart.js: 4.3.0
     dev: true
 
-  /chartjs-plugin-gradient@0.5.1(chart.js@4.1.1):
-    resolution: {integrity: sha512-vhwlYGZWan4MGZZ4Wj64Y4aIql1uCPCU1JcggLWn3cgYEv4G7pXp1YgM4XH5ugmyn6BVCgQqAhiJ2h6hppzHmQ==}
+  /chartjs-plugin-gradient@0.6.1(chart.js@4.3.0):
+    resolution: {integrity: sha512-TGHNIh8KqQMLdb+UfY80cBHYRyOC47eeokmgkeajRdKGbFt462lJiyiq4ZJ25fiM7BGsmzoBLhmVyEw4B3gQxw==}
     peerDependencies:
       chart.js: '>=2.6.0'
     dependencies:
-      chart.js: 4.1.1
+      chart.js: 4.3.0
     dev: true
 
-  /chartjs-plugin-zoom@1.2.1(chart.js@4.1.1):
-    resolution: {integrity: sha512-2zbWvw2pljrtMLMXkKw1uxYzAne5PtjJiOZftcut4Lo3Ee8qUt95RpMKDWrZ+pBZxZKQKOD/etdU4pN2jxZUmg==}
+  /chartjs-plugin-zoom@2.0.1(chart.js@4.3.0):
+    resolution: {integrity: sha512-ogOmLu6e+Q7E1XWOCOz9YwybMslz9qNfGV2a+qjfmqJYpsw5ZMoRHZBUyW+NGhkpQ5PwwPA/+rikHpBZb7PZuA==}
     peerDependencies:
-      chart.js: ^3.2.0
+      chart.js: '>=3.2.0'
     dependencies:
-      chart.js: 4.1.1
+      chart.js: 4.3.0
       hammerjs: 2.0.8
     dev: true
 
@@ -6332,9 +6340,11 @@ packages:
       whatwg-url: 11.0.0
     dev: false
 
-  /date-fns@2.29.3:
-    resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==}
+  /date-fns@2.30.0:
+    resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
     engines: {node: '>=0.11'}
+    dependencies:
+      '@babel/runtime': 7.22.3
 
   /date-time@3.1.0:
     resolution: {integrity: sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==}
@@ -14754,7 +14764,7 @@ packages:
       buffer: 6.0.3
       chalk: 4.1.2
       cli-highlight: 2.1.11
-      date-fns: 2.29.3
+      date-fns: 2.30.0
       debug: 4.3.4(supports-color@8.1.1)
       dotenv: 16.0.3
       glob: 7.2.3

From a80b6eeb82644a852f4168c12ee331da4c1ec79b Mon Sep 17 00:00:00 2001
From: Kainoa Kanter <kainoa@t1c.dev>
Date: Sat, 3 Jun 2023 09:12:32 +0000
Subject: [PATCH 198/198] fix outdated docker deps

---
 Dockerfile | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Dockerfile b/Dockerfile
index c378444f7..0aa13aefe 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,6 +3,7 @@ FROM node:19-alpine as build
 WORKDIR /calckey
 
 # Install compilation dependencies
+RUN apk update
 RUN apk add --no-cache --no-progress git alpine-sdk python3 rust cargo vips
 
 # Copy only the dependency-related files first, to cache efficiently