diff --git a/.gitignore b/.gitignore
index 5a8e49a..89bb5eb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,7 +30,7 @@ coverage
/docker-compose.yml
# misskey
-built
+# built
elasticsearch
redis
meili_data
diff --git a/packages/backend/built/@types/hcaptcha.d.js b/packages/backend/built/@types/hcaptcha.d.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/@types/http-signature.d.js b/packages/backend/built/@types/http-signature.d.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/@types/koa-remove-trailing-slashes/index.d.js b/packages/backend/built/@types/koa-remove-trailing-slashes/index.d.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/@types/koa-slow.d.js b/packages/backend/built/@types/koa-slow.d.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/@types/os-utils.d.js b/packages/backend/built/@types/os-utils.d.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/@types/package.json.d.js b/packages/backend/built/@types/package.json.d.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/boot/index.js b/packages/backend/built/boot/index.js
new file mode 100644
index 0000000..6552107
--- /dev/null
+++ b/packages/backend/built/boot/index.js
@@ -0,0 +1,76 @@
+import cluster from "node:cluster";
+import chalk from "chalk";
+import Xev from "xev";
+import Logger from "../services/logger.js";
+import { envOption } from "../env.js";
+// for typeorm
+import "reflect-metadata";
+import { masterMain } from "./master.js";
+import { workerMain } from "./worker.js";
+import os from "node:os";
+import { isIgnorableConnectionError } from "../server/is-ignorable-connection-error.js";
+const logger = new Logger("core", "cyan");
+const clusterLogger = logger.createSubLogger("cluster", "orange", false);
+const ev = new Xev();
+/**
+ * Init process
+ */ export default async function() {
+ process.title = `FrozenFriendsYume (${cluster.isPrimary ? "master" : "worker"})`;
+ if (cluster.isPrimary || envOption.disableClustering) {
+ await masterMain();
+ if (cluster.isPrimary) {
+ ev.mount();
+ }
+ }
+ if (cluster.isWorker || envOption.disableClustering) {
+ await workerMain();
+ }
+ if (cluster.isPrimary) {
+ // Leave the master process with a marginally lower priority but not too low.
+ os.setPriority(2);
+ }
+ if (cluster.isWorker) {
+ // Set workers to a much lower priority so that the master process will be
+ // able to respond to api calls even if the workers gank everything.
+ os.setPriority(10);
+ }
+ // For when FrozenFriendsYume is started in a child process during unit testing.
+ // Otherwise, process.send cannot be used, so start it.
+ if (process.send) {
+ process.send("ok");
+ }
+}
+//#region Events
+// Listen new workers
+cluster.on("fork", (worker)=>{
+ clusterLogger.debug(`Process forked: [${worker.id}]`);
+});
+// Listen online workers
+cluster.on("online", (worker)=>{
+ clusterLogger.debug(`Process is now online: [${worker.id}]`);
+});
+// Listen for dying workers
+cluster.on("exit", (worker)=>{
+ // Replace the dead worker,
+ // we're not sentimental
+ clusterLogger.error(chalk.red(`[${worker.id}] died :(`));
+ cluster.fork();
+});
+// Display detail of unhandled promise rejection
+if (!envOption.quiet) {
+ process.on("unhandledRejection", (err)=>{
+ if (isIgnorableConnectionError(err)) return;
+ console.dir(err);
+ });
+}
+// Display detail of uncaught exception
+process.on("uncaughtException", (err)=>{
+ if (isIgnorableConnectionError(err)) return;
+ try {
+ logger.error(err);
+ } catch {}
+});
+// Dying away...
+process.on("exit", (code)=>{
+ logger.info(`The process is going to exit with code ${code}`);
+}); //#endregion
diff --git a/packages/backend/built/boot/master.js b/packages/backend/built/boot/master.js
new file mode 100644
index 0000000..88c2637
--- /dev/null
+++ b/packages/backend/built/boot/master.js
@@ -0,0 +1,152 @@
+import * as fs from "node:fs";
+import { fileURLToPath } from "node:url";
+import { dirname } from "node:path";
+import * as os from "node:os";
+import cluster from "node:cluster";
+import net from "node:net";
+import repl from "node:repl";
+import chalk from "chalk";
+import chalkTemplate from "chalk-template";
+import semver from "semver";
+import Logger from "../services/logger.js";
+import loadConfig from "../config/load.js";
+import { envOption } from "../env.js";
+import { showMachineInfo } from "../misc/show-machine-info.js";
+import { db, initDb } from "../db/postgre.js";
+const _filename = fileURLToPath(import.meta.url);
+const _dirname = dirname(_filename);
+const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, "utf-8"));
+const logger = new Logger("core", "cyan");
+const bootLogger = logger.createSubLogger("boot", "magenta", false);
+const themeColor = chalk.hex("#31748f");
+function greet() {
+ if (!envOption.quiet) {
+ console.log(chalkTemplate`--- ${os.hostname()} {gray (PID: ${process.pid.toString()})} ---`);
+ }
+ const displayVersion = meta.version.startsWith("v") ? meta.version : `v${meta.version}`;
+ bootLogger.info(`FrozenFriendsYume ${displayVersion}, initializing...`, null, true);
+}
+/**
+ * Init master process
+ */ export async function masterMain() {
+ let config;
+ // initialize app
+ try {
+ greet();
+ showEnvironment();
+ await showMachineInfo(bootLogger);
+ showNodejsVersion();
+ config = loadConfigBoot();
+ await connectDb();
+ if (config.shellAddress !== undefined) spawnShell(config.shellAddress);
+ } catch (e1) {
+ bootLogger.error("Fatal error occurred during initialization", null, true);
+ bootLogger.error(e1);
+ process.exit(1);
+ }
+ bootLogger.succ("FrozenFriendsYume initialized");
+ if (!envOption.disableClustering) {
+ await spawnWorkers(config.clusterLimit);
+ }
+ bootLogger.succ(`Now listening on port ${config.port} on ${config.url} - using ${config.domain}`, null, true);
+ if (!envOption.noDaemons && !config.onlyQueueProcessor) {
+ import("../daemons/server-stats.js").then((x)=>x.default());
+ import("../daemons/queue-stats.js").then((x)=>x.default());
+ import("../daemons/janitor.js").then((x)=>x.default());
+ }
+}
+function showEnvironment() {
+ const env = process.env.NODE_ENV;
+ const logger = bootLogger.createSubLogger("env");
+ logger.info(typeof env === "undefined" ? "NODE_ENV is not set" : `NODE_ENV: ${env}`);
+ if (env !== "production") {
+ logger.warn("The environment is not in production mode.");
+ logger.warn("DO NOT USE FOR PRODUCTION PURPOSE!", null, true);
+ }
+}
+function showNodejsVersion() {
+ const nodejsLogger = bootLogger.createSubLogger("nodejs");
+ nodejsLogger.info(`Version ${process.version} detected.`);
+ const minVersion = fs.readFileSync(`${_dirname}/../../../../.node-version`, "utf-8").trim();
+ if (semver.lt(process.version, minVersion)) {
+ nodejsLogger.error(`At least Node.js ${minVersion} required!`);
+ process.exit(1);
+ }
+}
+function loadConfigBoot() {
+ const configLogger = bootLogger.createSubLogger("config");
+ let config;
+ try {
+ config = loadConfig();
+ } catch (exception) {
+ if (exception.code === "ENOENT") {
+ configLogger.error("Configuration file not found", null, true);
+ process.exit(1);
+ } else if (e instanceof Error) {
+ configLogger.error(e.message);
+ process.exit(1);
+ }
+ throw exception;
+ }
+ configLogger.succ("Loaded");
+ return config;
+}
+async function connectDb() {
+ const dbLogger = bootLogger.createSubLogger("db");
+ // Try to connect to DB
+ try {
+ dbLogger.info("Connecting...");
+ await initDb();
+ const v = await db.query("SHOW server_version").then((x)=>x[0].server_version);
+ dbLogger.succ(`Connected: v${v}`);
+ } catch (e1) {
+ dbLogger.error("Cannot connect", null, true);
+ dbLogger.error(e1);
+ process.exit(1);
+ }
+}
+async function spawnWorkers(limit = 1) {
+ const workers = Math.min(limit, os.cpus().length);
+ bootLogger.info(`Starting ${workers} worker${workers === 1 ? "" : "s"}...`);
+ await Promise.all([
+ ...Array(workers)
+ ].map(spawnWorker));
+ bootLogger.succ("All workers started");
+}
+function spawnWorker() {
+ return new Promise((res)=>{
+ const worker = cluster.fork();
+ worker.on("message", (message)=>{
+ if (message === "listenFailed") {
+ bootLogger.error("The server Listen failed due to the previous error.");
+ process.exit(1);
+ }
+ if (message !== "ready") return;
+ res();
+ });
+ });
+}
+function spawnShell(address) {
+ logger.info(`Spawning debug shell on ${address}`);
+ if (typeof address == "string") {
+ try {
+ fs.unlinkSync(address);
+ } catch {}
+ }
+ net.createServer().unref().listen(address).on("connection", (socket)=>{
+ const r = repl.start({
+ input: socket,
+ output: socket
+ });
+ socket.on("close", ()=>r.close());
+ socket.on("error", ()=>r.close());
+ r.on("close", ()=>socket.destroy());
+ if (!envOption.disableClustering) {
+ r.defineCommand("worker", (iid)=>{
+ r.close();
+ const id = parseInt(iid.trim());
+ cluster.workers[id].send("debug-shell", socket);
+ });
+ }
+ }).on("error", (error)=>logger.error(error));
+}
diff --git a/packages/backend/built/boot/worker.js b/packages/backend/built/boot/worker.js
new file mode 100644
index 0000000..31d60dc
--- /dev/null
+++ b/packages/backend/built/boot/worker.js
@@ -0,0 +1,33 @@
+import repl from "node:repl";
+import cluster from "node:cluster";
+import { initDb } from "../db/postgre.js";
+import config from "../config/index.js";
+/**
+ * Init worker process
+ */ export async function workerMain() {
+ await initDb();
+ if (!config.onlyQueueProcessor) {
+ // start server
+ await import("../server/index.js").then((x)=>x.default());
+ }
+ // start job queue
+ import("../queue/index.js").then((x)=>x.default());
+ if (cluster.isWorker) {
+ // Send a 'ready' message to parent process
+ process.send("ready");
+ cluster.worker.on("message", (message, handle)=>{
+ if (message == "debug-shell") spawnWorkerShell(handle);
+ });
+ }
+}
+function spawnWorkerShell(socket) {
+ const r = repl.start({
+ input: socket,
+ output: socket
+ });
+ r.defineCommand("worker", (iid)=>{
+ r.close();
+ const id = parseInt(iid.trim());
+ cluster.workers[id].send("debug-shell", socket);
+ });
+}
diff --git a/packages/backend/built/config/index.js b/packages/backend/built/config/index.js
new file mode 100644
index 0000000..5235550
--- /dev/null
+++ b/packages/backend/built/config/index.js
@@ -0,0 +1,2 @@
+import load from "./load.js";
+export default load();
diff --git a/packages/backend/built/config/load.js b/packages/backend/built/config/load.js
new file mode 100644
index 0000000..37097eb
--- /dev/null
+++ b/packages/backend/built/config/load.js
@@ -0,0 +1,75 @@
+/**
+ * Config loader
+ */ import * as fs from "node:fs";
+import { fileURLToPath } from "node:url";
+import { dirname } from "node:path";
+import * as yaml from "js-yaml";
+import path from "node:path";
+import parseDuration from "parse-duration";
+export default function load() {
+ const _filename = fileURLToPath(import.meta.url);
+ const _dirname = dirname(_filename);
+ const dir = `${_dirname}/../../../..`;
+ const { ICESHRIMP_CONFIG: configFile, ICESHRIMP_SECRETS: secretsFile, ICESHRIMP_MEDIA_DIR: mediaDir } = process.env;
+ const configPath = process.env.NODE_ENV === "test" ? `${dir}/.config/test.yml` : configFile ?? `${dir}/.config/default.yml`;
+ const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, "utf-8"));
+ const clientManifest = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/_client_dist_/manifest.json`, "utf-8"));
+ let config = yaml.load(fs.readFileSync(configPath, "utf-8"));
+ if (secretsFile !== undefined) config = Object.assign(config, yaml.load(fs.readFileSync(secretsFile, "utf-8")));
+ const mixin = {};
+ const url = tryCreateUrl(config.url);
+ config.url = url.origin;
+ config.port = config.port || parseInt(process.env.PORT || "", 10);
+ config.listen = process.env.ICESHRIMP_LISTEN ?? config.listen ?? "0.0.0.0";
+ if (config.tls != null) {
+ config.tls = {
+ keyPath: typeof config.tls.keyPath === "string" ? path.resolve(dir, config.tls.keyPath) : undefined,
+ certPath: typeof config.tls.certPath === "string" ? path.resolve(dir, config.tls.certPath) : undefined
+ };
+ }
+ config.images = {
+ info: '/twemoji/1f440.svg',
+ notFound: '/twemoji/2049.svg',
+ error: '/twemoji/1f480.svg',
+ ...config.images
+ };
+ config.htmlCache = {
+ ttlSeconds: parseDuration(config.htmlCache?.ttl ?? '1h', 's'),
+ prewarm: false,
+ dbFallback: false,
+ ...config.htmlCache
+ };
+ if (config.htmlCache.ttlSeconds == null) throw new Error('Failed to parse config.htmlCache.ttl');
+ config.wordMuteCache = {
+ ttlSeconds: parseDuration(config.wordMuteCache?.ttl ?? '24h', 's')
+ };
+ if (config.wordMuteCache.ttlSeconds == null) throw new Error('Failed to parse config.wordMuteCache.ttl');
+ config.searchEngine = config.searchEngine ?? 'https://duckduckgo.com/?q=';
+ config.metrics = config.metrics ?? {};
+ config.metrics.enable = config.metrics?.enable ?? false;
+ if (typeof config.shellAddress === "string") {
+ config.shellAddress = path.isAbsolute(config.shellAddress) ? config.shellAddress : path.resolve(`../../${config.shellAddress}`);
+ }
+ mixin.version = meta.version;
+ mixin.host = url.host;
+ mixin.hostname = url.hostname;
+ mixin.domain = config.accountDomain ?? url.host;
+ mixin.scheme = url.protocol.replace(/:$/, "");
+ mixin.wsScheme = mixin.scheme.replace("http", "ws");
+ mixin.wsUrl = `${mixin.wsScheme}://${mixin.host}`;
+ mixin.apiUrl = `${mixin.scheme}://${mixin.host}/api`;
+ mixin.authUrl = `${mixin.scheme}://${mixin.host}/auth`;
+ mixin.driveUrl = `${mixin.scheme}://${mixin.host}/files`;
+ mixin.userAgent = `FrozenFriendsYume/${meta.version} (${config.url})`;
+ mixin.clientEntry = clientManifest["src/init.ts"];
+ mixin.mediaDir = mediaDir ?? `${dir}/files`;
+ if (!config.redis.prefix) config.redis.prefix = mixin.hostname;
+ return Object.assign(config, mixin);
+}
+function tryCreateUrl(url) {
+ try {
+ return new URL(url);
+ } catch (e) {
+ throw new Error(`url="${url}" is not a valid URL.`);
+ }
+}
diff --git a/packages/backend/built/config/types.js b/packages/backend/built/config/types.js
new file mode 100644
index 0000000..b1374c4
--- /dev/null
+++ b/packages/backend/built/config/types.js
@@ -0,0 +1,3 @@
+/**
+ * ユーザーが設定する必要のある情報
+ */ export { };
diff --git a/packages/backend/built/const.js b/packages/backend/built/const.js
new file mode 100644
index 0000000..f66ede5
--- /dev/null
+++ b/packages/backend/built/const.js
@@ -0,0 +1,59 @@
+import config from "./config/index.js";
+import { DB_MAX_NOTE_TEXT_LENGTH, DB_MAX_IMAGE_COMMENT_LENGTH } from "./misc/hard-limits.js";
+export const MAX_NOTE_TEXT_LENGTH = Math.min(config.maxNoteLength ?? 3000, DB_MAX_NOTE_TEXT_LENGTH);
+export const MAX_CAPTION_TEXT_LENGTH = Math.min(config.maxCaptionLength ?? 1500, DB_MAX_IMAGE_COMMENT_LENGTH);
+export const SECOND = 1000;
+export const SEC = 1000; // why do we need this duplicate here?
+export const MINUTE = 60 * SEC;
+export const MIN = 60 * SEC; // why do we need this duplicate here?
+export const HOUR = 60 * MIN;
+export const DAY = 24 * HOUR;
+export const USER_ONLINE_THRESHOLD = 10 * MINUTE;
+export const USER_ACTIVE_THRESHOLD = 3 * DAY;
+// List of file types allowed to be viewed directly in the browser
+// Anything not included here will be responded as application/octet-stream
+// SVG is not allowed because it generates XSS <- we need to fix this and later allow it to be viewed directly
+export const FILE_TYPE_BROWSERSAFE = [
+ // Images
+ "image/png",
+ "image/gif",
+ "image/jpeg",
+ "image/webp",
+ "image/apng",
+ "image/bmp",
+ "image/tiff",
+ "image/x-icon",
+ "image/avif",
+ // OggS
+ "audio/opus",
+ "video/ogg",
+ "audio/ogg",
+ "application/ogg",
+ // ISO/IEC base media file format
+ "video/quicktime",
+ "video/mp4",
+ "video/vnd.avi",
+ "audio/mp4",
+ "video/x-m4v",
+ "audio/x-m4a",
+ "video/3gpp",
+ "video/3gpp2",
+ "video/3gp2",
+ "audio/3gpp",
+ "audio/3gpp2",
+ "audio/3gp2",
+ "video/mpeg",
+ "audio/mpeg",
+ "video/webm",
+ "audio/webm",
+ "audio/aac",
+ "audio/x-flac",
+ "audio/flac",
+ "audio/vnd.wave",
+ // Documents
+ "application/epub+zip"
+]; /*
+https://github.com/sindresorhus/file-type/blob/main/supported.js
+https://github.com/sindresorhus/file-type/blob/main/core.js
+https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers
+*/
diff --git a/packages/backend/built/daemons/janitor.js b/packages/backend/built/daemons/janitor.js
new file mode 100644
index 0000000..6de733c
--- /dev/null
+++ b/packages/backend/built/daemons/janitor.js
@@ -0,0 +1,15 @@
+// TODO: 消したい
+const interval = 30 * 60 * 1000;
+import { AttestationChallenges } from "../models/index.js";
+import { LessThan } from "typeorm";
+/**
+ * Clean up database occasionally
+ */ export default function() {
+ async function tick() {
+ await AttestationChallenges.delete({
+ createdAt: LessThan(new Date(new Date().getTime() - 5 * 60 * 1000))
+ });
+ }
+ tick();
+ setInterval(tick, interval);
+}
diff --git a/packages/backend/built/daemons/queue-stats.js b/packages/backend/built/daemons/queue-stats.js
new file mode 100644
index 0000000..8d9f6f2
--- /dev/null
+++ b/packages/backend/built/daemons/queue-stats.js
@@ -0,0 +1,45 @@
+import Xev from "xev";
+import { deliverQueue, inboxQueue } from "../queue/queues.js";
+const ev = new Xev();
+const interval = 10000;
+/**
+ * Report queue stats regularly
+ */ export default function() {
+ const log = [];
+ ev.on("requestQueueStatsLog", (x)=>{
+ ev.emit(`queueStatsLog:${x.id}`, log.slice(0, x.length || 50));
+ });
+ let activeDeliverJobs = 0;
+ let activeInboxJobs = 0;
+ deliverQueue.on("global:active", ()=>{
+ activeDeliverJobs++;
+ });
+ inboxQueue.on("global:active", ()=>{
+ activeInboxJobs++;
+ });
+ async function tick() {
+ const deliverJobCounts = await deliverQueue.getJobCounts();
+ const inboxJobCounts = await inboxQueue.getJobCounts();
+ const stats = {
+ deliver: {
+ activeSincePrevTick: activeDeliverJobs,
+ active: deliverJobCounts.active,
+ waiting: deliverJobCounts.waiting,
+ delayed: deliverJobCounts.delayed
+ },
+ inbox: {
+ activeSincePrevTick: activeInboxJobs,
+ active: inboxJobCounts.active,
+ waiting: inboxJobCounts.waiting,
+ delayed: inboxJobCounts.delayed
+ }
+ };
+ ev.emit("queueStats", stats);
+ log.unshift(stats);
+ if (log.length > 200) log.pop();
+ activeDeliverJobs = 0;
+ activeInboxJobs = 0;
+ }
+ tick();
+ setInterval(tick, interval);
+}
diff --git a/packages/backend/built/daemons/server-stats.js b/packages/backend/built/daemons/server-stats.js
new file mode 100644
index 0000000..01664fc
--- /dev/null
+++ b/packages/backend/built/daemons/server-stats.js
@@ -0,0 +1,76 @@
+import si from "systeminformation";
+import Xev from "xev";
+import * as osUtils from "os-utils";
+import { fetchMeta } from "../misc/fetch-meta.js";
+const ev = new Xev();
+const interval = 2000;
+const roundCpu = (num)=>Math.round(num * 1000) / 1000;
+const round = (num)=>Math.round(num * 10) / 10;
+/**
+ * Report server stats regularly
+ */ export default function() {
+ const log = [];
+ ev.on("requestServerStatsLog", (x)=>{
+ ev.emit(`serverStatsLog:${x.id}`, log.slice(0, x.length || 50));
+ });
+ fetchMeta().then((meta)=>{
+ if (!meta.enableServerMachineStats) return;
+ });
+ async function tick() {
+ const cpu = await cpuUsage();
+ const memStats = await mem();
+ const netStats = await net();
+ const fsStats = await fs();
+ const stats = {
+ cpu: roundCpu(cpu),
+ mem: {
+ used: round(memStats.used - memStats.buffers - memStats.cached),
+ active: round(memStats.active),
+ total: round(memStats.total)
+ },
+ net: {
+ rx: round(Math.max(0, netStats.rx_sec)),
+ tx: round(Math.max(0, netStats.tx_sec))
+ },
+ fs: {
+ r: round(Math.max(0, fsStats.rIO_sec ?? 0)),
+ w: round(Math.max(0, fsStats.wIO_sec ?? 0))
+ }
+ };
+ ev.emit("serverStats", stats);
+ log.unshift(stats);
+ if (log.length > 200) log.pop();
+ }
+ tick();
+ setInterval(tick, interval);
+}
+// CPU STAT
+function cpuUsage() {
+ return new Promise((res, rej)=>{
+ osUtils.cpuUsage((cpuUsage)=>{
+ res(cpuUsage);
+ });
+ });
+}
+// MEMORY STAT
+async function mem() {
+ const data = await si.mem();
+ return data;
+}
+// NETWORK STAT
+async function net() {
+ const iface = await si.networkInterfaceDefault();
+ const data = await si.networkStats(iface);
+ return data[0];
+}
+// FS STAT
+async function fs() {
+ const data = await si.disksIO().catch(()=>({
+ rIO_sec: 0,
+ wIO_sec: 0
+ }));
+ return data || {
+ rIO_sec: 0,
+ wIO_sec: 0
+ };
+}
diff --git a/packages/backend/built/db/logger.js b/packages/backend/built/db/logger.js
new file mode 100644
index 0000000..6eaae79
--- /dev/null
+++ b/packages/backend/built/db/logger.js
@@ -0,0 +1,2 @@
+import Logger from "../services/logger.js";
+export const dbLogger = new Logger("db");
diff --git a/packages/backend/built/db/postgre.js b/packages/backend/built/db/postgre.js
new file mode 100644
index 0000000..340c75a
--- /dev/null
+++ b/packages/backend/built/db/postgre.js
@@ -0,0 +1,284 @@
+// https://github.com/typeorm/typeorm/issues/2400
+import pg from "pg";
+pg.types.setTypeParser(20, Number);
+import { DataSource } from "typeorm";
+import * as highlight from "cli-highlight";
+import config from "../config/index.js";
+import { User } from "../models/entities/user.js";
+import { DriveFile } from "../models/entities/drive-file.js";
+import { DriveFolder } from "../models/entities/drive-folder.js";
+import { AccessToken } from "../models/entities/access-token.js";
+import { App } from "../models/entities/app.js";
+import { PollVote } from "../models/entities/poll-vote.js";
+import { Note } from "../models/entities/note.js";
+import { NoteReaction } from "../models/entities/note-reaction.js";
+import { NoteWatching } from "../models/entities/note-watching.js";
+import { NoteThreadMuting } from "../models/entities/note-thread-muting.js";
+import { NoteUnread } from "../models/entities/note-unread.js";
+import { Notification } from "../models/entities/notification.js";
+import { Meta } from "../models/entities/meta.js";
+import { Following } from "../models/entities/following.js";
+import { Instance } from "../models/entities/instance.js";
+import { Muting } from "../models/entities/muting.js";
+import { RenoteMuting } from "../models/entities/renote-muting.js";
+import { SwSubscription } from "../models/entities/sw-subscription.js";
+import { Blocking } from "../models/entities/blocking.js";
+import { CallBlocking } from "../models/entities/call-blocking.js";
+import { UserList } from "../models/entities/user-list.js";
+import { UserListJoining } from "../models/entities/user-list-joining.js";
+import { UserGroup } from "../models/entities/user-group.js";
+import { UserGroupJoining } from "../models/entities/user-group-joining.js";
+import { UserGroupInvitation } from "../models/entities/user-group-invitation.js";
+import { Hashtag } from "../models/entities/hashtag.js";
+import { NoteFavorite } from "../models/entities/note-favorite.js";
+import { AbuseUserReport } from "../models/entities/abuse-user-report.js";
+import { RegistrationTicket } from "../models/entities/registration-tickets.js";
+import { MessagingMessage } from "../models/entities/messaging-message.js";
+import { Signin } from "../models/entities/signin.js";
+import { AuthSession } from "../models/entities/auth-session.js";
+import { FollowRequest } from "../models/entities/follow-request.js";
+import { Emoji } from "../models/entities/emoji.js";
+import { UserNotePining } from "../models/entities/user-note-pining.js";
+import { Poll } from "../models/entities/poll.js";
+import { UserKeypair } from "../models/entities/user-keypair.js";
+import { UserPublickey } from "../models/entities/user-publickey.js";
+import { UserProfile } from "../models/entities/user-profile.js";
+import { UserSecurityKey } from "../models/entities/user-security-key.js";
+import { AttestationChallenge } from "../models/entities/attestation-challenge.js";
+import { Page } from "../models/entities/page.js";
+import { PageLike } from "../models/entities/page-like.js";
+import { GalleryPost } from "../models/entities/gallery-post.js";
+import { GalleryLike } from "../models/entities/gallery-like.js";
+import { ModerationLog } from "../models/entities/moderation-log.js";
+import { UsedUsername } from "../models/entities/used-username.js";
+import { Announcement } from "../models/entities/announcement.js";
+import { AnnouncementRead } from "../models/entities/announcement-read.js";
+import { Clip } from "../models/entities/clip.js";
+import { ClipNote } from "../models/entities/clip-note.js";
+import { Antenna } from "../models/entities/antenna.js";
+import { PromoNote } from "../models/entities/promo-note.js";
+import { PromoRead } from "../models/entities/promo-read.js";
+import { Relay } from "../models/entities/relay.js";
+import { Channel } from "../models/entities/channel.js";
+import { ChannelFollowing } from "../models/entities/channel-following.js";
+import { ChannelNotePining } from "../models/entities/channel-note-pining.js";
+import { RegistryItem } from "../models/entities/registry-item.js";
+import { PasswordResetRequest } from "../models/entities/password-reset-request.js";
+import { UserPending } from "../models/entities/user-pending.js";
+import { Webhook } from "../models/entities/webhook.js";
+import { UserIp } from "../models/entities/user-ip.js";
+import { UserEmoji } from "../models/entities/user-emoji.js";
+import { NoteEdit } from "../models/entities/note-edit.js";
+import { entities as charts } from "../services/chart/entities.js";
+import { dbLogger } from "./logger.js";
+import { OAuthApp } from "../models/entities/oauth-app.js";
+import { OAuthToken } from "../models/entities/oauth-token.js";
+import { HtmlNoteCacheEntry } from "../models/entities/html-note-cache-entry.js";
+import { HtmlUserCacheEntry } from "../models/entities/html-user-cache-entry.js";
+import { Bite } from "../models/entities/bite.js";
+import { InteractionStamp } from "../models/entities/interaction-stamp.js";
+import { ReversiGame } from "../models/entities/reversi-game.js";
+import { ReversiMatching } from "../models/entities/reversi-matching.js";
+import { ShogiGame } from "../models/entities/shogi-game.js";
+import { ScheduledNote } from "../models/entities/scheduled-note.js";
+import { Memoriet } from "../models/entities/memoriet.js";
+import { MemorietArchive } from "../models/entities/memoriet-archive.js";
+import { MemorietView } from "../models/entities/memoriet-view.js";
+import { VerifiedBadgeRequest } from "../models/entities/verified-badge-request.js";
+import { Plan } from "../models/entities/plan.js";
+import { UserPlan } from "../models/entities/user-plan.js";
+const sqlLogger = dbLogger.createSubLogger("sql", "gray", false);
+const isLogEnabled = (level)=>{
+ const logLevel = config.db.logging;
+ return Array.isArray(logLevel) ? logLevel.includes(level) : logLevel === level || logLevel?.trim()?.toLowerCase() === "all";
+};
+const isLoggingEnabled = ()=>{
+ const logLevel = config.db.logging;
+ return Array.isArray(logLevel) ? logLevel.length > 0 : logLevel != null;
+};
+class MyCustomLogger {
+ highlight(sql) {
+ return highlight.highlight(sql, {
+ language: "sql",
+ ignoreIllegals: true
+ });
+ }
+ logQuery(query, parameters) {
+ if (isLogEnabled("query")) sqlLogger.info(this.highlight(query).substring(0, 100));
+ }
+ logQueryError(error, query, parameters) {
+ if (isLogEnabled("error")) sqlLogger.error(this.highlight(query));
+ }
+ logQuerySlow(time, query, parameters) {
+ if (isLogEnabled("slow")) sqlLogger.warn(this.highlight(query));
+ }
+ logSchemaBuild(message) {
+ if (isLogEnabled("schema")) sqlLogger.info(message);
+ }
+ log(message) {
+ if (isLogEnabled("log")) sqlLogger.info(message);
+ }
+ logMigration(message) {
+ if (isLogEnabled("info")) sqlLogger.info(message);
+ }
+}
+export const entities = [
+ Announcement,
+ AnnouncementRead,
+ Meta,
+ Instance,
+ App,
+ AuthSession,
+ AccessToken,
+ User,
+ UserProfile,
+ UserKeypair,
+ UserPublickey,
+ UserList,
+ UserListJoining,
+ UserGroup,
+ UserGroupJoining,
+ UserGroupInvitation,
+ UserNotePining,
+ UserSecurityKey,
+ UsedUsername,
+ AttestationChallenge,
+ Following,
+ FollowRequest,
+ Muting,
+ RenoteMuting,
+ Blocking,
+ CallBlocking,
+ Note,
+ NoteEdit,
+ NoteFavorite,
+ NoteReaction,
+ NoteWatching,
+ NoteThreadMuting,
+ NoteUnread,
+ Page,
+ PageLike,
+ GalleryPost,
+ GalleryLike,
+ DriveFile,
+ DriveFolder,
+ Poll,
+ PollVote,
+ Notification,
+ Emoji,
+ Hashtag,
+ SwSubscription,
+ AbuseUserReport,
+ RegistrationTicket,
+ MessagingMessage,
+ Signin,
+ ModerationLog,
+ Clip,
+ ClipNote,
+ Antenna,
+ PromoNote,
+ PromoRead,
+ Relay,
+ Channel,
+ ChannelFollowing,
+ ChannelNotePining,
+ RegistryItem,
+ PasswordResetRequest,
+ UserPending,
+ Webhook,
+ UserIp,
+ UserEmoji,
+ OAuthApp,
+ OAuthToken,
+ HtmlNoteCacheEntry,
+ HtmlUserCacheEntry,
+ Bite,
+ InteractionStamp,
+ ReversiGame,
+ ReversiMatching,
+ ShogiGame,
+ ScheduledNote,
+ Memoriet,
+ MemorietArchive,
+ MemorietView,
+ VerifiedBadgeRequest,
+ Plan,
+ UserPlan,
+ ...charts
+];
+const log = isLoggingEnabled() || process.env.LOG_SQL === "true";
+export const db = new DataSource({
+ type: "postgres",
+ host: config.db.host,
+ port: config.db.port,
+ username: config.db.user,
+ password: config.db.pass,
+ database: config.db.db,
+ extra: {
+ statement_timeout: 1000 * 10,
+ ...config.db.extra
+ },
+ synchronize: process.env.NODE_ENV === "test",
+ dropSchema: process.env.NODE_ENV === "test",
+ cache: !config.db.disableCache ? {
+ type: "ioredis",
+ options: {
+ host: config.redis.host,
+ port: config.redis.port,
+ family: config.redis.family == null ? 0 : config.redis.family,
+ username: config.redis.user ?? "default",
+ password: config.redis.pass,
+ keyPrefix: `${config.redis.prefix}:query:`,
+ db: config.redis.db || 0,
+ tls: config.redis.tls
+ }
+ } : false,
+ logging: log,
+ logger: new MyCustomLogger(),
+ maxQueryExecutionTime: 300,
+ entities: entities,
+ migrations: [
+ "../../migration/*.js"
+ ]
+});
+export async function initDb(force = false) {
+ if (force) {
+ if (db.isInitialized) {
+ await db.destroy();
+ }
+ await db.initialize();
+ return;
+ }
+ if (db.isInitialized) {
+ // nop
+ } else {
+ await db.initialize();
+ }
+}
+export async function resetDb() {
+ const reset = async ()=>{
+ const { redisClient } = await import("./redis.js");
+ await redisClient.flushdb();
+ const tables = await db.query(`SELECT relname AS "table"
+ FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
+ WHERE nspname NOT IN ('pg_catalog', 'information_schema')
+ AND C.relkind = 'r'
+ AND nspname !~ '^pg_toast';`);
+ for (const table of tables){
+ await db.query(`DELETE FROM "${table.table}" CASCADE`);
+ }
+ };
+ for(let i = 1; i <= 3; i++){
+ try {
+ await reset();
+ } catch (e) {
+ if (i === 3) {
+ throw e;
+ } else {
+ await new Promise((resolve)=>setTimeout(resolve, 1000));
+ continue;
+ }
+ }
+ break;
+ }
+}
diff --git a/packages/backend/built/db/redis.js b/packages/backend/built/db/redis.js
new file mode 100644
index 0000000..4a392db
--- /dev/null
+++ b/packages/backend/built/db/redis.js
@@ -0,0 +1,18 @@
+import Redis from "ioredis";
+import config from "../config/index.js";
+export function createConnection() {
+ let source = config.redis;
+ return new Redis({
+ port: source.port,
+ host: source.host,
+ family: source.family ?? 0,
+ password: source.pass,
+ username: source.user ?? "default",
+ keyPrefix: `${source.prefix}:`,
+ db: source.db || 0,
+ tls: source.tls
+ });
+}
+export const subscriber = createConnection();
+subscriber.subscribe(config.redis.prefix ?? config.host);
+export const redisClient = createConnection();
diff --git a/packages/backend/built/env.js b/packages/backend/built/env.js
new file mode 100644
index 0000000..58efd6d
--- /dev/null
+++ b/packages/backend/built/env.js
@@ -0,0 +1,17 @@
+const envOption = {
+ onlyQueue: false,
+ onlyServer: false,
+ noDaemons: false,
+ disableClustering: false,
+ verbose: false,
+ withLogTime: false,
+ quiet: false,
+ slow: false
+};
+for (const key of Object.keys(envOption)){
+ if (process.env[`MK_${key.replace(/[A-Z]/g, (letter)=>`_${letter}`).toUpperCase()}`]) envOption[key] = true;
+}
+if (process.env.NODE_ENV === "test") envOption.disableClustering = true;
+if (process.env.NODE_ENV === "test") envOption.quiet = true;
+if (process.env.NODE_ENV === "test") envOption.noDaemons = true;
+export { envOption };
diff --git a/packages/backend/built/global.d.js b/packages/backend/built/global.d.js
new file mode 100644
index 0000000..1e440ee
--- /dev/null
+++ b/packages/backend/built/global.d.js
@@ -0,0 +1 @@
+// rome-ignore lint/suspicious/noExplicitAny: i have no idea
diff --git a/packages/backend/built/index.js b/packages/backend/built/index.js
new file mode 100644
index 0000000..6899978
--- /dev/null
+++ b/packages/backend/built/index.js
@@ -0,0 +1,15 @@
+/**
+ * Misskey Entry Point!
+ */ import { EventEmitter } from "node:events";
+Error.stackTraceLimit = Infinity;
+EventEmitter.defaultMaxListeners = 128;
+const emitWarning = process.emitWarning;
+process.emitWarning = (warning, ...args)=>{
+ const code = (warning instanceof Error ? warning.code : undefined) ?? (typeof args[0] === "object" ? args[0]?.code : undefined) ?? (typeof args[1] === "string" ? args[1] : undefined);
+ if (code === "DEP0040" || code === "DEP0060") return;
+ return emitWarning.call(process, warning, ...args);
+};
+const { default: boot } = await import("./boot/index.js");
+boot().catch((err)=>{
+ console.error(err);
+});
diff --git a/packages/backend/built/metrics.js b/packages/backend/built/metrics.js
new file mode 100644
index 0000000..fe5aa63
--- /dev/null
+++ b/packages/backend/built/metrics.js
@@ -0,0 +1,138 @@
+import { collectDefaultMetrics, register, Gauge, Counter } from "prom-client";
+import config from "./config/index.js";
+import { queues } from "./queue/queues.js";
+import cluster from "node:cluster";
+import Xev from "xev";
+const xev = new Xev();
+if (config.metrics?.enable) {
+ if (cluster.isPrimary) {
+ collectDefaultMetrics();
+ new Gauge({
+ name: "iceshrimp_queue_jobs",
+ help: "Amount of jobs in the bull queues",
+ labelNames: [
+ "queue",
+ "status"
+ ],
+ async collect () {
+ for (const queue of queues){
+ const counts = await queue.getJobCounts();
+ this.set({
+ queue: queue.name,
+ status: "completed"
+ }, counts.completed);
+ this.set({
+ queue: queue.name,
+ status: "waiting"
+ }, counts.waiting);
+ this.set({
+ queue: queue.name,
+ status: "active"
+ }, counts.active);
+ this.set({
+ queue: queue.name,
+ status: "delayed"
+ }, counts.delayed);
+ this.set({
+ queue: queue.name,
+ status: "failed"
+ }, counts.failed);
+ }
+ }
+ });
+ }
+}
+if (cluster.isPrimary) {
+ xev.on("registry-request", async ()=>{
+ try {
+ const metrics = await register.metrics();
+ xev.emit("registry-response", {
+ contentType: register.contentType,
+ body: metrics
+ });
+ } catch (error) {
+ xev.emit("registry-response", {
+ error
+ });
+ }
+ });
+}
+export const handleMetrics = async (ctx)=>{
+ if (config.metrics?.token !== undefined) {
+ if (ctx.query.token === undefined) {
+ ctx.res.statusCode = 401;
+ ctx.body = "Missing token parameter";
+ return;
+ }
+ const correct = config.metrics.token === ctx.query.token;
+ if (!correct) {
+ ctx.res.statusCode = 403;
+ ctx.body = "Incorrect token";
+ return;
+ }
+ }
+ try {
+ if (cluster.isPrimary) {
+ ctx.set("content-type", register.contentType);
+ ctx.body = await register.metrics();
+ } else {
+ const wait = new Promise((resolve, reject)=>{
+ const timeout = setTimeout(()=>reject("Timeout while waiting for cluster master"), 1000 * 60);
+ xev.once("registry-response", (response)=>{
+ clearTimeout(timeout);
+ if (response.error) reject(response.error);
+ ctx.set("content-type", response.contentType);
+ ctx.body = response.body;
+ resolve();
+ });
+ });
+ xev.emit("registry-request");
+ await wait;
+ }
+ } catch (err) {
+ ctx.res.statusCode = 500;
+ ctx.body = err;
+ }
+};
+const counter = (configuration)=>{
+ if (config.metrics?.enable) {
+ if (cluster.isPrimary) {
+ const counter = new Counter(configuration);
+ counter.reset(); // initialize internal hashmap
+ xev.on(`metrics-counter-${configuration.name}`, ()=>counter.inc());
+ return ()=>counter.inc();
+ } else {
+ return ()=>xev.emit(`metrics-counter-${configuration.name}`);
+ }
+ } else {
+ return ()=>{};
+ }
+};
+export const tickOutbox = counter({
+ name: "iceshrimp_outbox_total",
+ help: "Total AP outbox calls"
+});
+export const tickInbox = counter({
+ name: "iceshrimp_inbox_total",
+ help: "Total AP inbox calls"
+});
+export const tickFetch = counter({
+ name: "iceshrimp_fetch_total",
+ help: "Total AP fetch calls"
+});
+export const tickResolve = counter({
+ name: "iceshrimp_resolve_total",
+ help: "Total AP resolve calls"
+});
+export const tickBiteIncoming = counter({
+ name: "iceshrimp_bite_remote_incoming_total",
+ help: "Total bites received from remote"
+});
+export const tickBiteOutgoing = counter({
+ name: "iceshrimp_bite_remote_outgoing_total",
+ help: "Total bites sent to remote"
+});
+export const tickBiteLocal = counter({
+ name: "iceshrimp_bite_local_total",
+ help: "Total local bites"
+});
diff --git a/packages/backend/built/mfm/from-html.js b/packages/backend/built/mfm/from-html.js
new file mode 100644
index 0000000..6d48d16
--- /dev/null
+++ b/packages/backend/built/mfm/from-html.js
@@ -0,0 +1,194 @@
+import * as parse5 from "parse5";
+import { defaultTreeAdapter as treeAdapter } from "parse5";
+import { getSubjectHostFromUriAndUsernameCached } from "../remote/resolve-user.js";
+const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;
+const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/;
+export async function fromHtml(html, hashtagNames) {
+ // some AP servers like Pixelfed use br tags as well as newlines
+ html = html.replace(/
\r?\n/gi, "\n");
+ const dom = parse5.parseFragment(html);
+ let text = "";
+ for (const n of dom.childNodes){
+ await analyze(n);
+ }
+ return text.trim();
+ function getText(node) {
+ if (treeAdapter.isTextNode(node)) return node.value;
+ if (!treeAdapter.isElementNode(node)) return "";
+ if (node.nodeName === "br") return "\n";
+ if (node.childNodes) {
+ return node.childNodes.map((n)=>getText(n)).join("");
+ }
+ return "";
+ }
+ async function appendChildren(childNodes) {
+ if (childNodes) {
+ for (const n of childNodes){
+ await analyze(n);
+ }
+ }
+ }
+ async function analyze(node) {
+ if (treeAdapter.isTextNode(node)) {
+ text += node.value;
+ return;
+ }
+ // Skip comment or document type node
+ if (!treeAdapter.isElementNode(node)) return;
+ // Strip quote marker
+ const classes = node.attrs.find((x)=>x.name === "class");
+ if (classes && classes.value.split(" ").includes("quote-inline")) {
+ return;
+ }
+ switch(node.nodeName){
+ case "br":
+ {
+ text += "\n";
+ break;
+ }
+ case "a":
+ {
+ const txt = getText(node);
+ const rel = node.attrs.find((x)=>x.name === "rel");
+ const href = node.attrs.find((x)=>x.name === "href");
+ // ハッシュタグ
+ if (hashtagNames && href && hashtagNames.map((x)=>x.toLowerCase()).includes(txt.toLowerCase())) {
+ text += txt;
+ // メンション
+ } else if (txt.startsWith("@") && !rel?.value.match(/^me /)) {
+ const part = txt.split("@");
+ if (part.length === 2 && href) {
+ //#region ホスト名部分が省略されているので復元する
+ const acct = `${txt}@${await getSubjectHostFromUriAndUsernameCached(href.value, txt)}`;
+ text += acct;
+ //#endregion
+ } else if (part.length === 3) {
+ text += txt;
+ }
+ // その他
+ } else {
+ const generateLink = ()=>{
+ if (!(href || txt)) {
+ return "";
+ }
+ if (!href) {
+ return txt;
+ }
+ if (!txt || txt === href.value) {
+ // #6383: Missing text node
+ if (href.value.match(urlRegexFull)) {
+ return href.value;
+ } else {
+ return `<${href.value}>`;
+ }
+ }
+ if (href.value.match(urlRegex) && !href.value.match(urlRegexFull)) {
+ return `[${txt}](<${href.value}>)`; // #6846
+ } else {
+ return `[${txt}](${href.value})`;
+ }
+ };
+ text += generateLink();
+ }
+ break;
+ }
+ case "h1":
+ {
+ text += "【";
+ await appendChildren(node.childNodes);
+ text += "】\n";
+ break;
+ }
+ case "b":
+ case "strong":
+ {
+ text += "**";
+ await appendChildren(node.childNodes);
+ text += "**";
+ break;
+ }
+ case "small":
+ {
+ text += "";
+ await appendChildren(node.childNodes);
+ text += "";
+ break;
+ }
+ case "s":
+ case "del":
+ {
+ text += "~~";
+ await appendChildren(node.childNodes);
+ text += "~~";
+ break;
+ }
+ case "i":
+ case "em":
+ {
+ text += "";
+ await appendChildren(node.childNodes);
+ text += "";
+ break;
+ }
+ // block code (
)
+ case "pre":
+ {
+ if (node.childNodes.length === 1 && node.childNodes[0].nodeName === "code") {
+ text += "\n```\n";
+ text += getText(node.childNodes[0]);
+ text += "\n```\n";
+ } else {
+ await appendChildren(node.childNodes);
+ }
+ break;
+ }
+ // inline code ()
+ case "code":
+ {
+ text += "`";
+ await appendChildren(node.childNodes);
+ text += "`";
+ break;
+ }
+ case "blockquote":
+ {
+ const t = getText(node);
+ if (t) {
+ text += "\n> ";
+ text += t.split("\n").join("\n> ");
+ }
+ break;
+ }
+ case "p":
+ case "h2":
+ case "h3":
+ case "h4":
+ case "h5":
+ case "h6":
+ {
+ text += "\n\n";
+ await appendChildren(node.childNodes);
+ break;
+ }
+ // other block elements
+ case "div":
+ case "header":
+ case "footer":
+ case "article":
+ case "li":
+ case "dt":
+ case "dd":
+ {
+ text += "\n";
+ await appendChildren(node.childNodes);
+ break;
+ }
+ default:
+ {
+ // includes inline elements
+ await appendChildren(node.childNodes);
+ break;
+ }
+ }
+ }
+}
diff --git a/packages/backend/built/mfm/to-html.js b/packages/backend/built/mfm/to-html.js
new file mode 100644
index 0000000..ef7c991
--- /dev/null
+++ b/packages/backend/built/mfm/to-html.js
@@ -0,0 +1,161 @@
+import { Window as HappyDom } from "happy-dom";
+import config from "../config/index.js";
+import { intersperse } from "../prelude/array.js";
+import { resolveMentionFromCache } from "../remote/resolve-user.js";
+export async function toHtml(nodes, mentionedRemoteUsers = [], objectHost) {
+ if (nodes == null) {
+ return null;
+ }
+ const window = new HappyDom();
+ const doc = window.document;
+ function appendTextWithGlyphs(text, targetElement) {
+ const regexp = /;([^:;\s]{1,100});/g;
+ let last = 0;
+ for (const match of text.matchAll(regexp)){
+ if (match.index > last) {
+ targetElement.appendChild(doc.createTextNode(text.slice(last, match.index)));
+ }
+ targetElement.appendChild(doc.createTextNode(`\u200B:${match[1]}:\u200B`));
+ last = match.index + match[0].length;
+ }
+ if (last < text.length) {
+ targetElement.appendChild(doc.createTextNode(text.slice(last)));
+ }
+ }
+ async function appendChildren(children, targetElement) {
+ if (children) {
+ for (const child of (await Promise.all(children.map(async (x)=>await handlers[x.type](x)))))targetElement.appendChild(child);
+ }
+ }
+ const handlers = {
+ async bold (node) {
+ const el = doc.createElement("b");
+ await appendChildren(node.children, el);
+ return el;
+ },
+ async small (node) {
+ const el = doc.createElement("small");
+ await appendChildren(node.children, el);
+ return el;
+ },
+ async strike (node) {
+ const el = doc.createElement("del");
+ await appendChildren(node.children, el);
+ return el;
+ },
+ async italic (node) {
+ const el = doc.createElement("i");
+ await appendChildren(node.children, el);
+ return el;
+ },
+ async fn (node) {
+ const el = doc.createElement("i");
+ await appendChildren(node.children, el);
+ return el;
+ },
+ blockCode (node) {
+ const pre = doc.createElement("pre");
+ const inner = doc.createElement("code");
+ inner.textContent = node.props.code;
+ pre.appendChild(inner);
+ return pre;
+ },
+ async center (node) {
+ const el = doc.createElement("div");
+ await appendChildren(node.children, el);
+ return el;
+ },
+ emojiCode (node) {
+ return doc.createTextNode(`\u200B:${node.props.name}:\u200B`);
+ },
+ unicodeEmoji (node) {
+ return doc.createTextNode(node.props.emoji);
+ },
+ hashtag (node) {
+ const a = doc.createElement("a");
+ a.setAttribute('href', `${config.url}/tags/${node.props.hashtag}`);
+ a.textContent = `#${node.props.hashtag}`;
+ a.setAttribute("rel", "tag");
+ return a;
+ },
+ inlineCode (node) {
+ const el = doc.createElement("code");
+ el.textContent = node.props.code;
+ return el;
+ },
+ mathInline (node) {
+ const el = doc.createElement("code");
+ el.textContent = node.props.formula;
+ return el;
+ },
+ mathBlock (node) {
+ const el = doc.createElement("code");
+ el.textContent = node.props.formula;
+ return el;
+ },
+ async link (node) {
+ const a = doc.createElement("a");
+ a.setAttribute('href', node.props.url);
+ await appendChildren(node.children, a);
+ return a;
+ },
+ async mention (node) {
+ const { username, host, acct } = node.props;
+ const resolved = await resolveMentionFromCache(username, host, objectHost, mentionedRemoteUsers);
+ const el = doc.createElement("span");
+ if (resolved === null) {
+ el.textContent = acct;
+ } else {
+ el.setAttribute("class", "h-card");
+ el.setAttribute("translate", "no");
+ const a = doc.createElement("a");
+ a.setAttribute('href', resolved.href);
+ a.className = "u-url mention";
+ const span = doc.createElement("span");
+ span.textContent = resolved.username;
+ a.textContent = '@';
+ a.appendChild(span);
+ el.appendChild(a);
+ }
+ return el;
+ },
+ async quote (node) {
+ const el = doc.createElement("blockquote");
+ await appendChildren(node.children, el);
+ return el;
+ },
+ text (node) {
+ const el = doc.createElement("span");
+ const lines = node.props.text.split(/\r\n|\r|\n/);
+ for (const x of intersperse("br", lines)){
+ if (x === "br") {
+ el.appendChild(doc.createElement("br"));
+ continue;
+ }
+ appendTextWithGlyphs(x, el);
+ }
+ return el;
+ },
+ url (node) {
+ const a = doc.createElement("a");
+ a.setAttribute('href', node.props.url);
+ a.textContent = node.props.url.replace(/^https?:\/\//, '');
+ return a;
+ },
+ search (node) {
+ const a = doc.createElement("a");
+ a.setAttribute('href', `${config.searchEngine}${node.props.query}`);
+ a.textContent = node.props.content;
+ return a;
+ },
+ async plain (node) {
+ const el = doc.createElement("span");
+ await appendChildren(node.children, el);
+ return el;
+ }
+ };
+ await appendChildren(nodes, doc.body);
+ const html = `${doc.body.innerHTML}
`;
+ await window.happyDOM.close();
+ return html;
+}
diff --git a/packages/backend/built/migration/1000000000000-Init.js b/packages/backend/built/migration/1000000000000-Init.js
new file mode 100644
index 0000000..d8c24e9
--- /dev/null
+++ b/packages/backend/built/migration/1000000000000-Init.js
@@ -0,0 +1,480 @@
+export class Init1000000000000 {
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TYPE "log_level_enum" AS ENUM('error', 'warning', 'info', 'success', 'debug')`);
+ await queryRunner.query(`CREATE TABLE "log" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "domain" character varying(64) array NOT NULL DEFAULT '{}'::varchar[], "level" "log_level_enum" NOT NULL, "worker" character varying(8) NOT NULL, "machine" character varying(128) NOT NULL, "message" character varying(1024) NOT NULL, "data" jsonb NOT NULL DEFAULT '{}', CONSTRAINT "PK_350604cbdf991d5930d9e618fbd" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_8e4eb51a35d81b64dda28eed0a" ON "log" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_8cb40cfc8f3c28261e6f887b03" ON "log" ("domain") `);
+ await queryRunner.query(`CREATE INDEX "IDX_584b536b49e53ac81beb39a177" ON "log" ("level") `);
+ await queryRunner.query(`CREATE TABLE "drive_folder" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "name" character varying(128) NOT NULL, "userId" character varying(32), "parentId" character varying(32), CONSTRAINT "PK_7a0c089191f5ebdc214e0af808a" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_02878d441ceae15ce060b73daf" ON "drive_folder" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f4fc06e49c0171c85f1c48060d" ON "drive_folder" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_00ceffb0cdc238b3233294f08f" ON "drive_folder" ("parentId") `);
+ await queryRunner.query(`CREATE TABLE "drive_file" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32), "userHost" character varying(128), "md5" character varying(32) NOT NULL, "name" character varying(256) NOT NULL, "type" character varying(128) NOT NULL, "size" integer NOT NULL, "comment" character varying(512), "properties" jsonb NOT NULL DEFAULT '{}', "storedInternal" boolean NOT NULL, "url" character varying(512) NOT NULL, "thumbnailUrl" character varying(512), "webpublicUrl" character varying(512), "accessKey" character varying(256), "thumbnailAccessKey" character varying(256), "webpublicAccessKey" character varying(256), "uri" character varying(512), "src" character varying(512), "folderId" character varying(32), "isSensitive" boolean NOT NULL DEFAULT false, "isLink" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_43ddaaaf18c9e68029b7cbb032e" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_c8dfad3b72196dd1d6b5db168a" ON "drive_file" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_860fa6f6c7df5bb887249fba22" ON "drive_file" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_92779627994ac79277f070c91e" ON "drive_file" ("userHost") `);
+ await queryRunner.query(`CREATE INDEX "IDX_37bb9a1b4585f8a3beb24c62d6" ON "drive_file" ("md5") `);
+ await queryRunner.query(`CREATE INDEX "IDX_a40b8df8c989d7db937ea27cf6" ON "drive_file" ("type") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_d85a184c2540d2deba33daf642" ON "drive_file" ("accessKey") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e74022ce9a074b3866f70e0d27" ON "drive_file" ("thumbnailAccessKey") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_c55b2b7c284d9fef98026fc88e" ON "drive_file" ("webpublicAccessKey") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e5848eac4940934e23dbc17581" ON "drive_file" ("uri") `);
+ await queryRunner.query(`CREATE INDEX "IDX_bb90d1956dafc4068c28aa7560" ON "drive_file" ("folderId") `);
+ await queryRunner.query(`CREATE TABLE "user" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE, "lastFetchedAt" TIMESTAMP WITH TIME ZONE, "username" character varying(128) NOT NULL, "usernameLower" character varying(128) NOT NULL, "name" character varying(128), "followersCount" integer NOT NULL DEFAULT 0, "followingCount" integer NOT NULL DEFAULT 0, "notesCount" integer NOT NULL DEFAULT 0, "avatarId" character varying(32), "bannerId" character varying(32), "tags" character varying(128) array NOT NULL DEFAULT '{}'::varchar[], "avatarUrl" character varying(512), "bannerUrl" character varying(512), "avatarColor" character varying(32), "bannerColor" character varying(32), "isSuspended" boolean NOT NULL DEFAULT false, "isSilenced" boolean NOT NULL DEFAULT false, "isLocked" boolean NOT NULL DEFAULT false, "isBot" boolean NOT NULL DEFAULT false, "isCat" boolean NOT NULL DEFAULT false, "isAdmin" boolean NOT NULL DEFAULT false, "isModerator" boolean NOT NULL DEFAULT false, "isVerified" boolean NOT NULL DEFAULT false, "emojis" character varying(128) array NOT NULL DEFAULT '{}'::varchar[], "host" character varying(128), "inbox" character varying(512), "sharedInbox" character varying(512), "featured" character varying(512), "uri" character varying(512), "token" character(16), CONSTRAINT "UQ_a854e557b1b14814750c7c7b0c9" UNIQUE ("token"), CONSTRAINT "REL_58f5c71eaab331645112cf8cfa" UNIQUE ("avatarId"), CONSTRAINT "REL_afc64b53f8db3707ceb34eb28e" UNIQUE ("bannerId"), CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_e11e649824a45d8ed01d597fd9" ON "user" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_80ca6e6ef65fb9ef34ea8c90f4" ON "user" ("updatedAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_a27b942a0d6dcff90e3ee9b5e8" ON "user" ("usernameLower") `);
+ await queryRunner.query(`CREATE INDEX "IDX_fa99d777623947a5b05f394cae" ON "user" ("tags") `);
+ await queryRunner.query(`CREATE INDEX "IDX_3252a5df8d5bbd16b281f7799e" ON "user" ("host") `);
+ await queryRunner.query(`CREATE INDEX "IDX_be623adaa4c566baf5d29ce0c8" ON "user" ("uri") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a854e557b1b14814750c7c7b0c" ON "user" ("token") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_5deb01ae162d1d70b80d064c27" ON "user" ("usernameLower", "host") `);
+ await queryRunner.query(`CREATE TABLE "app" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32), "secret" character varying(64) NOT NULL, "name" character varying(128) NOT NULL, "description" character varying(512) NOT NULL, "permission" character varying(64) array NOT NULL, "callbackUrl" character varying(512), CONSTRAINT "PK_9478629fc093d229df09e560aea" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_048a757923ed8b157e9895da53" ON "app" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_3f5b0899ef90527a3462d7c2cb" ON "app" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f49922d511d666848f250663c4" ON "app" ("secret") `);
+ await queryRunner.query(`CREATE TABLE "access_token" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "token" character varying(128) NOT NULL, "hash" character varying(128) NOT NULL, "userId" character varying(32) NOT NULL, "appId" character varying(32) NOT NULL, CONSTRAINT "PK_f20f028607b2603deabd8182d12" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_70ba8f6af34bc924fc9e12adb8" ON "access_token" ("token") `);
+ await queryRunner.query(`CREATE INDEX "IDX_64c327441248bae40f7d92f34f" ON "access_token" ("hash") `);
+ await queryRunner.query(`CREATE INDEX "IDX_9949557d0e1b2c19e5344c171e" ON "access_token" ("userId") `);
+ await queryRunner.query(`CREATE TYPE "note_visibility_enum" AS ENUM('public', 'home', 'followers', 'specified')`);
+ await queryRunner.query(`CREATE TABLE "note" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "replyId" character varying(32), "renoteId" character varying(32), "text" text, "name" character varying(256), "cw" character varying(512), "appId" character varying(32), "userId" character varying(32) NOT NULL, "viaMobile" boolean NOT NULL DEFAULT false, "localOnly" boolean NOT NULL DEFAULT false, "renoteCount" smallint NOT NULL DEFAULT 0, "repliesCount" smallint NOT NULL DEFAULT 0, "reactions" jsonb NOT NULL DEFAULT '{}', "visibility" "note_visibility_enum" NOT NULL, "uri" character varying(512), "score" integer NOT NULL DEFAULT 0, "fileIds" character varying(32) array NOT NULL DEFAULT '{}'::varchar[], "attachedFileTypes" character varying(256) array NOT NULL DEFAULT '{}'::varchar[], "visibleUserIds" character varying(32) array NOT NULL DEFAULT '{}'::varchar[], "mentions" character varying(32) array NOT NULL DEFAULT '{}'::varchar[], "mentionedRemoteUsers" text NOT NULL DEFAULT '[]', "emojis" character varying(128) array NOT NULL DEFAULT '{}'::varchar[], "tags" character varying(128) array NOT NULL DEFAULT '{}'::varchar[], "hasPoll" boolean NOT NULL DEFAULT false, "geo" jsonb DEFAULT null, "userHost" character varying(128), "replyUserId" character varying(32), "replyUserHost" character varying(128), "renoteUserId" character varying(32), "renoteUserHost" character varying(128), CONSTRAINT "PK_96d0c172a4fba276b1bbed43058" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_e7c0567f5261063592f022e9b5" ON "note" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_17cb3553c700a4985dff5a30ff" ON "note" ("replyId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_52ccc804d7c69037d558bac4c9" ON "note" ("renoteId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_5b87d9d19127bd5d92026017a7" ON "note" ("userId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_153536c67d05e9adb24e99fc2b" ON "note" ("uri") `);
+ await queryRunner.query(`CREATE INDEX "IDX_51c063b6a133a9cb87145450f5" ON "note" ("fileIds") `);
+ await queryRunner.query(`CREATE INDEX "IDX_25dfc71b0369b003a4cd434d0b" ON "note" ("attachedFileTypes") `);
+ await queryRunner.query(`CREATE INDEX "IDX_796a8c03959361f97dc2be1d5c" ON "note" ("visibleUserIds") `);
+ await queryRunner.query(`CREATE INDEX "IDX_54ebcb6d27222913b908d56fd8" ON "note" ("mentions") `);
+ await queryRunner.query(`CREATE INDEX "IDX_88937d94d7443d9a99a76fa5c0" ON "note" ("tags") `);
+ await queryRunner.query(`CREATE INDEX "IDX_7125a826ab192eb27e11d358a5" ON "note" ("userHost") `);
+ await queryRunner.query(`CREATE TABLE "poll_vote" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, "choice" integer NOT NULL, CONSTRAINT "PK_fd002d371201c472490ba89c6a0" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_0fb627e1c2f753262a74f0562d" ON "poll_vote" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_66d2bd2ee31d14bcc23069a89f" ON "poll_vote" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_aecfbd5ef60374918e63ee95fa" ON "poll_vote" ("noteId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_50bd7164c5b78f1f4a42c4d21f" ON "poll_vote" ("userId", "noteId", "choice") `);
+ await queryRunner.query(`CREATE TABLE "note_reaction" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, "reaction" character varying(128) NOT NULL, CONSTRAINT "PK_767ec729b108799b587a3fcc9cf" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_01f4581f114e0ebd2bbb876f0b" ON "note_reaction" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_13761f64257f40c5636d0ff95e" ON "note_reaction" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_45145e4953780f3cd5656f0ea6" ON "note_reaction" ("noteId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ad0c221b25672daf2df320a817" ON "note_reaction" ("userId", "noteId") `);
+ await queryRunner.query(`CREATE TABLE "note_watching" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, "noteUserId" character varying(32) NOT NULL, CONSTRAINT "PK_49286fdb23725945a74aa27d757" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_318cdf42a9cfc11f479bd802bb" ON "note_watching" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_b0134ec406e8d09a540f818288" ON "note_watching" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_03e7028ab8388a3f5e3ce2a861" ON "note_watching" ("noteId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_44499765eec6b5489d72c4253b" ON "note_watching" ("noteUserId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a42c93c69989ce1d09959df4cf" ON "note_watching" ("userId", "noteId") `);
+ await queryRunner.query(`CREATE TABLE "note_unread" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, "noteUserId" character varying(32) NOT NULL, "isSpecified" boolean NOT NULL, CONSTRAINT "PK_1904eda61a784f57e6e51fa9c1f" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_56b0166d34ddae49d8ef7610bb" ON "note_unread" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e637cba4dc4410218c4251260e" ON "note_unread" ("noteId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_d908433a4953cc13216cd9c274" ON "note_unread" ("userId", "noteId") `);
+ await queryRunner.query(`CREATE TABLE "notification" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "notifieeId" character varying(32) NOT NULL, "notifierId" character varying(32) NOT NULL, "type" character varying(32) NOT NULL, "isRead" boolean NOT NULL DEFAULT false, "noteId" character varying(32), "reaction" character varying(128), "choice" integer, CONSTRAINT "PK_705b6c7cdf9b2c2ff7ac7872cb7" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_b11a5e627c41d4dc3170f1d370" ON "notification" ("createdAt") `);
+ await queryRunner.query(`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 '/twemoji/1f440.svg', "bannerUrl" character varying(512), "errorImageUrl" character varying(512) DEFAULT '/twemoji/1f480.svg', "iconUrl" character varying(512), "cacheRemoteFiles" boolean NOT NULL DEFAULT false, "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"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_582f8fab771a9040a12961f3e7" ON "following" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_24e0042143a18157b234df186c" ON "following" ("followeeId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_6516c5a6f3c015b4eed39978be" ON "following" ("followerId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_307be5f1d1252e0388662acb96" ON "following" ("followerId", "followeeId") `);
+ await queryRunner.query(`CREATE TABLE "instance" ("id" character varying(32) NOT NULL, "caughtAt" TIMESTAMP WITH TIME ZONE NOT NULL, "host" character varying(128) NOT NULL, "system" character varying(64), "usersCount" integer NOT NULL DEFAULT 0, "notesCount" integer NOT NULL DEFAULT 0, "followingCount" integer NOT NULL DEFAULT 0, "followersCount" integer NOT NULL DEFAULT 0, "driveUsage" integer NOT NULL DEFAULT 0, "driveFiles" integer NOT NULL DEFAULT 0, "latestRequestSentAt" TIMESTAMP WITH TIME ZONE, "latestStatus" integer, "latestRequestReceivedAt" TIMESTAMP WITH TIME ZONE, "lastCommunicatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "isNotResponding" boolean NOT NULL DEFAULT false, "isMarkedAsClosed" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_eaf60e4a0c399c9935413e06474" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_2cd3b2a6b4cf0b910b260afe08" ON "instance" ("caughtAt") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_8d5afc98982185799b160e10eb" ON "instance" ("host") `);
+ await queryRunner.query(`CREATE TABLE "muting" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "muteeId" character varying(32) NOT NULL, "muterId" character varying(32) NOT NULL, CONSTRAINT "PK_2e92d06c8b5c602eeb27ca9ba48" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_f86d57fbca33c7a4e6897490cc" ON "muting" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_ec96b4fed9dae517e0dbbe0675" ON "muting" ("muteeId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_93060675b4a79a577f31d260c6" ON "muting" ("muterId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_1eb9d9824a630321a29fd3b290" ON "muting" ("muterId", "muteeId") `);
+ await queryRunner.query(`CREATE TABLE "sw_subscription" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "endpoint" character varying(512) NOT NULL, "auth" character varying(256) NOT NULL, "publickey" character varying(128) NOT NULL, CONSTRAINT "PK_e8f763631530051b95eb6279b91" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_97754ca6f2baff9b4abb7f853d" ON "sw_subscription" ("userId") `);
+ await queryRunner.query(`CREATE TABLE "blocking" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "blockeeId" character varying(32) NOT NULL, "blockerId" character varying(32) NOT NULL, CONSTRAINT "PK_e5d9a541cc1965ee7e048ea09dd" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_b9a354f7941c1e779f3b33aea6" ON "blocking" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_2cd4a2743a99671308f5417759" ON "blocking" ("blockeeId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0627125f1a8a42c9a1929edb55" ON "blocking" ("blockerId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_98a1bc5cb30dfd159de056549f" ON "blocking" ("blockerId", "blockeeId") `);
+ await queryRunner.query(`CREATE TABLE "user_list" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "name" character varying(128) NOT NULL, CONSTRAINT "PK_87bab75775fd9b1ff822b656402" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_b7fcefbdd1c18dce86687531f9" ON "user_list" ("userId") `);
+ await queryRunner.query(`CREATE TABLE "user_list_joining" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "userListId" character varying(32) NOT NULL, CONSTRAINT "PK_11abb3768da1c5f8de101c9df45" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_d844bfc6f3f523a05189076efa" ON "user_list_joining" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_605472305f26818cc93d1baaa7" ON "user_list_joining" ("userListId") `);
+ await queryRunner.query(`CREATE TABLE "hashtag" ("id" character varying(32) NOT NULL, "name" character varying(128) NOT NULL, "mentionedUserIds" character varying(32) array NOT NULL, "mentionedUsersCount" integer NOT NULL DEFAULT 0, "mentionedLocalUserIds" character varying(32) array NOT NULL, "mentionedLocalUsersCount" integer NOT NULL DEFAULT 0, "mentionedRemoteUserIds" character varying(32) array NOT NULL, "mentionedRemoteUsersCount" integer NOT NULL DEFAULT 0, "attachedUserIds" character varying(32) array NOT NULL, "attachedUsersCount" integer NOT NULL DEFAULT 0, "attachedLocalUserIds" character varying(32) array NOT NULL, "attachedLocalUsersCount" integer NOT NULL DEFAULT 0, "attachedRemoteUserIds" character varying(32) array NOT NULL, "attachedRemoteUsersCount" integer NOT NULL DEFAULT 0, CONSTRAINT "PK_cb36eb8af8412bfa978f1165d78" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_347fec870eafea7b26c8a73bac" ON "hashtag" ("name") `);
+ await queryRunner.query(`CREATE INDEX "IDX_2710a55f826ee236ea1a62698f" ON "hashtag" ("mentionedUsersCount") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0e206cec573f1edff4a3062923" ON "hashtag" ("mentionedLocalUsersCount") `);
+ await queryRunner.query(`CREATE INDEX "IDX_4c02d38a976c3ae132228c6fce" ON "hashtag" ("mentionedRemoteUsersCount") `);
+ await queryRunner.query(`CREATE INDEX "IDX_d57f9030cd3af7f63ffb1c267c" ON "hashtag" ("attachedUsersCount") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0c44bf4f680964145f2a68a341" ON "hashtag" ("attachedLocalUsersCount") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0b03cbcd7e6a7ce068efa8ecc2" ON "hashtag" ("attachedRemoteUsersCount") `);
+ await queryRunner.query(`CREATE TABLE "note_favorite" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, CONSTRAINT "PK_af0da35a60b9fa4463a62082b36" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_47f4b1892f5d6ba8efb3057d81" ON "note_favorite" ("userId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0f4fb9ad355f3effff221ef245" ON "note_favorite" ("userId", "noteId") `);
+ await queryRunner.query(`CREATE TABLE "abuse_user_report" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "reporterId" character varying(32) NOT NULL, "comment" character varying(512) NOT NULL, CONSTRAINT "PK_87873f5f5cc5c321a1306b2d18c" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_db2098070b2b5a523c58181f74" ON "abuse_user_report" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_d049123c413e68ca52abe73420" ON "abuse_user_report" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_04cc96756f89d0b7f9473e8cdf" ON "abuse_user_report" ("reporterId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_5cd442c3b2e74fdd99dae20243" ON "abuse_user_report" ("userId", "reporterId") `);
+ await queryRunner.query(`CREATE TABLE "registration_ticket" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "code" character varying(64) NOT NULL, CONSTRAINT "PK_f11696b6fafcf3662d4292734f8" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0ff69e8dfa9fe31bb4a4660f59" ON "registration_ticket" ("code") `);
+ await queryRunner.query(`CREATE TABLE "messaging_message" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "recipientId" character varying(32) NOT NULL, "text" character varying(4096), "isRead" boolean NOT NULL DEFAULT false, "fileId" character varying(32), CONSTRAINT "PK_db398fd79dc95d0eb8c30456eaa" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_e21cd3646e52ef9c94aaf17c2e" ON "messaging_message" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_5377c307783fce2b6d352e1203" ON "messaging_message" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_cac14a4e3944454a5ce7daa514" ON "messaging_message" ("recipientId") `);
+ await queryRunner.query(`CREATE TABLE "signin" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "ip" character varying(128) NOT NULL, "headers" jsonb NOT NULL, "success" boolean NOT NULL, CONSTRAINT "PK_9e96ddc025712616fc492b3b588" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_2c308dbdc50d94dc625670055f" ON "signin" ("userId") `);
+ await queryRunner.query(`CREATE TABLE "auth_session" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "token" character varying(128) NOT NULL, "userId" character varying(32), "appId" character varying(32) NOT NULL, CONSTRAINT "PK_19354ed146424a728c1112a8cbf" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_62cb09e1129f6ec024ef66e183" ON "auth_session" ("token") `);
+ await queryRunner.query(`CREATE TABLE "follow_request" ("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, "requestId" character varying(128), "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_53a9aa3725f7a3deb150b39dbfc" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_12c01c0d1a79f77d9f6c15fadd" ON "follow_request" ("followeeId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_a7fd92dd6dc519e6fb435dd108" ON "follow_request" ("followerId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_d54a512b822fac7ed52800f6b4" ON "follow_request" ("followerId", "followeeId") `);
+ await queryRunner.query(`CREATE TABLE "emoji" ("id" character varying(32) NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE, "name" character varying(128) NOT NULL, "host" character varying(128), "url" character varying(512) NOT NULL, "uri" character varying(512), "type" character varying(64), "aliases" character varying(128) array NOT NULL DEFAULT '{}'::varchar[], CONSTRAINT "PK_df74ce05e24999ee01ea0bc50a3" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_b37dafc86e9af007e3295c2781" ON "emoji" ("name") `);
+ await queryRunner.query(`CREATE INDEX "IDX_5900e907bb46516ddf2871327c" ON "emoji" ("host") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_4f4d35e1256c84ae3d1f0eab10" ON "emoji" ("name", "host") `);
+ await queryRunner.query(`CREATE TABLE "reversi_game" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "startedAt" TIMESTAMP WITH TIME ZONE, "user1Id" character varying(32) NOT NULL, "user2Id" character varying(32) NOT NULL, "user1Accepted" boolean NOT NULL DEFAULT false, "user2Accepted" boolean NOT NULL DEFAULT false, "black" integer, "isStarted" boolean NOT NULL DEFAULT false, "isEnded" boolean NOT NULL DEFAULT false, "winnerId" character varying(32), "surrendered" character varying(32), "logs" jsonb NOT NULL DEFAULT '[]', "map" character varying(64) array NOT NULL, "bw" character varying(32) NOT NULL, "isLlotheo" boolean NOT NULL DEFAULT false, "canPutEverywhere" boolean NOT NULL DEFAULT false, "loopedBoard" boolean NOT NULL DEFAULT false, "form1" jsonb DEFAULT null, "form2" jsonb DEFAULT null, "crc32" character varying(32), CONSTRAINT "PK_76b30eeba71b1193ad7c5311c3f" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_b46ec40746efceac604142be1c" ON "reversi_game" ("createdAt") `);
+ await queryRunner.query(`CREATE TABLE "reversi_matching" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "parentId" character varying(32) NOT NULL, "childId" character varying(32) NOT NULL, CONSTRAINT "PK_880bd0afbab232f21c8b9d146cf" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_b604d92d6c7aec38627f6eaf16" ON "reversi_matching" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_3b25402709dd9882048c2bbade" ON "reversi_matching" ("parentId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e247b23a3c9b45f89ec1299d06" ON "reversi_matching" ("childId") `);
+ await queryRunner.query(`CREATE TABLE "user_note_pining" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, CONSTRAINT "PK_a6a2dad4ae000abce2ea9d9b103" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_bfbc6f79ba4007b4ce5097f08d" ON "user_note_pining" ("userId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_410cd649884b501c02d6e72738" ON "user_note_pining" ("userId", "noteId") `);
+ await queryRunner.query(`CREATE TYPE "poll_notevisibility_enum" AS ENUM('public', 'home', 'followers', 'specified')`);
+ await queryRunner.query(`CREATE TABLE "poll" ("noteId" character varying(32) NOT NULL, "expiresAt" TIMESTAMP WITH TIME ZONE, "multiple" boolean NOT NULL, "choices" character varying(128) array NOT NULL DEFAULT '{}'::varchar[], "votes" integer array NOT NULL, "noteVisibility" "poll_notevisibility_enum" NOT NULL, "userId" character varying(32) NOT NULL, "userHost" character varying(128), CONSTRAINT "REL_da851e06d0dfe2ef397d8b1bf1" UNIQUE ("noteId"), CONSTRAINT "PK_da851e06d0dfe2ef397d8b1bf1b" PRIMARY KEY ("noteId"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_0610ebcfcfb4a18441a9bcdab2" ON "poll" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_7fa20a12319c7f6dc3aed98c0a" ON "poll" ("userHost") `);
+ await queryRunner.query(`CREATE TABLE "user_keypair" ("userId" character varying(32) NOT NULL, "publicKey" character varying(4096) NOT NULL, "privateKey" character varying(4096) NOT NULL, CONSTRAINT "REL_f4853eb41ab722fe05f81cedeb" UNIQUE ("userId"), CONSTRAINT "PK_f4853eb41ab722fe05f81cedeb6" PRIMARY KEY ("userId"))`);
+ await queryRunner.query(`CREATE TABLE "user_publickey" ("userId" character varying(32) NOT NULL, "keyId" character varying(256) NOT NULL, "keyPem" character varying(4096) NOT NULL, CONSTRAINT "REL_10c146e4b39b443ede016f6736" UNIQUE ("userId"), CONSTRAINT "PK_10c146e4b39b443ede016f6736d" PRIMARY KEY ("userId"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_171e64971c780ebd23fae140bb" ON "user_publickey" ("keyId") `);
+ await queryRunner.query(`CREATE TABLE "user_profile" ("userId" character varying(32) NOT NULL, "location" character varying(128), "birthday" character(10), "description" character varying(1024), "fields" jsonb NOT NULL DEFAULT '[]', "url" character varying(512), "email" character varying(128), "emailVerifyCode" character varying(128), "emailVerified" boolean NOT NULL DEFAULT false, "twoFactorTempSecret" character varying(128), "twoFactorSecret" character varying(128), "twoFactorEnabled" boolean NOT NULL DEFAULT false, "password" character varying(128), "clientData" jsonb NOT NULL DEFAULT '{}', "autoWatch" boolean NOT NULL DEFAULT false, "autoAcceptFollowed" boolean NOT NULL DEFAULT false, "alwaysMarkNsfw" boolean NOT NULL DEFAULT false, "carefulBot" boolean NOT NULL DEFAULT false, "twitter" boolean NOT NULL DEFAULT false, "twitterAccessToken" character varying(64) DEFAULT null, "twitterAccessTokenSecret" character varying(64) DEFAULT null, "twitterUserId" character varying(64) DEFAULT null, "twitterScreenName" character varying(64) DEFAULT null, "github" boolean NOT NULL DEFAULT false, "githubAccessToken" character varying(64) DEFAULT null, "githubId" integer DEFAULT null, "githubLogin" character varying(64) DEFAULT null, "discord" boolean NOT NULL DEFAULT false, "discordAccessToken" character varying(64) DEFAULT null, "discordRefreshToken" character varying(64) DEFAULT null, "discordExpiresDate" integer DEFAULT null, "discordId" character varying(64) DEFAULT null, "discordUsername" character varying(64) DEFAULT null, "discordDiscriminator" character varying(64) DEFAULT null, "userHost" character varying(128), CONSTRAINT "REL_51cb79b5555effaf7d69ba1cff" UNIQUE ("userId"), CONSTRAINT "PK_51cb79b5555effaf7d69ba1cff9" PRIMARY KEY ("userId"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_dce530b98e454793dac5ec2f5a" ON "user_profile" ("userHost") `);
+ await queryRunner.query(`CREATE TYPE "__chart__active_users_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`CREATE TABLE "__chart__active_users" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__active_users_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___local_count" bigint NOT NULL, "___remote_count" bigint NOT NULL, CONSTRAINT "PK_317237a9f733b970604a11e314f" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE TYPE "__chart__drive_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`CREATE TABLE "__chart__drive" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__drive_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___local_totalCount" bigint NOT NULL, "___local_totalSize" bigint NOT NULL, "___local_incCount" bigint NOT NULL, "___local_incSize" bigint NOT NULL, "___local_decCount" bigint NOT NULL, "___local_decSize" bigint NOT NULL, "___remote_totalCount" bigint NOT NULL, "___remote_totalSize" bigint NOT NULL, "___remote_incCount" bigint NOT NULL, "___remote_incSize" bigint NOT NULL, "___remote_decCount" bigint NOT NULL, "___remote_decSize" bigint NOT NULL, CONSTRAINT "PK_f96bc548a765cd4b3b354221ce7" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE TYPE "__chart__federation_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`CREATE TABLE "__chart__federation" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__federation_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___instance_total" bigint NOT NULL, "___instance_inc" bigint NOT NULL, "___instance_dec" bigint NOT NULL, CONSTRAINT "PK_b39dcd31a0fe1a7757e348e85fd" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE TYPE "__chart__hashtag_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`CREATE TABLE "__chart__hashtag" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__hashtag_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___local_count" bigint NOT NULL, "___remote_count" bigint NOT NULL, CONSTRAINT "PK_c32f1ea2b44a5d2f7881e37f8f9" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE TYPE "__chart__instance_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`CREATE TABLE "__chart__instance" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__instance_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___requests_failed" bigint NOT NULL, "___requests_succeeded" bigint NOT NULL, "___requests_received" bigint NOT NULL, "___notes_total" bigint NOT NULL, "___notes_inc" bigint NOT NULL, "___notes_dec" bigint NOT NULL, "___notes_diffs_normal" bigint NOT NULL, "___notes_diffs_reply" bigint NOT NULL, "___notes_diffs_renote" bigint NOT NULL, "___users_total" bigint NOT NULL, "___users_inc" bigint NOT NULL, "___users_dec" bigint NOT NULL, "___following_total" bigint NOT NULL, "___following_inc" bigint NOT NULL, "___following_dec" bigint NOT NULL, "___followers_total" bigint NOT NULL, "___followers_inc" bigint NOT NULL, "___followers_dec" bigint NOT NULL, "___drive_totalFiles" bigint NOT NULL, "___drive_totalUsage" bigint NOT NULL, "___drive_incFiles" bigint NOT NULL, "___drive_incUsage" bigint NOT NULL, "___drive_decFiles" bigint NOT NULL, "___drive_decUsage" bigint NOT NULL, CONSTRAINT "PK_1267c67c7c2d47b4903975f2c00" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE TYPE "__chart__network_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`CREATE TABLE "__chart__network" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__network_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___incomingRequests" bigint NOT NULL, "___outgoingRequests" bigint NOT NULL, "___totalTime" bigint NOT NULL, "___incomingBytes" bigint NOT NULL, "___outgoingBytes" bigint NOT NULL, CONSTRAINT "PK_bc4290c2e27fad14ef0c1ca93f3" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE TYPE "__chart__notes_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`CREATE TABLE "__chart__notes" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__notes_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___local_total" bigint NOT NULL, "___local_inc" bigint NOT NULL, "___local_dec" bigint NOT NULL, "___local_diffs_normal" bigint NOT NULL, "___local_diffs_reply" bigint NOT NULL, "___local_diffs_renote" bigint NOT NULL, "___remote_total" bigint NOT NULL, "___remote_inc" bigint NOT NULL, "___remote_dec" bigint NOT NULL, "___remote_diffs_normal" bigint NOT NULL, "___remote_diffs_reply" bigint NOT NULL, "___remote_diffs_renote" bigint NOT NULL, CONSTRAINT "PK_0aec823fa85c7f901bdb3863b14" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE TYPE "__chart__per_user_drive_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`CREATE TABLE "__chart__per_user_drive" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__per_user_drive_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___totalCount" bigint NOT NULL, "___totalSize" bigint NOT NULL, "___incCount" bigint NOT NULL, "___incSize" bigint NOT NULL, "___decCount" bigint NOT NULL, "___decSize" bigint NOT NULL, CONSTRAINT "PK_d0ef23d24d666e1a44a0cd3d208" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE TYPE "__chart__per_user_following_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`CREATE TABLE "__chart__per_user_following" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__per_user_following_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___local_followings_total" bigint NOT NULL, "___local_followings_inc" bigint NOT NULL, "___local_followings_dec" bigint NOT NULL, "___local_followers_total" bigint NOT NULL, "___local_followers_inc" bigint NOT NULL, "___local_followers_dec" bigint NOT NULL, "___remote_followings_total" bigint NOT NULL, "___remote_followings_inc" bigint NOT NULL, "___remote_followings_dec" bigint NOT NULL, "___remote_followers_total" bigint NOT NULL, "___remote_followers_inc" bigint NOT NULL, "___remote_followers_dec" bigint NOT NULL, CONSTRAINT "PK_85bb1b540363a29c2fec83bd907" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE TYPE "__chart__per_user_notes_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`CREATE TABLE "__chart__per_user_notes" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__per_user_notes_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___total" bigint NOT NULL, "___inc" bigint NOT NULL, "___dec" bigint NOT NULL, "___diffs_normal" bigint NOT NULL, "___diffs_reply" bigint NOT NULL, "___diffs_renote" bigint NOT NULL, CONSTRAINT "PK_334acf6e915af2f29edc11b8e50" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE TYPE "__chart__per_user_reaction_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`CREATE TABLE "__chart__per_user_reaction" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__per_user_reaction_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___local_count" bigint NOT NULL, "___remote_count" bigint NOT NULL, CONSTRAINT "PK_984f54dae441e65b633e8d27a7f" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE TYPE "__chart__test_grouped_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`CREATE TABLE "__chart__test_grouped" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__test_grouped_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___foo_total" bigint NOT NULL, "___foo_inc" bigint NOT NULL, "___foo_dec" bigint NOT NULL, CONSTRAINT "PK_f4a2b175d308695af30d4293272" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE TYPE "__chart__test_unique_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`CREATE TABLE "__chart__test_unique" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__test_unique_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___foo" bigint NOT NULL, CONSTRAINT "PK_409bac9c97cc612d8500012319d" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE TYPE "__chart__test_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`CREATE TABLE "__chart__test" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__test_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___foo_total" bigint NOT NULL, "___foo_inc" bigint NOT NULL, "___foo_dec" bigint NOT NULL, CONSTRAINT "PK_b4bc31dffbd1b785276a3ecfc1e" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE TYPE "__chart__users_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`CREATE TABLE "__chart__users" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128), "span" "__chart__users_span_enum" NOT NULL, "unique" jsonb NOT NULL DEFAULT '{}', "___local_total" bigint NOT NULL, "___local_inc" bigint NOT NULL, "___local_dec" bigint NOT NULL, "___remote_total" bigint NOT NULL, "___remote_inc" bigint NOT NULL, "___remote_dec" bigint NOT NULL, CONSTRAINT "PK_4dfcf2c78d03524b9eb2c99d328" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`ALTER TABLE "drive_folder" ADD CONSTRAINT "FK_f4fc06e49c0171c85f1c48060d2" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "drive_folder" ADD CONSTRAINT "FK_00ceffb0cdc238b3233294f08f2" FOREIGN KEY ("parentId") REFERENCES "drive_folder"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "drive_file" ADD CONSTRAINT "FK_860fa6f6c7df5bb887249fba22e" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "drive_file" ADD CONSTRAINT "FK_bb90d1956dafc4068c28aa7560a" FOREIGN KEY ("folderId") REFERENCES "drive_folder"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user" ADD CONSTRAINT "FK_58f5c71eaab331645112cf8cfa5" FOREIGN KEY ("avatarId") REFERENCES "drive_file"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user" ADD CONSTRAINT "FK_afc64b53f8db3707ceb34eb28e2" FOREIGN KEY ("bannerId") REFERENCES "drive_file"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "app" ADD CONSTRAINT "FK_3f5b0899ef90527a3462d7c2cb3" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "access_token" ADD CONSTRAINT "FK_9949557d0e1b2c19e5344c171e9" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "access_token" ADD CONSTRAINT "FK_a3ff16c90cc87a82a0b5959e560" FOREIGN KEY ("appId") REFERENCES "app"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_17cb3553c700a4985dff5a30ff5" FOREIGN KEY ("replyId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_52ccc804d7c69037d558bac4c96" FOREIGN KEY ("renoteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_ec5c201576192ba8904c345c5cc" FOREIGN KEY ("appId") REFERENCES "app"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_5b87d9d19127bd5d92026017a7b" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "poll_vote" ADD CONSTRAINT "FK_66d2bd2ee31d14bcc23069a89f8" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "poll_vote" ADD CONSTRAINT "FK_aecfbd5ef60374918e63ee95fa7" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "note_reaction" ADD CONSTRAINT "FK_13761f64257f40c5636d0ff95ee" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "note_reaction" ADD CONSTRAINT "FK_45145e4953780f3cd5656f0ea6a" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "note_watching" ADD CONSTRAINT "FK_b0134ec406e8d09a540f8182888" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "note_watching" ADD CONSTRAINT "FK_03e7028ab8388a3f5e3ce2a8619" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "note_unread" ADD CONSTRAINT "FK_56b0166d34ddae49d8ef7610bb9" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "note_unread" ADD CONSTRAINT "FK_e637cba4dc4410218c4251260e4" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_3c601b70a1066d2c8b517094cb9" FOREIGN KEY ("notifieeId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_3b4e96eec8d36a8bbb9d02aa710" FOREIGN KEY ("notifierId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_769cb6b73a1efe22ddf733ac453" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "following" ADD CONSTRAINT "FK_24e0042143a18157b234df186c3" FOREIGN KEY ("followeeId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "following" ADD CONSTRAINT "FK_6516c5a6f3c015b4eed39978be5" FOREIGN KEY ("followerId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "muting" ADD CONSTRAINT "FK_ec96b4fed9dae517e0dbbe0675c" FOREIGN KEY ("muteeId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "muting" ADD CONSTRAINT "FK_93060675b4a79a577f31d260c67" FOREIGN KEY ("muterId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "sw_subscription" ADD CONSTRAINT "FK_97754ca6f2baff9b4abb7f853dd" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "blocking" ADD CONSTRAINT "FK_2cd4a2743a99671308f5417759e" FOREIGN KEY ("blockeeId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "blocking" ADD CONSTRAINT "FK_0627125f1a8a42c9a1929edb552" FOREIGN KEY ("blockerId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_list" ADD CONSTRAINT "FK_b7fcefbdd1c18dce86687531f99" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_list_joining" ADD CONSTRAINT "FK_d844bfc6f3f523a05189076efaa" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_list_joining" ADD CONSTRAINT "FK_605472305f26818cc93d1baaa74" FOREIGN KEY ("userListId") REFERENCES "user_list"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "note_favorite" ADD CONSTRAINT "FK_47f4b1892f5d6ba8efb3057d81a" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "note_favorite" ADD CONSTRAINT "FK_0e00498f180193423c992bc4370" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_d049123c413e68ca52abe734203" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_04cc96756f89d0b7f9473e8cdf3" FOREIGN KEY ("reporterId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "messaging_message" ADD CONSTRAINT "FK_5377c307783fce2b6d352e1203b" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "messaging_message" ADD CONSTRAINT "FK_cac14a4e3944454a5ce7daa5142" FOREIGN KEY ("recipientId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "messaging_message" ADD CONSTRAINT "FK_535def119223ac05ad3fa9ef64b" FOREIGN KEY ("fileId") REFERENCES "drive_file"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "signin" ADD CONSTRAINT "FK_2c308dbdc50d94dc625670055f7" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "auth_session" ADD CONSTRAINT "FK_c072b729d71697f959bde66ade0" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "auth_session" ADD CONSTRAINT "FK_dbe037d4bddd17b03a1dc778dee" FOREIGN KEY ("appId") REFERENCES "app"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "follow_request" ADD CONSTRAINT "FK_12c01c0d1a79f77d9f6c15fadd2" FOREIGN KEY ("followeeId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "follow_request" ADD CONSTRAINT "FK_a7fd92dd6dc519e6fb435dd108f" FOREIGN KEY ("followerId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "reversi_game" ADD CONSTRAINT "FK_f7467510c60a45ce5aca6292743" FOREIGN KEY ("user1Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "reversi_game" ADD CONSTRAINT "FK_6649a4e8c5d5cf32fb03b5da9f6" FOREIGN KEY ("user2Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "reversi_matching" ADD CONSTRAINT "FK_3b25402709dd9882048c2bbade0" FOREIGN KEY ("parentId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "reversi_matching" ADD CONSTRAINT "FK_e247b23a3c9b45f89ec1299d066" FOREIGN KEY ("childId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_note_pining" ADD CONSTRAINT "FK_bfbc6f79ba4007b4ce5097f08d6" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_note_pining" ADD CONSTRAINT "FK_68881008f7c3588ad7ecae471cf" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_publickey" ADD CONSTRAINT "FK_10c146e4b39b443ede016f6736d" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9"`);
+ await queryRunner.query(`ALTER TABLE "user_publickey" DROP CONSTRAINT "FK_10c146e4b39b443ede016f6736d"`);
+ await queryRunner.query(`ALTER TABLE "user_keypair" DROP CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6"`);
+ await queryRunner.query(`ALTER TABLE "poll" DROP CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b"`);
+ await queryRunner.query(`ALTER TABLE "user_note_pining" DROP CONSTRAINT "FK_68881008f7c3588ad7ecae471cf"`);
+ await queryRunner.query(`ALTER TABLE "user_note_pining" DROP CONSTRAINT "FK_bfbc6f79ba4007b4ce5097f08d6"`);
+ await queryRunner.query(`ALTER TABLE "reversi_matching" DROP CONSTRAINT "FK_e247b23a3c9b45f89ec1299d066"`);
+ await queryRunner.query(`ALTER TABLE "reversi_matching" DROP CONSTRAINT "FK_3b25402709dd9882048c2bbade0"`);
+ await queryRunner.query(`ALTER TABLE "reversi_game" DROP CONSTRAINT "FK_6649a4e8c5d5cf32fb03b5da9f6"`);
+ await queryRunner.query(`ALTER TABLE "reversi_game" DROP CONSTRAINT "FK_f7467510c60a45ce5aca6292743"`);
+ await queryRunner.query(`ALTER TABLE "follow_request" DROP CONSTRAINT "FK_a7fd92dd6dc519e6fb435dd108f"`);
+ await queryRunner.query(`ALTER TABLE "follow_request" DROP CONSTRAINT "FK_12c01c0d1a79f77d9f6c15fadd2"`);
+ await queryRunner.query(`ALTER TABLE "auth_session" DROP CONSTRAINT "FK_dbe037d4bddd17b03a1dc778dee"`);
+ await queryRunner.query(`ALTER TABLE "auth_session" DROP CONSTRAINT "FK_c072b729d71697f959bde66ade0"`);
+ await queryRunner.query(`ALTER TABLE "signin" DROP CONSTRAINT "FK_2c308dbdc50d94dc625670055f7"`);
+ await queryRunner.query(`ALTER TABLE "messaging_message" DROP CONSTRAINT "FK_535def119223ac05ad3fa9ef64b"`);
+ await queryRunner.query(`ALTER TABLE "messaging_message" DROP CONSTRAINT "FK_cac14a4e3944454a5ce7daa5142"`);
+ await queryRunner.query(`ALTER TABLE "messaging_message" DROP CONSTRAINT "FK_5377c307783fce2b6d352e1203b"`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_04cc96756f89d0b7f9473e8cdf3"`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_d049123c413e68ca52abe734203"`);
+ await queryRunner.query(`ALTER TABLE "note_favorite" DROP CONSTRAINT "FK_0e00498f180193423c992bc4370"`);
+ await queryRunner.query(`ALTER TABLE "note_favorite" DROP CONSTRAINT "FK_47f4b1892f5d6ba8efb3057d81a"`);
+ await queryRunner.query(`ALTER TABLE "user_list_joining" DROP CONSTRAINT "FK_605472305f26818cc93d1baaa74"`);
+ await queryRunner.query(`ALTER TABLE "user_list_joining" DROP CONSTRAINT "FK_d844bfc6f3f523a05189076efaa"`);
+ await queryRunner.query(`ALTER TABLE "user_list" DROP CONSTRAINT "FK_b7fcefbdd1c18dce86687531f99"`);
+ await queryRunner.query(`ALTER TABLE "blocking" DROP CONSTRAINT "FK_0627125f1a8a42c9a1929edb552"`);
+ await queryRunner.query(`ALTER TABLE "blocking" DROP CONSTRAINT "FK_2cd4a2743a99671308f5417759e"`);
+ await queryRunner.query(`ALTER TABLE "sw_subscription" DROP CONSTRAINT "FK_97754ca6f2baff9b4abb7f853dd"`);
+ await queryRunner.query(`ALTER TABLE "muting" DROP CONSTRAINT "FK_93060675b4a79a577f31d260c67"`);
+ await queryRunner.query(`ALTER TABLE "muting" DROP CONSTRAINT "FK_ec96b4fed9dae517e0dbbe0675c"`);
+ await queryRunner.query(`ALTER TABLE "following" DROP CONSTRAINT "FK_6516c5a6f3c015b4eed39978be5"`);
+ await queryRunner.query(`ALTER TABLE "following" DROP CONSTRAINT "FK_24e0042143a18157b234df186c3"`);
+ await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_769cb6b73a1efe22ddf733ac453"`);
+ await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_3b4e96eec8d36a8bbb9d02aa710"`);
+ await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_3c601b70a1066d2c8b517094cb9"`);
+ await queryRunner.query(`ALTER TABLE "note_unread" DROP CONSTRAINT "FK_e637cba4dc4410218c4251260e4"`);
+ await queryRunner.query(`ALTER TABLE "note_unread" DROP CONSTRAINT "FK_56b0166d34ddae49d8ef7610bb9"`);
+ await queryRunner.query(`ALTER TABLE "note_watching" DROP CONSTRAINT "FK_03e7028ab8388a3f5e3ce2a8619"`);
+ await queryRunner.query(`ALTER TABLE "note_watching" DROP CONSTRAINT "FK_b0134ec406e8d09a540f8182888"`);
+ await queryRunner.query(`ALTER TABLE "note_reaction" DROP CONSTRAINT "FK_45145e4953780f3cd5656f0ea6a"`);
+ await queryRunner.query(`ALTER TABLE "note_reaction" DROP CONSTRAINT "FK_13761f64257f40c5636d0ff95ee"`);
+ await queryRunner.query(`ALTER TABLE "poll_vote" DROP CONSTRAINT "FK_aecfbd5ef60374918e63ee95fa7"`);
+ await queryRunner.query(`ALTER TABLE "poll_vote" DROP CONSTRAINT "FK_66d2bd2ee31d14bcc23069a89f8"`);
+ await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_5b87d9d19127bd5d92026017a7b"`);
+ await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_ec5c201576192ba8904c345c5cc"`);
+ await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_52ccc804d7c69037d558bac4c96"`);
+ await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_17cb3553c700a4985dff5a30ff5"`);
+ await queryRunner.query(`ALTER TABLE "access_token" DROP CONSTRAINT "FK_a3ff16c90cc87a82a0b5959e560"`);
+ await queryRunner.query(`ALTER TABLE "access_token" DROP CONSTRAINT "FK_9949557d0e1b2c19e5344c171e9"`);
+ await queryRunner.query(`ALTER TABLE "app" DROP CONSTRAINT "FK_3f5b0899ef90527a3462d7c2cb3"`);
+ await queryRunner.query(`ALTER TABLE "user" DROP CONSTRAINT "FK_afc64b53f8db3707ceb34eb28e2"`);
+ await queryRunner.query(`ALTER TABLE "user" DROP CONSTRAINT "FK_58f5c71eaab331645112cf8cfa5"`);
+ await queryRunner.query(`ALTER TABLE "drive_file" DROP CONSTRAINT "FK_bb90d1956dafc4068c28aa7560a"`);
+ await queryRunner.query(`ALTER TABLE "drive_file" DROP CONSTRAINT "FK_860fa6f6c7df5bb887249fba22e"`);
+ await queryRunner.query(`ALTER TABLE "drive_folder" DROP CONSTRAINT "FK_00ceffb0cdc238b3233294f08f2"`);
+ await queryRunner.query(`ALTER TABLE "drive_folder" DROP CONSTRAINT "FK_f4fc06e49c0171c85f1c48060d2"`);
+ await queryRunner.query(`DROP TABLE "__chart__users"`);
+ await queryRunner.query(`DROP TYPE "__chart__users_span_enum"`);
+ await queryRunner.query(`DROP TABLE "__chart__test"`);
+ await queryRunner.query(`DROP TYPE "__chart__test_span_enum"`);
+ await queryRunner.query(`DROP TABLE "__chart__test_unique"`);
+ await queryRunner.query(`DROP TYPE "__chart__test_unique_span_enum"`);
+ await queryRunner.query(`DROP TABLE "__chart__test_grouped"`);
+ await queryRunner.query(`DROP TYPE "__chart__test_grouped_span_enum"`);
+ await queryRunner.query(`DROP TABLE "__chart__per_user_reaction"`);
+ await queryRunner.query(`DROP TYPE "__chart__per_user_reaction_span_enum"`);
+ await queryRunner.query(`DROP TABLE "__chart__per_user_notes"`);
+ await queryRunner.query(`DROP TYPE "__chart__per_user_notes_span_enum"`);
+ await queryRunner.query(`DROP TABLE "__chart__per_user_following"`);
+ await queryRunner.query(`DROP TYPE "__chart__per_user_following_span_enum"`);
+ await queryRunner.query(`DROP TABLE "__chart__per_user_drive"`);
+ await queryRunner.query(`DROP TYPE "__chart__per_user_drive_span_enum"`);
+ await queryRunner.query(`DROP TABLE "__chart__notes"`);
+ await queryRunner.query(`DROP TYPE "__chart__notes_span_enum"`);
+ await queryRunner.query(`DROP TABLE "__chart__network"`);
+ await queryRunner.query(`DROP TYPE "__chart__network_span_enum"`);
+ await queryRunner.query(`DROP TABLE "__chart__instance"`);
+ await queryRunner.query(`DROP TYPE "__chart__instance_span_enum"`);
+ await queryRunner.query(`DROP TABLE "__chart__hashtag"`);
+ await queryRunner.query(`DROP TYPE "__chart__hashtag_span_enum"`);
+ await queryRunner.query(`DROP TABLE "__chart__federation"`);
+ await queryRunner.query(`DROP TYPE "__chart__federation_span_enum"`);
+ await queryRunner.query(`DROP TABLE "__chart__drive"`);
+ await queryRunner.query(`DROP TYPE "__chart__drive_span_enum"`);
+ await queryRunner.query(`DROP TABLE "__chart__active_users"`);
+ await queryRunner.query(`DROP TYPE "__chart__active_users_span_enum"`);
+ await queryRunner.query(`DROP INDEX "IDX_dce530b98e454793dac5ec2f5a"`);
+ await queryRunner.query(`DROP TABLE "user_profile"`);
+ await queryRunner.query(`DROP INDEX "IDX_171e64971c780ebd23fae140bb"`);
+ await queryRunner.query(`DROP TABLE "user_publickey"`);
+ await queryRunner.query(`DROP TABLE "user_keypair"`);
+ await queryRunner.query(`DROP INDEX "IDX_7fa20a12319c7f6dc3aed98c0a"`);
+ await queryRunner.query(`DROP INDEX "IDX_0610ebcfcfb4a18441a9bcdab2"`);
+ await queryRunner.query(`DROP TABLE "poll"`);
+ await queryRunner.query(`DROP TYPE "poll_notevisibility_enum"`);
+ await queryRunner.query(`DROP INDEX "IDX_410cd649884b501c02d6e72738"`);
+ await queryRunner.query(`DROP INDEX "IDX_bfbc6f79ba4007b4ce5097f08d"`);
+ await queryRunner.query(`DROP TABLE "user_note_pining"`);
+ await queryRunner.query(`DROP INDEX "IDX_e247b23a3c9b45f89ec1299d06"`);
+ await queryRunner.query(`DROP INDEX "IDX_3b25402709dd9882048c2bbade"`);
+ await queryRunner.query(`DROP INDEX "IDX_b604d92d6c7aec38627f6eaf16"`);
+ await queryRunner.query(`DROP TABLE "reversi_matching"`);
+ await queryRunner.query(`DROP INDEX "IDX_b46ec40746efceac604142be1c"`);
+ await queryRunner.query(`DROP TABLE "reversi_game"`);
+ await queryRunner.query(`DROP INDEX "IDX_4f4d35e1256c84ae3d1f0eab10"`);
+ await queryRunner.query(`DROP INDEX "IDX_5900e907bb46516ddf2871327c"`);
+ await queryRunner.query(`DROP INDEX "IDX_b37dafc86e9af007e3295c2781"`);
+ await queryRunner.query(`DROP TABLE "emoji"`);
+ await queryRunner.query(`DROP INDEX "IDX_d54a512b822fac7ed52800f6b4"`);
+ await queryRunner.query(`DROP INDEX "IDX_a7fd92dd6dc519e6fb435dd108"`);
+ await queryRunner.query(`DROP INDEX "IDX_12c01c0d1a79f77d9f6c15fadd"`);
+ await queryRunner.query(`DROP TABLE "follow_request"`);
+ await queryRunner.query(`DROP INDEX "IDX_62cb09e1129f6ec024ef66e183"`);
+ await queryRunner.query(`DROP TABLE "auth_session"`);
+ await queryRunner.query(`DROP INDEX "IDX_2c308dbdc50d94dc625670055f"`);
+ await queryRunner.query(`DROP TABLE "signin"`);
+ await queryRunner.query(`DROP INDEX "IDX_cac14a4e3944454a5ce7daa514"`);
+ await queryRunner.query(`DROP INDEX "IDX_5377c307783fce2b6d352e1203"`);
+ await queryRunner.query(`DROP INDEX "IDX_e21cd3646e52ef9c94aaf17c2e"`);
+ await queryRunner.query(`DROP TABLE "messaging_message"`);
+ await queryRunner.query(`DROP INDEX "IDX_0ff69e8dfa9fe31bb4a4660f59"`);
+ await queryRunner.query(`DROP TABLE "registration_ticket"`);
+ await queryRunner.query(`DROP INDEX "IDX_5cd442c3b2e74fdd99dae20243"`);
+ await queryRunner.query(`DROP INDEX "IDX_04cc96756f89d0b7f9473e8cdf"`);
+ await queryRunner.query(`DROP INDEX "IDX_d049123c413e68ca52abe73420"`);
+ await queryRunner.query(`DROP INDEX "IDX_db2098070b2b5a523c58181f74"`);
+ await queryRunner.query(`DROP TABLE "abuse_user_report"`);
+ await queryRunner.query(`DROP INDEX "IDX_0f4fb9ad355f3effff221ef245"`);
+ await queryRunner.query(`DROP INDEX "IDX_47f4b1892f5d6ba8efb3057d81"`);
+ await queryRunner.query(`DROP TABLE "note_favorite"`);
+ await queryRunner.query(`DROP INDEX "IDX_0b03cbcd7e6a7ce068efa8ecc2"`);
+ await queryRunner.query(`DROP INDEX "IDX_0c44bf4f680964145f2a68a341"`);
+ await queryRunner.query(`DROP INDEX "IDX_d57f9030cd3af7f63ffb1c267c"`);
+ await queryRunner.query(`DROP INDEX "IDX_4c02d38a976c3ae132228c6fce"`);
+ await queryRunner.query(`DROP INDEX "IDX_0e206cec573f1edff4a3062923"`);
+ await queryRunner.query(`DROP INDEX "IDX_2710a55f826ee236ea1a62698f"`);
+ await queryRunner.query(`DROP INDEX "IDX_347fec870eafea7b26c8a73bac"`);
+ await queryRunner.query(`DROP TABLE "hashtag"`);
+ await queryRunner.query(`DROP INDEX "IDX_605472305f26818cc93d1baaa7"`);
+ await queryRunner.query(`DROP INDEX "IDX_d844bfc6f3f523a05189076efa"`);
+ await queryRunner.query(`DROP TABLE "user_list_joining"`);
+ await queryRunner.query(`DROP INDEX "IDX_b7fcefbdd1c18dce86687531f9"`);
+ await queryRunner.query(`DROP TABLE "user_list"`);
+ await queryRunner.query(`DROP INDEX "IDX_98a1bc5cb30dfd159de056549f"`);
+ await queryRunner.query(`DROP INDEX "IDX_0627125f1a8a42c9a1929edb55"`);
+ await queryRunner.query(`DROP INDEX "IDX_2cd4a2743a99671308f5417759"`);
+ await queryRunner.query(`DROP INDEX "IDX_b9a354f7941c1e779f3b33aea6"`);
+ await queryRunner.query(`DROP TABLE "blocking"`);
+ await queryRunner.query(`DROP INDEX "IDX_97754ca6f2baff9b4abb7f853d"`);
+ await queryRunner.query(`DROP TABLE "sw_subscription"`);
+ await queryRunner.query(`DROP INDEX "IDX_1eb9d9824a630321a29fd3b290"`);
+ await queryRunner.query(`DROP INDEX "IDX_93060675b4a79a577f31d260c6"`);
+ await queryRunner.query(`DROP INDEX "IDX_ec96b4fed9dae517e0dbbe0675"`);
+ await queryRunner.query(`DROP INDEX "IDX_f86d57fbca33c7a4e6897490cc"`);
+ await queryRunner.query(`DROP TABLE "muting"`);
+ await queryRunner.query(`DROP INDEX "IDX_8d5afc98982185799b160e10eb"`);
+ await queryRunner.query(`DROP INDEX "IDX_2cd3b2a6b4cf0b910b260afe08"`);
+ await queryRunner.query(`DROP TABLE "instance"`);
+ await queryRunner.query(`DROP INDEX "IDX_307be5f1d1252e0388662acb96"`);
+ await queryRunner.query(`DROP INDEX "IDX_6516c5a6f3c015b4eed39978be"`);
+ await queryRunner.query(`DROP INDEX "IDX_24e0042143a18157b234df186c"`);
+ await queryRunner.query(`DROP INDEX "IDX_582f8fab771a9040a12961f3e7"`);
+ await queryRunner.query(`DROP TABLE "following"`);
+ await queryRunner.query(`DROP TABLE "meta"`);
+ await queryRunner.query(`DROP INDEX "IDX_3c601b70a1066d2c8b517094cb"`);
+ await queryRunner.query(`DROP INDEX "IDX_b11a5e627c41d4dc3170f1d370"`);
+ await queryRunner.query(`DROP TABLE "notification"`);
+ await queryRunner.query(`DROP INDEX "IDX_d908433a4953cc13216cd9c274"`);
+ await queryRunner.query(`DROP INDEX "IDX_e637cba4dc4410218c4251260e"`);
+ await queryRunner.query(`DROP INDEX "IDX_56b0166d34ddae49d8ef7610bb"`);
+ await queryRunner.query(`DROP TABLE "note_unread"`);
+ await queryRunner.query(`DROP INDEX "IDX_a42c93c69989ce1d09959df4cf"`);
+ await queryRunner.query(`DROP INDEX "IDX_44499765eec6b5489d72c4253b"`);
+ await queryRunner.query(`DROP INDEX "IDX_03e7028ab8388a3f5e3ce2a861"`);
+ await queryRunner.query(`DROP INDEX "IDX_b0134ec406e8d09a540f818288"`);
+ await queryRunner.query(`DROP INDEX "IDX_318cdf42a9cfc11f479bd802bb"`);
+ await queryRunner.query(`DROP TABLE "note_watching"`);
+ await queryRunner.query(`DROP INDEX "IDX_ad0c221b25672daf2df320a817"`);
+ await queryRunner.query(`DROP INDEX "IDX_45145e4953780f3cd5656f0ea6"`);
+ await queryRunner.query(`DROP INDEX "IDX_13761f64257f40c5636d0ff95e"`);
+ await queryRunner.query(`DROP INDEX "IDX_01f4581f114e0ebd2bbb876f0b"`);
+ await queryRunner.query(`DROP TABLE "note_reaction"`);
+ await queryRunner.query(`DROP INDEX "IDX_50bd7164c5b78f1f4a42c4d21f"`);
+ await queryRunner.query(`DROP INDEX "IDX_aecfbd5ef60374918e63ee95fa"`);
+ await queryRunner.query(`DROP INDEX "IDX_66d2bd2ee31d14bcc23069a89f"`);
+ await queryRunner.query(`DROP INDEX "IDX_0fb627e1c2f753262a74f0562d"`);
+ await queryRunner.query(`DROP TABLE "poll_vote"`);
+ await queryRunner.query(`DROP INDEX "IDX_7125a826ab192eb27e11d358a5"`);
+ await queryRunner.query(`DROP INDEX "IDX_88937d94d7443d9a99a76fa5c0"`);
+ await queryRunner.query(`DROP INDEX "IDX_54ebcb6d27222913b908d56fd8"`);
+ await queryRunner.query(`DROP INDEX "IDX_796a8c03959361f97dc2be1d5c"`);
+ await queryRunner.query(`DROP INDEX "IDX_25dfc71b0369b003a4cd434d0b"`);
+ await queryRunner.query(`DROP INDEX "IDX_51c063b6a133a9cb87145450f5"`);
+ await queryRunner.query(`DROP INDEX "IDX_153536c67d05e9adb24e99fc2b"`);
+ await queryRunner.query(`DROP INDEX "IDX_5b87d9d19127bd5d92026017a7"`);
+ await queryRunner.query(`DROP INDEX "IDX_52ccc804d7c69037d558bac4c9"`);
+ await queryRunner.query(`DROP INDEX "IDX_17cb3553c700a4985dff5a30ff"`);
+ await queryRunner.query(`DROP INDEX "IDX_e7c0567f5261063592f022e9b5"`);
+ await queryRunner.query(`DROP TABLE "note"`);
+ await queryRunner.query(`DROP TYPE "note_visibility_enum"`);
+ await queryRunner.query(`DROP INDEX "IDX_9949557d0e1b2c19e5344c171e"`);
+ await queryRunner.query(`DROP INDEX "IDX_64c327441248bae40f7d92f34f"`);
+ await queryRunner.query(`DROP INDEX "IDX_70ba8f6af34bc924fc9e12adb8"`);
+ await queryRunner.query(`DROP TABLE "access_token"`);
+ await queryRunner.query(`DROP INDEX "IDX_f49922d511d666848f250663c4"`);
+ await queryRunner.query(`DROP INDEX "IDX_3f5b0899ef90527a3462d7c2cb"`);
+ await queryRunner.query(`DROP INDEX "IDX_048a757923ed8b157e9895da53"`);
+ await queryRunner.query(`DROP TABLE "app"`);
+ await queryRunner.query(`DROP INDEX "IDX_5deb01ae162d1d70b80d064c27"`);
+ await queryRunner.query(`DROP INDEX "IDX_a854e557b1b14814750c7c7b0c"`);
+ await queryRunner.query(`DROP INDEX "IDX_be623adaa4c566baf5d29ce0c8"`);
+ await queryRunner.query(`DROP INDEX "IDX_3252a5df8d5bbd16b281f7799e"`);
+ await queryRunner.query(`DROP INDEX "IDX_fa99d777623947a5b05f394cae"`);
+ await queryRunner.query(`DROP INDEX "IDX_a27b942a0d6dcff90e3ee9b5e8"`);
+ await queryRunner.query(`DROP INDEX "IDX_80ca6e6ef65fb9ef34ea8c90f4"`);
+ await queryRunner.query(`DROP INDEX "IDX_e11e649824a45d8ed01d597fd9"`);
+ await queryRunner.query(`DROP TABLE "user"`);
+ await queryRunner.query(`DROP INDEX "IDX_bb90d1956dafc4068c28aa7560"`);
+ await queryRunner.query(`DROP INDEX "IDX_e5848eac4940934e23dbc17581"`);
+ await queryRunner.query(`DROP INDEX "IDX_c55b2b7c284d9fef98026fc88e"`);
+ await queryRunner.query(`DROP INDEX "IDX_e74022ce9a074b3866f70e0d27"`);
+ await queryRunner.query(`DROP INDEX "IDX_d85a184c2540d2deba33daf642"`);
+ await queryRunner.query(`DROP INDEX "IDX_a40b8df8c989d7db937ea27cf6"`);
+ await queryRunner.query(`DROP INDEX "IDX_37bb9a1b4585f8a3beb24c62d6"`);
+ await queryRunner.query(`DROP INDEX "IDX_92779627994ac79277f070c91e"`);
+ await queryRunner.query(`DROP INDEX "IDX_860fa6f6c7df5bb887249fba22"`);
+ await queryRunner.query(`DROP INDEX "IDX_c8dfad3b72196dd1d6b5db168a"`);
+ await queryRunner.query(`DROP TABLE "drive_file"`);
+ await queryRunner.query(`DROP INDEX "IDX_00ceffb0cdc238b3233294f08f"`);
+ await queryRunner.query(`DROP INDEX "IDX_f4fc06e49c0171c85f1c48060d"`);
+ await queryRunner.query(`DROP INDEX "IDX_02878d441ceae15ce060b73daf"`);
+ await queryRunner.query(`DROP TABLE "drive_folder"`);
+ await queryRunner.query(`DROP INDEX "IDX_584b536b49e53ac81beb39a177"`);
+ await queryRunner.query(`DROP INDEX "IDX_8cb40cfc8f3c28261e6f887b03"`);
+ await queryRunner.query(`DROP INDEX "IDX_8e4eb51a35d81b64dda28eed0a"`);
+ await queryRunner.query(`DROP TABLE "log"`);
+ await queryRunner.query(`DROP TYPE "log_level_enum"`);
+ }
+}
diff --git a/packages/backend/built/migration/1556348509290-Pages.js b/packages/backend/built/migration/1556348509290-Pages.js
new file mode 100644
index 0000000..6bfc60c
--- /dev/null
+++ b/packages/backend/built/migration/1556348509290-Pages.js
@@ -0,0 +1,26 @@
+export class Pages1556348509290 {
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TYPE "page_visibility_enum" AS ENUM('public', 'followers', 'specified')`);
+ await queryRunner.query(`CREATE TABLE "page" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "title" character varying(256) NOT NULL, "name" character varying(256) NOT NULL, "summary" character varying(256), "alignCenter" boolean NOT NULL, "font" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "eyeCatchingImageId" character varying(32), "content" jsonb NOT NULL DEFAULT '[]', "variables" jsonb NOT NULL DEFAULT '[]', "visibility" "page_visibility_enum" NOT NULL, "visibleUserIds" character varying(32) array NOT NULL DEFAULT '{}'::varchar[], CONSTRAINT "PK_742f4117e065c5b6ad21b37ba1f" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_fbb4297c927a9b85e9cefa2eb1" ON "page" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_af639b066dfbca78b01a920f8a" ON "page" ("updatedAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_b82c19c08afb292de4600d99e4" ON "page" ("name") `);
+ await queryRunner.query(`CREATE INDEX "IDX_ae1d917992dd0c9d9bbdad06c4" ON "page" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_90148bbc2bf0854428786bfc15" ON "page" ("visibleUserIds") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_2133ef8317e4bdb839c0dcbf13" ON "page" ("userId", "name") `);
+ await queryRunner.query(`ALTER TABLE "page" ADD CONSTRAINT "FK_ae1d917992dd0c9d9bbdad06c4a" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "page" ADD CONSTRAINT "FK_3126dd7c502c9e4d7597ef7ef10" FOREIGN KEY ("eyeCatchingImageId") REFERENCES "drive_file"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "page" DROP CONSTRAINT "FK_3126dd7c502c9e4d7597ef7ef10"`);
+ await queryRunner.query(`ALTER TABLE "page" DROP CONSTRAINT "FK_ae1d917992dd0c9d9bbdad06c4a"`);
+ await queryRunner.query(`DROP INDEX "IDX_2133ef8317e4bdb839c0dcbf13"`);
+ await queryRunner.query(`DROP INDEX "IDX_90148bbc2bf0854428786bfc15"`);
+ await queryRunner.query(`DROP INDEX "IDX_ae1d917992dd0c9d9bbdad06c4"`);
+ await queryRunner.query(`DROP INDEX "IDX_b82c19c08afb292de4600d99e4"`);
+ await queryRunner.query(`DROP INDEX "IDX_af639b066dfbca78b01a920f8a"`);
+ await queryRunner.query(`DROP INDEX "IDX_fbb4297c927a9b85e9cefa2eb1"`);
+ await queryRunner.query(`DROP TABLE "page"`);
+ await queryRunner.query(`DROP TYPE "page_visibility_enum"`);
+ }
+}
diff --git a/packages/backend/built/migration/1556746559567-UserProfile.js b/packages/backend/built/migration/1556746559567-UserProfile.js
new file mode 100644
index 0000000..f69d6dc
--- /dev/null
+++ b/packages/backend/built/migration/1556746559567-UserProfile.js
@@ -0,0 +1,11 @@
+export class UserProfile1556746559567 {
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "githubId" TYPE VARCHAR(64) USING "githubId"::VARCHAR(64)`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "discordExpiresDate" TYPE VARCHAR(64) USING "discordExpiresDate"::VARCHAR(64)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`UPDATE "user_profile" SET github = FALSE, discord = FALSE`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "githubId" TYPE INTEGER USING NULL`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "discordExpiresDate" TYPE INTEGER USING NULL`);
+ }
+}
diff --git a/packages/backend/built/migration/1557476068003-PinnedUsers.js b/packages/backend/built/migration/1557476068003-PinnedUsers.js
new file mode 100644
index 0000000..0eeef56
--- /dev/null
+++ b/packages/backend/built/migration/1557476068003-PinnedUsers.js
@@ -0,0 +1,8 @@
+export class PinnedUsers1557476068003 {
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "pinnedUsers" character varying(256) array NOT NULL DEFAULT '{}'::varchar[]`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "pinnedUsers"`);
+ }
+}
diff --git a/packages/backend/built/migration/1557761316509-AddSomeUrls.js b/packages/backend/built/migration/1557761316509-AddSomeUrls.js
new file mode 100644
index 0000000..df718eb
--- /dev/null
+++ b/packages/backend/built/migration/1557761316509-AddSomeUrls.js
@@ -0,0 +1,12 @@
+export class AddSomeUrls1557761316509 {
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "ToSUrl" character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "repositoryUrl" character varying(512) NOT NULL DEFAULT 'https://codeberg.org/firefish/firefish'`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "feedbackUrl" character varying(512) DEFAULT 'https://codeberg.org/firefish/firefish/issues'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "feedbackUrl"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "repositoryUrl"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "ToSUrl"`);
+ }
+}
diff --git a/packages/backend/built/migration/1557932705754-ObjectStorageSetting.js b/packages/backend/built/migration/1557932705754-ObjectStorageSetting.js
new file mode 100644
index 0000000..26b7d68
--- /dev/null
+++ b/packages/backend/built/migration/1557932705754-ObjectStorageSetting.js
@@ -0,0 +1,26 @@
+export class ObjectStorageSetting1557932705754 {
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "useObjectStorage" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageBucket" character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "objectStoragePrefix" character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageBaseUrl" character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageEndpoint" character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageRegion" character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageAccessKey" character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageSecretKey" character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "objectStoragePort" integer`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageUseSSL" boolean NOT NULL DEFAULT true`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageUseSSL"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStoragePort"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageSecretKey"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageAccessKey"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageRegion"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageEndpoint"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageBaseUrl"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStoragePrefix"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageBucket"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "useObjectStorage"`);
+ }
+}
diff --git a/packages/backend/built/migration/1558072954435-PageLike.js b/packages/backend/built/migration/1558072954435-PageLike.js
new file mode 100644
index 0000000..e68d55a
--- /dev/null
+++ b/packages/backend/built/migration/1558072954435-PageLike.js
@@ -0,0 +1,18 @@
+export class PageLike1558072954435 {
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "page_like" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "pageId" character varying(32) NOT NULL, CONSTRAINT "PK_813f034843af992d3ae0f43c64c" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_0e61efab7f88dbb79c9166dbb4" ON "page_like" ("userId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_4ce6fb9c70529b4c8ac46c9bfa" ON "page_like" ("userId", "pageId") `);
+ await queryRunner.query(`ALTER TABLE "page" ADD "likedCount" integer NOT NULL DEFAULT 0`);
+ await queryRunner.query(`ALTER TABLE "page_like" ADD CONSTRAINT "FK_0e61efab7f88dbb79c9166dbb48" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "page_like" ADD CONSTRAINT "FK_cf8782626dced3176038176a847" FOREIGN KEY ("pageId") REFERENCES "page"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "page_like" DROP CONSTRAINT "FK_cf8782626dced3176038176a847"`);
+ await queryRunner.query(`ALTER TABLE "page_like" DROP CONSTRAINT "FK_0e61efab7f88dbb79c9166dbb48"`);
+ await queryRunner.query(`ALTER TABLE "page" DROP COLUMN "likedCount"`);
+ await queryRunner.query(`DROP INDEX "IDX_4ce6fb9c70529b4c8ac46c9bfa"`);
+ await queryRunner.query(`DROP INDEX "IDX_0e61efab7f88dbb79c9166dbb4"`);
+ await queryRunner.query(`DROP TABLE "page_like"`);
+ }
+}
diff --git a/packages/backend/built/migration/1558103093633-UserGroup.js b/packages/backend/built/migration/1558103093633-UserGroup.js
new file mode 100644
index 0000000..a1707ef
--- /dev/null
+++ b/packages/backend/built/migration/1558103093633-UserGroup.js
@@ -0,0 +1,36 @@
+export class UserGroup1558103093633 {
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "user_group" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "name" character varying(256) NOT NULL, "userId" character varying(32) NOT NULL, "isPrivate" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_3c29fba6fe013ec8724378ce7c9" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_20e30aa35180e317e133d75316" ON "user_group" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_3d6b372788ab01be58853003c9" ON "user_group" ("userId") `);
+ await queryRunner.query(`CREATE TABLE "user_group_joining" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "userGroupId" character varying(32) NOT NULL, CONSTRAINT "PK_15f2425885253c5507e1599cfe7" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_f3a1b4bd0c7cabba958a0c0b23" ON "user_group_joining" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_67dc758bc0566985d1b3d39986" ON "user_group_joining" ("userGroupId") `);
+ await queryRunner.query(`ALTER TABLE "messaging_message" ADD "groupId" character varying(32)`);
+ await queryRunner.query(`ALTER TABLE "messaging_message" ADD "reads" character varying(32) array NOT NULL DEFAULT '{}'::varchar[]`);
+ await queryRunner.query(`ALTER TABLE "messaging_message" ALTER COLUMN "recipientId" DROP NOT NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "messaging_message"."recipientId" IS 'The recipient user ID.'`);
+ await queryRunner.query(`CREATE INDEX "IDX_2c4be03b446884f9e9c502135b" ON "messaging_message" ("groupId") `);
+ await queryRunner.query(`ALTER TABLE "messaging_message" ADD CONSTRAINT "FK_2c4be03b446884f9e9c502135be" FOREIGN KEY ("groupId") REFERENCES "user_group"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_group" ADD CONSTRAINT "FK_3d6b372788ab01be58853003c93" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_group_joining" ADD CONSTRAINT "FK_f3a1b4bd0c7cabba958a0c0b231" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_group_joining" ADD CONSTRAINT "FK_67dc758bc0566985d1b3d399865" FOREIGN KEY ("userGroupId") REFERENCES "user_group"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_group_joining" DROP CONSTRAINT "FK_67dc758bc0566985d1b3d399865"`);
+ await queryRunner.query(`ALTER TABLE "user_group_joining" DROP CONSTRAINT "FK_f3a1b4bd0c7cabba958a0c0b231"`);
+ await queryRunner.query(`ALTER TABLE "user_group" DROP CONSTRAINT "FK_3d6b372788ab01be58853003c93"`);
+ await queryRunner.query(`ALTER TABLE "messaging_message" DROP CONSTRAINT "FK_2c4be03b446884f9e9c502135be"`);
+ await queryRunner.query(`DROP INDEX "IDX_2c4be03b446884f9e9c502135b"`);
+ await queryRunner.query(`COMMENT ON COLUMN "messaging_message"."recipientId" IS ''`);
+ await queryRunner.query(`ALTER TABLE "messaging_message" ALTER COLUMN "recipientId" SET NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "messaging_message" DROP COLUMN "reads"`);
+ await queryRunner.query(`ALTER TABLE "messaging_message" DROP COLUMN "groupId"`);
+ await queryRunner.query(`DROP INDEX "IDX_67dc758bc0566985d1b3d39986"`);
+ await queryRunner.query(`DROP INDEX "IDX_f3a1b4bd0c7cabba958a0c0b23"`);
+ await queryRunner.query(`DROP TABLE "user_group_joining"`);
+ await queryRunner.query(`DROP INDEX "IDX_3d6b372788ab01be58853003c9"`);
+ await queryRunner.query(`DROP INDEX "IDX_20e30aa35180e317e133d75316"`);
+ await queryRunner.query(`DROP TABLE "user_group"`);
+ }
+}
diff --git a/packages/backend/built/migration/1558257926829-UserGroupInvite.js b/packages/backend/built/migration/1558257926829-UserGroupInvite.js
new file mode 100644
index 0000000..ec728c8
--- /dev/null
+++ b/packages/backend/built/migration/1558257926829-UserGroupInvite.js
@@ -0,0 +1,20 @@
+export class UserGroupInvite1558257926829 {
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "user_group_invite" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "userGroupId" character varying(32) NOT NULL, CONSTRAINT "PK_3893884af0d3a5f4d01e7921a97" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_1039988afa3bf991185b277fe0" ON "user_group_invite" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e10924607d058004304611a436" ON "user_group_invite" ("userGroupId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_78787741f9010886796f2320a4" ON "user_group_invite" ("userId", "userGroupId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_d9ecaed8c6dc43f3592c229282" ON "user_group_joining" ("userId", "userGroupId") `);
+ await queryRunner.query(`ALTER TABLE "user_group_invite" ADD CONSTRAINT "FK_1039988afa3bf991185b277fe03" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_group_invite" ADD CONSTRAINT "FK_e10924607d058004304611a436a" FOREIGN KEY ("userGroupId") REFERENCES "user_group"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_group_invite" DROP CONSTRAINT "FK_e10924607d058004304611a436a"`);
+ await queryRunner.query(`ALTER TABLE "user_group_invite" DROP CONSTRAINT "FK_1039988afa3bf991185b277fe03"`);
+ await queryRunner.query(`DROP INDEX "IDX_d9ecaed8c6dc43f3592c229282"`);
+ await queryRunner.query(`DROP INDEX "IDX_78787741f9010886796f2320a4"`);
+ await queryRunner.query(`DROP INDEX "IDX_e10924607d058004304611a436"`);
+ await queryRunner.query(`DROP INDEX "IDX_1039988afa3bf991185b277fe0"`);
+ await queryRunner.query(`DROP TABLE "user_group_invite"`);
+ }
+}
diff --git a/packages/backend/built/migration/1558266512381-UserListJoining.js b/packages/backend/built/migration/1558266512381-UserListJoining.js
new file mode 100644
index 0000000..6889bed
--- /dev/null
+++ b/packages/backend/built/migration/1558266512381-UserListJoining.js
@@ -0,0 +1,8 @@
+export class UserListJoining1558266512381 {
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_90f7da835e4c10aca6853621e1" ON "user_list_joining" ("userId", "userListId") `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_90f7da835e4c10aca6853621e1"`);
+ }
+}
diff --git a/packages/backend/built/migration/1561706992953-webauthn.js b/packages/backend/built/migration/1561706992953-webauthn.js
new file mode 100644
index 0000000..423c888
--- /dev/null
+++ b/packages/backend/built/migration/1561706992953-webauthn.js
@@ -0,0 +1,24 @@
+export class webauthn1561706992953 {
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "attestation_challenge" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "challenge" character varying(64) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "registrationChallenge" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_d0ba6786e093f1bcb497572a6b5" PRIMARY KEY ("id", "userId"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_f1a461a618fa1755692d0e0d59" ON "attestation_challenge" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_47efb914aed1f72dd39a306c7b" ON "attestation_challenge" ("challenge") `);
+ await queryRunner.query(`CREATE TABLE "user_security_key" ("id" character varying NOT NULL, "userId" character varying(32) NOT NULL, "publicKey" character varying NOT NULL, "lastUsed" TIMESTAMP WITH TIME ZONE NOT NULL, "name" character varying(30) NOT NULL, CONSTRAINT "PK_3e508571121ab39c5f85d10c166" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_ff9ca3b5f3ee3d0681367a9b44" ON "user_security_key" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0d7718e562dcedd0aa5cf2c9f7" ON "user_security_key" ("publicKey") `);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "securityKeysAvailable" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "attestation_challenge" ADD CONSTRAINT "FK_f1a461a618fa1755692d0e0d592" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_security_key" ADD CONSTRAINT "FK_ff9ca3b5f3ee3d0681367a9b447" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_security_key" DROP CONSTRAINT "FK_ff9ca3b5f3ee3d0681367a9b447"`);
+ await queryRunner.query(`ALTER TABLE "attestation_challenge" DROP CONSTRAINT "FK_f1a461a618fa1755692d0e0d592"`);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "securityKeysAvailable"`);
+ await queryRunner.query(`DROP INDEX "IDX_0d7718e562dcedd0aa5cf2c9f7"`);
+ await queryRunner.query(`DROP INDEX "IDX_ff9ca3b5f3ee3d0681367a9b44"`);
+ await queryRunner.query(`DROP TABLE "user_security_key"`);
+ await queryRunner.query(`DROP INDEX "IDX_47efb914aed1f72dd39a306c7b"`);
+ await queryRunner.query(`DROP INDEX "IDX_f1a461a618fa1755692d0e0d59"`);
+ await queryRunner.query(`DROP TABLE "attestation_challenge"`);
+ }
+}
diff --git a/packages/backend/built/migration/1561873850023-ChartIndexes.js b/packages/backend/built/migration/1561873850023-ChartIndexes.js
new file mode 100644
index 0000000..dfa4bd9
--- /dev/null
+++ b/packages/backend/built/migration/1561873850023-ChartIndexes.js
@@ -0,0 +1,196 @@
+export class ChartIndexes1561873850023 {
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE INDEX "IDX_0ad37b7ef50f4ddc84363d7ccc" ON "__chart__active_users" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_15e91a03aeeac9dbccdf43fc06" ON "__chart__active_users" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_00ed5f86db1f7efafb1978bf21" ON "__chart__active_users" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_20f57cc8f142c131340ee16742" ON "__chart__active_users" ("span", "date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_9a3ed15a30ab7e3a37702e6e08" ON "__chart__active_users" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_c26e2c1cbb6e911e0554b27416" ON "__chart__active_users" ("span", "date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_13565815f618a1ff53886c5b28" ON "__chart__drive" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_3fa0d0f17ca72e3dc80999a032" ON "__chart__drive" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_7a170f67425e62a8fabb76c872" ON "__chart__drive" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_6e1df243476e20cbf86572ecc0" ON "__chart__drive" ("span", "date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_3313d7288855ec105b5bbf6c21" ON "__chart__drive" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_06690fc959f1c9fdaf21928222" ON "__chart__drive" ("span", "date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_36cb699c49580d4e6c2e6159f9" ON "__chart__federation" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e447064455928cf627590ef527" ON "__chart__federation" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_76e87c7bfc5d925fcbba405d84" ON "__chart__federation" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_2d416e6af791a82e338c79d480" ON "__chart__federation" ("span", "date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_dd907becf76104e4b656659e6b" ON "__chart__federation" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e9cd07672b37d8966cf3709283" ON "__chart__federation" ("span", "date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_07747a1038c05f532a718fe1de" ON "__chart__hashtag" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_fcc181fb8283009c61cc4083ef" ON "__chart__hashtag" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_99a7d2faaef84a6f728d714ad6" ON "__chart__hashtag" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_49975586f50ed7b800fdd88fbd" ON "__chart__hashtag" ("span", "date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_25a97c02003338124b2b75fdbc" ON "__chart__hashtag" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_6d6f156ceefc6bc5f273a0e370" ON "__chart__hashtag" ("span", "date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_6b8f34a1a64b06014b6fb66824" ON "__chart__instance" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_c12f0af4a66cdd30c2287ce8aa" ON "__chart__instance" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_da8a46ba84ca1d8bb5a29bfb63" ON "__chart__instance" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_d0a4f79af5a97b08f37b547197" ON "__chart__instance" ("span", "date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_39ee857ab2f23493037c6b6631" ON "__chart__instance" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f5448d9633cff74208d850aabe" ON "__chart__instance" ("span", "date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_a1efd3e0048a5f2793a47360dc" ON "__chart__network" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f8dd01baeded2ffa833e0a610a" ON "__chart__network" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_7b5da130992ec9df96712d4290" ON "__chart__network" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_08fac0eb3b11f04c200c0b40dd" ON "__chart__network" ("span", "date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0a905b992fecd2b5c3fb98759e" ON "__chart__network" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_9ff6944f01acb756fdc92d7563" ON "__chart__network" ("span", "date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_42eb716a37d381cdf566192b2b" ON "__chart__notes" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e69096589f11e3baa98ddd64d0" ON "__chart__notes" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_7036f2957151588b813185c794" ON "__chart__notes" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0c9a159c5082cbeef3ca6706b5" ON "__chart__notes" ("span", "date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f09d543e3acb16c5976bdb31fa" ON "__chart__notes" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_924fc196c80ca24bae01dd37e4" ON "__chart__notes" ("span", "date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_5f86db6492274e07c1a3cdf286" ON "__chart__per_user_drive" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_328f259961e60c4fa0bfcf55ca" ON "__chart__per_user_drive" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e496ca8096d28f6b9b509264dc" ON "__chart__per_user_drive" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_42ea9381f0fda8dfe0fa1c8b53" ON "__chart__per_user_drive" ("span", "date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_30bf67687f483ace115c5ca642" ON "__chart__per_user_drive" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f2aeafde2ae6fbad38e857631b" ON "__chart__per_user_drive" ("span", "date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_7af07790712aa3438ff6773f3b" ON "__chart__per_user_following" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f92dd6d03f8d994f29987f6214" ON "__chart__per_user_following" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_4b3593098b6edc9c5afe36b18b" ON "__chart__per_user_following" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_57b5458d0d3d6d1e7f13d4e57f" ON "__chart__per_user_following" ("span", "date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_b77d4dd9562c3a899d9a286fcd" ON "__chart__per_user_following" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_4db3b84c7be0d3464714f3e0b1" ON "__chart__per_user_following" ("span", "date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_84234bd1abb873f07329681c83" ON "__chart__per_user_notes" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_8d2cbbc8114d90d19b44d626b6" ON "__chart__per_user_notes" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_55bf20f366979f2436de99206b" ON "__chart__per_user_notes" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_046feeb12e9ef5f783f409866a" ON "__chart__per_user_notes" ("span", "date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_5048e9daccbbbc6d567bb142d3" ON "__chart__per_user_notes" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f68a5ab958f9f5fa17a32ac23b" ON "__chart__per_user_notes" ("span", "date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f7bf4c62059764c2c2bb40fdab" ON "__chart__per_user_reaction" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_65633a106bce43fc7c5c30a5c7" ON "__chart__per_user_reaction" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_8cf3156fd7a6b15c43459c6e3b" ON "__chart__per_user_reaction" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_edeb73c09c3143a81bcb34d569" ON "__chart__per_user_reaction" ("span", "date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_229a41ad465f9205f1f5703291" ON "__chart__per_user_reaction" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e316f01a6d24eb31db27f88262" ON "__chart__per_user_reaction" ("span", "date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0c641990ecf47d2545df4edb75" ON "__chart__test_grouped" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_2be7ec6cebddc14dc11e206686" ON "__chart__test_grouped" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_234dff3c0b56a6150b95431ab9" ON "__chart__test_grouped" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_a5133470f4825902e170328ca5" ON "__chart__test_grouped" ("span", "date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_b14489029e4b3aaf4bba5fb524" ON "__chart__test_grouped" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_84e661abb7bd1e51b690d4b017" ON "__chart__test_grouped" ("span", "date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_437bab3c6061d90f6bb65fd2cc" ON "__chart__test_unique" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_5c73bf61da4f6e6f15bae88ed1" ON "__chart__test_unique" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_bbfa573a8181018851ed0b6357" ON "__chart__test_unique" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_d70c86baedc68326be11f9c0ce" ON "__chart__test_unique" ("span", "date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_a0cd75442dd10d0643a17c4a49" ON "__chart__test_unique" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_66e1e1ecd2f29e57778af35b59" ON "__chart__test_unique" ("span", "date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_b070a906db04b44c67c6c2144d" ON "__chart__test" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_92255988735563f0fe4aba1f05" ON "__chart__test" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_d41cce6aee1a50bfc062038f9b" ON "__chart__test" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_c5870993e25c3d5771f91f5003" ON "__chart__test" ("span", "date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_a319e5dbf47e8a17497623beae" ON "__chart__test" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f170de677ea75ad4533de2723e" ON "__chart__test" ("span", "date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_845254b3eaf708ae8a6cac3026" ON "__chart__users" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_7c184198ecf66a8d3ecb253ab3" ON "__chart__users" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_ed9b95919c672a13008e9487ee" ON "__chart__users" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f091abb24193d50c653c6b77fc" ON "__chart__users" ("span", "date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_337e9599f278bd7537fe30876f" ON "__chart__users" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_a770a57c70e668cc61590c9161" ON "__chart__users" ("span", "date", "group") `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_a770a57c70e668cc61590c9161"`);
+ await queryRunner.query(`DROP INDEX "IDX_337e9599f278bd7537fe30876f"`);
+ await queryRunner.query(`DROP INDEX "IDX_f091abb24193d50c653c6b77fc"`);
+ await queryRunner.query(`DROP INDEX "IDX_ed9b95919c672a13008e9487ee"`);
+ await queryRunner.query(`DROP INDEX "IDX_7c184198ecf66a8d3ecb253ab3"`);
+ await queryRunner.query(`DROP INDEX "IDX_845254b3eaf708ae8a6cac3026"`);
+ await queryRunner.query(`DROP INDEX "IDX_f170de677ea75ad4533de2723e"`);
+ await queryRunner.query(`DROP INDEX "IDX_a319e5dbf47e8a17497623beae"`);
+ await queryRunner.query(`DROP INDEX "IDX_c5870993e25c3d5771f91f5003"`);
+ await queryRunner.query(`DROP INDEX "IDX_d41cce6aee1a50bfc062038f9b"`);
+ await queryRunner.query(`DROP INDEX "IDX_92255988735563f0fe4aba1f05"`);
+ await queryRunner.query(`DROP INDEX "IDX_b070a906db04b44c67c6c2144d"`);
+ await queryRunner.query(`DROP INDEX "IDX_66e1e1ecd2f29e57778af35b59"`);
+ await queryRunner.query(`DROP INDEX "IDX_a0cd75442dd10d0643a17c4a49"`);
+ await queryRunner.query(`DROP INDEX "IDX_d70c86baedc68326be11f9c0ce"`);
+ await queryRunner.query(`DROP INDEX "IDX_bbfa573a8181018851ed0b6357"`);
+ await queryRunner.query(`DROP INDEX "IDX_5c73bf61da4f6e6f15bae88ed1"`);
+ await queryRunner.query(`DROP INDEX "IDX_437bab3c6061d90f6bb65fd2cc"`);
+ await queryRunner.query(`DROP INDEX "IDX_84e661abb7bd1e51b690d4b017"`);
+ await queryRunner.query(`DROP INDEX "IDX_b14489029e4b3aaf4bba5fb524"`);
+ await queryRunner.query(`DROP INDEX "IDX_a5133470f4825902e170328ca5"`);
+ await queryRunner.query(`DROP INDEX "IDX_234dff3c0b56a6150b95431ab9"`);
+ await queryRunner.query(`DROP INDEX "IDX_2be7ec6cebddc14dc11e206686"`);
+ await queryRunner.query(`DROP INDEX "IDX_0c641990ecf47d2545df4edb75"`);
+ await queryRunner.query(`DROP INDEX "IDX_e316f01a6d24eb31db27f88262"`);
+ await queryRunner.query(`DROP INDEX "IDX_229a41ad465f9205f1f5703291"`);
+ await queryRunner.query(`DROP INDEX "IDX_edeb73c09c3143a81bcb34d569"`);
+ await queryRunner.query(`DROP INDEX "IDX_8cf3156fd7a6b15c43459c6e3b"`);
+ await queryRunner.query(`DROP INDEX "IDX_65633a106bce43fc7c5c30a5c7"`);
+ await queryRunner.query(`DROP INDEX "IDX_f7bf4c62059764c2c2bb40fdab"`);
+ await queryRunner.query(`DROP INDEX "IDX_f68a5ab958f9f5fa17a32ac23b"`);
+ await queryRunner.query(`DROP INDEX "IDX_5048e9daccbbbc6d567bb142d3"`);
+ await queryRunner.query(`DROP INDEX "IDX_046feeb12e9ef5f783f409866a"`);
+ await queryRunner.query(`DROP INDEX "IDX_55bf20f366979f2436de99206b"`);
+ await queryRunner.query(`DROP INDEX "IDX_8d2cbbc8114d90d19b44d626b6"`);
+ await queryRunner.query(`DROP INDEX "IDX_84234bd1abb873f07329681c83"`);
+ await queryRunner.query(`DROP INDEX "IDX_4db3b84c7be0d3464714f3e0b1"`);
+ await queryRunner.query(`DROP INDEX "IDX_b77d4dd9562c3a899d9a286fcd"`);
+ await queryRunner.query(`DROP INDEX "IDX_57b5458d0d3d6d1e7f13d4e57f"`);
+ await queryRunner.query(`DROP INDEX "IDX_4b3593098b6edc9c5afe36b18b"`);
+ await queryRunner.query(`DROP INDEX "IDX_f92dd6d03f8d994f29987f6214"`);
+ await queryRunner.query(`DROP INDEX "IDX_7af07790712aa3438ff6773f3b"`);
+ await queryRunner.query(`DROP INDEX "IDX_f2aeafde2ae6fbad38e857631b"`);
+ await queryRunner.query(`DROP INDEX "IDX_30bf67687f483ace115c5ca642"`);
+ await queryRunner.query(`DROP INDEX "IDX_42ea9381f0fda8dfe0fa1c8b53"`);
+ await queryRunner.query(`DROP INDEX "IDX_e496ca8096d28f6b9b509264dc"`);
+ await queryRunner.query(`DROP INDEX "IDX_328f259961e60c4fa0bfcf55ca"`);
+ await queryRunner.query(`DROP INDEX "IDX_5f86db6492274e07c1a3cdf286"`);
+ await queryRunner.query(`DROP INDEX "IDX_924fc196c80ca24bae01dd37e4"`);
+ await queryRunner.query(`DROP INDEX "IDX_f09d543e3acb16c5976bdb31fa"`);
+ await queryRunner.query(`DROP INDEX "IDX_0c9a159c5082cbeef3ca6706b5"`);
+ await queryRunner.query(`DROP INDEX "IDX_7036f2957151588b813185c794"`);
+ await queryRunner.query(`DROP INDEX "IDX_e69096589f11e3baa98ddd64d0"`);
+ await queryRunner.query(`DROP INDEX "IDX_42eb716a37d381cdf566192b2b"`);
+ await queryRunner.query(`DROP INDEX "IDX_9ff6944f01acb756fdc92d7563"`);
+ await queryRunner.query(`DROP INDEX "IDX_0a905b992fecd2b5c3fb98759e"`);
+ await queryRunner.query(`DROP INDEX "IDX_08fac0eb3b11f04c200c0b40dd"`);
+ await queryRunner.query(`DROP INDEX "IDX_7b5da130992ec9df96712d4290"`);
+ await queryRunner.query(`DROP INDEX "IDX_f8dd01baeded2ffa833e0a610a"`);
+ await queryRunner.query(`DROP INDEX "IDX_a1efd3e0048a5f2793a47360dc"`);
+ await queryRunner.query(`DROP INDEX "IDX_f5448d9633cff74208d850aabe"`);
+ await queryRunner.query(`DROP INDEX "IDX_39ee857ab2f23493037c6b6631"`);
+ await queryRunner.query(`DROP INDEX "IDX_d0a4f79af5a97b08f37b547197"`);
+ await queryRunner.query(`DROP INDEX "IDX_da8a46ba84ca1d8bb5a29bfb63"`);
+ await queryRunner.query(`DROP INDEX "IDX_c12f0af4a66cdd30c2287ce8aa"`);
+ await queryRunner.query(`DROP INDEX "IDX_6b8f34a1a64b06014b6fb66824"`);
+ await queryRunner.query(`DROP INDEX "IDX_6d6f156ceefc6bc5f273a0e370"`);
+ await queryRunner.query(`DROP INDEX "IDX_25a97c02003338124b2b75fdbc"`);
+ await queryRunner.query(`DROP INDEX "IDX_49975586f50ed7b800fdd88fbd"`);
+ await queryRunner.query(`DROP INDEX "IDX_99a7d2faaef84a6f728d714ad6"`);
+ await queryRunner.query(`DROP INDEX "IDX_fcc181fb8283009c61cc4083ef"`);
+ await queryRunner.query(`DROP INDEX "IDX_07747a1038c05f532a718fe1de"`);
+ await queryRunner.query(`DROP INDEX "IDX_e9cd07672b37d8966cf3709283"`);
+ await queryRunner.query(`DROP INDEX "IDX_dd907becf76104e4b656659e6b"`);
+ await queryRunner.query(`DROP INDEX "IDX_2d416e6af791a82e338c79d480"`);
+ await queryRunner.query(`DROP INDEX "IDX_76e87c7bfc5d925fcbba405d84"`);
+ await queryRunner.query(`DROP INDEX "IDX_e447064455928cf627590ef527"`);
+ await queryRunner.query(`DROP INDEX "IDX_36cb699c49580d4e6c2e6159f9"`);
+ await queryRunner.query(`DROP INDEX "IDX_06690fc959f1c9fdaf21928222"`);
+ await queryRunner.query(`DROP INDEX "IDX_3313d7288855ec105b5bbf6c21"`);
+ await queryRunner.query(`DROP INDEX "IDX_6e1df243476e20cbf86572ecc0"`);
+ await queryRunner.query(`DROP INDEX "IDX_7a170f67425e62a8fabb76c872"`);
+ await queryRunner.query(`DROP INDEX "IDX_3fa0d0f17ca72e3dc80999a032"`);
+ await queryRunner.query(`DROP INDEX "IDX_13565815f618a1ff53886c5b28"`);
+ await queryRunner.query(`DROP INDEX "IDX_c26e2c1cbb6e911e0554b27416"`);
+ await queryRunner.query(`DROP INDEX "IDX_9a3ed15a30ab7e3a37702e6e08"`);
+ await queryRunner.query(`DROP INDEX "IDX_20f57cc8f142c131340ee16742"`);
+ await queryRunner.query(`DROP INDEX "IDX_00ed5f86db1f7efafb1978bf21"`);
+ await queryRunner.query(`DROP INDEX "IDX_15e91a03aeeac9dbccdf43fc06"`);
+ await queryRunner.query(`DROP INDEX "IDX_0ad37b7ef50f4ddc84363d7ccc"`);
+ await queryRunner.query(`DROP INDEX "IDX_90148bbc2bf0854428786bfc15"`);
+ await queryRunner.query(`DROP INDEX "IDX_88937d94d7443d9a99a76fa5c0"`);
+ await queryRunner.query(`DROP INDEX "IDX_54ebcb6d27222913b908d56fd8"`);
+ await queryRunner.query(`DROP INDEX "IDX_796a8c03959361f97dc2be1d5c"`);
+ await queryRunner.query(`DROP INDEX "IDX_25dfc71b0369b003a4cd434d0b"`);
+ await queryRunner.query(`DROP INDEX "IDX_51c063b6a133a9cb87145450f5"`);
+ await queryRunner.query(`DROP INDEX "IDX_fa99d777623947a5b05f394cae"`);
+ await queryRunner.query(`DROP INDEX "IDX_315c779174fe8247ab324f036e"`);
+ await queryRunner.query(`DROP INDEX "IDX_c5d46cbfda48b1c33ed852e21b"`);
+ await queryRunner.query(`DROP INDEX "IDX_8cb40cfc8f3c28261e6f887b03"`);
+ }
+}
diff --git a/packages/backend/built/migration/1562422242907-PasswordLessLogin.js b/packages/backend/built/migration/1562422242907-PasswordLessLogin.js
new file mode 100644
index 0000000..ce01551
--- /dev/null
+++ b/packages/backend/built/migration/1562422242907-PasswordLessLogin.js
@@ -0,0 +1,8 @@
+export class PasswordLessLogin1562422242907 {
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD COLUMN "usePasswordLessLogin" boolean DEFAULT false NOT NULL`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "usePasswordLessLogin"`);
+ }
+}
diff --git a/packages/backend/built/migration/1562444565093-PinnedPage.js b/packages/backend/built/migration/1562444565093-PinnedPage.js
new file mode 100644
index 0000000..a6ff9e5
--- /dev/null
+++ b/packages/backend/built/migration/1562444565093-PinnedPage.js
@@ -0,0 +1,12 @@
+export class PinnedPage1562444565093 {
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "pinnedPageId" character varying(32)`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "UQ_6dc44f1ceb65b1e72bacef2ca27" UNIQUE ("pinnedPageId")`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "FK_6dc44f1ceb65b1e72bacef2ca27" FOREIGN KEY ("pinnedPageId") REFERENCES "page"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "FK_6dc44f1ceb65b1e72bacef2ca27"`);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "UQ_6dc44f1ceb65b1e72bacef2ca27"`);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "pinnedPageId"`);
+ }
+}
diff --git a/packages/backend/built/migration/1562448332510-PageTitleHideOption.js b/packages/backend/built/migration/1562448332510-PageTitleHideOption.js
new file mode 100644
index 0000000..fe6ef14
--- /dev/null
+++ b/packages/backend/built/migration/1562448332510-PageTitleHideOption.js
@@ -0,0 +1,8 @@
+export class PageTitleHideOption1562448332510 {
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "page" ADD "hideTitleWhenPinned" boolean NOT NULL DEFAULT false`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "page" DROP COLUMN "hideTitleWhenPinned"`);
+ }
+}
diff --git a/packages/backend/built/migration/1562869971568-ModerationLog.js b/packages/backend/built/migration/1562869971568-ModerationLog.js
new file mode 100644
index 0000000..84beff7
--- /dev/null
+++ b/packages/backend/built/migration/1562869971568-ModerationLog.js
@@ -0,0 +1,12 @@
+export class ModerationLog1562869971568 {
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "moderation_log" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "type" character varying(128) NOT NULL, "info" jsonb NOT NULL, CONSTRAINT "PK_d0adca6ecfd068db83e4526cc26" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_a08ad074601d204e0f69da9a95" ON "moderation_log" ("userId") `);
+ await queryRunner.query(`ALTER TABLE "moderation_log" ADD CONSTRAINT "FK_a08ad074601d204e0f69da9a954" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "moderation_log" DROP CONSTRAINT "FK_a08ad074601d204e0f69da9a954"`);
+ await queryRunner.query(`DROP INDEX "IDX_a08ad074601d204e0f69da9a95"`);
+ await queryRunner.query(`DROP TABLE "moderation_log"`);
+ }
+}
diff --git a/packages/backend/built/migration/1563757595828-UsedUsername.js b/packages/backend/built/migration/1563757595828-UsedUsername.js
new file mode 100644
index 0000000..de43028
--- /dev/null
+++ b/packages/backend/built/migration/1563757595828-UsedUsername.js
@@ -0,0 +1,8 @@
+export class UsedUsername1563757595828 {
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "used_username" ("username" character varying(128) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, CONSTRAINT "PK_78fd79d2d24c6ac2f4cc9a31a5d" PRIMARY KEY ("username"))`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP TABLE "used_username"`);
+ }
+}
diff --git a/packages/backend/built/migration/1565634203341-room.js b/packages/backend/built/migration/1565634203341-room.js
new file mode 100644
index 0000000..a9d792f
--- /dev/null
+++ b/packages/backend/built/migration/1565634203341-room.js
@@ -0,0 +1,8 @@
+export class room1565634203341 {
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "room" jsonb NOT NULL DEFAULT '{}'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "room"`);
+ }
+}
diff --git a/packages/backend/built/migration/1571220798684-CustomEmojiCategory.js b/packages/backend/built/migration/1571220798684-CustomEmojiCategory.js
new file mode 100644
index 0000000..e181a19
--- /dev/null
+++ b/packages/backend/built/migration/1571220798684-CustomEmojiCategory.js
@@ -0,0 +1,8 @@
+export class CustomEmojiCategory1571220798684 {
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "emoji" ADD "category" character varying(128)`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "category"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1572760203493-nodeinfo.js b/packages/backend/built/migration/1572760203493-nodeinfo.js
new file mode 100644
index 0000000..7651ab9
--- /dev/null
+++ b/packages/backend/built/migration/1572760203493-nodeinfo.js
@@ -0,0 +1,24 @@
+export class nodeinfo1572760203493 {
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "system"`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" ADD "softwareName" character varying(64) DEFAULT null`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" ADD "softwareVersion" character varying(64) DEFAULT null`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" ADD "openRegistrations" boolean DEFAULT null`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" ADD "name" character varying(256) DEFAULT null`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" ADD "description" character varying(4096) DEFAULT null`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" ADD "maintainerName" character varying(128) DEFAULT null`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" ADD "maintainerEmail" character varying(256) DEFAULT null`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" ADD "infoUpdatedAt" TIMESTAMP WITH TIME ZONE`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "infoUpdatedAt"`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "maintainerEmail"`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "maintainerName"`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "description"`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "name"`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "openRegistrations"`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "softwareVersion"`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "softwareName"`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" ADD "system" character varying(64)`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1576269851876-TalkFederationId.js b/packages/backend/built/migration/1576269851876-TalkFederationId.js
new file mode 100644
index 0000000..a5b77f9
--- /dev/null
+++ b/packages/backend/built/migration/1576269851876-TalkFederationId.js
@@ -0,0 +1,11 @@
+export class TalkFederationId1576269851876 {
+ constructor(){
+ this.name = "TalkFederationId1576269851876";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "messaging_message" ADD "uri" character varying(512)`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "messaging_message" DROP COLUMN "uri"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1576869585998-ProxyRemoteFiles.js b/packages/backend/built/migration/1576869585998-ProxyRemoteFiles.js
new file mode 100644
index 0000000..723c853
--- /dev/null
+++ b/packages/backend/built/migration/1576869585998-ProxyRemoteFiles.js
@@ -0,0 +1,11 @@
+export class ProxyRemoteFiles1576869585998 {
+ constructor(){
+ this.name = "ProxyRemoteFiles1576869585998";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "proxyRemoteFiles" boolean NOT NULL DEFAULT false`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "proxyRemoteFiles"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1579267006611-v12.js b/packages/backend/built/migration/1579267006611-v12.js
new file mode 100644
index 0000000..8e66001
--- /dev/null
+++ b/packages/backend/built/migration/1579267006611-v12.js
@@ -0,0 +1,31 @@
+export class v121579267006611 {
+ constructor(){
+ this.name = "v121579267006611";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "announcement" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "text" character varying(8192) NOT NULL, "title" character varying(256) NOT NULL, "imageUrl" character varying(1024), CONSTRAINT "PK_e0ef0550174fd1099a308fd18a0" PRIMARY KEY ("id"))`, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_118ec703e596086fc4515acb39" ON "announcement" ("createdAt") `, undefined);
+ await queryRunner.query(`CREATE TABLE "announcement_read" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "announcementId" character varying(32) NOT NULL, CONSTRAINT "PK_4b90ad1f42681d97b2683890c5e" PRIMARY KEY ("id"))`, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_8288151386172b8109f7239ab2" ON "announcement_read" ("userId") `, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_603a7b1e7aa0533c6c88e9bfaf" ON "announcement_read" ("announcementId") `, undefined);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_924fa71815cfa3941d003702a0" ON "announcement_read" ("userId", "announcementId") `, undefined);
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isVerified"`, undefined);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "announcements"`, undefined);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableEmojiReaction"`, undefined);
+ await queryRunner.query(`ALTER TABLE "announcement_read" ADD CONSTRAINT "FK_8288151386172b8109f7239ab28" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ await queryRunner.query(`ALTER TABLE "announcement_read" ADD CONSTRAINT "FK_603a7b1e7aa0533c6c88e9bfafe" FOREIGN KEY ("announcementId") REFERENCES "announcement"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "announcement_read" DROP CONSTRAINT "FK_603a7b1e7aa0533c6c88e9bfafe"`, undefined);
+ await queryRunner.query(`ALTER TABLE "announcement_read" DROP CONSTRAINT "FK_8288151386172b8109f7239ab28"`, undefined);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "enableEmojiReaction" boolean NOT NULL DEFAULT true`, undefined);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "announcements" jsonb NOT NULL DEFAULT '[]'`, undefined);
+ await queryRunner.query(`ALTER TABLE "user" ADD "isVerified" boolean NOT NULL DEFAULT false`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_924fa71815cfa3941d003702a0"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_603a7b1e7aa0533c6c88e9bfaf"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_8288151386172b8109f7239ab2"`, undefined);
+ await queryRunner.query(`DROP TABLE "announcement_read"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_118ec703e596086fc4515acb39"`, undefined);
+ await queryRunner.query(`DROP TABLE "announcement"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1579270193251-v12-2.js b/packages/backend/built/migration/1579270193251-v12-2.js
new file mode 100644
index 0000000..9ed5ff7
--- /dev/null
+++ b/packages/backend/built/migration/1579270193251-v12-2.js
@@ -0,0 +1,11 @@
+export class v1221579270193251 {
+ constructor(){
+ this.name = "v1221579270193251";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "announcement_read" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "announcement_read" DROP COLUMN "createdAt"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1579282808087-v12-3.js b/packages/backend/built/migration/1579282808087-v12-3.js
new file mode 100644
index 0000000..c62590c
--- /dev/null
+++ b/packages/backend/built/migration/1579282808087-v12-3.js
@@ -0,0 +1,11 @@
+export class v1231579282808087 {
+ constructor(){
+ this.name = "v1231579282808087";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "announcement" ADD "updatedAt" TIMESTAMP WITH TIME ZONE`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "announcement" DROP COLUMN "updatedAt"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1579544426412-v12-4.js b/packages/backend/built/migration/1579544426412-v12-4.js
new file mode 100644
index 0000000..03aa6a5
--- /dev/null
+++ b/packages/backend/built/migration/1579544426412-v12-4.js
@@ -0,0 +1,13 @@
+export class v1241579544426412 {
+ constructor(){
+ this.name = "v1241579544426412";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "notification" ADD "followRequestId" character varying(32)`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_bd7fab507621e635b32cd31892c" FOREIGN KEY ("followRequestId") REFERENCES "follow_request"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_bd7fab507621e635b32cd31892c"`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "followRequestId"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1579977526288-v12-5.js b/packages/backend/built/migration/1579977526288-v12-5.js
new file mode 100644
index 0000000..eefe308
--- /dev/null
+++ b/packages/backend/built/migration/1579977526288-v12-5.js
@@ -0,0 +1,51 @@
+export class v1251579977526288 {
+ constructor(){
+ this.name = "v1251579977526288";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "clip" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "name" character varying(128) NOT NULL, "isPublic" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_f0685dac8d4dd056d7255670b75" PRIMARY KEY ("id"))`, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_2b5ec6c574d6802c94c80313fb" ON "clip" ("userId") `, undefined);
+ await queryRunner.query(`CREATE TABLE "clip_note" ("id" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, "clipId" character varying(32) NOT NULL, CONSTRAINT "PK_e94cda2f40a99b57e032a1a738b" PRIMARY KEY ("id"))`, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_a012eaf5c87c65da1deb5fdbfa" ON "clip_note" ("noteId") `, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_ebe99317bbbe9968a0c6f579ad" ON "clip_note" ("clipId") `, undefined);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_6fc0ec357d55a18646262fdfff" ON "clip_note" ("noteId", "clipId") `, undefined);
+ await queryRunner.query(`CREATE TYPE "antenna_src_enum" AS ENUM('home', 'all', 'list')`, undefined);
+ await queryRunner.query(`CREATE TABLE "antenna" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "name" character varying(128) NOT NULL, "src" "antenna_src_enum" NOT NULL, "userListId" character varying(32), "keywords" jsonb NOT NULL DEFAULT '[]', "withFile" boolean NOT NULL, "expression" character varying(2048), "notify" boolean NOT NULL, "hasNewNote" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_c170b99775e1dccca947c9f2d5f" PRIMARY KEY ("id"))`, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_6446c571a0e8d0f05f01c78909" ON "antenna" ("userId") `, undefined);
+ await queryRunner.query(`CREATE TABLE "antenna_note" ("id" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, "antennaId" character varying(32) NOT NULL, CONSTRAINT "PK_fb28d94d0989a3872df19fd6ef8" PRIMARY KEY ("id"))`, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_bd0397be22147e17210940e125" ON "antenna_note" ("noteId") `, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_0d775946662d2575dfd2068a5f" ON "antenna_note" ("antennaId") `, undefined);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_335a0bf3f904406f9ef3dd51c2" ON "antenna_note" ("noteId", "antennaId") `, undefined);
+ await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "geo"`, undefined);
+ await queryRunner.query(`ALTER TABLE "clip" ADD CONSTRAINT "FK_2b5ec6c574d6802c94c80313fb2" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ await queryRunner.query(`ALTER TABLE "clip_note" ADD CONSTRAINT "FK_a012eaf5c87c65da1deb5fdbfa3" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ await queryRunner.query(`ALTER TABLE "clip_note" ADD CONSTRAINT "FK_ebe99317bbbe9968a0c6f579adf" FOREIGN KEY ("clipId") REFERENCES "clip"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna" ADD CONSTRAINT "FK_6446c571a0e8d0f05f01c789096" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna" ADD CONSTRAINT "FK_709d7d32053d0dd7620f678eeb9" FOREIGN KEY ("userListId") REFERENCES "user_list"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna_note" ADD CONSTRAINT "FK_bd0397be22147e17210940e125b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna_note" ADD CONSTRAINT "FK_0d775946662d2575dfd2068a5f5" FOREIGN KEY ("antennaId") REFERENCES "antenna"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "antenna_note" DROP CONSTRAINT "FK_0d775946662d2575dfd2068a5f5"`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna_note" DROP CONSTRAINT "FK_bd0397be22147e17210940e125b"`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna" DROP CONSTRAINT "FK_709d7d32053d0dd7620f678eeb9"`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna" DROP CONSTRAINT "FK_6446c571a0e8d0f05f01c789096"`, undefined);
+ await queryRunner.query(`ALTER TABLE "clip_note" DROP CONSTRAINT "FK_ebe99317bbbe9968a0c6f579adf"`, undefined);
+ await queryRunner.query(`ALTER TABLE "clip_note" DROP CONSTRAINT "FK_a012eaf5c87c65da1deb5fdbfa3"`, undefined);
+ await queryRunner.query(`ALTER TABLE "clip" DROP CONSTRAINT "FK_2b5ec6c574d6802c94c80313fb2"`, undefined);
+ await queryRunner.query(`ALTER TABLE "note" ADD "geo" jsonb`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_335a0bf3f904406f9ef3dd51c2"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_0d775946662d2575dfd2068a5f"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_bd0397be22147e17210940e125"`, undefined);
+ await queryRunner.query(`DROP TABLE "antenna_note"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_6446c571a0e8d0f05f01c78909"`, undefined);
+ await queryRunner.query(`DROP TABLE "antenna"`, undefined);
+ await queryRunner.query(`DROP TYPE "antenna_src_enum"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_6fc0ec357d55a18646262fdfff"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_ebe99317bbbe9968a0c6f579ad"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_a012eaf5c87c65da1deb5fdbfa"`, undefined);
+ await queryRunner.query(`DROP TABLE "clip_note"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_2b5ec6c574d6802c94c80313fb"`, undefined);
+ await queryRunner.query(`DROP TABLE "clip"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1579993013959-v12-6.js b/packages/backend/built/migration/1579993013959-v12-6.js
new file mode 100644
index 0000000..c4b2646
--- /dev/null
+++ b/packages/backend/built/migration/1579993013959-v12-6.js
@@ -0,0 +1,15 @@
+export class v1261579993013959 {
+ constructor(){
+ this.name = "v1261579993013959";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "hasNewNote"`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna_note" ADD "read" boolean NOT NULL DEFAULT false`, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_9937ea48d7ae97ffb4f3f063a4" ON "antenna_note" ("read") `, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_9937ea48d7ae97ffb4f3f063a4"`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna_note" DROP COLUMN "read"`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna" ADD "hasNewNote" boolean NOT NULL DEFAULT false`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1580069531114-v12-7.js b/packages/backend/built/migration/1580069531114-v12-7.js
new file mode 100644
index 0000000..84ea17b
--- /dev/null
+++ b/packages/backend/built/migration/1580069531114-v12-7.js
@@ -0,0 +1,21 @@
+export class v1271580069531114 {
+ constructor(){
+ this.name = "v1271580069531114";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "antenna" ADD "users" character varying(1024) array NOT NULL DEFAULT '{}'::varchar[]`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna" ADD "caseSensitive" boolean NOT NULL DEFAULT false`, undefined);
+ await queryRunner.query(`ALTER TYPE "public"."antenna_src_enum" RENAME TO "antenna_src_enum_old"`, undefined);
+ await queryRunner.query(`CREATE TYPE "antenna_src_enum" AS ENUM('home', 'all', 'users', 'list')`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "src" TYPE "antenna_src_enum" USING "src"::"text"::"antenna_src_enum"`, undefined);
+ await queryRunner.query(`DROP TYPE "antenna_src_enum_old"`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`CREATE TYPE "antenna_src_enum_old" AS ENUM('home', 'all', 'list')`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "src" TYPE "antenna_src_enum_old" USING "src"::"text"::"antenna_src_enum_old"`, undefined);
+ await queryRunner.query(`DROP TYPE "antenna_src_enum"`, undefined);
+ await queryRunner.query(`ALTER TYPE "antenna_src_enum_old" RENAME TO "antenna_src_enum"`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "caseSensitive"`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "users"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1580148575182-v12-8.js b/packages/backend/built/migration/1580148575182-v12-8.js
new file mode 100644
index 0000000..e08b296
--- /dev/null
+++ b/packages/backend/built/migration/1580148575182-v12-8.js
@@ -0,0 +1,13 @@
+export class v1281580148575182 {
+ constructor(){
+ this.name = "v1281580148575182";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_ec5c201576192ba8904c345c5cc"`, undefined);
+ await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "appId"`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note" ADD "appId" character varying(32)`, undefined);
+ await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_ec5c201576192ba8904c345c5cc" FOREIGN KEY ("appId") REFERENCES "app"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1580154400017-v12-9.js b/packages/backend/built/migration/1580154400017-v12-9.js
new file mode 100644
index 0000000..5bc4b3f
--- /dev/null
+++ b/packages/backend/built/migration/1580154400017-v12-9.js
@@ -0,0 +1,11 @@
+export class v1291580154400017 {
+ constructor(){
+ this.name = "v1291580154400017";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "antenna" ADD "withReplies" boolean NOT NULL DEFAULT false`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "withReplies"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1580276619901-v12-10.js b/packages/backend/built/migration/1580276619901-v12-10.js
new file mode 100644
index 0000000..ba8656d
--- /dev/null
+++ b/packages/backend/built/migration/1580276619901-v12-10.js
@@ -0,0 +1,16 @@
+export class v12101580276619901 {
+ constructor(){
+ this.name = "v12101580276619901";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`TRUNCATE TABLE "notification"`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "type"`, undefined);
+ await queryRunner.query(`CREATE TYPE "notification_type_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted')`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" ADD "type" "notification_type_enum" NOT NULL`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "type"`, undefined);
+ await queryRunner.query(`DROP TYPE "notification_type_enum"`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" ADD "type" character varying(32) NOT NULL`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1580331224276-v12-11.js b/packages/backend/built/migration/1580331224276-v12-11.js
new file mode 100644
index 0000000..3df4491
--- /dev/null
+++ b/packages/backend/built/migration/1580331224276-v12-11.js
@@ -0,0 +1,15 @@
+export class v12111580331224276 {
+ constructor(){
+ this.name = "v12111580331224276";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "isMarkedAsClosed"`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" ADD "isSuspended" boolean NOT NULL DEFAULT false`, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_34500da2e38ac393f7bb6b299c" ON "instance" ("isSuspended") `, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_34500da2e38ac393f7bb6b299c"`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "isSuspended"`, undefined);
+ await queryRunner.query(`ALTER TABLE "instance" ADD "isMarkedAsClosed" boolean NOT NULL DEFAULT false`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1580508795118-v12-12.js b/packages/backend/built/migration/1580508795118-v12-12.js
new file mode 100644
index 0000000..12aa976
--- /dev/null
+++ b/packages/backend/built/migration/1580508795118-v12-12.js
@@ -0,0 +1,43 @@
+export class v12121580508795118 {
+ constructor(){
+ this.name = "v12121580508795118";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "twitter"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "twitterAccessToken"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "twitterAccessTokenSecret"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "twitterUserId"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "twitterScreenName"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "github"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "githubAccessToken"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "githubId"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "githubLogin"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "discord"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "discordAccessToken"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "discordRefreshToken"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "discordExpiresDate"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "discordId"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "discordUsername"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "discordDiscriminator"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "integrations" jsonb NOT NULL DEFAULT '{}'`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "integrations"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "discordDiscriminator" character varying(64) DEFAULT NULL`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "discordUsername" character varying(64) DEFAULT NULL`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "discordId" character varying(64) DEFAULT NULL`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "discordExpiresDate" character varying(64)`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "discordRefreshToken" character varying(64) DEFAULT NULL`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "discordAccessToken" character varying(64) DEFAULT NULL`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "discord" boolean NOT NULL DEFAULT false`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "githubLogin" character varying(64) DEFAULT NULL`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "githubId" character varying(64)`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "githubAccessToken" character varying(64) DEFAULT NULL`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "github" boolean NOT NULL DEFAULT false`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "twitterScreenName" character varying(64) DEFAULT NULL`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "twitterUserId" character varying(64) DEFAULT NULL`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "twitterAccessTokenSecret" character varying(64) DEFAULT NULL`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "twitterAccessToken" character varying(64) DEFAULT NULL`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "twitter" boolean NOT NULL DEFAULT false`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1580543501339-v12-13.js b/packages/backend/built/migration/1580543501339-v12-13.js
new file mode 100644
index 0000000..0023a9d
--- /dev/null
+++ b/packages/backend/built/migration/1580543501339-v12-13.js
@@ -0,0 +1,11 @@
+export class v12131580543501339 {
+ constructor(){
+ this.name = "v12131580543501339";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE INDEX "IDX_NOTE_TAGS" ON "note" USING gin ("tags")`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_NOTE_TAGS"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1580864313253-v12-14.js b/packages/backend/built/migration/1580864313253-v12-14.js
new file mode 100644
index 0000000..04e3982
--- /dev/null
+++ b/packages/backend/built/migration/1580864313253-v12-14.js
@@ -0,0 +1,17 @@
+export class v12141580864313253 {
+ constructor(){
+ this.name = "v12141580864313253";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" RENAME COLUMN "proxyAccount" TO "proxyAccountId"`, undefined);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "proxyAccountId"`, undefined);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "proxyAccountId" character varying(32)`, undefined);
+ await queryRunner.query(`ALTER TABLE "meta" ADD CONSTRAINT "FK_ab1bc0c1e209daa77b8e8d212ad" FOREIGN KEY ("proxyAccountId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP CONSTRAINT "FK_ab1bc0c1e209daa77b8e8d212ad"`, undefined);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "proxyAccountId"`, undefined);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "proxyAccountId" character varying(128)`, undefined);
+ await queryRunner.query(`ALTER TABLE "meta" RENAME COLUMN "proxyAccountId" TO "proxyAccount"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1581526429287-user-group-invitation.js b/packages/backend/built/migration/1581526429287-user-group-invitation.js
new file mode 100644
index 0000000..7ec69a5
--- /dev/null
+++ b/packages/backend/built/migration/1581526429287-user-group-invitation.js
@@ -0,0 +1,35 @@
+export class userGroupInvitation1581526429287 {
+ constructor(){
+ this.name = "userGroupInvitation1581526429287";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "user_group_invitation" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "userGroupId" character varying(32) NOT NULL, CONSTRAINT "PK_160c63ec02bf23f6a5c5e8140d6" PRIMARY KEY ("id"))`, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_bfbc6305547539369fe73eb144" ON "user_group_invitation" ("userId") `, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_5cc8c468090e129857e9fecce5" ON "user_group_invitation" ("userGroupId") `, undefined);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e9793f65f504e5a31fbaedbf2f" ON "user_group_invitation" ("userId", "userGroupId") `, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" ADD "userGroupInvitationId" character varying(32)`, undefined);
+ await queryRunner.query(`ALTER TYPE "public"."notification_type_enum" RENAME TO "notification_type_enum_old"`, undefined);
+ await queryRunner.query(`CREATE TYPE "notification_type_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited')`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "notification_type_enum" USING "type"::"text"::"notification_type_enum"`, undefined);
+ await queryRunner.query(`DROP TYPE "notification_type_enum_old"`, undefined);
+ await queryRunner.query(`COMMENT ON COLUMN "notification"."type" IS 'The type of the Notification.'`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_group_invitation" ADD CONSTRAINT "FK_bfbc6305547539369fe73eb144a" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_group_invitation" ADD CONSTRAINT "FK_5cc8c468090e129857e9fecce5a" FOREIGN KEY ("userGroupId") REFERENCES "user_group"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_8fe87814e978053a53b1beb7e98" FOREIGN KEY ("userGroupInvitationId") REFERENCES "user_group_invitation"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_8fe87814e978053a53b1beb7e98"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_group_invitation" DROP CONSTRAINT "FK_5cc8c468090e129857e9fecce5a"`, undefined);
+ await queryRunner.query(`ALTER TABLE "user_group_invitation" DROP CONSTRAINT "FK_bfbc6305547539369fe73eb144a"`, undefined);
+ await queryRunner.query(`COMMENT ON COLUMN "notification"."type" IS ''`, undefined);
+ await queryRunner.query(`CREATE TYPE "notification_type_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted')`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "notification_type_enum_old" USING "type"::"text"::"notification_type_enum_old"`, undefined);
+ await queryRunner.query(`DROP TYPE "notification_type_enum"`, undefined);
+ await queryRunner.query(`ALTER TYPE "notification_type_enum_old" RENAME TO "notification_type_enum"`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "userGroupInvitationId"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_e9793f65f504e5a31fbaedbf2f"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_5cc8c468090e129857e9fecce5"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_bfbc6305547539369fe73eb144"`, undefined);
+ await queryRunner.query(`DROP TABLE "user_group_invitation"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1581695816408-user-group-antenna.js b/packages/backend/built/migration/1581695816408-user-group-antenna.js
new file mode 100644
index 0000000..2c6db6c
--- /dev/null
+++ b/packages/backend/built/migration/1581695816408-user-group-antenna.js
@@ -0,0 +1,25 @@
+export class userGroupAntenna1581695816408 {
+ constructor(){
+ this.name = "userGroupAntenna1581695816408";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "antenna" ADD "userGroupJoiningId" character varying(32)`, undefined);
+ await queryRunner.query(`ALTER TYPE "public"."antenna_src_enum" RENAME TO "antenna_src_enum_old"`, undefined);
+ await queryRunner.query(`CREATE TYPE "antenna_src_enum" AS ENUM('home', 'all', 'users', 'list', 'group')`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "src" TYPE "antenna_src_enum" USING "src"::"text"::"antenna_src_enum"`, undefined);
+ await queryRunner.query(`DROP TYPE "antenna_src_enum_old"`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "users"`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna" ADD "users" character varying(1024) array NOT NULL DEFAULT '{}'::varchar[]`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna" ADD CONSTRAINT "FK_ccbf5a8c0be4511133dcc50ddeb" FOREIGN KEY ("userGroupJoiningId") REFERENCES "user_group_joining"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "antenna" DROP CONSTRAINT "FK_ccbf5a8c0be4511133dcc50ddeb"`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "users"`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna" ADD "users" character varying array NOT NULL DEFAULT '{}'`, undefined);
+ await queryRunner.query(`CREATE TYPE "antenna_src_enum_old" AS ENUM('home', 'all', 'users', 'list')`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "src" TYPE "antenna_src_enum_old" USING "src"::"text"::"antenna_src_enum_old"`, undefined);
+ await queryRunner.query(`DROP TYPE "antenna_src_enum"`, undefined);
+ await queryRunner.query(`ALTER TYPE "antenna_src_enum_old" RENAME TO "antenna_src_enum"`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "userGroupJoiningId"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1581708415836-drive-user-folder-id-index.js b/packages/backend/built/migration/1581708415836-drive-user-folder-id-index.js
new file mode 100644
index 0000000..fa0e408
--- /dev/null
+++ b/packages/backend/built/migration/1581708415836-drive-user-folder-id-index.js
@@ -0,0 +1,11 @@
+export class driveUserFolderIdIndex1581708415836 {
+ constructor(){
+ this.name = "driveUserFolderIdIndex1581708415836";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE INDEX "IDX_55720b33a61a7c806a8215b825" ON "drive_file" ("userId", "folderId", "id") `, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_55720b33a61a7c806a8215b825"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1581979837262-promo.js b/packages/backend/built/migration/1581979837262-promo.js
new file mode 100644
index 0000000..2fe627b
--- /dev/null
+++ b/packages/backend/built/migration/1581979837262-promo.js
@@ -0,0 +1,25 @@
+export class promo1581979837262 {
+ constructor(){
+ this.name = "promo1581979837262";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "promo_note" ("noteId" character varying(32) NOT NULL, "expiresAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, CONSTRAINT "REL_e263909ca4fe5d57f8d4230dd5" UNIQUE ("noteId"), CONSTRAINT "PK_e263909ca4fe5d57f8d4230dd5c" PRIMARY KEY ("noteId"))`, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_83f0862e9bae44af52ced7099e" ON "promo_note" ("userId") `, undefined);
+ await queryRunner.query(`CREATE TABLE "promo_read" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, CONSTRAINT "PK_61917c1541002422b703318b7c9" PRIMARY KEY ("id"))`, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_9657d55550c3d37bfafaf7d4b0" ON "promo_read" ("userId") `, undefined);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_2882b8a1a07c7d281a98b6db16" ON "promo_read" ("userId", "noteId") `, undefined);
+ await queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ await queryRunner.query(`ALTER TABLE "promo_read" ADD CONSTRAINT "FK_9657d55550c3d37bfafaf7d4b05" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ await queryRunner.query(`ALTER TABLE "promo_read" ADD CONSTRAINT "FK_a46a1a603ecee695d7db26da5f4" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "promo_read" DROP CONSTRAINT "FK_a46a1a603ecee695d7db26da5f4"`, undefined);
+ await queryRunner.query(`ALTER TABLE "promo_read" DROP CONSTRAINT "FK_9657d55550c3d37bfafaf7d4b05"`, undefined);
+ await queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_2882b8a1a07c7d281a98b6db16"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_9657d55550c3d37bfafaf7d4b0"`, undefined);
+ await queryRunner.query(`DROP TABLE "promo_read"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_83f0862e9bae44af52ced7099e"`, undefined);
+ await queryRunner.query(`DROP TABLE "promo_note"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1582019042083-featured-injecttion.js b/packages/backend/built/migration/1582019042083-featured-injecttion.js
new file mode 100644
index 0000000..7add481
--- /dev/null
+++ b/packages/backend/built/migration/1582019042083-featured-injecttion.js
@@ -0,0 +1,11 @@
+export class featuredInjecttion1582019042083 {
+ constructor(){
+ this.name = "featuredInjecttion1582019042083";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "injectFeaturedNote" boolean NOT NULL DEFAULT true`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "injectFeaturedNote"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1582210532752-antenna-exclude.js b/packages/backend/built/migration/1582210532752-antenna-exclude.js
new file mode 100644
index 0000000..47ac337
--- /dev/null
+++ b/packages/backend/built/migration/1582210532752-antenna-exclude.js
@@ -0,0 +1,11 @@
+export class antennaExclude1582210532752 {
+ constructor(){
+ this.name = "antennaExclude1582210532752";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "antenna" ADD "excludeKeywords" jsonb NOT NULL DEFAULT '[]'`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "excludeKeywords"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1582875306439-note-reaction-length.js b/packages/backend/built/migration/1582875306439-note-reaction-length.js
new file mode 100644
index 0000000..8dcc1ee
--- /dev/null
+++ b/packages/backend/built/migration/1582875306439-note-reaction-length.js
@@ -0,0 +1,11 @@
+export class noteReactionLength1582875306439 {
+ constructor(){
+ this.name = "noteReactionLength1582875306439";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note_reaction" ALTER COLUMN "reaction" TYPE character varying(130)`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note_reaction" ALTER COLUMN "reaction" TYPE character varying(128)`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1585361548360-miauth.js b/packages/backend/built/migration/1585361548360-miauth.js
new file mode 100644
index 0000000..32f462c
--- /dev/null
+++ b/packages/backend/built/migration/1585361548360-miauth.js
@@ -0,0 +1,33 @@
+export class miauth1585361548360 {
+ constructor(){
+ this.name = "miauth1585361548360";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "access_token" ADD "lastUsedAt" TIMESTAMP WITH TIME ZONE DEFAULT null`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" ADD "session" character varying(128) DEFAULT null`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" ADD "name" character varying(128) DEFAULT null`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" ADD "description" character varying(512) DEFAULT null`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" ADD "iconUrl" character varying(512) DEFAULT null`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" ADD "permission" character varying(64) array NOT NULL DEFAULT '{}'::varchar[]`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" ADD "fetched" boolean NOT NULL DEFAULT false`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" DROP CONSTRAINT "FK_a3ff16c90cc87a82a0b5959e560"`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "appId" DROP NOT NULL`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "appId" SET DEFAULT null`, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_bf3a053c07d9fb5d87317c56ee" ON "access_token" ("session") `, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" ADD CONSTRAINT "FK_a3ff16c90cc87a82a0b5959e560" FOREIGN KEY ("appId") REFERENCES "app"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "access_token" DROP CONSTRAINT "FK_a3ff16c90cc87a82a0b5959e560"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_bf3a053c07d9fb5d87317c56ee"`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "appId" DROP DEFAULT`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "appId" SET NOT NULL`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" ADD CONSTRAINT "FK_a3ff16c90cc87a82a0b5959e560" FOREIGN KEY ("appId") REFERENCES "app"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" DROP COLUMN "fetched"`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" DROP COLUMN "permission"`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" DROP COLUMN "iconUrl"`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" DROP COLUMN "description"`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" DROP COLUMN "name"`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" DROP COLUMN "session"`, undefined);
+ await queryRunner.query(`ALTER TABLE "access_token" DROP COLUMN "lastUsedAt"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1585385921215-custom-notification.js b/packages/backend/built/migration/1585385921215-custom-notification.js
new file mode 100644
index 0000000..9d7dd17
--- /dev/null
+++ b/packages/backend/built/migration/1585385921215-custom-notification.js
@@ -0,0 +1,45 @@
+export class customNotification1585385921215 {
+ constructor(){
+ this.name = "customNotification1585385921215";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "notification" ADD "customBody" character varying(2048)`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" ADD "customHeader" character varying(256)`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" ADD "customIcon" character varying(1024)`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" ADD "appAccessTokenId" character varying(32)`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_3b4e96eec8d36a8bbb9d02aa710"`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "notifierId" DROP NOT NULL`, undefined);
+ await queryRunner.query(`COMMENT ON COLUMN "notification"."notifierId" IS 'The ID of sender user of the Notification.'`, undefined);
+ await queryRunner.query(`ALTER TYPE "public"."notification_type_enum" RENAME TO "notification_type_enum_old"`, undefined);
+ await queryRunner.query(`CREATE TYPE "notification_type_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "notification_type_enum" USING "type"::"text"::"notification_type_enum"`, undefined);
+ await queryRunner.query(`DROP TYPE "notification_type_enum_old"`, undefined);
+ await queryRunner.query(`COMMENT ON COLUMN "notification"."type" IS 'The type of the Notification.'`, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_3b4e96eec8d36a8bbb9d02aa71" ON "notification" ("notifierId") `, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_33f33cc8ef29d805a97ff4628b" ON "notification" ("type") `, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_080ab397c379af09b9d2169e5b" ON "notification" ("isRead") `, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_e22bf6bda77b6adc1fd9e75c8c" ON "notification" ("appAccessTokenId") `, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_3b4e96eec8d36a8bbb9d02aa710" FOREIGN KEY ("notifierId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_e22bf6bda77b6adc1fd9e75c8c9" FOREIGN KEY ("appAccessTokenId") REFERENCES "access_token"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_e22bf6bda77b6adc1fd9e75c8c9"`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_3b4e96eec8d36a8bbb9d02aa710"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_e22bf6bda77b6adc1fd9e75c8c"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_080ab397c379af09b9d2169e5b"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_33f33cc8ef29d805a97ff4628b"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_3b4e96eec8d36a8bbb9d02aa71"`, undefined);
+ await queryRunner.query(`COMMENT ON COLUMN "notification"."type" IS ''`, undefined);
+ await queryRunner.query(`CREATE TYPE "notification_type_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited')`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "notification_type_enum_old" USING "type"::"text"::"notification_type_enum_old"`, undefined);
+ await queryRunner.query(`DROP TYPE "notification_type_enum"`, undefined);
+ await queryRunner.query(`ALTER TYPE "notification_type_enum_old" RENAME TO "notification_type_enum"`, undefined);
+ await queryRunner.query(`COMMENT ON COLUMN "notification"."notifierId" IS ''`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "notifierId" SET NOT NULL`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_3b4e96eec8d36a8bbb9d02aa710" FOREIGN KEY ("notifierId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "appAccessTokenId"`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "customIcon"`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "customHeader"`, undefined);
+ await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "customBody"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1585772678853-ap-url.js b/packages/backend/built/migration/1585772678853-ap-url.js
new file mode 100644
index 0000000..b822ef2
--- /dev/null
+++ b/packages/backend/built/migration/1585772678853-ap-url.js
@@ -0,0 +1,11 @@
+export class apUrl1585772678853 {
+ constructor(){
+ this.name = "apUrl1585772678853";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note" ADD "url" character varying(512)`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "url"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1586624197029-AddObjectStorageUseProxy.js b/packages/backend/built/migration/1586624197029-AddObjectStorageUseProxy.js
new file mode 100644
index 0000000..80d6517
--- /dev/null
+++ b/packages/backend/built/migration/1586624197029-AddObjectStorageUseProxy.js
@@ -0,0 +1,11 @@
+export class AddObjectStorageUseProxy1586624197029 {
+ constructor(){
+ this.name = "AddObjectStorageUseProxy1586624197029";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageUseProxy" boolean NOT NULL DEFAULT true`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageUseProxy"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1586641139527-remote-reaction.js b/packages/backend/built/migration/1586641139527-remote-reaction.js
new file mode 100644
index 0000000..b37cc9b
--- /dev/null
+++ b/packages/backend/built/migration/1586641139527-remote-reaction.js
@@ -0,0 +1,11 @@
+export class remoteReaction1586641139527 {
+ constructor(){
+ this.name = "remoteReaction1586641139527";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note_reaction" ALTER COLUMN "reaction" TYPE character varying(260)`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note_reaction" ALTER COLUMN "reaction" TYPE character varying(130)`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1586708940386-pageAiScript.js b/packages/backend/built/migration/1586708940386-pageAiScript.js
new file mode 100644
index 0000000..c492f83
--- /dev/null
+++ b/packages/backend/built/migration/1586708940386-pageAiScript.js
@@ -0,0 +1,11 @@
+export class pageAiScript1586708940386 {
+ constructor(){
+ this.name = "pageAiScript1586708940386";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "page" ADD "script" character varying(16384) NOT NULL DEFAULT ''`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "page" DROP COLUMN "script"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1588044505511-hCaptcha.js b/packages/backend/built/migration/1588044505511-hCaptcha.js
new file mode 100644
index 0000000..c391319
--- /dev/null
+++ b/packages/backend/built/migration/1588044505511-hCaptcha.js
@@ -0,0 +1,15 @@
+export class hCaptcha1588044505511 {
+ constructor(){
+ this.name = "hCaptcha1588044505511";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "enableHcaptcha" boolean NOT NULL DEFAULT false`, undefined);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "hcaptchaSiteKey" character varying(64)`, undefined);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "hcaptchaSecretKey" character varying(64)`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "hcaptchaSecretKey"`, undefined);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "hcaptchaSiteKey"`, undefined);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableHcaptcha"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1589023282116-pubRelay.js b/packages/backend/built/migration/1589023282116-pubRelay.js
new file mode 100644
index 0000000..6dfa0fe
--- /dev/null
+++ b/packages/backend/built/migration/1589023282116-pubRelay.js
@@ -0,0 +1,15 @@
+export class pubRelay1589023282116 {
+ constructor(){
+ this.name = "pubRelay1589023282116";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TYPE "relay_status_enum" AS ENUM('requesting', 'accepted', 'rejected')`, undefined);
+ await queryRunner.query(`CREATE TABLE "relay" ("id" character varying(32) NOT NULL, "inbox" character varying(512) NOT NULL, "status" "relay_status_enum" NOT NULL, CONSTRAINT "PK_78ebc9cfddf4292633b7ba57aee" PRIMARY KEY ("id"))`, undefined);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0d9a1738f2cf7f3b1c3334dfab" ON "relay" ("inbox") `, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_0d9a1738f2cf7f3b1c3334dfab"`, undefined);
+ await queryRunner.query(`DROP TABLE "relay"`, undefined);
+ await queryRunner.query(`DROP TYPE "relay_status_enum"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1595075960584-blurhash.js b/packages/backend/built/migration/1595075960584-blurhash.js
new file mode 100644
index 0000000..c269473
--- /dev/null
+++ b/packages/backend/built/migration/1595075960584-blurhash.js
@@ -0,0 +1,11 @@
+export class blurhash1595075960584 {
+ constructor(){
+ this.name = "blurhash1595075960584";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "drive_file" ADD "blurhash" character varying(128)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "blurhash"`);
+ }
+}
diff --git a/packages/backend/built/migration/1595077605646-blurhash-for-avatar-banner.js b/packages/backend/built/migration/1595077605646-blurhash-for-avatar-banner.js
new file mode 100644
index 0000000..4c6b63b
--- /dev/null
+++ b/packages/backend/built/migration/1595077605646-blurhash-for-avatar-banner.js
@@ -0,0 +1,17 @@
+export class blurhashForAvatarBanner1595077605646 {
+ constructor(){
+ this.name = "blurhashForAvatarBanner1595077605646";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarColor"`);
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "bannerColor"`);
+ await queryRunner.query(`ALTER TABLE "user" ADD "avatarBlurhash" character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "user" ADD "bannerBlurhash" character varying(128)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "bannerBlurhash"`);
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarBlurhash"`);
+ await queryRunner.query(`ALTER TABLE "user" ADD "bannerColor" character varying(32)`);
+ await queryRunner.query(`ALTER TABLE "user" ADD "avatarColor" character varying(32)`);
+ }
+}
diff --git a/packages/backend/built/migration/1595676934834-instance-icon-url.js b/packages/backend/built/migration/1595676934834-instance-icon-url.js
new file mode 100644
index 0000000..d867aa5
--- /dev/null
+++ b/packages/backend/built/migration/1595676934834-instance-icon-url.js
@@ -0,0 +1,11 @@
+export class instanceIconUrl1595676934834 {
+ constructor(){
+ this.name = "instanceIconUrl1595676934834";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "instance" ADD "iconUrl" character varying(256) DEFAULT null`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "iconUrl"`);
+ }
+}
diff --git a/packages/backend/built/migration/1595771249699-word-mute.js b/packages/backend/built/migration/1595771249699-word-mute.js
new file mode 100644
index 0000000..0de4322
--- /dev/null
+++ b/packages/backend/built/migration/1595771249699-word-mute.js
@@ -0,0 +1,27 @@
+export class wordMute1595771249699 {
+ constructor(){
+ this.name = "wordMute1595771249699";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "muted_note" ("id" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, CONSTRAINT "PK_897e2eff1c0b9b64e55ca1418a4" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_70ab9786313d78e4201d81cdb8" ON "muted_note" ("noteId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_d8e07aa18c2d64e86201601aec" ON "muted_note" ("userId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a8c6bfd637d3f1d67a27c48e27" ON "muted_note" ("noteId", "userId") `);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "enableWordMute" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "mutedWords" jsonb NOT NULL DEFAULT '[]'`);
+ await queryRunner.query(`CREATE INDEX "IDX_3befe6f999c86aff06eb0257b4" ON "user_profile" ("enableWordMute") `);
+ await queryRunner.query(`ALTER TABLE "muted_note" ADD CONSTRAINT "FK_70ab9786313d78e4201d81cdb89" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "muted_note" ADD CONSTRAINT "FK_d8e07aa18c2d64e86201601aec1" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "muted_note" DROP CONSTRAINT "FK_d8e07aa18c2d64e86201601aec1"`);
+ await queryRunner.query(`ALTER TABLE "muted_note" DROP CONSTRAINT "FK_70ab9786313d78e4201d81cdb89"`);
+ await queryRunner.query(`DROP INDEX "IDX_3befe6f999c86aff06eb0257b4"`);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "mutedWords"`);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "enableWordMute"`);
+ await queryRunner.query(`DROP INDEX "IDX_a8c6bfd637d3f1d67a27c48e27"`);
+ await queryRunner.query(`DROP INDEX "IDX_d8e07aa18c2d64e86201601aec"`);
+ await queryRunner.query(`DROP INDEX "IDX_70ab9786313d78e4201d81cdb8"`);
+ await queryRunner.query(`DROP TABLE "muted_note"`);
+ }
+}
diff --git a/packages/backend/built/migration/1595782306083-word-mute2.js b/packages/backend/built/migration/1595782306083-word-mute2.js
new file mode 100644
index 0000000..f470c46
--- /dev/null
+++ b/packages/backend/built/migration/1595782306083-word-mute2.js
@@ -0,0 +1,15 @@
+export class wordMute21595782306083 {
+ constructor(){
+ this.name = "wordMute21595782306083";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TYPE "muted_note_reason_enum" AS ENUM('word', 'manual', 'spam', 'other')`);
+ await queryRunner.query(`ALTER TABLE "muted_note" ADD "reason" "muted_note_reason_enum" NOT NULL`);
+ await queryRunner.query(`CREATE INDEX "IDX_636e977ff90b23676fb5624b25" ON "muted_note" ("reason") `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_636e977ff90b23676fb5624b25"`);
+ await queryRunner.query(`ALTER TABLE "muted_note" DROP COLUMN "reason"`);
+ await queryRunner.query(`DROP TYPE "muted_note_reason_enum"`);
+ }
+}
diff --git a/packages/backend/built/migration/1596548170836-channel.js b/packages/backend/built/migration/1596548170836-channel.js
new file mode 100644
index 0000000..1a09450
--- /dev/null
+++ b/packages/backend/built/migration/1596548170836-channel.js
@@ -0,0 +1,55 @@
+export class channel1596548170836 {
+ constructor(){
+ this.name = "channel1596548170836";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "channel" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "lastNotedAt" TIMESTAMP WITH TIME ZONE, "userId" character varying(32) NOT NULL, "name" character varying(128) NOT NULL, "description" character varying(2048), "bannerId" character varying(32), "notesCount" integer NOT NULL DEFAULT 0, "usersCount" integer NOT NULL DEFAULT 0, CONSTRAINT "PK_590f33ee6ee7d76437acf362e39" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_71cb7b435b7c0d4843317e7e16" ON "channel" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_29ef80c6f13bcea998447fce43" ON "channel" ("lastNotedAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_823bae55bd81b3be6e05cff438" ON "channel" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0f58c11241e649d2a638a8de94" ON "channel" ("notesCount") `);
+ await queryRunner.query(`CREATE INDEX "IDX_094b86cd36bb805d1aa1e8cc9a" ON "channel" ("usersCount") `);
+ await queryRunner.query(`CREATE TABLE "channel_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, CONSTRAINT "PK_8b104be7f7415113f2a02cd5bdd" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_11e71f2511589dcc8a4d3214f9" ON "channel_following" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0e43068c3f92cab197c3d3cd86" ON "channel_following" ("followeeId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_6d8084ec9496e7334a4602707e" ON "channel_following" ("followerId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_2e230dd45a10e671d781d99f3e" ON "channel_following" ("followerId", "followeeId") `);
+ await queryRunner.query(`CREATE TABLE "channel_note_pining" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "channelId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, CONSTRAINT "PK_44f7474496bcf2e4b741681146d" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_8125f950afd3093acb10d2db8a" ON "channel_note_pining" ("channelId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_f36fed37d6d4cdcc68c803cd9c" ON "channel_note_pining" ("channelId", "noteId") `);
+ await queryRunner.query(`ALTER TABLE "note" ADD "channelId" character varying(32) DEFAULT null`);
+ await queryRunner.query(`CREATE INDEX "IDX_f22169eb10657bded6d875ac8f" ON "note" ("channelId") `);
+ await queryRunner.query(`ALTER TABLE "channel" ADD CONSTRAINT "FK_823bae55bd81b3be6e05cff4383" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "channel" ADD CONSTRAINT "FK_999da2bcc7efadbfe0e92d3bc19" FOREIGN KEY ("bannerId") REFERENCES "drive_file"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_f22169eb10657bded6d875ac8f9" FOREIGN KEY ("channelId") REFERENCES "channel"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "channel_following" ADD CONSTRAINT "FK_0e43068c3f92cab197c3d3cd86e" FOREIGN KEY ("followeeId") REFERENCES "channel"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "channel_following" ADD CONSTRAINT "FK_6d8084ec9496e7334a4602707e1" FOREIGN KEY ("followerId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "channel_note_pining" ADD CONSTRAINT "FK_8125f950afd3093acb10d2db8a8" FOREIGN KEY ("channelId") REFERENCES "channel"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "channel_note_pining" ADD CONSTRAINT "FK_10b19ef67d297ea9de325cd4502" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "channel_note_pining" DROP CONSTRAINT "FK_10b19ef67d297ea9de325cd4502"`);
+ await queryRunner.query(`ALTER TABLE "channel_note_pining" DROP CONSTRAINT "FK_8125f950afd3093acb10d2db8a8"`);
+ await queryRunner.query(`ALTER TABLE "channel_following" DROP CONSTRAINT "FK_6d8084ec9496e7334a4602707e1"`);
+ await queryRunner.query(`ALTER TABLE "channel_following" DROP CONSTRAINT "FK_0e43068c3f92cab197c3d3cd86e"`);
+ await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_f22169eb10657bded6d875ac8f9"`);
+ await queryRunner.query(`ALTER TABLE "channel" DROP CONSTRAINT "FK_999da2bcc7efadbfe0e92d3bc19"`);
+ await queryRunner.query(`ALTER TABLE "channel" DROP CONSTRAINT "FK_823bae55bd81b3be6e05cff4383"`);
+ await queryRunner.query(`DROP INDEX "IDX_f22169eb10657bded6d875ac8f"`);
+ await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "channelId"`);
+ await queryRunner.query(`DROP INDEX "IDX_f36fed37d6d4cdcc68c803cd9c"`);
+ await queryRunner.query(`DROP INDEX "IDX_8125f950afd3093acb10d2db8a"`);
+ await queryRunner.query(`DROP TABLE "channel_note_pining"`);
+ await queryRunner.query(`DROP INDEX "IDX_2e230dd45a10e671d781d99f3e"`);
+ await queryRunner.query(`DROP INDEX "IDX_6d8084ec9496e7334a4602707e"`);
+ await queryRunner.query(`DROP INDEX "IDX_0e43068c3f92cab197c3d3cd86"`);
+ await queryRunner.query(`DROP INDEX "IDX_11e71f2511589dcc8a4d3214f9"`);
+ await queryRunner.query(`DROP TABLE "channel_following"`);
+ await queryRunner.query(`DROP INDEX "IDX_094b86cd36bb805d1aa1e8cc9a"`);
+ await queryRunner.query(`DROP INDEX "IDX_0f58c11241e649d2a638a8de94"`);
+ await queryRunner.query(`DROP INDEX "IDX_823bae55bd81b3be6e05cff438"`);
+ await queryRunner.query(`DROP INDEX "IDX_29ef80c6f13bcea998447fce43"`);
+ await queryRunner.query(`DROP INDEX "IDX_71cb7b435b7c0d4843317e7e16"`);
+ await queryRunner.query(`DROP TABLE "channel"`);
+ }
+}
diff --git a/packages/backend/built/migration/1596786425167-channel2.js b/packages/backend/built/migration/1596786425167-channel2.js
new file mode 100644
index 0000000..1545699
--- /dev/null
+++ b/packages/backend/built/migration/1596786425167-channel2.js
@@ -0,0 +1,11 @@
+export class channel21596786425167 {
+ constructor(){
+ this.name = "channel21596786425167";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "channel_following" ADD "readCursor" TIMESTAMP WITH TIME ZONE NOT NULL`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "channel_following" DROP COLUMN "readCursor"`);
+ }
+}
diff --git a/packages/backend/built/migration/1597230137744-objectStorageSetPublicRead.js b/packages/backend/built/migration/1597230137744-objectStorageSetPublicRead.js
new file mode 100644
index 0000000..1d42e67
--- /dev/null
+++ b/packages/backend/built/migration/1597230137744-objectStorageSetPublicRead.js
@@ -0,0 +1,11 @@
+export class objectStorageSetPublicRead1597230137744 {
+ constructor(){
+ this.name = "objectStorageSetPublicRead1597230137744";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageSetPublicRead" boolean NOT NULL DEFAULT false`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageSetPublicRead"`);
+ }
+}
diff --git a/packages/backend/built/migration/1597236229720-IncludingNotificationTypes.js b/packages/backend/built/migration/1597236229720-IncludingNotificationTypes.js
new file mode 100644
index 0000000..986dc92
--- /dev/null
+++ b/packages/backend/built/migration/1597236229720-IncludingNotificationTypes.js
@@ -0,0 +1,13 @@
+export class IncludingNotificationTypes1597236229720 {
+ constructor(){
+ this.name = "IncludingNotificationTypes1597236229720";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TYPE "user_profile_includingnotificationtypes_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "includingNotificationTypes" "user_profile_includingnotificationtypes_enum" array`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "includingNotificationTypes"`);
+ await queryRunner.query(`DROP TYPE "user_profile_includingnotificationtypes_enum"`);
+ }
+}
diff --git a/packages/backend/built/migration/1597385880794-add-sensitive-index.js b/packages/backend/built/migration/1597385880794-add-sensitive-index.js
new file mode 100644
index 0000000..a230fd4
--- /dev/null
+++ b/packages/backend/built/migration/1597385880794-add-sensitive-index.js
@@ -0,0 +1,11 @@
+export class addSensitiveIndex1597385880794 {
+ constructor(){
+ this.name = "addSensitiveIndex1597385880794";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE INDEX "IDX_a7eba67f8b3fa27271e85d2e26" ON "drive_file" ("isSensitive") `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_a7eba67f8b3fa27271e85d2e26"`);
+ }
+}
diff --git a/packages/backend/built/migration/1597459042300-channel-unread.js b/packages/backend/built/migration/1597459042300-channel-unread.js
new file mode 100644
index 0000000..2fcbf1f
--- /dev/null
+++ b/packages/backend/built/migration/1597459042300-channel-unread.js
@@ -0,0 +1,24 @@
+export class channelUnread1597459042300 {
+ constructor(){
+ this.name = "channelUnread1597459042300";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`TRUNCATE TABLE "note_unread"`, undefined);
+ await queryRunner.query(`ALTER TABLE "channel_following" DROP COLUMN "readCursor"`);
+ await queryRunner.query(`ALTER TABLE "note_unread" ADD "isMentioned" boolean NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "note_unread" ADD "noteChannelId" character varying(32)`);
+ await queryRunner.query(`CREATE INDEX "IDX_25b1dd384bec391b07b74b861c" ON "note_unread" ("isMentioned") `);
+ await queryRunner.query(`CREATE INDEX "IDX_89a29c9237b8c3b6b3cbb4cb30" ON "note_unread" ("isSpecified") `);
+ await queryRunner.query(`CREATE INDEX "IDX_29e8c1d579af54d4232939f994" ON "note_unread" ("noteUserId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_6a57f051d82c6d4036c141e107" ON "note_unread" ("noteChannelId") `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_6a57f051d82c6d4036c141e107"`);
+ await queryRunner.query(`DROP INDEX "IDX_29e8c1d579af54d4232939f994"`);
+ await queryRunner.query(`DROP INDEX "IDX_89a29c9237b8c3b6b3cbb4cb30"`);
+ await queryRunner.query(`DROP INDEX "IDX_25b1dd384bec391b07b74b861c"`);
+ await queryRunner.query(`ALTER TABLE "note_unread" DROP COLUMN "noteChannelId"`);
+ await queryRunner.query(`ALTER TABLE "note_unread" DROP COLUMN "isMentioned"`);
+ await queryRunner.query(`ALTER TABLE "channel_following" ADD "readCursor" TIMESTAMP WITH TIME ZONE NOT NULL`);
+ }
+}
diff --git a/packages/backend/built/migration/1597893996136-ChannelNoteIdDescIndex.js b/packages/backend/built/migration/1597893996136-ChannelNoteIdDescIndex.js
new file mode 100644
index 0000000..14a9eb3
--- /dev/null
+++ b/packages/backend/built/migration/1597893996136-ChannelNoteIdDescIndex.js
@@ -0,0 +1,13 @@
+export class ChannelNoteIdDescIndex1597893996136 {
+ constructor(){
+ this.name = "ChannelNoteIdDescIndex1597893996136";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_f22169eb10657bded6d875ac8f"`);
+ await queryRunner.query(`CREATE INDEX "IDX_note_on_channelId_and_id_desc" ON "note" ("channelId", "id" desc)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_note_on_channelId_and_id_desc"`);
+ await queryRunner.query(`CREATE INDEX "IDX_f22169eb10657bded6d875ac8f" ON "note" ("channelId") `);
+ }
+}
diff --git a/packages/backend/built/migration/1600353287890-mutingNotificationTypes.js b/packages/backend/built/migration/1600353287890-mutingNotificationTypes.js
new file mode 100644
index 0000000..32e70c3
--- /dev/null
+++ b/packages/backend/built/migration/1600353287890-mutingNotificationTypes.js
@@ -0,0 +1,17 @@
+export class mutingNotificationTypes1600353287890 {
+ constructor(){
+ this.name = "mutingNotificationTypes1600353287890";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "includingNotificationTypes"`);
+ await queryRunner.query(`DROP TYPE "public"."user_profile_includingnotificationtypes_enum"`);
+ await queryRunner.query(`CREATE TYPE "user_profile_mutingnotificationtypes_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "mutingNotificationTypes" "user_profile_mutingnotificationtypes_enum" array NOT NULL DEFAULT '{}'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "mutingNotificationTypes"`);
+ await queryRunner.query(`DROP TYPE "user_profile_mutingnotificationtypes_enum"`);
+ await queryRunner.query(`CREATE TYPE "public"."user_profile_includingnotificationtypes_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "includingNotificationTypes" "user_profile_includingnotificationtypes_enum" array`);
+ }
+}
diff --git a/packages/backend/built/migration/1603094348345-refine-abuse-user-report.js b/packages/backend/built/migration/1603094348345-refine-abuse-user-report.js
new file mode 100644
index 0000000..79dfcfe
--- /dev/null
+++ b/packages/backend/built/migration/1603094348345-refine-abuse-user-report.js
@@ -0,0 +1,29 @@
+export class refineAbuseUserReport1603094348345 {
+ constructor(){
+ this.name = "refineAbuseUserReport1603094348345";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_d049123c413e68ca52abe734203"`);
+ await queryRunner.query(`DROP INDEX "IDX_d049123c413e68ca52abe73420"`);
+ await queryRunner.query(`DROP INDEX "IDX_5cd442c3b2e74fdd99dae20243"`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" RENAME COLUMN "userId" TO "targetUserId"`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "assigneeId" character varying(32)`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "resolved" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "comment"`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "comment" character varying(2048) NOT NULL DEFAULT '{}'::varchar[]`);
+ await queryRunner.query(`CREATE INDEX "IDX_2b15aaf4a0dc5be3499af7ab6a" ON "abuse_user_report" ("resolved") `);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_08b883dd5fdd6f9c4c1572b36de" FOREIGN KEY ("assigneeId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_08b883dd5fdd6f9c4c1572b36de"`);
+ await queryRunner.query(`DROP INDEX "IDX_2b15aaf4a0dc5be3499af7ab6a"`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "comment"`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "comment" character varying(512) NOT NULL DEFAULT '{}'::varchar[]`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "resolved"`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "assigneeId"`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" RENAME COLUMN "targetUserId" TO "userId"`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_5cd442c3b2e74fdd99dae20243" ON "abuse_user_report" ("userId", "reporterId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_d049123c413e68ca52abe73420" ON "abuse_user_report" ("userId") `);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_d049123c413e68ca52abe734203" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+}
diff --git a/packages/backend/built/migration/1603095701770-refine-abuse-user-report2.js b/packages/backend/built/migration/1603095701770-refine-abuse-user-report2.js
new file mode 100644
index 0000000..c96b54f
--- /dev/null
+++ b/packages/backend/built/migration/1603095701770-refine-abuse-user-report2.js
@@ -0,0 +1,17 @@
+export class refineAbuseUserReport21603095701770 {
+ constructor(){
+ this.name = "refineAbuseUserReport21603095701770";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "targetUserHost" character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "reporterHost" character varying(128)`);
+ await queryRunner.query(`CREATE INDEX "IDX_4ebbf7f93cdc10e8d1ef2fc6cd" ON "abuse_user_report" ("targetUserHost") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f8d8b93740ad12c4ce8213a199" ON "abuse_user_report" ("reporterHost") `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_f8d8b93740ad12c4ce8213a199"`);
+ await queryRunner.query(`DROP INDEX "IDX_4ebbf7f93cdc10e8d1ef2fc6cd"`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "reporterHost"`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "targetUserHost"`);
+ }
+}
diff --git a/packages/backend/built/migration/1603776877564-instance-theme-color.js b/packages/backend/built/migration/1603776877564-instance-theme-color.js
new file mode 100644
index 0000000..732296e
--- /dev/null
+++ b/packages/backend/built/migration/1603776877564-instance-theme-color.js
@@ -0,0 +1,11 @@
+export class instanceThemeColor1603776877564 {
+ constructor(){
+ this.name = "instanceThemeColor1603776877564";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "instance" ADD "themeColor" character varying(64) DEFAULT null`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "themeColor"`);
+ }
+}
diff --git a/packages/backend/built/migration/1603781553011-instance-favicon.js b/packages/backend/built/migration/1603781553011-instance-favicon.js
new file mode 100644
index 0000000..6be795c
--- /dev/null
+++ b/packages/backend/built/migration/1603781553011-instance-favicon.js
@@ -0,0 +1,11 @@
+export class instanceFavicon1603781553011 {
+ constructor(){
+ this.name = "instanceFavicon1603781553011";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "instance" ADD "faviconUrl" character varying(256) DEFAULT null`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "faviconUrl"`);
+ }
+}
diff --git a/packages/backend/built/migration/1604821689616-delete-auto-watch.js b/packages/backend/built/migration/1604821689616-delete-auto-watch.js
new file mode 100644
index 0000000..89efcc1
--- /dev/null
+++ b/packages/backend/built/migration/1604821689616-delete-auto-watch.js
@@ -0,0 +1,11 @@
+export class deleteAutoWatch1604821689616 {
+ constructor(){
+ this.name = "deleteAutoWatch1604821689616";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "autoWatch"`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "autoWatch" boolean NOT NULL DEFAULT false`);
+ }
+}
diff --git a/packages/backend/built/migration/1605408848373-clip-description.js b/packages/backend/built/migration/1605408848373-clip-description.js
new file mode 100644
index 0000000..9bf00fc
--- /dev/null
+++ b/packages/backend/built/migration/1605408848373-clip-description.js
@@ -0,0 +1,11 @@
+export class clipDescription1605408848373 {
+ constructor(){
+ this.name = "clipDescription1605408848373";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "clip" ADD "description" character varying(2048) DEFAULT null`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "clip" DROP COLUMN "description"`);
+ }
+}
diff --git a/packages/backend/built/migration/1605408971051-comments.js b/packages/backend/built/migration/1605408971051-comments.js
new file mode 100644
index 0000000..f018982
--- /dev/null
+++ b/packages/backend/built/migration/1605408971051-comments.js
@@ -0,0 +1,431 @@
+export class comments1605408971051 {
+ constructor(){
+ this.name = "comments1605408971051";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`COMMENT ON COLUMN "log"."createdAt" IS 'The created date of the Log.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_folder"."createdAt" IS 'The created date of the DriveFolder.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_folder"."name" IS 'The name of the DriveFolder.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_folder"."userId" IS 'The owner ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_folder"."parentId" IS 'The parent folder ID. If null, it means the DriveFolder is located in root.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."createdAt" IS 'The created date of the DriveFile.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."userId" IS 'The owner ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."userHost" IS 'The host of owner. It will be null if the user in local.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."md5" IS 'The MD5 hash of the DriveFile.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."name" IS 'The file name of the DriveFile.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."type" IS 'The content type (MIME) of the DriveFile.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."size" IS 'The file size (bytes) of the DriveFile.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."comment" IS 'The comment of the DriveFile.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."blurhash" IS 'The BlurHash string.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."properties" IS 'The any properties of the DriveFile. For example, it includes image width/height.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."url" IS 'The URL of the DriveFile.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."thumbnailUrl" IS 'The URL of the thumbnail of the DriveFile.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."webpublicUrl" IS 'The URL of the webpublic of the DriveFile.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."uri" IS 'The URI of the DriveFile. it will be null when the DriveFile is local.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."folderId" IS 'The parent folder ID. If null, it means the DriveFile is located in root.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."isSensitive" IS 'Whether the DriveFile is NSFW.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."isLink" IS 'Whether the DriveFile is direct link to remote server.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."createdAt" IS 'The created date of the User.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."updatedAt" IS 'The updated date of the User.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."username" IS 'The username of the User.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."usernameLower" IS 'The username (lowercased) of the User.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."name" IS 'The name of the User.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."followersCount" IS 'The count of followers.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."followingCount" IS 'The count of following.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."notesCount" IS 'The count of notes.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."avatarId" IS 'The ID of avatar DriveFile.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."bannerId" IS 'The ID of banner DriveFile.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isSuspended" IS 'Whether the User is suspended.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isSilenced" IS 'Whether the User is silenced.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isLocked" IS 'Whether the User is locked.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isBot" IS 'Whether the User is a bot.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isCat" IS 'Whether the User is a cat.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isAdmin" IS 'Whether the User is the admin.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isModerator" IS 'Whether the User is a moderator.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."host" IS 'The host of the User. It will be null if the origin of the user is local.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."inbox" IS 'The inbox URL of the User. It will be null if the origin of the user is local.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."sharedInbox" IS 'The sharedInbox URL of the User. It will be null if the origin of the user is local.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."featured" IS 'The featured URL of the User. It will be null if the origin of the user is local.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."uri" IS 'The URI of the User. It will be null if the origin of the user is local.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."token" IS 'The native access token of the User. It will be null if the origin of the user is local.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "app"."createdAt" IS 'The created date of the App.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "app"."userId" IS 'The owner ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "app"."secret" IS 'The secret key of the App.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "app"."name" IS 'The name of the App.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "app"."description" IS 'The description of the App.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "app"."permission" IS 'The permission of the App.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "app"."callbackUrl" IS 'The callbackUrl of the App.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "access_token"."createdAt" IS 'The created date of the AccessToken.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "access_token"."lastUsedAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "access_token"."session" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "access_token"."appId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "access_token"."name" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "access_token"."description" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "access_token"."iconUrl" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel"."createdAt" IS 'The created date of the Channel.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel"."userId" IS 'The owner ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel"."name" IS 'The name of the Channel.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel"."description" IS 'The description of the Channel.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel"."bannerId" IS 'The ID of banner Channel.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel"."notesCount" IS 'The count of notes.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel"."usersCount" IS 'The count of users.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."createdAt" IS 'The created date of the Note.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."replyId" IS 'The ID of reply target.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."renoteId" IS 'The ID of renote target.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."userId" IS 'The ID of author.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."uri" IS 'The URI of a note. it will be null when the note is local.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."url" IS 'The human readable url of a note. it will be null when the note is local.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."channelId" IS 'The ID of source channel.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."userHost" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."replyUserId" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."replyUserHost" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."renoteUserId" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."renoteUserHost" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "poll_vote"."createdAt" IS 'The created date of the PollVote.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note_reaction"."createdAt" IS 'The created date of the NoteReaction.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note_watching"."createdAt" IS 'The created date of the NoteWatching.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note_watching"."userId" IS 'The watcher ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note_watching"."noteId" IS 'The target Note ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note_watching"."noteUserId" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note_unread"."noteUserId" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note_unread"."noteChannelId" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."createdAt" IS 'The created date of the FollowRequest.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followeeId" IS 'The followee user ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followerId" IS 'The follower user ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."requestId" IS 'id of Follow Activity.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followerHost" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followerInbox" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followerSharedInbox" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followeeHost" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followeeInbox" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followeeSharedInbox" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_group"."createdAt" IS 'The created date of the UserGroup.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_group"."userId" IS 'The ID of owner.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_group_invitation"."createdAt" IS 'The created date of the UserGroupInvitation.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_group_invitation"."userId" IS 'The user ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_group_invitation"."userGroupId" IS 'The group ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "notification"."createdAt" IS 'The created date of the Notification.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "notification"."notifieeId" IS 'The ID of recipient user of the Notification.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "notification"."isRead" IS 'Whether the Notification is read.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "meta"."localDriveCapacityMb" IS 'Drive capacity of a local user (MB)'`);
+ await queryRunner.query(`COMMENT ON COLUMN "meta"."remoteDriveCapacityMb" IS 'Drive capacity of a remote user (MB)'`);
+ await queryRunner.query(`COMMENT ON COLUMN "meta"."maxNoteTextLength" IS 'Max allowed note text length in characters'`);
+ await queryRunner.query(`COMMENT ON COLUMN "following"."createdAt" IS 'The created date of the Following.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "following"."followeeId" IS 'The followee user ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "following"."followerId" IS 'The follower user ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "following"."followerHost" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "following"."followerInbox" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "following"."followerSharedInbox" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "following"."followeeHost" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "following"."followeeInbox" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "following"."followeeSharedInbox" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."caughtAt" IS 'The caught date of the Instance.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."host" IS 'The host of the Instance.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."usersCount" IS 'The count of the users of the Instance.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."notesCount" IS 'The count of the notes of the Instance.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."softwareName" IS 'The software of the Instance.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."softwareVersion" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."openRegistrations" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."name" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."description" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."maintainerName" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."maintainerEmail" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."iconUrl" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."faviconUrl" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."themeColor" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "muting"."createdAt" IS 'The created date of the Muting.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "muting"."muteeId" IS 'The mutee user ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "muting"."muterId" IS 'The muter user ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "blocking"."createdAt" IS 'The created date of the Blocking.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "blocking"."blockeeId" IS 'The blockee user ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "blocking"."blockerId" IS 'The blocker user ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_list"."createdAt" IS 'The created date of the UserList.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_list"."userId" IS 'The owner ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_list"."name" IS 'The name of the UserList.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_list_joining"."createdAt" IS 'The created date of the UserListJoining.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_list_joining"."userId" IS 'The user ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_list_joining"."userListId" IS 'The list ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_group_joining"."createdAt" IS 'The created date of the UserGroupJoining.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_group_joining"."userId" IS 'The user ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_group_joining"."userGroupId" IS 'The group ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "note_favorite"."createdAt" IS 'The created date of the NoteFavorite.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "abuse_user_report"."createdAt" IS 'The created date of the AbuseUserReport.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "abuse_user_report"."targetUserHost" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "abuse_user_report"."reporterHost" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "messaging_message"."createdAt" IS 'The created date of the MessagingMessage.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "messaging_message"."userId" IS 'The sender user ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "messaging_message"."groupId" IS 'The recipient group ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "signin"."createdAt" IS 'The created date of the Signin.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "auth_session"."createdAt" IS 'The created date of the AuthSession.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."createdAt" IS 'The created date of the ReversiGame.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."startedAt" IS 'The started date of the ReversiGame.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."form1" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."form2" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "reversi_matching"."createdAt" IS 'The created date of the ReversiMatching.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_note_pining"."createdAt" IS 'The created date of the UserNotePinings.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "poll"."noteId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "poll"."noteVisibility" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "poll"."userId" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "poll"."userHost" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_keypair"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_publickey"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "page"."createdAt" IS 'The created date of the Page.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "page"."updatedAt" IS 'The updated date of the Page.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "page"."userId" IS 'The ID of author.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."location" IS 'The location of the User.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."birthday" IS 'The birthday (YYYY-MM-DD) of the User.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."description" IS 'The description (bio) of the User.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."url" IS 'Remote URL of the user.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."email" IS 'The email address of the User.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."password" IS 'The password hash of the User. It will be null if the origin of the user is local.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."clientData" IS 'The client-specific data of the User.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."room" IS 'The room data of the User.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."userHost" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."id" IS 'Variable-length id given to navigator.credentials.get()'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."publicKey" IS 'Variable-length public key used to verify attestations (hex-encoded).'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."lastUsed" IS 'The date of the last time the UserSecurityKey was successfully validated.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."name" IS 'User-defined name for this key'`);
+ await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."challenge" IS 'Hex-encoded sha256 hash of the challenge.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."createdAt" IS 'The date challenge was created for expiry purposes.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."registrationChallenge" IS 'Indicates that the challenge is only for registration purposes if true to prevent the challenge for being used as authentication.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "moderation_log"."createdAt" IS 'The created date of the ModerationLog.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "announcement"."createdAt" IS 'The created date of the Announcement.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "announcement"."updatedAt" IS 'The updated date of the Announcement.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "announcement_read"."createdAt" IS 'The created date of the AnnouncementRead.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "clip"."createdAt" IS 'The created date of the Clip.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "clip"."userId" IS 'The owner ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "clip"."name" IS 'The name of the Clip.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "clip"."description" IS 'The description of the Clip.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "clip_note"."noteId" IS 'The note ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "clip_note"."clipId" IS 'The clip ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "antenna"."createdAt" IS 'The created date of the Antenna.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "antenna"."userId" IS 'The owner ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "antenna"."name" IS 'The name of the Antenna.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "antenna_note"."noteId" IS 'The note ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "antenna_note"."antennaId" IS 'The antenna ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "promo_note"."noteId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "promo_note"."userId" IS '[Denormalized]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "promo_read"."createdAt" IS 'The created date of the PromoRead.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "muted_note"."noteId" IS 'The note ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "muted_note"."userId" IS 'The user ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "muted_note"."reason" IS 'The reason of the MutedNote.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel_following"."createdAt" IS 'The created date of the ChannelFollowing.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel_following"."followeeId" IS 'The followee channel ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel_following"."followerId" IS 'The follower user ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel_note_pining"."createdAt" IS 'The created date of the ChannelNotePining.'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`COMMENT ON COLUMN "channel_note_pining"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel_following"."followerId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel_following"."followeeId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel_following"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "muted_note"."reason" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "muted_note"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "muted_note"."noteId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "promo_read"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "promo_note"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "promo_note"."noteId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "antenna_note"."antennaId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "antenna_note"."noteId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "antenna"."name" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "antenna"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "antenna"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "clip_note"."clipId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "clip_note"."noteId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "clip"."description" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "clip"."name" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "clip"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "clip"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "announcement_read"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "announcement"."updatedAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "announcement"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "moderation_log"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."registrationChallenge" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."challenge" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."name" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."lastUsed" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."publicKey" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."id" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."userHost" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."room" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."clientData" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."password" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."email" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."url" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."description" IS 'The description (bio) of the User.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."birthday" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."location" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "page"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "page"."updatedAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "page"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_publickey"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_keypair"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "poll"."userHost" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "poll"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "poll"."noteVisibility" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "poll"."noteId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_note_pining"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "reversi_matching"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."form2" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."form1" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."startedAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "auth_session"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "signin"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "messaging_message"."groupId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "messaging_message"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "messaging_message"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "abuse_user_report"."reporterHost" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "abuse_user_report"."targetUserHost" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "abuse_user_report"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note_favorite"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_group_joining"."userGroupId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_group_joining"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_group_joining"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_list_joining"."userListId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_list_joining"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_list_joining"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_list"."name" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_list"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_list"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "blocking"."blockerId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "blocking"."blockeeId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "blocking"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "muting"."muterId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "muting"."muteeId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "muting"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."themeColor" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."faviconUrl" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."iconUrl" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."maintainerEmail" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."maintainerName" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."description" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."name" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."openRegistrations" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."softwareVersion" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."softwareName" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."notesCount" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."usersCount" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."host" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "instance"."caughtAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "following"."followeeSharedInbox" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "following"."followeeInbox" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "following"."followeeHost" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "following"."followerSharedInbox" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "following"."followerInbox" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "following"."followerHost" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "following"."followerId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "following"."followeeId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "following"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "meta"."maxNoteTextLength" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "meta"."remoteDriveCapacityMb" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "meta"."localDriveCapacityMb" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "notification"."isRead" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "notification"."notifieeId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "notification"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_group_invitation"."userGroupId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_group_invitation"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_group_invitation"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_group"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_group"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followeeSharedInbox" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followeeInbox" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followeeHost" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followerSharedInbox" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followerInbox" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followerHost" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."requestId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followerId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followeeId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "follow_request"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note_unread"."noteChannelId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note_unread"."noteUserId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note_watching"."noteUserId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note_watching"."noteId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note_watching"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note_watching"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note_reaction"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "poll_vote"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."renoteUserHost" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."renoteUserId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."replyUserHost" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."replyUserId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."userHost" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."channelId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."url" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."uri" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."renoteId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."replyId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "note"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel"."usersCount" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel"."notesCount" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel"."bannerId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel"."description" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel"."name" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "channel"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "access_token"."iconUrl" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "access_token"."description" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "access_token"."name" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "access_token"."appId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "access_token"."session" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "access_token"."lastUsedAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "access_token"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "app"."callbackUrl" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "app"."permission" IS 'The permission of the App.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "app"."description" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "app"."name" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "app"."secret" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "app"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "app"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."token" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."uri" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."featured" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."sharedInbox" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."inbox" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."host" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isModerator" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isAdmin" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isCat" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isBot" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isLocked" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isSilenced" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isSuspended" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."bannerId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."avatarId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."notesCount" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."followingCount" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."followersCount" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."name" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."usernameLower" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."username" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."updatedAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."isLink" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."isSensitive" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."folderId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."uri" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."webpublicUrl" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."thumbnailUrl" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."url" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."properties" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."blurhash" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."comment" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."size" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."type" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."name" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."md5" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."userHost" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_folder"."parentId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_folder"."userId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_folder"."name" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_folder"."createdAt" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "log"."createdAt" IS NULL`);
+ }
+}
diff --git a/packages/backend/built/migration/1605585339718-instance-pinned-pages.js b/packages/backend/built/migration/1605585339718-instance-pinned-pages.js
new file mode 100644
index 0000000..2d70846
--- /dev/null
+++ b/packages/backend/built/migration/1605585339718-instance-pinned-pages.js
@@ -0,0 +1,11 @@
+export class instancePinnedPages1605585339718 {
+ constructor(){
+ this.name = "instancePinnedPages1605585339718";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "pinnedPages" character varying(512) array NOT NULL DEFAULT '{"/featured", "/channels", "/explore", "/pages", "/about-misskey"}'::varchar[]`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "pinnedPages"`);
+ }
+}
diff --git a/packages/backend/built/migration/1605965516823-instance-images.js b/packages/backend/built/migration/1605965516823-instance-images.js
new file mode 100644
index 0000000..db32f1e
--- /dev/null
+++ b/packages/backend/built/migration/1605965516823-instance-images.js
@@ -0,0 +1,13 @@
+export class instanceImages1605965516823 {
+ constructor(){
+ this.name = "instanceImages1605965516823";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "backgroundImageUrl" character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "logoImageUrl" character varying(512)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "logoImageUrl"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "backgroundImageUrl"`);
+ }
+}
diff --git a/packages/backend/built/migration/1606191203881-no-crawle.js b/packages/backend/built/migration/1606191203881-no-crawle.js
new file mode 100644
index 0000000..0e839bb
--- /dev/null
+++ b/packages/backend/built/migration/1606191203881-no-crawle.js
@@ -0,0 +1,13 @@
+export class noCrawle1606191203881 {
+ constructor(){
+ this.name = "noCrawle1606191203881";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "noCrawle" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."noCrawle" IS 'Whether reject index by crawler.'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."noCrawle" IS 'Whether reject index by crawler.'`);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "noCrawle"`);
+ }
+}
diff --git a/packages/backend/built/migration/1607151207216-instance-pinned-clip.js b/packages/backend/built/migration/1607151207216-instance-pinned-clip.js
new file mode 100644
index 0000000..dd6a486
--- /dev/null
+++ b/packages/backend/built/migration/1607151207216-instance-pinned-clip.js
@@ -0,0 +1,11 @@
+export class instancePinnedClip1607151207216 {
+ constructor(){
+ this.name = "instancePinnedClip1607151207216";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "pinnedClipId" character varying(32)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "pinnedClipId"`);
+ }
+}
diff --git a/packages/backend/built/migration/1607353487793-isExplorable.js b/packages/backend/built/migration/1607353487793-isExplorable.js
new file mode 100644
index 0000000..d5c550c
--- /dev/null
+++ b/packages/backend/built/migration/1607353487793-isExplorable.js
@@ -0,0 +1,15 @@
+export class isExplorable1607353487793 {
+ constructor(){
+ this.name = "isExplorable1607353487793";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" ADD "isExplorable" boolean NOT NULL DEFAULT true`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isExplorable" IS 'Whether the User is explorable.'`);
+ await queryRunner.query(`CREATE INDEX "IDX_d5a1b83c7cab66f167e6888188" ON "user" ("isExplorable") `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_d5a1b83c7cab66f167e6888188"`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isExplorable" IS 'Whether the User is explorable.'`);
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isExplorable"`);
+ }
+}
diff --git a/packages/backend/built/migration/1610277136869-registry.js b/packages/backend/built/migration/1610277136869-registry.js
new file mode 100644
index 0000000..32fd80f
--- /dev/null
+++ b/packages/backend/built/migration/1610277136869-registry.js
@@ -0,0 +1,19 @@
+export class registry1610277136869 {
+ constructor(){
+ this.name = "registry1610277136869";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "registry_item" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "key" character varying(1024) NOT NULL, "scope" character varying(1024) array NOT NULL DEFAULT '{}'::varchar[], "domain" character varying(512), CONSTRAINT "PK_64b3f7e6008b4d89b826cd3af95" PRIMARY KEY ("id")); COMMENT ON COLUMN "registry_item"."createdAt" IS 'The created date of the RegistryItem.'; COMMENT ON COLUMN "registry_item"."updatedAt" IS 'The updated date of the RegistryItem.'; COMMENT ON COLUMN "registry_item"."userId" IS 'The owner ID.'; COMMENT ON COLUMN "registry_item"."key" IS 'The key of the RegistryItem.'`);
+ await queryRunner.query(`CREATE INDEX "IDX_fb9d21ba0abb83223263df6bcb" ON "registry_item" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_22baca135bb8a3ea1a83d13df3" ON "registry_item" ("scope") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0a72bdfcdb97c0eca11fe7ecad" ON "registry_item" ("domain") `);
+ await queryRunner.query(`ALTER TABLE "registry_item" ADD CONSTRAINT "FK_fb9d21ba0abb83223263df6bcb3" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "registry_item" DROP CONSTRAINT "FK_fb9d21ba0abb83223263df6bcb3"`);
+ await queryRunner.query(`DROP INDEX "IDX_0a72bdfcdb97c0eca11fe7ecad"`);
+ await queryRunner.query(`DROP INDEX "IDX_22baca135bb8a3ea1a83d13df3"`);
+ await queryRunner.query(`DROP INDEX "IDX_fb9d21ba0abb83223263df6bcb"`);
+ await queryRunner.query(`DROP TABLE "registry_item"`);
+ }
+}
diff --git a/packages/backend/built/migration/1610277585759-registry2.js b/packages/backend/built/migration/1610277585759-registry2.js
new file mode 100644
index 0000000..a2e844f
--- /dev/null
+++ b/packages/backend/built/migration/1610277585759-registry2.js
@@ -0,0 +1,13 @@
+export class registry21610277585759 {
+ constructor(){
+ this.name = "registry21610277585759";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "registry_item" ADD "value" jsonb NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`COMMENT ON COLUMN "registry_item"."value" IS 'The value of the RegistryItem.'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`COMMENT ON COLUMN "registry_item"."value" IS 'The value of the RegistryItem.'`);
+ await queryRunner.query(`ALTER TABLE "registry_item" DROP COLUMN "value"`);
+ }
+}
diff --git a/packages/backend/built/migration/1610283021566-registry3.js b/packages/backend/built/migration/1610283021566-registry3.js
new file mode 100644
index 0000000..d18b3a4
--- /dev/null
+++ b/packages/backend/built/migration/1610283021566-registry3.js
@@ -0,0 +1,11 @@
+export class registry31610283021566 {
+ constructor(){
+ this.name = "registry31610283021566";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "registry_item" ALTER COLUMN "value" DROP NOT NULL`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "registry_item" ALTER COLUMN "value" SET NOT NULL`);
+ }
+}
diff --git a/packages/backend/built/migration/1611354329133-followersUri.js b/packages/backend/built/migration/1611354329133-followersUri.js
new file mode 100644
index 0000000..06da7b8
--- /dev/null
+++ b/packages/backend/built/migration/1611354329133-followersUri.js
@@ -0,0 +1,13 @@
+export class followersUri1611354329133 {
+ constructor(){
+ this.name = "followersUri1611354329133";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" ADD "followersUri" varchar(512) DEFAULT NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."followersUri" IS 'The URI of the user Follower Collection. It will be null if the origin of the user is local.'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`COMMENT ON COLUMN "user"."followersUri" IS 'The URI of the user Follower Collection. It will be null if the origin of the user is local.'`);
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "followersUri"`);
+ }
+}
diff --git a/packages/backend/built/migration/1611397665007-gallery.js b/packages/backend/built/migration/1611397665007-gallery.js
new file mode 100644
index 0000000..9b996ff
--- /dev/null
+++ b/packages/backend/built/migration/1611397665007-gallery.js
@@ -0,0 +1,37 @@
+export class gallery1611397665007 {
+ constructor(){
+ this.name = "gallery1611397665007";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "gallery_post" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "title" character varying(256) NOT NULL, "description" character varying(2048), "userId" character varying(32) NOT NULL, "fileIds" character varying(32) array NOT NULL DEFAULT '{}'::varchar[], "isSensitive" boolean NOT NULL DEFAULT false, "likedCount" integer NOT NULL DEFAULT '0', "tags" character varying(128) array NOT NULL DEFAULT '{}'::varchar[], CONSTRAINT "PK_8e90d7b6015f2c4518881b14753" PRIMARY KEY ("id")); COMMENT ON COLUMN "gallery_post"."createdAt" IS 'The created date of the GalleryPost.'; COMMENT ON COLUMN "gallery_post"."updatedAt" IS 'The updated date of the GalleryPost.'; COMMENT ON COLUMN "gallery_post"."userId" IS 'The ID of author.'; COMMENT ON COLUMN "gallery_post"."isSensitive" IS 'Whether the post is sensitive.'`);
+ await queryRunner.query(`CREATE INDEX "IDX_8f1a239bd077c8864a20c62c2c" ON "gallery_post" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f631d37835adb04792e361807c" ON "gallery_post" ("updatedAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_985b836dddd8615e432d7043dd" ON "gallery_post" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_3ca50563facd913c425e7a89ee" ON "gallery_post" ("fileIds") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f2d744d9a14d0dfb8b96cb7fc5" ON "gallery_post" ("isSensitive") `);
+ await queryRunner.query(`CREATE INDEX "IDX_1a165c68a49d08f11caffbd206" ON "gallery_post" ("likedCount") `);
+ await queryRunner.query(`CREATE INDEX "IDX_05cca34b985d1b8edc1d1e28df" ON "gallery_post" ("tags") `);
+ await queryRunner.query(`CREATE TABLE "gallery_like" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "postId" character varying(32) NOT NULL, CONSTRAINT "PK_853ab02be39b8de45cd720cc15f" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_8fd5215095473061855ceb948c" ON "gallery_like" ("userId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_df1b5f4099e99fb0bc5eae53b6" ON "gallery_like" ("userId", "postId") `);
+ await queryRunner.query(`ALTER TABLE "gallery_post" ADD CONSTRAINT "FK_985b836dddd8615e432d7043ddb" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "gallery_like" ADD CONSTRAINT "FK_8fd5215095473061855ceb948cf" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "gallery_like" ADD CONSTRAINT "FK_b1cb568bfe569e47b7051699fc8" FOREIGN KEY ("postId") REFERENCES "gallery_post"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "gallery_like" DROP CONSTRAINT "FK_b1cb568bfe569e47b7051699fc8"`);
+ await queryRunner.query(`ALTER TABLE "gallery_like" DROP CONSTRAINT "FK_8fd5215095473061855ceb948cf"`);
+ await queryRunner.query(`ALTER TABLE "gallery_post" DROP CONSTRAINT "FK_985b836dddd8615e432d7043ddb"`);
+ await queryRunner.query(`DROP INDEX "IDX_df1b5f4099e99fb0bc5eae53b6"`);
+ await queryRunner.query(`DROP INDEX "IDX_8fd5215095473061855ceb948c"`);
+ await queryRunner.query(`DROP TABLE "gallery_like"`);
+ await queryRunner.query(`DROP INDEX "IDX_05cca34b985d1b8edc1d1e28df"`);
+ await queryRunner.query(`DROP INDEX "IDX_1a165c68a49d08f11caffbd206"`);
+ await queryRunner.query(`DROP INDEX "IDX_f2d744d9a14d0dfb8b96cb7fc5"`);
+ await queryRunner.query(`DROP INDEX "IDX_3ca50563facd913c425e7a89ee"`);
+ await queryRunner.query(`DROP INDEX "IDX_985b836dddd8615e432d7043dd"`);
+ await queryRunner.query(`DROP INDEX "IDX_f631d37835adb04792e361807c"`);
+ await queryRunner.query(`DROP INDEX "IDX_8f1a239bd077c8864a20c62c2c"`);
+ await queryRunner.query(`DROP TABLE "gallery_post"`);
+ }
+}
diff --git a/packages/backend/built/migration/1611547387175-objectStorageS3ForcePathStyle.js b/packages/backend/built/migration/1611547387175-objectStorageS3ForcePathStyle.js
new file mode 100644
index 0000000..a0597fc
--- /dev/null
+++ b/packages/backend/built/migration/1611547387175-objectStorageS3ForcePathStyle.js
@@ -0,0 +1,11 @@
+export class objectStorageS3ForcePathStyle1611547387175 {
+ constructor(){
+ this.name = "objectStorageS3ForcePathStyle1611547387175";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageS3ForcePathStyle" boolean NOT NULL DEFAULT true`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageS3ForcePathStyle"`);
+ }
+}
diff --git a/packages/backend/built/migration/1612619156584-announcement-email.js b/packages/backend/built/migration/1612619156584-announcement-email.js
new file mode 100644
index 0000000..08177b2
--- /dev/null
+++ b/packages/backend/built/migration/1612619156584-announcement-email.js
@@ -0,0 +1,11 @@
+export class announcementEmail1612619156584 {
+ constructor(){
+ this.name = "announcementEmail1612619156584";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "receiveAnnouncementEmail" boolean NOT NULL DEFAULT true`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "receiveAnnouncementEmail"`);
+ }
+}
diff --git a/packages/backend/built/migration/1613155914446-emailNotificationTypes.js b/packages/backend/built/migration/1613155914446-emailNotificationTypes.js
new file mode 100644
index 0000000..967122a
--- /dev/null
+++ b/packages/backend/built/migration/1613155914446-emailNotificationTypes.js
@@ -0,0 +1,11 @@
+export class emailNotificationTypes1613155914446 {
+ constructor(){
+ this.name = "emailNotificationTypes1613155914446";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "emailNotificationTypes" jsonb NOT NULL DEFAULT '["follow","receiveFollowRequest","groupInvited"]'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "emailNotificationTypes"`);
+ }
+}
diff --git a/packages/backend/built/migration/1613181457597-user-lang.js b/packages/backend/built/migration/1613181457597-user-lang.js
new file mode 100644
index 0000000..d7dfcee
--- /dev/null
+++ b/packages/backend/built/migration/1613181457597-user-lang.js
@@ -0,0 +1,11 @@
+export class userLang1613181457597 {
+ constructor(){
+ this.name = "userLang1613181457597";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "lang" character varying(32)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "lang"`);
+ }
+}
diff --git a/packages/backend/built/migration/1613503367223-use-bigint-for-driveUsage.js b/packages/backend/built/migration/1613503367223-use-bigint-for-driveUsage.js
new file mode 100644
index 0000000..9cc8bcc
--- /dev/null
+++ b/packages/backend/built/migration/1613503367223-use-bigint-for-driveUsage.js
@@ -0,0 +1,12 @@
+export class useBigintForDriveUsage1613503367223 {
+ constructor(){
+ this.name = "useBigintForDriveUsage1613503367223";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "driveUsage" TYPE bigint`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "driveUsage"`);
+ await queryRunner.query(`ALTER TABLE "instance" ADD "driveUsage" integer NOT NULL DEFAULT 0`);
+ }
+}
diff --git a/packages/backend/built/migration/1615965918224-chart-v2.js b/packages/backend/built/migration/1615965918224-chart-v2.js
new file mode 100644
index 0000000..07ae006
--- /dev/null
+++ b/packages/backend/built/migration/1615965918224-chart-v2.js
@@ -0,0 +1,214 @@
+export class chartV21615965918224 {
+ constructor(){
+ this.name = "chartV21615965918224";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`DELETE FROM "__chart__active_users" WHERE "span" = 'day'`);
+ await queryRunner.query(`DELETE FROM "__chart__drive" WHERE "span" = 'day'`);
+ await queryRunner.query(`DELETE FROM "__chart__federation" WHERE "span" = 'day'`);
+ await queryRunner.query(`DELETE FROM "__chart__hashtag" WHERE "span" = 'day'`);
+ await queryRunner.query(`DELETE FROM "__chart__instance" WHERE "span" = 'day'`);
+ await queryRunner.query(`DELETE FROM "__chart__network" WHERE "span" = 'day'`);
+ await queryRunner.query(`DELETE FROM "__chart__notes" WHERE "span" = 'day'`);
+ await queryRunner.query(`DELETE FROM "__chart__per_user_drive" WHERE "span" = 'day'`);
+ await queryRunner.query(`DELETE FROM "__chart__per_user_following" WHERE "span" = 'day'`);
+ await queryRunner.query(`DELETE FROM "__chart__per_user_notes" WHERE "span" = 'day'`);
+ await queryRunner.query(`DELETE FROM "__chart__per_user_reaction" WHERE "span" = 'day'`);
+ await queryRunner.query(`DELETE FROM "__chart__test" WHERE "span" = 'day'`);
+ await queryRunner.query(`DELETE FROM "__chart__test_grouped" WHERE "span" = 'day'`);
+ await queryRunner.query(`DELETE FROM "__chart__test_unique" WHERE "span" = 'day'`);
+ await queryRunner.query(`DELETE FROM "__chart__users" WHERE "span" = 'day'`);
+ await queryRunner.query(`DROP INDEX "IDX_15e91a03aeeac9dbccdf43fc06"`);
+ await queryRunner.query(`DROP INDEX "IDX_20f57cc8f142c131340ee16742"`);
+ await queryRunner.query(`DROP INDEX "IDX_c26e2c1cbb6e911e0554b27416"`);
+ await queryRunner.query(`DROP INDEX "IDX_3fa0d0f17ca72e3dc80999a032"`);
+ await queryRunner.query(`DROP INDEX "IDX_6e1df243476e20cbf86572ecc0"`);
+ await queryRunner.query(`DROP INDEX "IDX_06690fc959f1c9fdaf21928222"`);
+ await queryRunner.query(`DROP INDEX "IDX_e447064455928cf627590ef527"`);
+ await queryRunner.query(`DROP INDEX "IDX_2d416e6af791a82e338c79d480"`);
+ await queryRunner.query(`DROP INDEX "IDX_e9cd07672b37d8966cf3709283"`);
+ await queryRunner.query(`DROP INDEX "IDX_fcc181fb8283009c61cc4083ef"`);
+ await queryRunner.query(`DROP INDEX "IDX_49975586f50ed7b800fdd88fbd"`);
+ await queryRunner.query(`DROP INDEX "IDX_6d6f156ceefc6bc5f273a0e370"`);
+ await queryRunner.query(`DROP INDEX "IDX_c12f0af4a66cdd30c2287ce8aa"`);
+ await queryRunner.query(`DROP INDEX "IDX_d0a4f79af5a97b08f37b547197"`);
+ await queryRunner.query(`DROP INDEX "IDX_f5448d9633cff74208d850aabe"`);
+ await queryRunner.query(`DROP INDEX "IDX_f8dd01baeded2ffa833e0a610a"`);
+ await queryRunner.query(`DROP INDEX "IDX_08fac0eb3b11f04c200c0b40dd"`);
+ await queryRunner.query(`DROP INDEX "IDX_9ff6944f01acb756fdc92d7563"`);
+ await queryRunner.query(`DROP INDEX "IDX_e69096589f11e3baa98ddd64d0"`);
+ await queryRunner.query(`DROP INDEX "IDX_0c9a159c5082cbeef3ca6706b5"`);
+ await queryRunner.query(`DROP INDEX "IDX_924fc196c80ca24bae01dd37e4"`);
+ await queryRunner.query(`DROP INDEX "IDX_328f259961e60c4fa0bfcf55ca"`);
+ await queryRunner.query(`DROP INDEX "IDX_42ea9381f0fda8dfe0fa1c8b53"`);
+ await queryRunner.query(`DROP INDEX "IDX_f2aeafde2ae6fbad38e857631b"`);
+ await queryRunner.query(`DROP INDEX "IDX_f92dd6d03f8d994f29987f6214"`);
+ await queryRunner.query(`DROP INDEX "IDX_57b5458d0d3d6d1e7f13d4e57f"`);
+ await queryRunner.query(`DROP INDEX "IDX_4db3b84c7be0d3464714f3e0b1"`);
+ await queryRunner.query(`DROP INDEX "IDX_8d2cbbc8114d90d19b44d626b6"`);
+ await queryRunner.query(`DROP INDEX "IDX_046feeb12e9ef5f783f409866a"`);
+ await queryRunner.query(`DROP INDEX "IDX_f68a5ab958f9f5fa17a32ac23b"`);
+ await queryRunner.query(`DROP INDEX "IDX_65633a106bce43fc7c5c30a5c7"`);
+ await queryRunner.query(`DROP INDEX "IDX_edeb73c09c3143a81bcb34d569"`);
+ await queryRunner.query(`DROP INDEX "IDX_e316f01a6d24eb31db27f88262"`);
+ await queryRunner.query(`DROP INDEX "IDX_2be7ec6cebddc14dc11e206686"`);
+ await queryRunner.query(`DROP INDEX "IDX_a5133470f4825902e170328ca5"`);
+ await queryRunner.query(`DROP INDEX "IDX_84e661abb7bd1e51b690d4b017"`);
+ await queryRunner.query(`DROP INDEX "IDX_5c73bf61da4f6e6f15bae88ed1"`);
+ await queryRunner.query(`DROP INDEX "IDX_d70c86baedc68326be11f9c0ce"`);
+ await queryRunner.query(`DROP INDEX "IDX_66e1e1ecd2f29e57778af35b59"`);
+ await queryRunner.query(`DROP INDEX "IDX_92255988735563f0fe4aba1f05"`);
+ await queryRunner.query(`DROP INDEX "IDX_c5870993e25c3d5771f91f5003"`);
+ await queryRunner.query(`DROP INDEX "IDX_f170de677ea75ad4533de2723e"`);
+ await queryRunner.query(`DROP INDEX "IDX_7c184198ecf66a8d3ecb253ab3"`);
+ await queryRunner.query(`DROP INDEX "IDX_f091abb24193d50c653c6b77fc"`);
+ await queryRunner.query(`DROP INDEX "IDX_a770a57c70e668cc61590c9161"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "span"`);
+ await queryRunner.query(`DROP TYPE "public"."__chart__active_users_span_enum"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "unique"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___local_count"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___remote_count"`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" DROP COLUMN "span"`);
+ await queryRunner.query(`DROP TYPE "public"."__chart__drive_span_enum"`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" DROP COLUMN "unique"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "span"`);
+ await queryRunner.query(`DROP TYPE "public"."__chart__federation_span_enum"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "unique"`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP COLUMN "span"`);
+ await queryRunner.query(`DROP TYPE "public"."__chart__hashtag_span_enum"`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP COLUMN "unique"`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP COLUMN "___local_count"`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP COLUMN "___remote_count"`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" DROP COLUMN "span"`);
+ await queryRunner.query(`DROP TYPE "public"."__chart__instance_span_enum"`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" DROP COLUMN "unique"`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" DROP COLUMN "span"`);
+ await queryRunner.query(`DROP TYPE "public"."__chart__network_span_enum"`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" DROP COLUMN "unique"`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" DROP COLUMN "span"`);
+ await queryRunner.query(`DROP TYPE "public"."__chart__notes_span_enum"`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" DROP COLUMN "unique"`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" DROP COLUMN "span"`);
+ await queryRunner.query(`DROP TYPE "public"."__chart__per_user_drive_span_enum"`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" DROP COLUMN "unique"`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" DROP COLUMN "span"`);
+ await queryRunner.query(`DROP TYPE "public"."__chart__per_user_following_span_enum"`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" DROP COLUMN "unique"`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" DROP COLUMN "span"`);
+ await queryRunner.query(`DROP TYPE "public"."__chart__per_user_notes_span_enum"`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" DROP COLUMN "unique"`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" DROP COLUMN "span"`);
+ await queryRunner.query(`DROP TYPE "public"."__chart__per_user_reaction_span_enum"`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" DROP COLUMN "unique"`);
+ await queryRunner.query(`ALTER TABLE "__chart__test_grouped" DROP COLUMN "span"`);
+ await queryRunner.query(`DROP TYPE "public"."__chart__test_grouped_span_enum"`);
+ await queryRunner.query(`ALTER TABLE "__chart__test_grouped" DROP COLUMN "unique"`);
+ await queryRunner.query(`ALTER TABLE "__chart__test_unique" DROP COLUMN "span"`);
+ await queryRunner.query(`DROP TYPE "public"."__chart__test_unique_span_enum"`);
+ await queryRunner.query(`ALTER TABLE "__chart__test_unique" DROP COLUMN "unique"`);
+ await queryRunner.query(`ALTER TABLE "__chart__test_unique" DROP COLUMN "___foo"`);
+ await queryRunner.query(`ALTER TABLE "__chart__test" DROP COLUMN "span"`);
+ await queryRunner.query(`DROP TYPE "public"."__chart__test_span_enum"`);
+ await queryRunner.query(`ALTER TABLE "__chart__test" DROP COLUMN "unique"`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" DROP COLUMN "span"`);
+ await queryRunner.query(`DROP TYPE "public"."__chart__users_span_enum"`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" DROP COLUMN "unique"`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart__users" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`CREATE TYPE "public"."__chart__users_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ADD "span" "__chart__users_span_enum" NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__test" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`CREATE TYPE "public"."__chart__test_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`ALTER TABLE "__chart__test" ADD "span" "__chart__test_span_enum" NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__test_unique" ADD "___foo" bigint NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__test_unique" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`CREATE TYPE "public"."__chart__test_unique_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`ALTER TABLE "__chart__test_unique" ADD "span" "__chart__test_unique_span_enum" NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__test_grouped" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`CREATE TYPE "public"."__chart__test_grouped_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`ALTER TABLE "__chart__test_grouped" ADD "span" "__chart__test_grouped_span_enum" NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`CREATE TYPE "public"."__chart__per_user_reaction_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" ADD "span" "__chart__per_user_reaction_span_enum" NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`CREATE TYPE "public"."__chart__per_user_notes_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ADD "span" "__chart__per_user_notes_span_enum" NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`CREATE TYPE "public"."__chart__per_user_following_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ADD "span" "__chart__per_user_following_span_enum" NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`CREATE TYPE "public"."__chart__per_user_drive_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ADD "span" "__chart__per_user_drive_span_enum" NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`CREATE TYPE "public"."__chart__notes_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ADD "span" "__chart__notes_span_enum" NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`CREATE TYPE "public"."__chart__network_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ADD "span" "__chart__network_span_enum" NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`CREATE TYPE "public"."__chart__instance_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ADD "span" "__chart__instance_span_enum" NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD "___remote_count" bigint NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD "___local_count" bigint NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`CREATE TYPE "public"."__chart__hashtag_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD "span" "__chart__hashtag_span_enum" NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`CREATE TYPE "public"."__chart__federation_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "span" "__chart__federation_span_enum" NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`CREATE TYPE "public"."__chart__drive_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ADD "span" "__chart__drive_span_enum" NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___remote_count" bigint NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___local_count" bigint NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`CREATE TYPE "public"."__chart__active_users_span_enum" AS ENUM('hour', 'day')`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "span" "__chart__active_users_span_enum" NOT NULL`);
+ await queryRunner.query(`CREATE INDEX "IDX_a770a57c70e668cc61590c9161" ON "__chart__users" ("date", "group", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f091abb24193d50c653c6b77fc" ON "__chart__users" ("date", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_7c184198ecf66a8d3ecb253ab3" ON "__chart__users" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f170de677ea75ad4533de2723e" ON "__chart__test" ("date", "group", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_c5870993e25c3d5771f91f5003" ON "__chart__test" ("date", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_92255988735563f0fe4aba1f05" ON "__chart__test" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_66e1e1ecd2f29e57778af35b59" ON "__chart__test_unique" ("date", "group", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_d70c86baedc68326be11f9c0ce" ON "__chart__test_unique" ("date", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_5c73bf61da4f6e6f15bae88ed1" ON "__chart__test_unique" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_84e661abb7bd1e51b690d4b017" ON "__chart__test_grouped" ("date", "group", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_a5133470f4825902e170328ca5" ON "__chart__test_grouped" ("date", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_2be7ec6cebddc14dc11e206686" ON "__chart__test_grouped" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e316f01a6d24eb31db27f88262" ON "__chart__per_user_reaction" ("date", "group", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_edeb73c09c3143a81bcb34d569" ON "__chart__per_user_reaction" ("date", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_65633a106bce43fc7c5c30a5c7" ON "__chart__per_user_reaction" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f68a5ab958f9f5fa17a32ac23b" ON "__chart__per_user_notes" ("date", "group", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_046feeb12e9ef5f783f409866a" ON "__chart__per_user_notes" ("date", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_8d2cbbc8114d90d19b44d626b6" ON "__chart__per_user_notes" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_4db3b84c7be0d3464714f3e0b1" ON "__chart__per_user_following" ("date", "group", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_57b5458d0d3d6d1e7f13d4e57f" ON "__chart__per_user_following" ("date", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f92dd6d03f8d994f29987f6214" ON "__chart__per_user_following" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f2aeafde2ae6fbad38e857631b" ON "__chart__per_user_drive" ("date", "group", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_42ea9381f0fda8dfe0fa1c8b53" ON "__chart__per_user_drive" ("date", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_328f259961e60c4fa0bfcf55ca" ON "__chart__per_user_drive" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_924fc196c80ca24bae01dd37e4" ON "__chart__notes" ("date", "group", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0c9a159c5082cbeef3ca6706b5" ON "__chart__notes" ("date", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e69096589f11e3baa98ddd64d0" ON "__chart__notes" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_9ff6944f01acb756fdc92d7563" ON "__chart__network" ("date", "group", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_08fac0eb3b11f04c200c0b40dd" ON "__chart__network" ("date", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f8dd01baeded2ffa833e0a610a" ON "__chart__network" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f5448d9633cff74208d850aabe" ON "__chart__instance" ("date", "group", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_d0a4f79af5a97b08f37b547197" ON "__chart__instance" ("date", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_c12f0af4a66cdd30c2287ce8aa" ON "__chart__instance" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_6d6f156ceefc6bc5f273a0e370" ON "__chart__hashtag" ("date", "group", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_49975586f50ed7b800fdd88fbd" ON "__chart__hashtag" ("date", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_fcc181fb8283009c61cc4083ef" ON "__chart__hashtag" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e9cd07672b37d8966cf3709283" ON "__chart__federation" ("date", "group", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_2d416e6af791a82e338c79d480" ON "__chart__federation" ("date", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e447064455928cf627590ef527" ON "__chart__federation" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_06690fc959f1c9fdaf21928222" ON "__chart__drive" ("date", "group", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_6e1df243476e20cbf86572ecc0" ON "__chart__drive" ("date", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_3fa0d0f17ca72e3dc80999a032" ON "__chart__drive" ("span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_c26e2c1cbb6e911e0554b27416" ON "__chart__active_users" ("date", "group", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_20f57cc8f142c131340ee16742" ON "__chart__active_users" ("date", "span") `);
+ await queryRunner.query(`CREATE INDEX "IDX_15e91a03aeeac9dbccdf43fc06" ON "__chart__active_users" ("span") `);
+ }
+}
diff --git a/packages/backend/built/migration/1615966519402-chart-v2-2.js b/packages/backend/built/migration/1615966519402-chart-v2-2.js
new file mode 100644
index 0000000..b28f0a1
--- /dev/null
+++ b/packages/backend/built/migration/1615966519402-chart-v2-2.js
@@ -0,0 +1,19 @@
+export class chartV221615966519402 {
+ constructor(){
+ this.name = "chartV221615966519402";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___local_users" character varying array NOT NULL DEFAULT '{}'::varchar[]`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___remote_users" character varying array NOT NULL DEFAULT '{}'::varchar[]`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD "___local_users" character varying array NOT NULL DEFAULT '{}'::varchar[]`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD "___remote_users" character varying array NOT NULL DEFAULT '{}'::varchar[]`);
+ await queryRunner.query(`ALTER TABLE "__chart__test_unique" ADD "___foo" character varying array NOT NULL DEFAULT '{}'::varchar[]`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart__test_unique" DROP COLUMN "___foo"`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP COLUMN "___remote_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP COLUMN "___local_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___remote_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___local_users"`);
+ }
+}
diff --git a/packages/backend/built/migration/1618637372000-user-last-active-date.js b/packages/backend/built/migration/1618637372000-user-last-active-date.js
new file mode 100644
index 0000000..4cba66c
--- /dev/null
+++ b/packages/backend/built/migration/1618637372000-user-last-active-date.js
@@ -0,0 +1,13 @@
+export class userLastActiveDate1618637372000 {
+ constructor(){
+ this.name = "userLastActiveDate1618637372000";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" ADD "lastActiveDate" TIMESTAMP WITH TIME ZONE DEFAULT NULL`);
+ await queryRunner.query(`CREATE INDEX "IDX_seoignmeoprigmkpodgrjmkpormg" ON "user" ("lastActiveDate") `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_seoignmeoprigmkpodgrjmkpormg"`);
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "lastActiveDate"`);
+ }
+}
diff --git a/packages/backend/built/migration/1618639857000-user-hide-online-status.js b/packages/backend/built/migration/1618639857000-user-hide-online-status.js
new file mode 100644
index 0000000..89cdedd
--- /dev/null
+++ b/packages/backend/built/migration/1618639857000-user-hide-online-status.js
@@ -0,0 +1,11 @@
+export class userHideOnlineStatus1618639857000 {
+ constructor(){
+ this.name = "userHideOnlineStatus1618639857000";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" ADD "hideOnlineStatus" boolean NOT NULL DEFAULT false`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "hideOnlineStatus"`);
+ }
+}
diff --git a/packages/backend/built/migration/1619942102890-password-reset.js b/packages/backend/built/migration/1619942102890-password-reset.js
new file mode 100644
index 0000000..90e7b44
--- /dev/null
+++ b/packages/backend/built/migration/1619942102890-password-reset.js
@@ -0,0 +1,17 @@
+export class passwordReset1619942102890 {
+ constructor(){
+ this.name = "passwordReset1619942102890";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "password_reset_request" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "token" character varying(256) NOT NULL, "userId" character varying(32) NOT NULL, CONSTRAINT "PK_fcf4b02eae1403a2edaf87fd074" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0b575fa9a4cfe638a925949285" ON "password_reset_request" ("token") `);
+ await queryRunner.query(`CREATE INDEX "IDX_4bb7fd4a34492ae0e6cc8d30ac" ON "password_reset_request" ("userId") `);
+ await queryRunner.query(`ALTER TABLE "password_reset_request" ADD CONSTRAINT "FK_4bb7fd4a34492ae0e6cc8d30ac8" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "password_reset_request" DROP CONSTRAINT "FK_4bb7fd4a34492ae0e6cc8d30ac8"`);
+ await queryRunner.query(`DROP INDEX "IDX_4bb7fd4a34492ae0e6cc8d30ac"`);
+ await queryRunner.query(`DROP INDEX "IDX_0b575fa9a4cfe638a925949285"`);
+ await queryRunner.query(`DROP TABLE "password_reset_request"`);
+ }
+}
diff --git a/packages/backend/built/migration/1620019354680-ad.js b/packages/backend/built/migration/1620019354680-ad.js
new file mode 100644
index 0000000..4ca3d62
--- /dev/null
+++ b/packages/backend/built/migration/1620019354680-ad.js
@@ -0,0 +1,15 @@
+export class ad1620019354680 {
+ constructor(){
+ this.name = "ad1620019354680";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "ad" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "expiresAt" TIMESTAMP WITH TIME ZONE NOT NULL, "place" character varying(32) NOT NULL, "priority" character varying(32) NOT NULL, "url" character varying(1024) NOT NULL, "imageUrl" character varying(1024) NOT NULL, "memo" character varying(8192) NOT NULL, CONSTRAINT "PK_0193d5ef09746e88e9ea92c634d" PRIMARY KEY ("id")); COMMENT ON COLUMN "ad"."createdAt" IS 'The created date of the Ad.'; COMMENT ON COLUMN "ad"."expiresAt" IS 'The expired date of the Ad.'`);
+ await queryRunner.query(`CREATE INDEX "IDX_1129c2ef687fc272df040bafaa" ON "ad" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_2da24ce20ad209f1d9dc032457" ON "ad" ("expiresAt") `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_2da24ce20ad209f1d9dc032457"`);
+ await queryRunner.query(`DROP INDEX "IDX_1129c2ef687fc272df040bafaa"`);
+ await queryRunner.query(`DROP TABLE "ad"`);
+ }
+}
diff --git a/packages/backend/built/migration/1620364649428-ad2.js b/packages/backend/built/migration/1620364649428-ad2.js
new file mode 100644
index 0000000..1aa249f
--- /dev/null
+++ b/packages/backend/built/migration/1620364649428-ad2.js
@@ -0,0 +1,11 @@
+export class ad21620364649428 {
+ constructor(){
+ this.name = "ad21620364649428";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "ad" ADD "ratio" integer NOT NULL DEFAULT '1'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "ad" DROP COLUMN "ratio"`);
+ }
+}
diff --git a/packages/backend/built/migration/1621479946000-add-note-indexes.js b/packages/backend/built/migration/1621479946000-add-note-indexes.js
new file mode 100644
index 0000000..7ec8536
--- /dev/null
+++ b/packages/backend/built/migration/1621479946000-add-note-indexes.js
@@ -0,0 +1,13 @@
+export class addNoteIndexes1621479946000 {
+ constructor(){
+ this.name = "addNoteIndexes1621479946000";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE INDEX "IDX_NOTE_MENTIONS" ON "note" USING gin ("mentions")`, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_NOTE_VISIBLE_USER_IDS" ON "note" USING gin ("visibleUserIds")`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_NOTE_MENTIONS"`, undefined);
+ await queryRunner.query(`DROP INDEX "IDX_NOTE_VISIBLE_USER_IDS"`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1622679304522-user-profile-description-length.js b/packages/backend/built/migration/1622679304522-user-profile-description-length.js
new file mode 100644
index 0000000..6c1c1ef
--- /dev/null
+++ b/packages/backend/built/migration/1622679304522-user-profile-description-length.js
@@ -0,0 +1,11 @@
+export class userProfileDescriptionLength1622679304522 {
+ constructor(){
+ this.name = "userProfileDescriptionLength1622679304522";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "description" TYPE character varying(2048)`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "description" TYPE character varying(1024)`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1622681548499-log-message-length.js b/packages/backend/built/migration/1622681548499-log-message-length.js
new file mode 100644
index 0000000..343a287
--- /dev/null
+++ b/packages/backend/built/migration/1622681548499-log-message-length.js
@@ -0,0 +1,11 @@
+export class logMessageLength1622681548499 {
+ constructor(){
+ this.name = "logMessageLength1622681548499";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "log" ALTER COLUMN "message" TYPE character varying(2048)`, undefined);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "log" ALTER COLUMN "message" TYPE character varying(1024)`, undefined);
+ }
+}
diff --git a/packages/backend/built/migration/1626509500668-fix-remote-file-proxy.js b/packages/backend/built/migration/1626509500668-fix-remote-file-proxy.js
new file mode 100644
index 0000000..a56607d
--- /dev/null
+++ b/packages/backend/built/migration/1626509500668-fix-remote-file-proxy.js
@@ -0,0 +1,19 @@
+export class fixRemoteFileProxy1626509500668 {
+ constructor(){
+ this.name = "fixRemoteFileProxy1626509500668";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarUrl"`);
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "bannerUrl"`);
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarBlurhash"`);
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "bannerBlurhash"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "proxyRemoteFiles"`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "proxyRemoteFiles" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "user" ADD "bannerBlurhash" character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "user" ADD "avatarBlurhash" character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "user" ADD "bannerUrl" character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "user" ADD "avatarUrl" character varying(512)`);
+ }
+}
diff --git a/packages/backend/built/migration/1626733991004-allowlist-secure-mode.js b/packages/backend/built/migration/1626733991004-allowlist-secure-mode.js
new file mode 100644
index 0000000..4388547
--- /dev/null
+++ b/packages/backend/built/migration/1626733991004-allowlist-secure-mode.js
@@ -0,0 +1,13 @@
+export class allowlistSecureMode1626733991004 {
+ name = "allowlistSecureMode1626733991004";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "allowedHosts" character varying(256) [] default '{}'`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "secureMode" bool default false`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "privateMode" bool default false`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "allowedHosts"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "secureMode"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "privateMode"`);
+ }
+}
diff --git a/packages/backend/built/migration/1629004542760-chart-reindex.js b/packages/backend/built/migration/1629004542760-chart-reindex.js
new file mode 100644
index 0000000..347be9b
--- /dev/null
+++ b/packages/backend/built/migration/1629004542760-chart-reindex.js
@@ -0,0 +1,179 @@
+export class chartReindex1629004542760 {
+ constructor(){
+ this.name = "chartReindex1629004542760";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`DELETE FROM "__chart__active_users" a USING "__chart__active_users" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
+ await queryRunner.query(`DELETE FROM "__chart__drive" a USING "__chart__drive" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
+ await queryRunner.query(`DELETE FROM "__chart__federation" a USING "__chart__federation" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
+ await queryRunner.query(`DELETE FROM "__chart__hashtag" a USING "__chart__hashtag" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
+ await queryRunner.query(`DELETE FROM "__chart__instance" a USING "__chart__instance" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
+ await queryRunner.query(`DELETE FROM "__chart__network" a USING "__chart__network" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
+ await queryRunner.query(`DELETE FROM "__chart__notes" a USING "__chart__notes" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
+ await queryRunner.query(`DELETE FROM "__chart__per_user_drive" a USING "__chart__per_user_drive" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
+ await queryRunner.query(`DELETE FROM "__chart__per_user_following" a USING "__chart__per_user_following" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
+ await queryRunner.query(`DELETE FROM "__chart__per_user_notes" a USING "__chart__per_user_notes" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
+ await queryRunner.query(`DELETE FROM "__chart__per_user_reaction" a USING "__chart__per_user_reaction" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
+ await queryRunner.query(`DELETE FROM "__chart__test_grouped" a USING "__chart__test_grouped" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
+ await queryRunner.query(`DELETE FROM "__chart__test_unique" a USING "__chart__test_unique" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
+ await queryRunner.query(`DELETE FROM "__chart__users" a USING "__chart__users" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
+ await queryRunner.query(`DROP INDEX "IDX_0ad37b7ef50f4ddc84363d7ccc"`);
+ await queryRunner.query(`DROP INDEX "IDX_00ed5f86db1f7efafb1978bf21"`);
+ await queryRunner.query(`DROP INDEX "IDX_9a3ed15a30ab7e3a37702e6e08"`);
+ await queryRunner.query(`DROP INDEX "IDX_13565815f618a1ff53886c5b28"`);
+ await queryRunner.query(`DROP INDEX "IDX_7a170f67425e62a8fabb76c872"`);
+ await queryRunner.query(`DROP INDEX "IDX_3313d7288855ec105b5bbf6c21"`);
+ await queryRunner.query(`DROP INDEX "IDX_36cb699c49580d4e6c2e6159f9"`);
+ await queryRunner.query(`DROP INDEX "IDX_76e87c7bfc5d925fcbba405d84"`);
+ await queryRunner.query(`DROP INDEX "IDX_dd907becf76104e4b656659e6b"`);
+ await queryRunner.query(`DROP INDEX "IDX_07747a1038c05f532a718fe1de"`);
+ await queryRunner.query(`DROP INDEX "IDX_99a7d2faaef84a6f728d714ad6"`);
+ await queryRunner.query(`DROP INDEX "IDX_25a97c02003338124b2b75fdbc"`);
+ await queryRunner.query(`DROP INDEX "IDX_6b8f34a1a64b06014b6fb66824"`);
+ await queryRunner.query(`DROP INDEX "IDX_da8a46ba84ca1d8bb5a29bfb63"`);
+ await queryRunner.query(`DROP INDEX "IDX_39ee857ab2f23493037c6b6631"`);
+ await queryRunner.query(`DROP INDEX "IDX_a1efd3e0048a5f2793a47360dc"`);
+ await queryRunner.query(`DROP INDEX "IDX_7b5da130992ec9df96712d4290"`);
+ await queryRunner.query(`DROP INDEX "IDX_0a905b992fecd2b5c3fb98759e"`);
+ await queryRunner.query(`DROP INDEX "IDX_42eb716a37d381cdf566192b2b"`);
+ await queryRunner.query(`DROP INDEX "IDX_7036f2957151588b813185c794"`);
+ await queryRunner.query(`DROP INDEX "IDX_f09d543e3acb16c5976bdb31fa"`);
+ await queryRunner.query(`DROP INDEX "IDX_5f86db6492274e07c1a3cdf286"`);
+ await queryRunner.query(`DROP INDEX "IDX_e496ca8096d28f6b9b509264dc"`);
+ await queryRunner.query(`DROP INDEX "IDX_30bf67687f483ace115c5ca642"`);
+ await queryRunner.query(`DROP INDEX "IDX_7af07790712aa3438ff6773f3b"`);
+ await queryRunner.query(`DROP INDEX "IDX_4b3593098b6edc9c5afe36b18b"`);
+ await queryRunner.query(`DROP INDEX "IDX_b77d4dd9562c3a899d9a286fcd"`);
+ await queryRunner.query(`DROP INDEX "IDX_84234bd1abb873f07329681c83"`);
+ await queryRunner.query(`DROP INDEX "IDX_55bf20f366979f2436de99206b"`);
+ await queryRunner.query(`DROP INDEX "IDX_5048e9daccbbbc6d567bb142d3"`);
+ await queryRunner.query(`DROP INDEX "IDX_f7bf4c62059764c2c2bb40fdab"`);
+ await queryRunner.query(`DROP INDEX "IDX_8cf3156fd7a6b15c43459c6e3b"`);
+ await queryRunner.query(`DROP INDEX "IDX_229a41ad465f9205f1f5703291"`);
+ await queryRunner.query(`DROP INDEX "IDX_0c641990ecf47d2545df4edb75"`);
+ await queryRunner.query(`DROP INDEX "IDX_234dff3c0b56a6150b95431ab9"`);
+ await queryRunner.query(`DROP INDEX "IDX_b14489029e4b3aaf4bba5fb524"`);
+ await queryRunner.query(`DROP INDEX "IDX_437bab3c6061d90f6bb65fd2cc"`);
+ await queryRunner.query(`DROP INDEX "IDX_bbfa573a8181018851ed0b6357"`);
+ await queryRunner.query(`DROP INDEX "IDX_a0cd75442dd10d0643a17c4a49"`);
+ await queryRunner.query(`DROP INDEX "IDX_b070a906db04b44c67c6c2144d"`);
+ await queryRunner.query(`DROP INDEX "IDX_d41cce6aee1a50bfc062038f9b"`);
+ await queryRunner.query(`DROP INDEX "IDX_a319e5dbf47e8a17497623beae"`);
+ await queryRunner.query(`DROP INDEX "IDX_845254b3eaf708ae8a6cac3026"`);
+ await queryRunner.query(`DROP INDEX "IDX_ed9b95919c672a13008e9487ee"`);
+ await queryRunner.query(`DROP INDEX "IDX_337e9599f278bd7537fe30876f"`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_9a3ed15a30ab7e3a37702e6e08" ON "__chart__active_users" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_60c5c6e7e538c09aa274ecd1cf" ON "__chart__active_users" ("date") WHERE "group" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_3313d7288855ec105b5bbf6c21" ON "__chart__drive" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ceab80a6729f8e2e6f5b8a1a3d" ON "__chart__drive" ("date") WHERE "group" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_dd907becf76104e4b656659e6b" ON "__chart__federation" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_eddfed8fb40305a04c6f941050" ON "__chart__federation" ("date") WHERE "group" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_25a97c02003338124b2b75fdbc" ON "__chart__hashtag" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_53a3604b939e2b479eb2cfaac8" ON "__chart__hashtag" ("date") WHERE "group" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_39ee857ab2f23493037c6b6631" ON "__chart__instance" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_8111b817b9818c04d7eb8475b1" ON "__chart__instance" ("date") WHERE "group" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0a905b992fecd2b5c3fb98759e" ON "__chart__network" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_2082327b2699ce924fa654afc5" ON "__chart__network" ("date") WHERE "group" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_f09d543e3acb16c5976bdb31fa" ON "__chart__notes" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e60c358aaced5aab8900a4af31" ON "__chart__notes" ("date") WHERE "group" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_30bf67687f483ace115c5ca642" ON "__chart__per_user_drive" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a9a806d466b314f253a1a611c4" ON "__chart__per_user_drive" ("date") WHERE "group" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_b77d4dd9562c3a899d9a286fcd" ON "__chart__per_user_following" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_dabbb38a51ab86ee3cab291326" ON "__chart__per_user_following" ("date") WHERE "group" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_5048e9daccbbbc6d567bb142d3" ON "__chart__per_user_notes" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_583a157ed0cf0ed1b5ec2a833f" ON "__chart__per_user_notes" ("date") WHERE "group" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_229a41ad465f9205f1f5703291" ON "__chart__per_user_reaction" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_3b7697a96f522d0478972e6d6f" ON "__chart__per_user_reaction" ("date") WHERE "group" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_b14489029e4b3aaf4bba5fb524" ON "__chart__test_grouped" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_da522b4008a9f5d7743b87ad55" ON "__chart__test_grouped" ("date") WHERE "group" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a0cd75442dd10d0643a17c4a49" ON "__chart__test_unique" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_16effb2e888f6763673b579f80" ON "__chart__test_unique" ("date") WHERE "group" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a319e5dbf47e8a17497623beae" ON "__chart__test" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_dab383a36f3c9db4a0c9b02cf3" ON "__chart__test" ("date") WHERE "group" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_337e9599f278bd7537fe30876f" ON "__chart__users" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_66feba81e1795d176d06c0b1e6" ON "__chart__users" ("date") WHERE "group" IS NULL`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_66feba81e1795d176d06c0b1e6"`);
+ await queryRunner.query(`DROP INDEX "IDX_337e9599f278bd7537fe30876f"`);
+ await queryRunner.query(`DROP INDEX "IDX_dab383a36f3c9db4a0c9b02cf3"`);
+ await queryRunner.query(`DROP INDEX "IDX_a319e5dbf47e8a17497623beae"`);
+ await queryRunner.query(`DROP INDEX "IDX_16effb2e888f6763673b579f80"`);
+ await queryRunner.query(`DROP INDEX "IDX_a0cd75442dd10d0643a17c4a49"`);
+ await queryRunner.query(`DROP INDEX "IDX_da522b4008a9f5d7743b87ad55"`);
+ await queryRunner.query(`DROP INDEX "IDX_b14489029e4b3aaf4bba5fb524"`);
+ await queryRunner.query(`DROP INDEX "IDX_3b7697a96f522d0478972e6d6f"`);
+ await queryRunner.query(`DROP INDEX "IDX_229a41ad465f9205f1f5703291"`);
+ await queryRunner.query(`DROP INDEX "IDX_583a157ed0cf0ed1b5ec2a833f"`);
+ await queryRunner.query(`DROP INDEX "IDX_5048e9daccbbbc6d567bb142d3"`);
+ await queryRunner.query(`DROP INDEX "IDX_dabbb38a51ab86ee3cab291326"`);
+ await queryRunner.query(`DROP INDEX "IDX_b77d4dd9562c3a899d9a286fcd"`);
+ await queryRunner.query(`DROP INDEX "IDX_a9a806d466b314f253a1a611c4"`);
+ await queryRunner.query(`DROP INDEX "IDX_30bf67687f483ace115c5ca642"`);
+ await queryRunner.query(`DROP INDEX "IDX_e60c358aaced5aab8900a4af31"`);
+ await queryRunner.query(`DROP INDEX "IDX_f09d543e3acb16c5976bdb31fa"`);
+ await queryRunner.query(`DROP INDEX "IDX_2082327b2699ce924fa654afc5"`);
+ await queryRunner.query(`DROP INDEX "IDX_0a905b992fecd2b5c3fb98759e"`);
+ await queryRunner.query(`DROP INDEX "IDX_8111b817b9818c04d7eb8475b1"`);
+ await queryRunner.query(`DROP INDEX "IDX_39ee857ab2f23493037c6b6631"`);
+ await queryRunner.query(`DROP INDEX "IDX_53a3604b939e2b479eb2cfaac8"`);
+ await queryRunner.query(`DROP INDEX "IDX_25a97c02003338124b2b75fdbc"`);
+ await queryRunner.query(`DROP INDEX "IDX_eddfed8fb40305a04c6f941050"`);
+ await queryRunner.query(`DROP INDEX "IDX_dd907becf76104e4b656659e6b"`);
+ await queryRunner.query(`DROP INDEX "IDX_ceab80a6729f8e2e6f5b8a1a3d"`);
+ await queryRunner.query(`DROP INDEX "IDX_3313d7288855ec105b5bbf6c21"`);
+ await queryRunner.query(`DROP INDEX "IDX_60c5c6e7e538c09aa274ecd1cf"`);
+ await queryRunner.query(`DROP INDEX "IDX_9a3ed15a30ab7e3a37702e6e08"`);
+ await queryRunner.query(`DROP INDEX "IDX_a9021cc2e1feb5f72d3db6e9f5"`);
+ await queryRunner.query(`DROP INDEX "IDX_f22169eb10657bded6d875ac8f"`);
+ await queryRunner.query(`DROP INDEX "IDX_c8cc87bd0f2f4487d17c651fbf"`);
+ await queryRunner.query(`DROP INDEX "IDX_754499f9b2642336433769518d"`);
+ await queryRunner.query(`DROP INDEX "IDX_315c779174fe8247ab324f036e"`);
+ await queryRunner.query(`DROP INDEX "IDX_c5d46cbfda48b1c33ed852e21b"`);
+ await queryRunner.query(`CREATE INDEX "IDX_337e9599f278bd7537fe30876f" ON "__chart__users" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_ed9b95919c672a13008e9487ee" ON "__chart__users" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_845254b3eaf708ae8a6cac3026" ON "__chart__users" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_a319e5dbf47e8a17497623beae" ON "__chart__test" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_d41cce6aee1a50bfc062038f9b" ON "__chart__test" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_b070a906db04b44c67c6c2144d" ON "__chart__test" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_a0cd75442dd10d0643a17c4a49" ON "__chart__test_unique" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_bbfa573a8181018851ed0b6357" ON "__chart__test_unique" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_437bab3c6061d90f6bb65fd2cc" ON "__chart__test_unique" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_b14489029e4b3aaf4bba5fb524" ON "__chart__test_grouped" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_234dff3c0b56a6150b95431ab9" ON "__chart__test_grouped" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0c641990ecf47d2545df4edb75" ON "__chart__test_grouped" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_229a41ad465f9205f1f5703291" ON "__chart__per_user_reaction" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_8cf3156fd7a6b15c43459c6e3b" ON "__chart__per_user_reaction" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f7bf4c62059764c2c2bb40fdab" ON "__chart__per_user_reaction" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_5048e9daccbbbc6d567bb142d3" ON "__chart__per_user_notes" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_55bf20f366979f2436de99206b" ON "__chart__per_user_notes" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_84234bd1abb873f07329681c83" ON "__chart__per_user_notes" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_b77d4dd9562c3a899d9a286fcd" ON "__chart__per_user_following" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_4b3593098b6edc9c5afe36b18b" ON "__chart__per_user_following" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_7af07790712aa3438ff6773f3b" ON "__chart__per_user_following" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_30bf67687f483ace115c5ca642" ON "__chart__per_user_drive" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e496ca8096d28f6b9b509264dc" ON "__chart__per_user_drive" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_5f86db6492274e07c1a3cdf286" ON "__chart__per_user_drive" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f09d543e3acb16c5976bdb31fa" ON "__chart__notes" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_7036f2957151588b813185c794" ON "__chart__notes" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_42eb716a37d381cdf566192b2b" ON "__chart__notes" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0a905b992fecd2b5c3fb98759e" ON "__chart__network" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_7b5da130992ec9df96712d4290" ON "__chart__network" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_a1efd3e0048a5f2793a47360dc" ON "__chart__network" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_39ee857ab2f23493037c6b6631" ON "__chart__instance" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_da8a46ba84ca1d8bb5a29bfb63" ON "__chart__instance" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_6b8f34a1a64b06014b6fb66824" ON "__chart__instance" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_25a97c02003338124b2b75fdbc" ON "__chart__hashtag" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_99a7d2faaef84a6f728d714ad6" ON "__chart__hashtag" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_07747a1038c05f532a718fe1de" ON "__chart__hashtag" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_dd907becf76104e4b656659e6b" ON "__chart__federation" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_76e87c7bfc5d925fcbba405d84" ON "__chart__federation" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_36cb699c49580d4e6c2e6159f9" ON "__chart__federation" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_3313d7288855ec105b5bbf6c21" ON "__chart__drive" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_7a170f67425e62a8fabb76c872" ON "__chart__drive" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_13565815f618a1ff53886c5b28" ON "__chart__drive" ("date") `);
+ await queryRunner.query(`CREATE INDEX "IDX_9a3ed15a30ab7e3a37702e6e08" ON "__chart__active_users" ("date", "group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_00ed5f86db1f7efafb1978bf21" ON "__chart__active_users" ("group") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0ad37b7ef50f4ddc84363d7ccc" ON "__chart__active_users" ("date") `);
+ }
+}
diff --git a/packages/backend/built/migration/1629024377804-deepl-integration.js b/packages/backend/built/migration/1629024377804-deepl-integration.js
new file mode 100644
index 0000000..b10fd94
--- /dev/null
+++ b/packages/backend/built/migration/1629024377804-deepl-integration.js
@@ -0,0 +1,11 @@
+export class deeplIntegration1629024377804 {
+ constructor(){
+ this.name = "deeplIntegration1629024377804";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "deeplAuthKey" character varying(128)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "deeplAuthKey"`);
+ }
+}
diff --git a/packages/backend/built/migration/1629288472000-fix-channel-userId.js b/packages/backend/built/migration/1629288472000-fix-channel-userId.js
new file mode 100644
index 0000000..e28b94c
--- /dev/null
+++ b/packages/backend/built/migration/1629288472000-fix-channel-userId.js
@@ -0,0 +1,11 @@
+export class fixChannelUserId1629288472000 {
+ constructor(){
+ this.name = "fixChannelUserId1629288472000";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "channel" ALTER COLUMN "userId" DROP NOT NULL;`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "channel" ALTER COLUMN "userId" SET NOT NULL;`);
+ }
+}
diff --git a/packages/backend/built/migration/1629512953000-user-is-deleted.js b/packages/backend/built/migration/1629512953000-user-is-deleted.js
new file mode 100644
index 0000000..93ee825
--- /dev/null
+++ b/packages/backend/built/migration/1629512953000-user-is-deleted.js
@@ -0,0 +1,12 @@
+export class isUserDeleted1629512953000 {
+ constructor(){
+ this.name = "isUserDeleted1629512953000";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" ADD "isDeleted" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."isDeleted" IS 'Whether the User is deleted.'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isDeleted"`);
+ }
+}
diff --git a/packages/backend/built/migration/1629778475000-deepl-integration2.js b/packages/backend/built/migration/1629778475000-deepl-integration2.js
new file mode 100644
index 0000000..0b4b941
--- /dev/null
+++ b/packages/backend/built/migration/1629778475000-deepl-integration2.js
@@ -0,0 +1,11 @@
+export class deeplIntegration21629778475000 {
+ constructor(){
+ this.name = "deeplIntegration21629778475000";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "deeplIsPro" boolean NOT NULL DEFAULT false`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "deeplIsPro"`);
+ }
+}
diff --git a/packages/backend/built/migration/1629833361000-AddShowTLReplies.js b/packages/backend/built/migration/1629833361000-AddShowTLReplies.js
new file mode 100644
index 0000000..6656ae6
--- /dev/null
+++ b/packages/backend/built/migration/1629833361000-AddShowTLReplies.js
@@ -0,0 +1,12 @@
+export class addShowTLReplies1629833361000 {
+ constructor(){
+ this.name = "addShowTLReplies1629833361000";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" ADD "showTimelineReplies" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."showTimelineReplies" IS 'Whether to show users replying to other users in the timeline.'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "showTimelineReplies"`);
+ }
+}
diff --git a/packages/backend/built/migration/1629968054000_userInstanceBlocks.js b/packages/backend/built/migration/1629968054000_userInstanceBlocks.js
new file mode 100644
index 0000000..7c6e711
--- /dev/null
+++ b/packages/backend/built/migration/1629968054000_userInstanceBlocks.js
@@ -0,0 +1,12 @@
+export class userInstanceBlocks1629968054000 {
+ constructor(){
+ this.name = "userInstanceBlocks1629968054000";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "mutedInstances" jsonb NOT NULL DEFAULT '[]'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."mutedInstances" IS 'List of instances muted by the user.'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "mutedInstances"`);
+ }
+}
diff --git a/packages/backend/built/migration/1633068642000-email-required-for-signup.js b/packages/backend/built/migration/1633068642000-email-required-for-signup.js
new file mode 100644
index 0000000..c3d5afa
--- /dev/null
+++ b/packages/backend/built/migration/1633068642000-email-required-for-signup.js
@@ -0,0 +1,11 @@
+export class emailRequiredForSignup1633068642000 {
+ constructor(){
+ this.name = "emailRequiredForSignup1633068642000";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "emailRequiredForSignup" boolean NOT NULL DEFAULT false`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "emailRequiredForSignup"`);
+ }
+}
diff --git a/packages/backend/built/migration/1633071909016-user-pending.js b/packages/backend/built/migration/1633071909016-user-pending.js
new file mode 100644
index 0000000..09da0ac
--- /dev/null
+++ b/packages/backend/built/migration/1633071909016-user-pending.js
@@ -0,0 +1,13 @@
+export class userPending1633071909016 {
+ constructor(){
+ this.name = "userPending1633071909016";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "user_pending" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "code" character varying(128) NOT NULL, "username" character varying(128) NOT NULL, "email" character varying(128) NOT NULL, "password" character varying(128) NOT NULL, CONSTRAINT "PK_d4c84e013c98ec02d19b8fbbafa" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_4e5c4c99175638ec0761714ab0" ON "user_pending" ("code") `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_4e5c4c99175638ec0761714ab0"`);
+ await queryRunner.query(`DROP TABLE "user_pending"`);
+ }
+}
diff --git a/packages/backend/built/migration/1634486652000-user-public-reactions.js b/packages/backend/built/migration/1634486652000-user-public-reactions.js
new file mode 100644
index 0000000..8fff0a8
--- /dev/null
+++ b/packages/backend/built/migration/1634486652000-user-public-reactions.js
@@ -0,0 +1,11 @@
+export class userPublicReactions1634486652000 {
+ constructor(){
+ this.name = "userPublicReactions1634486652000";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "publicReactions" boolean NOT NULL DEFAULT false`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "publicReactions"`);
+ }
+}
diff --git a/packages/backend/built/migration/1634902659689-delete-log.js b/packages/backend/built/migration/1634902659689-delete-log.js
new file mode 100644
index 0000000..49503c2
--- /dev/null
+++ b/packages/backend/built/migration/1634902659689-delete-log.js
@@ -0,0 +1,9 @@
+export class deleteLog1634902659689 {
+ constructor(){
+ this.name = "deleteLog1634902659689";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`DROP TABLE "log"`);
+ }
+ async down(queryRunner) {}
+}
diff --git a/packages/backend/built/migration/1635500777168-note-thread-mute.js b/packages/backend/built/migration/1635500777168-note-thread-mute.js
new file mode 100644
index 0000000..7dab2eb
--- /dev/null
+++ b/packages/backend/built/migration/1635500777168-note-thread-mute.js
@@ -0,0 +1,23 @@
+export class noteThreadMute1635500777168 {
+ constructor(){
+ this.name = "noteThreadMute1635500777168";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "note_thread_muting" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "threadId" character varying(256) NOT NULL, CONSTRAINT "PK_ec5936d94d1a0369646d12a3a47" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_29c11c7deb06615076f8c95b80" ON "note_thread_muting" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_c426394644267453e76f036926" ON "note_thread_muting" ("threadId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ae7aab18a2641d3e5f25e0c4ea" ON "note_thread_muting" ("userId", "threadId") `);
+ await queryRunner.query(`ALTER TABLE "note" ADD "threadId" character varying(256)`);
+ await queryRunner.query(`CREATE INDEX "IDX_d4ebdef929896d6dc4a3c5bb48" ON "note" ("threadId") `);
+ await queryRunner.query(`ALTER TABLE "note_thread_muting" ADD CONSTRAINT "FK_29c11c7deb06615076f8c95b80a" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note_thread_muting" DROP CONSTRAINT "FK_29c11c7deb06615076f8c95b80a"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_d4ebdef929896d6dc4a3c5bb48"`);
+ await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "threadId"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_ae7aab18a2641d3e5f25e0c4ea"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_c426394644267453e76f036926"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_29c11c7deb06615076f8c95b80"`);
+ await queryRunner.query(`DROP TABLE "note_thread_muting"`);
+ }
+}
diff --git a/packages/backend/built/migration/1636197624383-ff-visibility.js b/packages/backend/built/migration/1636197624383-ff-visibility.js
new file mode 100644
index 0000000..6b864fe
--- /dev/null
+++ b/packages/backend/built/migration/1636197624383-ff-visibility.js
@@ -0,0 +1,13 @@
+export class ffVisibility1636197624383 {
+ constructor(){
+ this.name = "ffVisibility1636197624383";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TYPE "public"."user_profile_ffvisibility_enum" AS ENUM('public', 'followers', 'private')`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "ffVisibility" "public"."user_profile_ffvisibility_enum" NOT NULL DEFAULT 'public'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "ffVisibility"`);
+ await queryRunner.query(`DROP TYPE "public"."user_profile_ffvisibility_enum"`);
+ }
+}
diff --git a/packages/backend/built/migration/1636697408073-remove-via-mobile.js b/packages/backend/built/migration/1636697408073-remove-via-mobile.js
new file mode 100644
index 0000000..c4fdfb1
--- /dev/null
+++ b/packages/backend/built/migration/1636697408073-remove-via-mobile.js
@@ -0,0 +1,9 @@
+export class removeViaMobile1636697408073 {
+ name = "removeViaMobile1636697408073";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "viaMobile"`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note" ADD "viaMobile" boolean NOT NULL DEFAULT false`);
+ }
+}
diff --git a/packages/backend/built/migration/1637320813000-forwarded-report.js b/packages/backend/built/migration/1637320813000-forwarded-report.js
new file mode 100644
index 0000000..e8ca954
--- /dev/null
+++ b/packages/backend/built/migration/1637320813000-forwarded-report.js
@@ -0,0 +1,9 @@
+export class forwardedReport1637320813000 {
+ name = "forwardedReport1637320813000";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "forwarded" boolean NOT NULL DEFAULT false`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "forwarded"`);
+ }
+}
diff --git a/packages/backend/built/migration/1639325650583-chart-v3.js b/packages/backend/built/migration/1639325650583-chart-v3.js
new file mode 100644
index 0000000..51771fa
--- /dev/null
+++ b/packages/backend/built/migration/1639325650583-chart-v3.js
@@ -0,0 +1,184 @@
+export class chartV31639325650583 {
+ name = "chartV31639325650583";
+ async up(queryRunner) {
+ await queryRunner.query(`DELETE FROM "__chart__per_user_drive" WHERE "group" IS NULL`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_dd907becf76104e4b656659e6b"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_eddfed8fb40305a04c6f941050"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_f09d543e3acb16c5976bdb31fa"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_e60c358aaced5aab8900a4af31"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_337e9599f278bd7537fe30876f"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_66feba81e1795d176d06c0b1e6"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_0a905b992fecd2b5c3fb98759e"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_2082327b2699ce924fa654afc5"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_9a3ed15a30ab7e3a37702e6e08"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_60c5c6e7e538c09aa274ecd1cf"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_8111b817b9818c04d7eb8475b1"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_583a157ed0cf0ed1b5ec2a833f"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_3313d7288855ec105b5bbf6c21"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_ceab80a6729f8e2e6f5b8a1a3d"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_3b7697a96f522d0478972e6d6f"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_53a3604b939e2b479eb2cfaac8"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_dabbb38a51ab86ee3cab291326"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_a9a806d466b314f253a1a611c4"`);
+ await queryRunner.query(`CREATE TABLE "__chart_day__federation" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "___instance_total" bigint NOT NULL, "___instance_inc" bigint NOT NULL, "___instance_dec" bigint NOT NULL, CONSTRAINT "UQ_617a8fe225a6e701d89e02d2c74" UNIQUE ("date"), CONSTRAINT "PK_7ca721c769f31698e0e1331e8e6" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_617a8fe225a6e701d89e02d2c7" ON "__chart_day__federation" ("date") `);
+ await queryRunner.query(`CREATE TABLE "__chart_day__notes" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "___local_total" bigint NOT NULL, "___local_inc" bigint NOT NULL, "___local_dec" bigint NOT NULL, "___local_diffs_normal" bigint NOT NULL, "___local_diffs_reply" bigint NOT NULL, "___local_diffs_renote" bigint NOT NULL, "___remote_total" bigint NOT NULL, "___remote_inc" bigint NOT NULL, "___remote_dec" bigint NOT NULL, "___remote_diffs_normal" bigint NOT NULL, "___remote_diffs_reply" bigint NOT NULL, "___remote_diffs_renote" bigint NOT NULL, CONSTRAINT "UQ_1a527b423ad0858a1af5a056d43" UNIQUE ("date"), CONSTRAINT "PK_1fa4139e1f338272b758d05e090" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_1a527b423ad0858a1af5a056d4" ON "__chart_day__notes" ("date") `);
+ await queryRunner.query(`CREATE TABLE "__chart_day__users" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "___local_total" bigint NOT NULL, "___local_inc" bigint NOT NULL, "___local_dec" bigint NOT NULL, "___remote_total" bigint NOT NULL, "___remote_inc" bigint NOT NULL, "___remote_dec" bigint NOT NULL, CONSTRAINT "UQ_cad6e07c20037f31cdba8a350c3" UNIQUE ("date"), CONSTRAINT "PK_d7f7185abb9851f70c4726c54bd" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_cad6e07c20037f31cdba8a350c" ON "__chart_day__users" ("date") `);
+ await queryRunner.query(`CREATE TABLE "__chart_day__network" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "___incomingRequests" bigint NOT NULL, "___outgoingRequests" bigint NOT NULL, "___totalTime" bigint NOT NULL, "___incomingBytes" bigint NOT NULL, "___outgoingBytes" bigint NOT NULL, CONSTRAINT "UQ_8bfa548c2b31f9e07db113773ee" UNIQUE ("date"), CONSTRAINT "PK_cac499d6f471042dfed1e7e0132" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_8bfa548c2b31f9e07db113773e" ON "__chart_day__network" ("date") `);
+ await queryRunner.query(`CREATE TABLE "__chart_day__active_users" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "___local_users" character varying array NOT NULL, "___remote_users" character varying array NOT NULL, CONSTRAINT "UQ_d5954f3df5e5e3bdfc3c03f3906" UNIQUE ("date"), CONSTRAINT "PK_b1790489b14f005ae8f404f5795" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_d5954f3df5e5e3bdfc3c03f390" ON "__chart_day__active_users" ("date") `);
+ await queryRunner.query(`CREATE TABLE "__chart_day__instance" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128) NOT NULL, "___requests_failed" bigint NOT NULL, "___requests_succeeded" bigint NOT NULL, "___requests_received" bigint NOT NULL, "___notes_total" bigint NOT NULL, "___notes_inc" bigint NOT NULL, "___notes_dec" bigint NOT NULL, "___notes_diffs_normal" bigint NOT NULL, "___notes_diffs_reply" bigint NOT NULL, "___notes_diffs_renote" bigint NOT NULL, "___users_total" bigint NOT NULL, "___users_inc" bigint NOT NULL, "___users_dec" bigint NOT NULL, "___following_total" bigint NOT NULL, "___following_inc" bigint NOT NULL, "___following_dec" bigint NOT NULL, "___followers_total" bigint NOT NULL, "___followers_inc" bigint NOT NULL, "___followers_dec" bigint NOT NULL, "___drive_totalFiles" bigint NOT NULL, "___drive_totalUsage" bigint NOT NULL, "___drive_incFiles" bigint NOT NULL, "___drive_incUsage" bigint NOT NULL, "___drive_decFiles" bigint NOT NULL, "___drive_decUsage" bigint NOT NULL, CONSTRAINT "UQ_fea7c0278325a1a2492f2d6acbf" UNIQUE ("date", "group"), CONSTRAINT "PK_479a8ff9d959274981087043023" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_fea7c0278325a1a2492f2d6acb" ON "__chart_day__instance" ("date", "group") `);
+ await queryRunner.query(`CREATE TABLE "__chart_day__per_user_notes" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128) NOT NULL, "___total" bigint NOT NULL, "___inc" bigint NOT NULL, "___dec" bigint NOT NULL, "___diffs_normal" bigint NOT NULL, "___diffs_reply" bigint NOT NULL, "___diffs_renote" bigint NOT NULL, CONSTRAINT "UQ_c5545d4b31cdc684034e33b81c3" UNIQUE ("date", "group"), CONSTRAINT "PK_58bab6b6d3ad9310cbc7460fd28" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_c5545d4b31cdc684034e33b81c" ON "__chart_day__per_user_notes" ("date", "group") `);
+ await queryRunner.query(`CREATE TABLE "__chart_day__drive" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "___local_totalCount" bigint NOT NULL, "___local_totalSize" bigint NOT NULL, "___local_incCount" bigint NOT NULL, "___local_incSize" bigint NOT NULL, "___local_decCount" bigint NOT NULL, "___local_decSize" bigint NOT NULL, "___remote_totalCount" bigint NOT NULL, "___remote_totalSize" bigint NOT NULL, "___remote_incCount" bigint NOT NULL, "___remote_incSize" bigint NOT NULL, "___remote_decCount" bigint NOT NULL, "___remote_decSize" bigint NOT NULL, CONSTRAINT "UQ_0b60ebb3aa0065f10b0616c1171" UNIQUE ("date"), CONSTRAINT "PK_e7ec0de057c77c40fc8d8b62151" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0b60ebb3aa0065f10b0616c117" ON "__chart_day__drive" ("date") `);
+ await queryRunner.query(`CREATE TABLE "__chart_day__per_user_reaction" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128) NOT NULL, "___local_count" bigint NOT NULL, "___remote_count" bigint NOT NULL, CONSTRAINT "UQ_d54b653660d808b118e36c184c0" UNIQUE ("date", "group"), CONSTRAINT "PK_8af24e2d51ff781a354fe595eda" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_d54b653660d808b118e36c184c" ON "__chart_day__per_user_reaction" ("date", "group") `);
+ await queryRunner.query(`CREATE TABLE "__chart_day__hashtag" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128) NOT NULL, "___local_users" character varying array NOT NULL, "___remote_users" character varying array NOT NULL, CONSTRAINT "UQ_8f589cf056ff51f09d6096f6450" UNIQUE ("date", "group"), CONSTRAINT "PK_13d5a3b089344e5557f8e0980b4" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_8f589cf056ff51f09d6096f645" ON "__chart_day__hashtag" ("date", "group") `);
+ await queryRunner.query(`CREATE TABLE "__chart_day__per_user_following" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128) NOT NULL, "___local_followings_total" bigint NOT NULL, "___local_followings_inc" bigint NOT NULL, "___local_followings_dec" bigint NOT NULL, "___local_followers_total" bigint NOT NULL, "___local_followers_inc" bigint NOT NULL, "___local_followers_dec" bigint NOT NULL, "___remote_followings_total" bigint NOT NULL, "___remote_followings_inc" bigint NOT NULL, "___remote_followings_dec" bigint NOT NULL, "___remote_followers_total" bigint NOT NULL, "___remote_followers_inc" bigint NOT NULL, "___remote_followers_dec" bigint NOT NULL, CONSTRAINT "UQ_e4849a3231f38281280ea4c0eee" UNIQUE ("date", "group"), CONSTRAINT "PK_68ce6b67da57166da66fc8fb27e" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e4849a3231f38281280ea4c0ee" ON "__chart_day__per_user_following" ("date", "group") `);
+ await queryRunner.query(`CREATE TABLE "__chart_day__per_user_drive" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "group" character varying(128) NOT NULL, "___totalCount" bigint NOT NULL, "___totalSize" bigint NOT NULL, "___incCount" bigint NOT NULL, "___incSize" bigint NOT NULL, "___decCount" bigint NOT NULL, "___decSize" bigint NOT NULL, CONSTRAINT "UQ_62aa5047b5aec92524f24c701d7" UNIQUE ("date", "group"), CONSTRAINT "PK_1ae135254c137011645da7f4045" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_62aa5047b5aec92524f24c701d" ON "__chart_day__per_user_drive" ("date", "group") `);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "group"`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" DROP COLUMN "group"`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" DROP COLUMN "group"`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" DROP COLUMN "group"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "group"`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" DROP COLUMN "group"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD CONSTRAINT "UQ_36cb699c49580d4e6c2e6159f97" UNIQUE ("date")`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ADD CONSTRAINT "UQ_42eb716a37d381cdf566192b2be" UNIQUE ("date")`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ADD CONSTRAINT "UQ_845254b3eaf708ae8a6cac30265" UNIQUE ("date")`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ADD CONSTRAINT "UQ_a1efd3e0048a5f2793a47360dc6" UNIQUE ("date")`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD CONSTRAINT "UQ_0ad37b7ef50f4ddc84363d7ccca" UNIQUE ("date")`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ALTER COLUMN "___local_users" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ALTER COLUMN "___remote_users" DROP DEFAULT`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_39ee857ab2f23493037c6b6631"`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "group" SET NOT NULL`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_5048e9daccbbbc6d567bb142d3"`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "group" SET NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ADD CONSTRAINT "UQ_13565815f618a1ff53886c5b28a" UNIQUE ("date")`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_229a41ad465f9205f1f5703291"`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" ALTER COLUMN "group" SET NOT NULL`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_25a97c02003338124b2b75fdbc"`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ALTER COLUMN "group" SET NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ALTER COLUMN "___local_users" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ALTER COLUMN "___remote_users" DROP DEFAULT`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_b77d4dd9562c3a899d9a286fcd"`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "group" SET NOT NULL`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_30bf67687f483ace115c5ca642"`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "group" SET NOT NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_36cb699c49580d4e6c2e6159f9" ON "__chart__federation" ("date") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_42eb716a37d381cdf566192b2b" ON "__chart__notes" ("date") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_845254b3eaf708ae8a6cac3026" ON "__chart__users" ("date") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a1efd3e0048a5f2793a47360dc" ON "__chart__network" ("date") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0ad37b7ef50f4ddc84363d7ccc" ON "__chart__active_users" ("date") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_39ee857ab2f23493037c6b6631" ON "__chart__instance" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_5048e9daccbbbc6d567bb142d3" ON "__chart__per_user_notes" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_13565815f618a1ff53886c5b28" ON "__chart__drive" ("date") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_229a41ad465f9205f1f5703291" ON "__chart__per_user_reaction" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_25a97c02003338124b2b75fdbc" ON "__chart__hashtag" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_b77d4dd9562c3a899d9a286fcd" ON "__chart__per_user_following" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_30bf67687f483ace115c5ca642" ON "__chart__per_user_drive" ("date", "group") `);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ADD CONSTRAINT "UQ_39ee857ab2f23493037c6b66311" UNIQUE ("date", "group")`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ADD CONSTRAINT "UQ_5048e9daccbbbc6d567bb142d34" UNIQUE ("date", "group")`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" ADD CONSTRAINT "UQ_229a41ad465f9205f1f57032910" UNIQUE ("date", "group")`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD CONSTRAINT "UQ_25a97c02003338124b2b75fdbc8" UNIQUE ("date", "group")`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ADD CONSTRAINT "UQ_b77d4dd9562c3a899d9a286fcd7" UNIQUE ("date", "group")`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ADD CONSTRAINT "UQ_30bf67687f483ace115c5ca6429" UNIQUE ("date", "group")`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" DROP CONSTRAINT "UQ_30bf67687f483ace115c5ca6429"`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" DROP CONSTRAINT "UQ_b77d4dd9562c3a899d9a286fcd7"`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP CONSTRAINT "UQ_25a97c02003338124b2b75fdbc8"`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" DROP CONSTRAINT "UQ_229a41ad465f9205f1f57032910"`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" DROP CONSTRAINT "UQ_5048e9daccbbbc6d567bb142d34"`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" DROP CONSTRAINT "UQ_39ee857ab2f23493037c6b66311"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_30bf67687f483ace115c5ca642"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_b77d4dd9562c3a899d9a286fcd"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_25a97c02003338124b2b75fdbc"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_229a41ad465f9205f1f5703291"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_13565815f618a1ff53886c5b28"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_5048e9daccbbbc6d567bb142d3"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_39ee857ab2f23493037c6b6631"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_0ad37b7ef50f4ddc84363d7ccc"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_a1efd3e0048a5f2793a47360dc"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_845254b3eaf708ae8a6cac3026"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_42eb716a37d381cdf566192b2b"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_36cb699c49580d4e6c2e6159f9"`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "group" DROP NOT NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_30bf67687f483ace115c5ca642" ON "__chart__per_user_drive" ("date", "group") `);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "group" DROP NOT NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_b77d4dd9562c3a899d9a286fcd" ON "__chart__per_user_following" ("date", "group") `);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ALTER COLUMN "___remote_users" SET DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ALTER COLUMN "___local_users" SET DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ALTER COLUMN "group" DROP NOT NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_25a97c02003338124b2b75fdbc" ON "__chart__hashtag" ("date", "group") `);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" ALTER COLUMN "group" DROP NOT NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_229a41ad465f9205f1f5703291" ON "__chart__per_user_reaction" ("date", "group") `);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" DROP CONSTRAINT "UQ_13565815f618a1ff53886c5b28a"`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "group" DROP NOT NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_5048e9daccbbbc6d567bb142d3" ON "__chart__per_user_notes" ("date", "group") `);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "group" DROP NOT NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_39ee857ab2f23493037c6b6631" ON "__chart__instance" ("date", "group") `);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ALTER COLUMN "___remote_users" SET DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ALTER COLUMN "___local_users" SET DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP CONSTRAINT "UQ_0ad37b7ef50f4ddc84363d7ccca"`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" DROP CONSTRAINT "UQ_a1efd3e0048a5f2793a47360dc6"`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" DROP CONSTRAINT "UQ_845254b3eaf708ae8a6cac30265"`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" DROP CONSTRAINT "UQ_42eb716a37d381cdf566192b2be"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP CONSTRAINT "UQ_36cb699c49580d4e6c2e6159f97"`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ADD "group" character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "group" character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ADD "group" character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ADD "group" character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ADD "group" character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "group" character varying(128)`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_62aa5047b5aec92524f24c701d"`);
+ await queryRunner.query(`DROP TABLE "__chart_day__per_user_drive"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_e4849a3231f38281280ea4c0ee"`);
+ await queryRunner.query(`DROP TABLE "__chart_day__per_user_following"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_8f589cf056ff51f09d6096f645"`);
+ await queryRunner.query(`DROP TABLE "__chart_day__hashtag"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_d54b653660d808b118e36c184c"`);
+ await queryRunner.query(`DROP TABLE "__chart_day__per_user_reaction"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_0b60ebb3aa0065f10b0616c117"`);
+ await queryRunner.query(`DROP TABLE "__chart_day__drive"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_c5545d4b31cdc684034e33b81c"`);
+ await queryRunner.query(`DROP TABLE "__chart_day__per_user_notes"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_fea7c0278325a1a2492f2d6acb"`);
+ await queryRunner.query(`DROP TABLE "__chart_day__instance"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_d5954f3df5e5e3bdfc3c03f390"`);
+ await queryRunner.query(`DROP TABLE "__chart_day__active_users"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_8bfa548c2b31f9e07db113773e"`);
+ await queryRunner.query(`DROP TABLE "__chart_day__network"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_cad6e07c20037f31cdba8a350c"`);
+ await queryRunner.query(`DROP TABLE "__chart_day__users"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_1a527b423ad0858a1af5a056d4"`);
+ await queryRunner.query(`DROP TABLE "__chart_day__notes"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_617a8fe225a6e701d89e02d2c7"`);
+ await queryRunner.query(`DROP TABLE "__chart_day__federation"`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a9a806d466b314f253a1a611c4" ON "__chart__per_user_drive" ("date") WHERE ("group" IS NULL)`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_dabbb38a51ab86ee3cab291326" ON "__chart__per_user_following" ("date") WHERE ("group" IS NULL)`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_53a3604b939e2b479eb2cfaac8" ON "__chart__hashtag" ("date") WHERE ("group" IS NULL)`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_3b7697a96f522d0478972e6d6f" ON "__chart__per_user_reaction" ("date") WHERE ("group" IS NULL)`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ceab80a6729f8e2e6f5b8a1a3d" ON "__chart__drive" ("date") WHERE ("group" IS NULL)`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_3313d7288855ec105b5bbf6c21" ON "__chart__drive" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_583a157ed0cf0ed1b5ec2a833f" ON "__chart__per_user_notes" ("date") WHERE ("group" IS NULL)`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_8111b817b9818c04d7eb8475b1" ON "__chart__instance" ("date") WHERE ("group" IS NULL)`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_60c5c6e7e538c09aa274ecd1cf" ON "__chart__active_users" ("date") WHERE ("group" IS NULL)`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_9a3ed15a30ab7e3a37702e6e08" ON "__chart__active_users" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_2082327b2699ce924fa654afc5" ON "__chart__network" ("date") WHERE ("group" IS NULL)`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0a905b992fecd2b5c3fb98759e" ON "__chart__network" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_66feba81e1795d176d06c0b1e6" ON "__chart__users" ("date") WHERE ("group" IS NULL)`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_337e9599f278bd7537fe30876f" ON "__chart__users" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e60c358aaced5aab8900a4af31" ON "__chart__notes" ("date") WHERE ("group" IS NULL)`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_f09d543e3acb16c5976bdb31fa" ON "__chart__notes" ("date", "group") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_eddfed8fb40305a04c6f941050" ON "__chart__federation" ("date") WHERE ("group" IS NULL)`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_dd907becf76104e4b656659e6b" ON "__chart__federation" ("date", "group") `);
+ }
+}
diff --git a/packages/backend/built/migration/1642611822809-emoji-url.js b/packages/backend/built/migration/1642611822809-emoji-url.js
new file mode 100644
index 0000000..3182752
--- /dev/null
+++ b/packages/backend/built/migration/1642611822809-emoji-url.js
@@ -0,0 +1,11 @@
+export class emojiUrl1642611822809 {
+ name = "emojiUrl1642611822809";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "emoji" RENAME COLUMN "url" TO "originalUrl"`);
+ await queryRunner.query(`ALTER TABLE "emoji" ADD "publicUrl" character varying(512) NOT NULL DEFAULT ''`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "publicUrl"`);
+ await queryRunner.query(`ALTER TABLE "emoji" RENAME COLUMN "originalUrl" TO "url"`);
+ }
+}
diff --git a/packages/backend/built/migration/1642613870898-drive-file-webpublic-type.js b/packages/backend/built/migration/1642613870898-drive-file-webpublic-type.js
new file mode 100644
index 0000000..554fcbe
--- /dev/null
+++ b/packages/backend/built/migration/1642613870898-drive-file-webpublic-type.js
@@ -0,0 +1,9 @@
+export class driveFileWebpublicType1642613870898 {
+ name = "driveFileWebpublicType1642613870898";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "drive_file" ADD "webpublicType" character varying(128)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "webpublicType"`);
+ }
+}
diff --git a/packages/backend/built/migration/1643963705770-chart-v4.js b/packages/backend/built/migration/1643963705770-chart-v4.js
new file mode 100644
index 0000000..0db9f39
--- /dev/null
+++ b/packages/backend/built/migration/1643963705770-chart-v4.js
@@ -0,0 +1,59 @@
+export class chartV41643963705770 {
+ name = "chartV41643963705770";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart__instance" DROP COLUMN "___drive_totalUsage"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" DROP COLUMN "___drive_totalUsage"`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" DROP COLUMN "___local_totalCount"`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" DROP COLUMN "___local_totalSize"`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" DROP COLUMN "___remote_totalCount"`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" DROP COLUMN "___remote_totalSize"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" DROP COLUMN "___local_totalCount"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" DROP COLUMN "___local_totalSize"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" DROP COLUMN "___remote_totalCount"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" DROP COLUMN "___remote_totalSize"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___local_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___local_users" bigint NOT NULL DEFAULT 0`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___remote_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___remote_users" bigint NOT NULL DEFAULT 0`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___local_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___local_users" bigint NOT NULL DEFAULT 0`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___remote_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___remote_users" bigint NOT NULL DEFAULT 0`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP COLUMN "___local_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD "___local_users" bigint NOT NULL DEFAULT 0`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP COLUMN "___remote_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD "___remote_users" bigint NOT NULL DEFAULT 0`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__hashtag" DROP COLUMN "___local_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__hashtag" ADD "___local_users" bigint NOT NULL DEFAULT 0`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__hashtag" DROP COLUMN "___remote_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__hashtag" ADD "___remote_users" bigint NOT NULL DEFAULT 0`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart_day__hashtag" DROP COLUMN "___remote_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__hashtag" ADD "___remote_users" character varying array NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__hashtag" DROP COLUMN "___local_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__hashtag" ADD "___local_users" character varying array NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP COLUMN "___remote_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD "___remote_users" character varying array NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP COLUMN "___local_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD "___local_users" character varying array NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___remote_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___remote_users" character varying array NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___local_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___local_users" character varying array NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___remote_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___remote_users" character varying array NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___local_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___local_users" character varying array NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ADD "___remote_totalSize" bigint NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ADD "___remote_totalCount" bigint NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ADD "___local_totalSize" bigint NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ADD "___local_totalCount" bigint NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ADD "___remote_totalSize" bigint NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ADD "___remote_totalCount" bigint NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ADD "___local_totalSize" bigint NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ADD "___local_totalCount" bigint NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ADD "___drive_totalUsage" bigint NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ADD "___drive_totalUsage" bigint NOT NULL`);
+ }
+}
diff --git a/packages/backend/built/migration/1643966656277-chart-v5.js b/packages/backend/built/migration/1643966656277-chart-v5.js
new file mode 100644
index 0000000..8dac42d
--- /dev/null
+++ b/packages/backend/built/migration/1643966656277-chart-v5.js
@@ -0,0 +1,23 @@
+export class chartV51643966656277 {
+ name = "chartV51643966656277";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "unique_temp___local_users" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "unique_temp___remote_users" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "unique_temp___local_users" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "unique_temp___remote_users" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD "unique_temp___local_users" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD "unique_temp___remote_users" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__hashtag" ADD "unique_temp___local_users" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__hashtag" ADD "unique_temp___remote_users" character varying array NOT NULL DEFAULT '{}'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart_day__hashtag" DROP COLUMN "unique_temp___remote_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__hashtag" DROP COLUMN "unique_temp___local_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP COLUMN "unique_temp___remote_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP COLUMN "unique_temp___local_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "unique_temp___remote_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "unique_temp___local_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "unique_temp___remote_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "unique_temp___local_users"`);
+ }
+}
diff --git a/packages/backend/built/migration/1643967331284-chart-v6.js b/packages/backend/built/migration/1643967331284-chart-v6.js
new file mode 100644
index 0000000..4cdd95f
--- /dev/null
+++ b/packages/backend/built/migration/1643967331284-chart-v6.js
@@ -0,0 +1,339 @@
+export class chartV61643967331284 {
+ name = "chartV61643967331284";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ALTER COLUMN "___instance_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ALTER COLUMN "___instance_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ALTER COLUMN "___instance_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ALTER COLUMN "___instance_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ALTER COLUMN "___instance_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ALTER COLUMN "___instance_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_diffs_normal" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_diffs_reply" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_diffs_renote" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_diffs_normal" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_diffs_reply" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_diffs_renote" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_diffs_normal" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_diffs_reply" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_diffs_renote" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_diffs_normal" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_diffs_reply" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_diffs_renote" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___local_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___local_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___local_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___remote_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___remote_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___remote_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___local_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___local_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___local_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___remote_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___remote_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___remote_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___incomingRequests" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___outgoingRequests" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___totalTime" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___incomingBytes" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___outgoingBytes" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___incomingRequests" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___outgoingRequests" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___totalTime" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___incomingBytes" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___outgoingBytes" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___requests_failed" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___requests_succeeded" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___requests_received" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_diffs_normal" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_diffs_reply" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_diffs_renote" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___users_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___users_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___users_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___following_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___following_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___following_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___followers_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___followers_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___followers_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_totalFiles" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_incFiles" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_decFiles" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_incUsage" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_decUsage" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___requests_failed" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___requests_succeeded" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___requests_received" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_diffs_normal" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_diffs_reply" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_diffs_renote" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___users_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___users_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___users_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___following_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___following_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___following_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___followers_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___followers_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___followers_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_totalFiles" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_incFiles" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_decFiles" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_incUsage" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_decUsage" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___diffs_normal" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___diffs_reply" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___diffs_renote" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___diffs_normal" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___diffs_reply" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___diffs_renote" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___local_incCount" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___local_incSize" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___local_decCount" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___local_decSize" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___remote_incCount" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___remote_incSize" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___remote_decCount" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___remote_decSize" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___local_incCount" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___local_incSize" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___local_decCount" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___local_decSize" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___remote_incCount" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___remote_incSize" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___remote_decCount" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___remote_decSize" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" ALTER COLUMN "___local_count" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" ALTER COLUMN "___remote_count" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_reaction" ALTER COLUMN "___local_count" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_reaction" ALTER COLUMN "___remote_count" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followings_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followings_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followings_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followers_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followers_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followers_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followings_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followings_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followings_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followers_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followers_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followers_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followings_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followings_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followings_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followers_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followers_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followers_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followings_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followings_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followings_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followers_total" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followers_inc" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followers_dec" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___totalCount" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___totalSize" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___incCount" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___incSize" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___decCount" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___decSize" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___totalCount" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___totalSize" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___incCount" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___incSize" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___decCount" SET DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___decSize" SET DEFAULT '0'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___decSize" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___decCount" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___incSize" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___incCount" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___totalSize" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___totalCount" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___decSize" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___decCount" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___incSize" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___incCount" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___totalSize" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___totalCount" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followers_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followers_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followers_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followings_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followings_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followings_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followers_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followers_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followers_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followings_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followings_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followings_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followers_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followers_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followers_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followings_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followings_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followings_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followers_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followers_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followers_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followings_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followings_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followings_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_reaction" ALTER COLUMN "___remote_count" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_reaction" ALTER COLUMN "___local_count" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" ALTER COLUMN "___remote_count" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" ALTER COLUMN "___local_count" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___remote_decSize" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___remote_decCount" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___remote_incSize" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___remote_incCount" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___local_decSize" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___local_decCount" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___local_incSize" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___local_incCount" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___remote_decSize" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___remote_decCount" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___remote_incSize" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___remote_incCount" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___local_decSize" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___local_decCount" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___local_incSize" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___local_incCount" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___diffs_renote" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___diffs_reply" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___diffs_normal" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___diffs_renote" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___diffs_reply" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___diffs_normal" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_decUsage" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_incUsage" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_decFiles" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_incFiles" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_totalFiles" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___followers_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___followers_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___followers_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___following_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___following_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___following_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___users_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___users_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___users_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_diffs_renote" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_diffs_reply" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_diffs_normal" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___requests_received" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___requests_succeeded" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___requests_failed" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_decUsage" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_incUsage" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_decFiles" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_incFiles" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_totalFiles" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___followers_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___followers_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___followers_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___following_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___following_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___following_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___users_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___users_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___users_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_diffs_renote" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_diffs_reply" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_diffs_normal" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___requests_received" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___requests_succeeded" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___requests_failed" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___outgoingBytes" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___incomingBytes" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___totalTime" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___outgoingRequests" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___incomingRequests" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___outgoingBytes" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___incomingBytes" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___totalTime" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___outgoingRequests" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___incomingRequests" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___remote_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___remote_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___remote_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___local_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___local_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___local_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___remote_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___remote_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___remote_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___local_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___local_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___local_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_diffs_renote" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_diffs_reply" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_diffs_normal" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_diffs_renote" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_diffs_reply" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_diffs_normal" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_diffs_renote" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_diffs_reply" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_diffs_normal" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_diffs_renote" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_diffs_reply" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_diffs_normal" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ALTER COLUMN "___instance_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ALTER COLUMN "___instance_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ALTER COLUMN "___instance_total" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ALTER COLUMN "___instance_dec" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ALTER COLUMN "___instance_inc" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ALTER COLUMN "___instance_total" DROP DEFAULT`);
+ }
+}
diff --git a/packages/backend/built/migration/1644010796173-convert-hard-mutes.js b/packages/backend/built/migration/1644010796173-convert-hard-mutes.js
new file mode 100644
index 0000000..3b3ab07
--- /dev/null
+++ b/packages/backend/built/migration/1644010796173-convert-hard-mutes.js
@@ -0,0 +1,53 @@
+import RE2 from "re2";
+export class convertHardMutes1644010796173 {
+ name = "convertHardMutes1644010796173";
+ async up(queryRunner) {
+ let entries = await queryRunner.query(`SELECT "userId", "mutedWords" FROM "user_profile" WHERE "userHost" IS NULL`);
+ for(let i = 0; i < entries.length; i++){
+ let words = entries[i].mutedWords.map((line)=>{
+ if (typeof line === "string") return [];
+ const regexp = line.join(" ").match(/^\/(.+)\/(.*)$/);
+ if (regexp) {
+ // convert regexp's
+ try {
+ new RE2(regexp[1], regexp[2]);
+ return `/${regexp[1]}/${regexp[2]}`;
+ } catch (err) {
+ // invalid regex, ignore it
+ return [];
+ }
+ } else {
+ // remove empty segments
+ return line.filter((x)=>x !== "");
+ }
+ })// remove empty lines
+ .filter((x)=>!(Array.isArray(x) && x.length === 0));
+ await queryRunner.connection.createQueryBuilder().update("user_profile").set({
+ mutedWords: words
+ }).where("userId = :id", {
+ id: entries[i].userId
+ }).execute();
+ }
+ }
+ async down(queryRunner) {
+ let entries = await queryRunner.query(`SELECT "userId", "mutedWords" FROM "user_profile"`);
+ for(let i = 0; i < entries.length; i++){
+ let words = entries[i].mutedWords.map((line)=>{
+ if (Array.isArray(line)) {
+ return line;
+ } else {
+ // do not split regex at spaces again
+ return [
+ line
+ ];
+ }
+ })// remove empty lines
+ .filter((x)=>!(Array.isArray(x) && x.length === 0));
+ await queryRunner.connection.createQueryBuilder().update("user_profile").set({
+ mutedWords: words
+ }).where("userId = :id", {
+ id: entries[i].userId
+ }).execute();
+ }
+ }
+}
diff --git a/packages/backend/built/migration/1644058404077-chart-v7.js b/packages/backend/built/migration/1644058404077-chart-v7.js
new file mode 100644
index 0000000..ae5fd21
--- /dev/null
+++ b/packages/backend/built/migration/1644058404077-chart-v7.js
@@ -0,0 +1,495 @@
+export class chartV71644058404077 {
+ name = "chartV71644058404077";
+ async up(queryRunner) {
+ await queryRunner.query(`UPDATE "__chart__federation" SET "___instance_total"=2147483647 WHERE "___instance_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__federation" SET "___instance_inc"=32767 WHERE "___instance_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__federation" SET "___instance_dec"=32767 WHERE "___instance_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__federation" SET "___instance_total"=2147483647 WHERE "___instance_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__federation" SET "___instance_inc"=32767 WHERE "___instance_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__federation" SET "___instance_dec"=32767 WHERE "___instance_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__notes" SET "___local_total"=2147483647 WHERE "___local_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__notes" SET "___local_inc"=2147483647 WHERE "___local_inc" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__notes" SET "___local_dec"=2147483647 WHERE "___local_dec" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__notes" SET "___local_diffs_normal"=2147483647 WHERE "___local_diffs_normal" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__notes" SET "___local_diffs_reply"=2147483647 WHERE "___local_diffs_reply" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__notes" SET "___local_diffs_renote"=2147483647 WHERE "___local_diffs_renote" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__notes" SET "___remote_total"=2147483647 WHERE "___remote_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__notes" SET "___remote_inc"=2147483647 WHERE "___remote_inc" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__notes" SET "___remote_dec"=2147483647 WHERE "___remote_dec" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__notes" SET "___remote_diffs_normal"=2147483647 WHERE "___remote_diffs_normal" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__notes" SET "___remote_diffs_reply"=2147483647 WHERE "___remote_diffs_reply" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__notes" SET "___remote_diffs_renote"=2147483647 WHERE "___remote_diffs_renote" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__notes" SET "___local_total"=2147483647 WHERE "___local_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__notes" SET "___local_inc"=2147483647 WHERE "___local_inc" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__notes" SET "___local_dec"=2147483647 WHERE "___local_dec" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__notes" SET "___local_diffs_normal"=2147483647 WHERE "___local_diffs_normal" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__notes" SET "___local_diffs_reply"=2147483647 WHERE "___local_diffs_reply" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__notes" SET "___local_diffs_renote"=2147483647 WHERE "___local_diffs_renote" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__notes" SET "___remote_total"=2147483647 WHERE "___remote_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__notes" SET "___remote_inc"=2147483647 WHERE "___remote_inc" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__notes" SET "___remote_dec"=2147483647 WHERE "___remote_dec" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__notes" SET "___remote_diffs_normal"=2147483647 WHERE "___remote_diffs_normal" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__notes" SET "___remote_diffs_reply"=2147483647 WHERE "___remote_diffs_reply" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__notes" SET "___remote_diffs_renote"=2147483647 WHERE "___remote_diffs_renote" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__users" SET "___local_total"=2147483647 WHERE "___local_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__users" SET "___local_inc"=32767 WHERE "___local_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__users" SET "___local_dec"=32767 WHERE "___local_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__users" SET "___remote_total"=2147483647 WHERE "___remote_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__users" SET "___remote_inc"=32767 WHERE "___remote_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__users" SET "___remote_dec"=32767 WHERE "___remote_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__users" SET "___local_total"=2147483647 WHERE "___local_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__users" SET "___local_inc"=32767 WHERE "___local_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__users" SET "___local_dec"=32767 WHERE "___local_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__users" SET "___remote_total"=2147483647 WHERE "___remote_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__users" SET "___remote_inc"=32767 WHERE "___remote_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__users" SET "___remote_dec"=32767 WHERE "___remote_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__network" SET "___incomingRequests"=2147483647 WHERE "___incomingRequests" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__network" SET "___outgoingRequests"=2147483647 WHERE "___outgoingRequests" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__network" SET "___totalTime"=2147483647 WHERE "___totalTime" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__network" SET "___incomingBytes"=2147483647 WHERE "___incomingBytes" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__network" SET "___outgoingBytes"=2147483647 WHERE "___outgoingBytes" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__network" SET "___incomingRequests"=2147483647 WHERE "___incomingRequests" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__network" SET "___outgoingRequests"=2147483647 WHERE "___outgoingRequests" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__network" SET "___totalTime"=2147483647 WHERE "___totalTime" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__network" SET "___incomingBytes"=2147483647 WHERE "___incomingBytes" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__network" SET "___outgoingBytes"=2147483647 WHERE "___outgoingBytes" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___requests_failed"=32767 WHERE "___requests_failed" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___requests_succeeded"=32767 WHERE "___requests_succeeded" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___requests_received"=32767 WHERE "___requests_received" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___notes_total"=2147483647 WHERE "___notes_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___notes_inc"=2147483647 WHERE "___notes_inc" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___notes_dec"=2147483647 WHERE "___notes_dec" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___notes_diffs_normal"=2147483647 WHERE "___notes_diffs_normal" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___notes_diffs_reply"=2147483647 WHERE "___notes_diffs_reply" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___notes_diffs_renote"=2147483647 WHERE "___notes_diffs_renote" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___users_total"=2147483647 WHERE "___users_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___users_inc"=32767 WHERE "___users_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___users_dec"=32767 WHERE "___users_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___following_total"=2147483647 WHERE "___following_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___following_inc"=32767 WHERE "___following_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___following_dec"=32767 WHERE "___following_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___followers_total"=2147483647 WHERE "___followers_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___followers_inc"=32767 WHERE "___followers_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___followers_dec"=32767 WHERE "___followers_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___drive_totalFiles"=2147483647 WHERE "___drive_totalFiles" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___drive_incFiles"=2147483647 WHERE "___drive_incFiles" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___drive_decFiles"=2147483647 WHERE "___drive_decFiles" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___drive_incUsage"=2147483647 WHERE "___drive_incUsage" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__instance" SET "___drive_decUsage"=2147483647 WHERE "___drive_decUsage" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___requests_failed"=32767 WHERE "___requests_failed" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___requests_succeeded"=32767 WHERE "___requests_succeeded" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___requests_received"=32767 WHERE "___requests_received" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___notes_total"=2147483647 WHERE "___notes_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___notes_inc"=2147483647 WHERE "___notes_inc" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___notes_dec"=2147483647 WHERE "___notes_dec" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___notes_diffs_normal"=2147483647 WHERE "___notes_diffs_normal" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___notes_diffs_reply"=2147483647 WHERE "___notes_diffs_reply" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___notes_diffs_renote"=2147483647 WHERE "___notes_diffs_renote" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___users_total"=2147483647 WHERE "___users_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___users_inc"=32767 WHERE "___users_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___users_dec"=32767 WHERE "___users_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___following_total"=2147483647 WHERE "___following_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___following_inc"=32767 WHERE "___following_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___following_dec"=32767 WHERE "___following_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___followers_total"=2147483647 WHERE "___followers_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___followers_inc"=32767 WHERE "___followers_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___followers_dec"=32767 WHERE "___followers_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___drive_totalFiles"=2147483647 WHERE "___drive_totalFiles" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___drive_incFiles"=2147483647 WHERE "___drive_incFiles" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___drive_decFiles"=2147483647 WHERE "___drive_decFiles" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___drive_incUsage"=2147483647 WHERE "___drive_incUsage" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__instance" SET "___drive_decUsage"=2147483647 WHERE "___drive_decUsage" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__per_user_notes" SET "___total"=2147483647 WHERE "___total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__per_user_notes" SET "___inc"=32767 WHERE "___inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__per_user_notes" SET "___dec"=32767 WHERE "___dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__per_user_notes" SET "___diffs_normal"=32767 WHERE "___diffs_normal" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__per_user_notes" SET "___diffs_reply"=32767 WHERE "___diffs_reply" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__per_user_notes" SET "___diffs_renote"=32767 WHERE "___diffs_renote" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_notes" SET "___total"=2147483647 WHERE "___total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_notes" SET "___inc"=32767 WHERE "___inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_notes" SET "___dec"=32767 WHERE "___dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_notes" SET "___diffs_normal"=32767 WHERE "___diffs_normal" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_notes" SET "___diffs_reply"=32767 WHERE "___diffs_reply" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_notes" SET "___diffs_renote"=32767 WHERE "___diffs_renote" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__drive" SET "___local_incCount"=2147483647 WHERE "___local_incCount" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__drive" SET "___local_incSize"=2147483647 WHERE "___local_incSize" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__drive" SET "___local_decCount"=2147483647 WHERE "___local_decCount" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__drive" SET "___local_decSize"=2147483647 WHERE "___local_decSize" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__drive" SET "___remote_incCount"=2147483647 WHERE "___remote_incCount" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__drive" SET "___remote_incSize"=2147483647 WHERE "___remote_incSize" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__drive" SET "___remote_decCount"=2147483647 WHERE "___remote_decCount" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__drive" SET "___remote_decSize"=2147483647 WHERE "___remote_decSize" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__drive" SET "___local_incCount"=2147483647 WHERE "___local_incCount" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__drive" SET "___local_incSize"=2147483647 WHERE "___local_incSize" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__drive" SET "___local_decCount"=2147483647 WHERE "___local_decCount" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__drive" SET "___local_decSize"=2147483647 WHERE "___local_decSize" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__drive" SET "___remote_incCount"=2147483647 WHERE "___remote_incCount" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__drive" SET "___remote_incSize"=2147483647 WHERE "___remote_incSize" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__drive" SET "___remote_decCount"=2147483647 WHERE "___remote_decCount" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__drive" SET "___remote_decSize"=2147483647 WHERE "___remote_decSize" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__per_user_reaction" SET "___local_count"=32767 WHERE "___local_count" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__per_user_reaction" SET "___remote_count"=32767 WHERE "___remote_count" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_reaction" SET "___local_count"=32767 WHERE "___local_count" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_reaction" SET "___remote_count"=32767 WHERE "___remote_count" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__per_user_following" SET "___local_followings_total"=2147483647 WHERE "___local_followings_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__per_user_following" SET "___local_followings_inc"=32767 WHERE "___local_followings_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__per_user_following" SET "___local_followings_dec"=32767 WHERE "___local_followings_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__per_user_following" SET "___local_followers_total"=2147483647 WHERE "___local_followers_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__per_user_following" SET "___local_followers_inc"=32767 WHERE "___local_followers_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__per_user_following" SET "___local_followers_dec"=32767 WHERE "___local_followers_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__per_user_following" SET "___remote_followings_total"=2147483647 WHERE "___remote_followings_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__per_user_following" SET "___remote_followings_inc"=32767 WHERE "___remote_followings_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__per_user_following" SET "___remote_followings_dec"=32767 WHERE "___remote_followings_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__per_user_following" SET "___remote_followers_total"=2147483647 WHERE "___remote_followers_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__per_user_following" SET "___remote_followers_inc"=32767 WHERE "___remote_followers_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart__per_user_following" SET "___remote_followers_dec"=32767 WHERE "___remote_followers_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_following" SET "___local_followings_total"=2147483647 WHERE "___local_followings_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_following" SET "___local_followings_inc"=32767 WHERE "___local_followings_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_following" SET "___local_followings_dec"=32767 WHERE "___local_followings_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_following" SET "___local_followers_total"=2147483647 WHERE "___local_followers_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_following" SET "___local_followers_inc"=32767 WHERE "___local_followers_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_following" SET "___local_followers_dec"=32767 WHERE "___local_followers_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_following" SET "___remote_followings_total"=2147483647 WHERE "___remote_followings_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_following" SET "___remote_followings_inc"=32767 WHERE "___remote_followings_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_following" SET "___remote_followings_dec"=32767 WHERE "___remote_followings_dec" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_following" SET "___remote_followers_total"=2147483647 WHERE "___remote_followers_total" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_following" SET "___remote_followers_inc"=32767 WHERE "___remote_followers_inc" > 32767`);
+ await queryRunner.query(`UPDATE "__chart_day__per_user_following" SET "___remote_followers_dec"=32767 WHERE "___remote_followers_dec" > 32767`);
+ await queryRunner.query(`TRUNCATE TABLE "__chart__per_user_drive"`);
+ await queryRunner.query(`TRUNCATE TABLE "__chart_day__per_user_drive"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ALTER COLUMN "___instance_total" TYPE integer USING "___instance_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ALTER COLUMN "___instance_inc" TYPE smallint USING "___instance_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ALTER COLUMN "___instance_dec" TYPE smallint USING "___instance_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ALTER COLUMN "___instance_total" TYPE integer USING "___instance_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ALTER COLUMN "___instance_inc" TYPE smallint USING "___instance_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ALTER COLUMN "___instance_dec" TYPE smallint USING "___instance_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_total" TYPE integer USING "___local_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_inc" TYPE integer USING "___local_inc"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_dec" TYPE integer USING "___local_dec"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_diffs_normal" TYPE integer USING "___local_diffs_normal"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_diffs_reply" TYPE integer USING "___local_diffs_reply"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_diffs_renote" TYPE integer USING "___local_diffs_renote"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_total" TYPE integer USING "___remote_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_inc" TYPE integer USING "___remote_inc"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_dec" TYPE integer USING "___remote_dec"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_diffs_normal" TYPE integer USING "___remote_diffs_normal"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_diffs_reply" TYPE integer USING "___remote_diffs_reply"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_diffs_renote" TYPE integer USING "___remote_diffs_renote"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_total" TYPE integer USING "___local_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_inc" TYPE integer USING "___local_inc"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_dec" TYPE integer USING "___local_dec"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_diffs_normal" TYPE integer USING "___local_diffs_normal"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_diffs_reply" TYPE integer USING "___local_diffs_reply"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_diffs_renote" TYPE integer USING "___local_diffs_renote"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_total" TYPE integer USING "___remote_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_inc" TYPE integer USING "___remote_inc"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_dec" TYPE integer USING "___remote_dec"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_diffs_normal" TYPE integer USING "___remote_diffs_normal"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_diffs_reply" TYPE integer USING "___remote_diffs_reply"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_diffs_renote" TYPE integer USING "___remote_diffs_renote"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___local_total" TYPE integer USING "___local_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___local_inc" TYPE smallint USING "___local_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___local_dec" TYPE smallint USING "___local_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___remote_total" TYPE integer USING "___remote_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___remote_inc" TYPE smallint USING "___remote_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___remote_dec" TYPE smallint USING "___remote_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___local_total" TYPE integer USING "___local_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___local_inc" TYPE smallint USING "___local_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___local_dec" TYPE smallint USING "___local_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___remote_total" TYPE integer USING "___remote_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___remote_inc" TYPE smallint USING "___remote_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___remote_dec" TYPE smallint USING "___remote_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___incomingRequests" TYPE integer USING "___incomingRequests"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___outgoingRequests" TYPE integer USING "___outgoingRequests"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___totalTime" TYPE integer USING "___totalTime"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___incomingBytes" TYPE integer USING "___incomingBytes"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___outgoingBytes" TYPE integer USING "___outgoingBytes"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___incomingRequests" TYPE integer USING "___incomingRequests"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___outgoingRequests" TYPE integer USING "___outgoingRequests"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___totalTime" TYPE integer USING "___totalTime"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___incomingBytes" TYPE integer USING "___incomingBytes"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___outgoingBytes" TYPE integer USING "___outgoingBytes"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___requests_failed" TYPE smallint USING "___requests_failed"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___requests_succeeded" TYPE smallint USING "___requests_succeeded"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___requests_received" TYPE smallint USING "___requests_received"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_total" TYPE integer USING "___notes_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_inc" TYPE integer USING "___notes_inc"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_dec" TYPE integer USING "___notes_dec"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_diffs_normal" TYPE integer USING "___notes_diffs_normal"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_diffs_reply" TYPE integer USING "___notes_diffs_reply"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_diffs_renote" TYPE integer USING "___notes_diffs_renote"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___users_total" TYPE integer USING "___users_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___users_inc" TYPE smallint USING "___users_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___users_dec" TYPE smallint USING "___users_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___following_total" TYPE integer USING "___following_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___following_inc" TYPE smallint USING "___following_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___following_dec" TYPE smallint USING "___following_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___followers_total" TYPE integer USING "___followers_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___followers_inc" TYPE smallint USING "___followers_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___followers_dec" TYPE smallint USING "___followers_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_totalFiles" TYPE integer USING "___drive_totalFiles"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_incFiles" TYPE integer USING "___drive_incFiles"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_decFiles" TYPE integer USING "___drive_decFiles"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_incUsage" TYPE integer USING "___drive_incUsage"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_decUsage" TYPE integer USING "___drive_decUsage"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___requests_failed" TYPE smallint USING "___requests_failed"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___requests_succeeded" TYPE smallint USING "___requests_succeeded"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___requests_received" TYPE smallint USING "___requests_received"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_total" TYPE integer USING "___notes_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_inc" TYPE integer USING "___notes_inc"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_dec" TYPE integer USING "___notes_dec"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_diffs_normal" TYPE integer USING "___notes_diffs_normal"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_diffs_reply" TYPE integer USING "___notes_diffs_reply"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_diffs_renote" TYPE integer USING "___notes_diffs_renote"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___users_total" TYPE integer USING "___users_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___users_inc" TYPE smallint USING "___users_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___users_dec" TYPE smallint USING "___users_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___following_total" TYPE integer USING "___following_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___following_inc" TYPE smallint USING "___following_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___following_dec" TYPE smallint USING "___following_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___followers_total" TYPE integer USING "___followers_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___followers_inc" TYPE smallint USING "___followers_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___followers_dec" TYPE smallint USING "___followers_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_totalFiles" TYPE integer USING "___drive_totalFiles"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_incFiles" TYPE integer USING "___drive_incFiles"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_decFiles" TYPE integer USING "___drive_decFiles"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_incUsage" TYPE integer USING "___drive_incUsage"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_decUsage" TYPE integer USING "___drive_decUsage"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___total" TYPE integer USING "___total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___inc" TYPE smallint USING "___inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___dec" TYPE smallint USING "___dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___diffs_normal" TYPE smallint USING "___diffs_normal"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___diffs_reply" TYPE smallint USING "___diffs_reply"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___diffs_renote" TYPE smallint USING "___diffs_renote"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___total" TYPE integer USING "___total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___inc" TYPE smallint USING "___inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___dec" TYPE smallint USING "___dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___diffs_normal" TYPE smallint USING "___diffs_normal"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___diffs_reply" TYPE smallint USING "___diffs_reply"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___diffs_renote" TYPE smallint USING "___diffs_renote"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___local_incCount" TYPE integer USING "___local_incCount"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___local_incSize" TYPE integer USING "___local_incSize"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___local_decCount" TYPE integer USING "___local_decCount"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___local_decSize" TYPE integer USING "___local_decSize"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___remote_incCount" TYPE integer USING "___remote_incCount"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___remote_incSize" TYPE integer USING "___remote_incSize"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___remote_decCount" TYPE integer USING "___remote_decCount"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___remote_decSize" TYPE integer USING "___remote_decSize"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___local_incCount" TYPE integer USING "___local_incCount"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___local_incSize" TYPE integer USING "___local_incSize"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___local_decCount" TYPE integer USING "___local_decCount"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___local_decSize" TYPE integer USING "___local_decSize"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___remote_incCount" TYPE integer USING "___remote_incCount"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___remote_incSize" TYPE integer USING "___remote_incSize"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___remote_decCount" TYPE integer USING "___remote_decCount"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___remote_decSize" TYPE integer USING "___remote_decSize"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" ALTER COLUMN "___local_count" TYPE smallint USING "___local_count"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" ALTER COLUMN "___remote_count" TYPE smallint USING "___remote_count"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_reaction" ALTER COLUMN "___local_count" TYPE smallint USING "___local_count"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_reaction" ALTER COLUMN "___remote_count" TYPE smallint USING "___remote_count"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followings_total" TYPE integer USING "___local_followings_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followings_inc" TYPE smallint USING "___local_followings_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followings_dec" TYPE smallint USING "___local_followings_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followers_total" TYPE integer USING "___local_followers_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followers_inc" TYPE smallint USING "___local_followers_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followers_dec" TYPE smallint USING "___local_followers_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followings_total" TYPE integer USING "___remote_followings_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followings_inc" TYPE smallint USING "___remote_followings_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followings_dec" TYPE smallint USING "___remote_followings_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followers_total" TYPE integer USING "___remote_followers_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followers_inc" TYPE smallint USING "___remote_followers_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followers_dec" TYPE smallint USING "___remote_followers_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followings_total" TYPE integer USING "___local_followings_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followings_inc" TYPE smallint USING "___local_followings_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followings_dec" TYPE smallint USING "___local_followings_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followers_total" TYPE integer USING "___local_followers_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followers_inc" TYPE smallint USING "___local_followers_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followers_dec" TYPE smallint USING "___local_followers_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followings_total" TYPE integer USING "___remote_followings_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followings_inc" TYPE smallint USING "___remote_followings_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followings_dec" TYPE smallint USING "___remote_followings_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followers_total" TYPE integer USING "___remote_followers_total"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followers_inc" TYPE smallint USING "___remote_followers_inc"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followers_dec" TYPE smallint USING "___remote_followers_dec"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___totalCount" TYPE integer USING "___totalCount"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___totalSize" TYPE integer USING "___totalSize"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___incCount" TYPE smallint USING "___incCount"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___incSize" TYPE integer USING "___incSize"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___decCount" TYPE smallint USING "___decCount"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___decSize" TYPE integer USING "___decSize"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___totalCount" TYPE integer USING "___totalCount"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___totalSize" TYPE integer USING "___totalSize"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___incCount" TYPE smallint USING "___incCount"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___incSize" TYPE integer USING "___incSize"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___decCount" TYPE smallint USING "___decCount"::smallint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___decSize" TYPE integer USING "___decSize"::integer`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ALTER COLUMN "___instance_total" TYPE bigint USING "___instance_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ALTER COLUMN "___instance_inc" TYPE bigint USING "___instance_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ALTER COLUMN "___instance_dec" TYPE bigint USING "___instance_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ALTER COLUMN "___instance_total" TYPE bigint USING "___instance_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ALTER COLUMN "___instance_inc" TYPE bigint USING "___instance_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ALTER COLUMN "___instance_dec" TYPE bigint USING "___instance_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_total" TYPE bigint USING "___local_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_inc" TYPE bigint USING "___local_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_dec" TYPE bigint USING "___local_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_diffs_normal" TYPE bigint USING "___local_diffs_normal"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_diffs_reply" TYPE bigint USING "___local_diffs_reply"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___local_diffs_renote" TYPE bigint USING "___local_diffs_renote"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_total" TYPE bigint USING "___remote_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_inc" TYPE bigint USING "___remote_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_dec" TYPE bigint USING "___remote_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_diffs_normal" TYPE bigint USING "___remote_diffs_normal"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_diffs_reply" TYPE bigint USING "___remote_diffs_reply"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ALTER COLUMN "___remote_diffs_renote" TYPE bigint USING "___remote_diffs_renote"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_total" TYPE bigint USING "___local_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_inc" TYPE bigint USING "___local_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_dec" TYPE bigint USING "___local_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_diffs_normal" TYPE bigint USING "___local_diffs_normal"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_diffs_reply" TYPE bigint USING "___local_diffs_reply"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___local_diffs_renote" TYPE bigint USING "___local_diffs_renote"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_total" TYPE bigint USING "___remote_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_inc" TYPE bigint USING "___remote_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_dec" TYPE bigint USING "___remote_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_diffs_normal" TYPE bigint USING "___remote_diffs_normal"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_diffs_reply" TYPE bigint USING "___remote_diffs_reply"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ALTER COLUMN "___remote_diffs_renote" TYPE bigint USING "___remote_diffs_renote"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___local_total" TYPE bigint USING "___local_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___local_inc" TYPE bigint USING "___local_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___local_dec" TYPE bigint USING "___local_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___remote_total" TYPE bigint USING "___remote_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___remote_inc" TYPE bigint USING "___remote_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__users" ALTER COLUMN "___remote_dec" TYPE bigint USING "___remote_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___local_total" TYPE bigint USING "___local_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___local_inc" TYPE bigint USING "___local_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___local_dec" TYPE bigint USING "___local_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___remote_total" TYPE bigint USING "___remote_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___remote_inc" TYPE bigint USING "___remote_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__users" ALTER COLUMN "___remote_dec" TYPE bigint USING "___remote_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___incomingRequests" TYPE bigint USING "___incomingRequests"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___outgoingRequests" TYPE bigint USING "___outgoingRequests"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___totalTime" TYPE bigint USING "___totalTime"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___incomingBytes" TYPE bigint USING "___incomingBytes"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__network" ALTER COLUMN "___outgoingBytes" TYPE bigint USING "___outgoingBytes"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___incomingRequests" TYPE bigint USING "___incomingRequests"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___outgoingRequests" TYPE bigint USING "___outgoingRequests"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___totalTime" TYPE bigint USING "___totalTime"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___incomingBytes" TYPE bigint USING "___incomingBytes"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__network" ALTER COLUMN "___outgoingBytes" TYPE bigint USING "___outgoingBytes"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___requests_failed" TYPE bigint USING "___requests_failed"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___requests_succeeded" TYPE bigint USING "___requests_succeeded"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___requests_received" TYPE bigint USING "___requests_received"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_total" TYPE bigint USING "___notes_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_inc" TYPE bigint USING "___notes_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_dec" TYPE bigint USING "___notes_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_diffs_normal" TYPE bigint USING "___notes_diffs_normal"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_diffs_reply" TYPE bigint USING "___notes_diffs_reply"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___notes_diffs_renote" TYPE bigint USING "___notes_diffs_renote"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___users_total" TYPE bigint USING "___users_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___users_inc" TYPE bigint USING "___users_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___users_dec" TYPE bigint USING "___users_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___following_total" TYPE bigint USING "___following_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___following_inc" TYPE bigint USING "___following_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___following_dec" TYPE bigint USING "___following_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___followers_total" TYPE bigint USING "___followers_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___followers_inc" TYPE bigint USING "___followers_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___followers_dec" TYPE bigint USING "___followers_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_totalFiles" TYPE bigint USING "___drive_totalFiles"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_incFiles" TYPE bigint USING "___drive_incFiles"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_decFiles" TYPE bigint USING "___drive_decFiles"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_incUsage" TYPE bigint USING "___drive_incUsage"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ALTER COLUMN "___drive_decUsage" TYPE bigint USING "___drive_decUsage"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___requests_failed" TYPE bigint USING "___requests_failed"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___requests_succeeded" TYPE bigint USING "___requests_succeeded"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___requests_received" TYPE bigint USING "___requests_received"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_total" TYPE bigint USING "___notes_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_inc" TYPE bigint USING "___notes_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_dec" TYPE bigint USING "___notes_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_diffs_normal" TYPE bigint USING "___notes_diffs_normal"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_diffs_reply" TYPE bigint USING "___notes_diffs_reply"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___notes_diffs_renote" TYPE bigint USING "___notes_diffs_renote"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___users_total" TYPE bigint USING "___users_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___users_inc" TYPE bigint USING "___users_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___users_dec" TYPE bigint USING "___users_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___following_total" TYPE bigint USING "___following_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___following_inc" TYPE bigint USING "___following_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___following_dec" TYPE bigint USING "___following_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___followers_total" TYPE bigint USING "___followers_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___followers_inc" TYPE bigint USING "___followers_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___followers_dec" TYPE bigint USING "___followers_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_totalFiles" TYPE bigint USING "___drive_totalFiles"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_incFiles" TYPE bigint USING "___drive_incFiles"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_decFiles" TYPE bigint USING "___drive_decFiles"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_incUsage" TYPE bigint USING "___drive_incUsage"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ALTER COLUMN "___drive_decUsage" TYPE bigint USING "___drive_decUsage"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___total" TYPE bigint USING "___total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___inc" TYPE bigint USING "___inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___dec" TYPE bigint USING "___dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___diffs_normal" TYPE bigint USING "___diffs_normal"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___diffs_reply" TYPE bigint USING "___diffs_reply"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ALTER COLUMN "___diffs_renote" TYPE bigint USING "___diffs_renote"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___total" TYPE bigint USING "___total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___inc" TYPE bigint USING "___inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___dec" TYPE bigint USING "___dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___diffs_normal" TYPE bigint USING "___diffs_normal"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___diffs_reply" TYPE bigint USING "___diffs_reply"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ALTER COLUMN "___diffs_renote" TYPE bigint USING "___diffs_renote"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___local_incCount" TYPE bigint USING "___local_incCount"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___local_incSize" TYPE bigint USING "___local_incSize"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___local_decCount" TYPE bigint USING "___local_decCount"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___local_decSize" TYPE bigint USING "___local_decSize"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___remote_incCount" TYPE bigint USING "___remote_incCount"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___remote_incSize" TYPE bigint USING "___remote_incSize"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___remote_decCount" TYPE bigint USING "___remote_decCount"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__drive" ALTER COLUMN "___remote_decSize" TYPE bigint USING "___remote_decSize"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___local_incCount" TYPE bigint USING "___local_incCount"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___local_incSize" TYPE bigint USING "___local_incSize"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___local_decCount" TYPE bigint USING "___local_decCount"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___local_decSize" TYPE bigint USING "___local_decSize"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___remote_incCount" TYPE bigint USING "___remote_incCount"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___remote_incSize" TYPE bigint USING "___remote_incSize"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___remote_decCount" TYPE bigint USING "___remote_decCount"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__drive" ALTER COLUMN "___remote_decSize" TYPE bigint USING "___remote_decSize"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" ALTER COLUMN "___local_count" TYPE bigint USING "___local_count"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" ALTER COLUMN "___remote_count" TYPE bigint USING "___remote_count"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_reaction" ALTER COLUMN "___local_count" TYPE bigint USING "___local_count"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_reaction" ALTER COLUMN "___remote_count" TYPE bigint USING "___remote_count"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followings_total" TYPE bigint USING "___local_followings_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followings_inc" TYPE bigint USING "___local_followings_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followings_dec" TYPE bigint USING "___local_followings_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followers_total" TYPE bigint USING "___local_followers_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followers_inc" TYPE bigint USING "___local_followers_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___local_followers_dec" TYPE bigint USING "___local_followers_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followings_total" TYPE bigint USING "___remote_followings_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followings_inc" TYPE bigint USING "___remote_followings_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followings_dec" TYPE bigint USING "___remote_followings_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followers_total" TYPE bigint USING "___remote_followers_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followers_inc" TYPE bigint USING "___remote_followers_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ALTER COLUMN "___remote_followers_dec" TYPE bigint USING "___remote_followers_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followings_total" TYPE bigint USING "___local_followings_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followings_inc" TYPE bigint USING "___local_followings_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followings_dec" TYPE bigint USING "___local_followings_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followers_total" TYPE bigint USING "___local_followers_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followers_inc" TYPE bigint USING "___local_followers_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___local_followers_dec" TYPE bigint USING "___local_followers_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followings_total" TYPE bigint USING "___remote_followings_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followings_inc" TYPE bigint USING "___remote_followings_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followings_dec" TYPE bigint USING "___remote_followings_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followers_total" TYPE bigint USING "___remote_followers_total"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followers_inc" TYPE bigint USING "___remote_followers_inc"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_following" ALTER COLUMN "___remote_followers_dec" TYPE bigint USING "___remote_followers_dec"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___totalCount" TYPE bigint USING "___totalCount"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___totalSize" TYPE bigint USING "___totalSize"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___incCount" TYPE bigint USING "___incCount"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___incSize" TYPE bigint USING "___incSize"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___decCount" TYPE bigint USING "___decCount"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ALTER COLUMN "___decSize" TYPE bigint USING "___decSize"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___totalCount" TYPE bigint USING "___totalCount"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___totalSize" TYPE bigint USING "___totalSize"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___incCount" TYPE bigint USING "___incCount"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___incSize" TYPE bigint USING "___incSize"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___decCount" TYPE bigint USING "___decCount"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_drive" ALTER COLUMN "___decSize" TYPE bigint USING "___decSize"::bigint`);
+ }
+}
diff --git a/packages/backend/built/migration/1644059847460-chart-v8.js b/packages/backend/built/migration/1644059847460-chart-v8.js
new file mode 100644
index 0000000..eadfe7d
--- /dev/null
+++ b/packages/backend/built/migration/1644059847460-chart-v8.js
@@ -0,0 +1,19 @@
+export class chartV81644059847460 {
+ name = "chartV81644059847460";
+ async up(queryRunner) {
+ await queryRunner.query(`UPDATE "__chart__active_users" SET "___local_users"=2147483647 WHERE "___local_users" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__active_users" SET "___remote_users"=2147483647 WHERE "___remote_users" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__active_users" SET "___local_users"=2147483647 WHERE "___local_users" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__active_users" SET "___remote_users"=2147483647 WHERE "___remote_users" > 2147483647`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ALTER COLUMN "___local_users" TYPE integer USING "___local_users"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ALTER COLUMN "___remote_users" TYPE integer USING "___remote_users"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ALTER COLUMN "___local_users" TYPE integer USING "___local_users"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ALTER COLUMN "___remote_users" TYPE integer USING "___remote_users"::integer`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ALTER COLUMN "___local_users" TYPE bigint USING "___local_users"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ALTER COLUMN "___remote_users" TYPE bigint USING "___remote_users"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ALTER COLUMN "___local_users" TYPE bigint USING "___local_users"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ALTER COLUMN "___remote_users" TYPE bigint USING "___remote_users"::bigint`);
+ }
+}
diff --git a/packages/backend/built/migration/1644060125705-chart-v9.js b/packages/backend/built/migration/1644060125705-chart-v9.js
new file mode 100644
index 0000000..e24031a
--- /dev/null
+++ b/packages/backend/built/migration/1644060125705-chart-v9.js
@@ -0,0 +1,19 @@
+export class chartV91644060125705 {
+ name = "chartV91644060125705";
+ async up(queryRunner) {
+ await queryRunner.query(`UPDATE "__chart__hashtag" SET "___local_users"=2147483647 WHERE "___local_users" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart__hashtag" SET "___remote_users"=2147483647 WHERE "___remote_users" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__hashtag" SET "___local_users"=2147483647 WHERE "___local_users" > 2147483647`);
+ await queryRunner.query(`UPDATE "__chart_day__hashtag" SET "___remote_users"=2147483647 WHERE "___remote_users" > 2147483647`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ALTER COLUMN "___local_users" TYPE integer USING "___local_users"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ALTER COLUMN "___remote_users" TYPE integer USING "___remote_users"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__hashtag" ALTER COLUMN "___local_users" TYPE integer USING "___local_users"::integer`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__hashtag" ALTER COLUMN "___remote_users" TYPE integer USING "___remote_users"::integer`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ALTER COLUMN "___local_users" TYPE bigint USING "___local_users"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart__hashtag" ALTER COLUMN "___remote_users" TYPE bigint USING "___remote_users"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__hashtag" ALTER COLUMN "___local_users" TYPE bigint USING "___local_users"::bigint`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__hashtag" ALTER COLUMN "___remote_users" TYPE bigint USING "___remote_users"::bigint`);
+ }
+}
diff --git a/packages/backend/built/migration/1644073149413-chart-v10.js b/packages/backend/built/migration/1644073149413-chart-v10.js
new file mode 100644
index 0000000..af131ff
--- /dev/null
+++ b/packages/backend/built/migration/1644073149413-chart-v10.js
@@ -0,0 +1,31 @@
+export class chartV101644073149413 {
+ name = "chartV101644073149413";
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "__chart__ap_request" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "___deliverFailed" integer NOT NULL DEFAULT '0', "___deliverSucceeded" integer NOT NULL DEFAULT '0', "___inboxReceived" integer NOT NULL DEFAULT '0', CONSTRAINT "UQ_e56f4beac5746d44bc3e19c80d0" UNIQUE ("date"), CONSTRAINT "PK_56a25cd447c7ee08876b3baf8d8" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e56f4beac5746d44bc3e19c80d" ON "__chart__ap_request" ("date") `);
+ await queryRunner.query(`CREATE TABLE "__chart_day__ap_request" ("id" SERIAL NOT NULL, "date" integer NOT NULL, "___deliverFailed" integer NOT NULL DEFAULT '0', "___deliverSucceeded" integer NOT NULL DEFAULT '0', "___inboxReceived" integer NOT NULL DEFAULT '0', CONSTRAINT "UQ_a848f66d6cec11980a5dd595822" UNIQUE ("date"), CONSTRAINT "PK_9318b49daee320194e23f712e69" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a848f66d6cec11980a5dd59582" ON "__chart_day__ap_request" ("date") `);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "unique_temp___deliveredInstances" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___deliveredInstances" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "unique_temp___inboxInstances" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___inboxInstances" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "unique_temp___deliveredInstances" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___deliveredInstances" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "unique_temp___inboxInstances" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___inboxInstances" smallint NOT NULL DEFAULT '0'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___inboxInstances"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "unique_temp___inboxInstances"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___deliveredInstances"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "unique_temp___deliveredInstances"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___inboxInstances"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "unique_temp___inboxInstances"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___deliveredInstances"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "unique_temp___deliveredInstances"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_a848f66d6cec11980a5dd59582"`);
+ await queryRunner.query(`DROP TABLE "__chart_day__ap_request"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_e56f4beac5746d44bc3e19c80d"`);
+ await queryRunner.query(`DROP TABLE "__chart__ap_request"`);
+ }
+}
diff --git a/packages/backend/built/migration/1644095659741-chart-v11.js b/packages/backend/built/migration/1644095659741-chart-v11.js
new file mode 100644
index 0000000..665face
--- /dev/null
+++ b/packages/backend/built/migration/1644095659741-chart-v11.js
@@ -0,0 +1,87 @@
+export class chartV111644095659741 {
+ name = "chartV111644095659741";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___local_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___remote_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "unique_temp___local_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "unique_temp___remote_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___local_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___remote_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "unique_temp___local_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "unique_temp___remote_users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "unique_temp___users" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___users" integer NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "unique_temp___notedUsers" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___notedUsers" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "unique_temp___registeredWithinWeek" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___registeredWithinWeek" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "unique_temp___registeredWithinMonth" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___registeredWithinMonth" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "unique_temp___registeredWithinYear" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___registeredWithinYear" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "unique_temp___registeredOutsideWeek" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___registeredOutsideWeek" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "unique_temp___registeredOutsideMonth" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___registeredOutsideMonth" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "unique_temp___registeredOutsideYear" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___registeredOutsideYear" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "unique_temp___users" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___users" integer NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "unique_temp___notedUsers" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___notedUsers" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "unique_temp___registeredWithinWeek" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___registeredWithinWeek" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "unique_temp___registeredWithinMonth" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___registeredWithinMonth" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "unique_temp___registeredWithinYear" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___registeredWithinYear" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "unique_temp___registeredOutsideWeek" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___registeredOutsideWeek" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "unique_temp___registeredOutsideMonth" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___registeredOutsideMonth" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "unique_temp___registeredOutsideYear" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___registeredOutsideYear" smallint NOT NULL DEFAULT '0'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___registeredOutsideYear"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "unique_temp___registeredOutsideYear"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___registeredOutsideMonth"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "unique_temp___registeredOutsideMonth"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___registeredOutsideWeek"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "unique_temp___registeredOutsideWeek"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___registeredWithinYear"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "unique_temp___registeredWithinYear"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___registeredWithinMonth"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "unique_temp___registeredWithinMonth"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___registeredWithinWeek"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "unique_temp___registeredWithinWeek"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___notedUsers"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "unique_temp___notedUsers"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "unique_temp___users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___registeredOutsideYear"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "unique_temp___registeredOutsideYear"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___registeredOutsideMonth"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "unique_temp___registeredOutsideMonth"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___registeredOutsideWeek"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "unique_temp___registeredOutsideWeek"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___registeredWithinYear"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "unique_temp___registeredWithinYear"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___registeredWithinMonth"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "unique_temp___registeredWithinMonth"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___registeredWithinWeek"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "unique_temp___registeredWithinWeek"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___notedUsers"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "unique_temp___notedUsers"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "unique_temp___users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "unique_temp___remote_users" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "unique_temp___local_users" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___remote_users" integer NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___local_users" integer NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "unique_temp___remote_users" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "unique_temp___local_users" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___remote_users" integer NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___local_users" integer NOT NULL DEFAULT '0'`);
+ }
+}
diff --git a/packages/backend/built/migration/1644328606241-chart-v12.js b/packages/backend/built/migration/1644328606241-chart-v12.js
new file mode 100644
index 0000000..24e648d
--- /dev/null
+++ b/packages/backend/built/migration/1644328606241-chart-v12.js
@@ -0,0 +1,23 @@
+export class chartV121644328606241 {
+ name = "chartV121644328606241";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ADD "___local_diffs_withFile" integer NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" ADD "___remote_diffs_withFile" integer NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ADD "___local_diffs_withFile" integer NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" ADD "___remote_diffs_withFile" integer NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" ADD "___notes_diffs_withFile" integer NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" ADD "___notes_diffs_withFile" integer NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ADD "___diffs_withFile" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" ADD "___diffs_withFile" smallint NOT NULL DEFAULT '0'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart_day__per_user_notes" DROP COLUMN "___diffs_withFile"`);
+ await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" DROP COLUMN "___diffs_withFile"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__instance" DROP COLUMN "___notes_diffs_withFile"`);
+ await queryRunner.query(`ALTER TABLE "__chart__instance" DROP COLUMN "___notes_diffs_withFile"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" DROP COLUMN "___remote_diffs_withFile"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__notes" DROP COLUMN "___local_diffs_withFile"`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" DROP COLUMN "___remote_diffs_withFile"`);
+ await queryRunner.query(`ALTER TABLE "__chart__notes" DROP COLUMN "___local_diffs_withFile"`);
+ }
+}
diff --git a/packages/backend/built/migration/1644331238153-chart-v13.js b/packages/backend/built/migration/1644331238153-chart-v13.js
new file mode 100644
index 0000000..f476897
--- /dev/null
+++ b/packages/backend/built/migration/1644331238153-chart-v13.js
@@ -0,0 +1,15 @@
+export class chartV131644331238153 {
+ name = "chartV131644331238153";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "unique_temp___stalled" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___stalled" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "unique_temp___stalled" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___stalled" smallint NOT NULL DEFAULT '0'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___stalled"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "unique_temp___stalled"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___stalled"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "unique_temp___stalled"`);
+ }
+}
diff --git a/packages/backend/built/migration/1644344266289-chart-v14.js b/packages/backend/built/migration/1644344266289-chart-v14.js
new file mode 100644
index 0000000..4defcac
--- /dev/null
+++ b/packages/backend/built/migration/1644344266289-chart-v14.js
@@ -0,0 +1,43 @@
+export class chartV141644344266289 {
+ name = "chartV141644344266289";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "unique_temp___users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___users"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "unique_temp___notedUsers"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___notedUsers"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "unique_temp___users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___users"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "unique_temp___notedUsers"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___notedUsers"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___readWrite" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "unique_temp___read" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___read" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "unique_temp___write" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___write" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___readWrite" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "unique_temp___read" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___read" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "unique_temp___write" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___write" smallint NOT NULL DEFAULT '0'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___write"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "unique_temp___write"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___read"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "unique_temp___read"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" DROP COLUMN "___readWrite"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___write"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "unique_temp___write"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___read"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "unique_temp___read"`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___readWrite"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___notedUsers" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "unique_temp___notedUsers" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "___users" integer NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__active_users" ADD "unique_temp___users" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___notedUsers" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "unique_temp___notedUsers" character varying array NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___users" integer NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "unique_temp___users" character varying array NOT NULL DEFAULT '{}'`);
+ }
+}
diff --git a/packages/backend/built/migration/1644395759931-instance-theme-color.js b/packages/backend/built/migration/1644395759931-instance-theme-color.js
new file mode 100644
index 0000000..d825b62
--- /dev/null
+++ b/packages/backend/built/migration/1644395759931-instance-theme-color.js
@@ -0,0 +1,9 @@
+export class instanceThemeColor1644395759931 {
+ name = "instanceThemeColor1644395759931";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "themeColor" character varying(512)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "themeColor"`);
+ }
+}
diff --git a/packages/backend/built/migration/1644481657998-chart-v15.js b/packages/backend/built/migration/1644481657998-chart-v15.js
new file mode 100644
index 0000000..36868da
--- /dev/null
+++ b/packages/backend/built/migration/1644481657998-chart-v15.js
@@ -0,0 +1,27 @@
+export class chartV151644481657998 {
+ name = "chartV151644481657998";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___instance_total"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___instance_inc"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___instance_dec"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___instance_total"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___instance_inc"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___instance_dec"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___sub" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___pub" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___sub" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___pub" smallint NOT NULL DEFAULT '0'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___pub"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___sub"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___pub"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___sub"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___instance_dec" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___instance_inc" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___instance_total" integer NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___instance_dec" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___instance_inc" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___instance_total" integer NOT NULL DEFAULT '0'`);
+ }
+}
diff --git a/packages/backend/built/migration/1644551208096-following-indexes.js b/packages/backend/built/migration/1644551208096-following-indexes.js
new file mode 100644
index 0000000..ef3785a
--- /dev/null
+++ b/packages/backend/built/migration/1644551208096-following-indexes.js
@@ -0,0 +1,11 @@
+export class followingIndexes1644551208096 {
+ name = "followingIndexes1644551208096";
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE INDEX "IDX_4ccd2239268ebbd1b35e318754" ON "following" ("followerHost") `);
+ await queryRunner.query(`CREATE INDEX "IDX_fcdafee716dfe9c3b5fde90f30" ON "following" ("followeeHost") `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_fcdafee716dfe9c3b5fde90f30"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_4ccd2239268ebbd1b35e318754"`);
+ }
+}
diff --git a/packages/backend/built/migration/1645340161439-remove-max-note-text-length.js b/packages/backend/built/migration/1645340161439-remove-max-note-text-length.js
new file mode 100644
index 0000000..4db7011
--- /dev/null
+++ b/packages/backend/built/migration/1645340161439-remove-max-note-text-length.js
@@ -0,0 +1,9 @@
+export class removeMaxNoteTextLength1645340161439 {
+ name = "removeMaxNoteTextLength1645340161439";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "maxNoteTextLength"`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "maxNoteTextLength" integer NOT NULL DEFAULT '500'`);
+ }
+}
diff --git a/packages/backend/built/migration/1645599900873-federation-chart-pubsub.js b/packages/backend/built/migration/1645599900873-federation-chart-pubsub.js
new file mode 100644
index 0000000..ecb248c
--- /dev/null
+++ b/packages/backend/built/migration/1645599900873-federation-chart-pubsub.js
@@ -0,0 +1,11 @@
+export class federationChartPubsub1645599900873 {
+ name = "federationChartPubsub1645599900873";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___pubsub" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___pubsub" smallint NOT NULL DEFAULT '0'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___pubsub"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___pubsub"`);
+ }
+}
diff --git a/packages/backend/built/migration/1646143552768-instance-default-theme.js b/packages/backend/built/migration/1646143552768-instance-default-theme.js
new file mode 100644
index 0000000..5925a21
--- /dev/null
+++ b/packages/backend/built/migration/1646143552768-instance-default-theme.js
@@ -0,0 +1,11 @@
+export class instanceDefaultTheme1646143552768 {
+ name = "instanceDefaultTheme1646143552768";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "defaultLightTheme" character varying(8192)`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "defaultDarkTheme" character varying(8192)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "defaultDarkTheme"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "defaultLightTheme"`);
+ }
+}
diff --git a/packages/backend/built/migration/1646387162108-mute-expires-at.js b/packages/backend/built/migration/1646387162108-mute-expires-at.js
new file mode 100644
index 0000000..ea69d76
--- /dev/null
+++ b/packages/backend/built/migration/1646387162108-mute-expires-at.js
@@ -0,0 +1,11 @@
+export class muteExpiresAt1646387162108 {
+ name = "muteExpiresAt1646387162108";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "muting" ADD "expiresAt" TIMESTAMP WITH TIME ZONE`);
+ await queryRunner.query(`CREATE INDEX "IDX_c1fd1c3dfb0627aa36c253fd14" ON "muting" ("expiresAt") `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_c1fd1c3dfb0627aa36c253fd14"`);
+ await queryRunner.query(`ALTER TABLE "muting" DROP COLUMN "expiresAt"`);
+ }
+}
diff --git a/packages/backend/built/migration/1646549089451-poll-ended-notification.js b/packages/backend/built/migration/1646549089451-poll-ended-notification.js
new file mode 100644
index 0000000..2f5108d
--- /dev/null
+++ b/packages/backend/built/migration/1646549089451-poll-ended-notification.js
@@ -0,0 +1,15 @@
+export class pollEndedNotification1646549089451 {
+ name = "pollEndedNotification1646549089451";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TYPE "public"."notification_type_enum" RENAME TO "notification_type_enum_old"`);
+ await queryRunner.query(`CREATE TYPE "public"."notification_type_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`);
+ await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum" USING "type"::"text"::"public"."notification_type_enum"`);
+ await queryRunner.query(`DROP TYPE "public"."notification_type_enum_old"`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`CREATE TYPE "public"."notification_type_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`);
+ await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum_old" USING "type"::"text"::"public"."notification_type_enum_old"`);
+ await queryRunner.query(`DROP TYPE "public"."notification_type_enum"`);
+ await queryRunner.query(`ALTER TYPE "public"."notification_type_enum_old" RENAME TO "notification_type_enum"`);
+ }
+}
diff --git a/packages/backend/built/migration/1646633030285-chart-federation-active.js b/packages/backend/built/migration/1646633030285-chart-federation-active.js
new file mode 100644
index 0000000..f72c24d
--- /dev/null
+++ b/packages/backend/built/migration/1646633030285-chart-federation-active.js
@@ -0,0 +1,11 @@
+export class chartFederationActive1646633030285 {
+ name = "chartFederationActive1646633030285";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___active" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___active" smallint NOT NULL DEFAULT '0'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___active"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___active"`);
+ }
+}
diff --git a/packages/backend/built/migration/1646655454495-remove-instance-drive-columns.js b/packages/backend/built/migration/1646655454495-remove-instance-drive-columns.js
new file mode 100644
index 0000000..779fc60
--- /dev/null
+++ b/packages/backend/built/migration/1646655454495-remove-instance-drive-columns.js
@@ -0,0 +1,11 @@
+export class removeInstanceDriveColumns1646655454495 {
+ name = "removeInstanceDriveColumns1646655454495";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "driveUsage"`);
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "driveFiles"`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "instance" ADD "driveFiles" integer NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "instance" ADD "driveUsage" bigint NOT NULL DEFAULT '0'`);
+ }
+}
diff --git a/packages/backend/built/migration/1646732390560-chart-federation-active-sub-pub.js b/packages/backend/built/migration/1646732390560-chart-federation-active-sub-pub.js
new file mode 100644
index 0000000..1480cf3
--- /dev/null
+++ b/packages/backend/built/migration/1646732390560-chart-federation-active-sub-pub.js
@@ -0,0 +1,19 @@
+export class chartFederationActiveSubPub1646732390560 {
+ name = "chartFederationActiveSubPub1646732390560";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___active"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___active"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___subActive" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___pubActive" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___subActive" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___pubActive" smallint NOT NULL DEFAULT '0'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___pubActive"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___subActive"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___pubActive"`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___subActive"`);
+ await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___active" smallint NOT NULL DEFAULT '0'`);
+ await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___active" smallint NOT NULL DEFAULT '0'`);
+ }
+}
diff --git a/packages/backend/built/migration/1648548247382-webhook.js b/packages/backend/built/migration/1648548247382-webhook.js
new file mode 100644
index 0000000..a5ce7fd
--- /dev/null
+++ b/packages/backend/built/migration/1648548247382-webhook.js
@@ -0,0 +1,17 @@
+export class webhook1648548247382 {
+ name = "webhook1648548247382";
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "webhook" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "name" character varying(128) NOT NULL, "on" character varying(128) array NOT NULL DEFAULT '{}', "url" character varying(1024) NOT NULL, "secret" character varying(1024) NOT NULL, "active" boolean NOT NULL DEFAULT true, CONSTRAINT "PK_e6765510c2d078db49632b59020" PRIMARY KEY ("id")); COMMENT ON COLUMN "webhook"."createdAt" IS 'The created date of the Antenna.'; COMMENT ON COLUMN "webhook"."userId" IS 'The owner ID.'; COMMENT ON COLUMN "webhook"."name" IS 'The name of the Antenna.'`);
+ await queryRunner.query(`CREATE INDEX "IDX_f272c8c8805969e6a6449c77b3" ON "webhook" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_8063a0586ed1dfbe86e982d961" ON "webhook" ("on") `);
+ await queryRunner.query(`CREATE INDEX "IDX_5a056076f76b2efe08216ba655" ON "webhook" ("active") `);
+ await queryRunner.query(`ALTER TABLE "webhook" ADD CONSTRAINT "FK_f272c8c8805969e6a6449c77b3c" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "webhook" DROP CONSTRAINT "FK_f272c8c8805969e6a6449c77b3c"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_5a056076f76b2efe08216ba655"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_8063a0586ed1dfbe86e982d961"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_f272c8c8805969e6a6449c77b3"`);
+ await queryRunner.query(`DROP TABLE "webhook"`);
+ }
+}
diff --git a/packages/backend/built/migration/1648816172177-webhook-2.js b/packages/backend/built/migration/1648816172177-webhook-2.js
new file mode 100644
index 0000000..98c1c9e
--- /dev/null
+++ b/packages/backend/built/migration/1648816172177-webhook-2.js
@@ -0,0 +1,11 @@
+export class webhook21648816172177 {
+ name = "webhook21648816172177";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "webhook" ADD "latestSentAt" TIMESTAMP WITH TIME ZONE`);
+ await queryRunner.query(`ALTER TABLE "webhook" ADD "latestStatus" integer`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "webhook" DROP COLUMN "latestStatus"`);
+ await queryRunner.query(`ALTER TABLE "webhook" DROP COLUMN "latestSentAt"`);
+ }
+}
diff --git a/packages/backend/built/migration/1651224615271-foreign-key.js b/packages/backend/built/migration/1651224615271-foreign-key.js
new file mode 100644
index 0000000..58633be
--- /dev/null
+++ b/packages/backend/built/migration/1651224615271-foreign-key.js
@@ -0,0 +1,61 @@
+export class foreignKeyReports1651224615271 {
+ name = "foreignKeyReports1651224615271";
+ async up(queryRunner) {
+ await Promise.all([
+ queryRunner.query(`ALTER INDEX "public"."IDX_seoignmeoprigmkpodgrjmkpormg" RENAME TO "IDX_c8cc87bd0f2f4487d17c651fbf"`),
+ queryRunner.query(`DROP INDEX "public"."IDX_note_on_channelId_and_id_desc"`),
+ // remove unnecessary default null, see also down
+ queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "followersUri" DROP DEFAULT`),
+ queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "session" DROP DEFAULT`),
+ queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "appId" DROP DEFAULT`),
+ queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "name" DROP DEFAULT`),
+ queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "description" DROP DEFAULT`),
+ queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "iconUrl" DROP DEFAULT`),
+ queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "softwareName" DROP DEFAULT`),
+ queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "softwareVersion" DROP DEFAULT`),
+ queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "name" DROP DEFAULT`),
+ queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "description" DROP DEFAULT`),
+ queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "maintainerName" DROP DEFAULT`),
+ queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "maintainerEmail" DROP DEFAULT`),
+ queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "iconUrl" DROP DEFAULT`),
+ queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "faviconUrl" DROP DEFAULT`),
+ queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "themeColor" DROP DEFAULT`),
+ queryRunner.query(`ALTER TABLE "clip" ALTER COLUMN "description" DROP DEFAULT`),
+ queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "channelId" DROP DEFAULT`),
+ queryRunner.query(`ALTER TABLE "abuse_user_report" ALTER COLUMN "comment" DROP DEFAULT`),
+ queryRunner.query(`CREATE INDEX "IDX_315c779174fe8247ab324f036e" ON "drive_file" ("isLink")`),
+ queryRunner.query(`CREATE INDEX "IDX_f22169eb10657bded6d875ac8f" ON "note" ("channelId")`),
+ //queryRunner.query(`CREATE INDEX "IDX_a9021cc2e1feb5f72d3db6e9f5" ON "abuse_user_report" ("targetUserId")`),
+ //queryRunner.query(`DELETE FROM "abuse_user_report" WHERE "targetUserId" NOT IN (SELECT "id" FROM "user")`).then(() => {
+ // queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f" FOREIGN KEY ("targetUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ //}),
+ queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "UQ_da851e06d0dfe2ef397d8b1bf1b" UNIQUE ("noteId")`),
+ queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "UQ_f4853eb41ab722fe05f81cedeb6" UNIQUE ("userId")`),
+ queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "UQ_51cb79b5555effaf7d69ba1cff9" UNIQUE ("userId")`),
+ queryRunner.query(`ALTER TABLE "user_publickey" ADD CONSTRAINT "UQ_10c146e4b39b443ede016f6736d" UNIQUE ("userId")`),
+ queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "UQ_e263909ca4fe5d57f8d4230dd5c" UNIQUE ("noteId")`),
+ queryRunner.query(`ALTER TABLE "page" RENAME CONSTRAINT "FK_3126dd7c502c9e4d7597ef7ef10" TO "FK_a9ca79ad939bf06066b81c9d3aa"`),
+ queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum" ADD VALUE 'pollEnded' AFTER 'pollVote'`)
+ ]);
+ }
+ async down(queryRunner) {
+ await Promise.all([
+ // There is no ALTER TYPE REMOVE VALUE query, so the reverse operation is a bit more complex
+ queryRunner.query(`UPDATE "user_profile" SET "mutingNotificationTypes" = array_remove("mutingNotificationTypes", 'pollEnded')`).then(()=>queryRunner.query(`CREATE TYPE "public"."user_profile_mutingnotificationtypes_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`)).then(()=>queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT`)).then(()=>queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" TYPE "public"."user_profile_mutingnotificationtypes_enum_old"[] USING "mutingNotificationTypes"::"text"::"public"."user_profile_mutingnotificationtypes_enum_old"[]`)).then(()=>queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`)).then(()=>queryRunner.query(`DROP TYPE "public"."user_profile_mutingnotificationtypes_enum"`)).then(()=>queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum_old" RENAME TO "user_profile_mutingnotificationtypes_enum"`)),
+ queryRunner.query(`ALTER TABLE "page" RENAME CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa" TO "FK_3126dd7c502c9e4d7597ef7ef10"`),
+ queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT "UQ_e263909ca4fe5d57f8d4230dd5c"`),
+ queryRunner.query(`ALTER TABLE "user_publickey" DROP CONSTRAINT "UQ_10c146e4b39b443ede016f6736d"`),
+ queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "UQ_51cb79b5555effaf7d69ba1cff9"`),
+ queryRunner.query(`ALTER TABLE "user_keypair" DROP CONSTRAINT "UQ_f4853eb41ab722fe05f81cedeb6"`),
+ queryRunner.query(`ALTER TABLE "poll" DROP CONSTRAINT "UQ_da851e06d0dfe2ef397d8b1bf1b"`),
+ queryRunner.query(`ALTER TABLE "abuse_user_report" ALTER COLUMN "comment" SET DEFAULT '{}'`),
+ queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f"`),
+ queryRunner.query(`DROP INDEX "public"."IDX_a9021cc2e1feb5f72d3db6e9f5"`),
+ queryRunner.query(`DROP INDEX "public"."IDX_f22169eb10657bded6d875ac8f"`),
+ queryRunner.query(`DROP INDEX "public"."IDX_315c779174fe8247ab324f036e"`),
+ /* DEFAULT's are not set again because if the column can be NULL, then DEFAULT NULL is not necessary.
+ see also https://github.com/typeorm/typeorm/issues/7579#issuecomment-835423615 */ queryRunner.query(`CREATE INDEX "IDX_note_on_channelId_and_id_desc" ON "note" ("id", "channelId") `),
+ queryRunner.query(`ALTER INDEX "public"."IDX_c8cc87bd0f2f4487d17c651fbf" RENAME TO "IDX_seoignmeoprigmkpodgrjmkpormg"`)
+ ]);
+ }
+}
diff --git a/packages/backend/built/migration/1652859567549-uniform-themecolor.js b/packages/backend/built/migration/1652859567549-uniform-themecolor.js
new file mode 100644
index 0000000..df97afb
--- /dev/null
+++ b/packages/backend/built/migration/1652859567549-uniform-themecolor.js
@@ -0,0 +1,34 @@
+import tinycolor from "tinycolor2";
+export class uniformThemecolor1652859567549 {
+ name = "uniformThemecolor1652859567549";
+ async up(queryRunner) {
+ const formatColor = (color)=>{
+ let tc = new tinycolor(color);
+ if (tc.isValid()) {
+ return tc.toHexString();
+ } else {
+ return null;
+ }
+ };
+ await queryRunner.query('SELECT "id", "themeColor" FROM "instance" WHERE "themeColor" IS NOT NULL').then((instances)=>Promise.all(instances.map((instance)=>{
+ // update theme color to uniform format, e.g. #00ff00
+ // invalid theme colors get set to null
+ return queryRunner.query('UPDATE "instance" SET "themeColor" = $1 WHERE "id" = $2', [
+ formatColor(instance.themeColor),
+ instance.id
+ ]);
+ })));
+ // also fix own theme color
+ await queryRunner.query('SELECT "themeColor" FROM "meta" WHERE "themeColor" IS NOT NULL LIMIT 1').then((metas)=>{
+ if (metas.length > 0) {
+ return queryRunner.query('UPDATE "meta" SET "themeColor" = $1', [
+ formatColor(metas[0].themeColor)
+ ]);
+ }
+ });
+ }
+ async down(queryRunner) {
+ // The original representation is not stored, so migrating back is not possible.
+ // The new format also works in older versions so this is not a problem.
+ }
+}
diff --git a/packages/backend/built/migration/1655368940105-nsfw-detection.js b/packages/backend/built/migration/1655368940105-nsfw-detection.js
new file mode 100644
index 0000000..209dc66
--- /dev/null
+++ b/packages/backend/built/migration/1655368940105-nsfw-detection.js
@@ -0,0 +1,21 @@
+export class nsfwDetection1655368940105 {
+ name = "nsfwDetection1655368940105";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "drive_file" ADD "forceIsSensitive" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "drive_file" ADD "predictedIsSensitive" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."predictedIsSensitive" IS 'Whether the DriveFile is NSFW. (predict)'`);
+ await queryRunner.query(`CREATE TYPE "public"."meta_sensitiveimagedetection_enum" AS ENUM('none', 'all', 'local', 'remote')`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "sensitiveImageDetection" "public"."meta_sensitiveimagedetection_enum" NOT NULL DEFAULT 'none'`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "forceIsSensitiveWhenPredicted" boolean NOT NULL DEFAULT true`);
+ await queryRunner.query(`CREATE INDEX "IDX_fc2d74a6d7d8b11292a851d8f8" ON "drive_file" ("predictedIsSensitive") `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_fc2d74a6d7d8b11292a851d8f8"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "forceIsSensitiveWhenPredicted"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "sensitiveImageDetection"`);
+ await queryRunner.query(`DROP TYPE "public"."meta_sensitiveimagedetection_enum"`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."predictedIsSensitive" IS 'Whether the DriveFile is NSFW. (predict)'`);
+ await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "predictedIsSensitive"`);
+ await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "forceIsSensitive"`);
+ }
+}
diff --git a/packages/backend/built/migration/1655371960534-nsfw-detection-2.js b/packages/backend/built/migration/1655371960534-nsfw-detection-2.js
new file mode 100644
index 0000000..94d4dda
--- /dev/null
+++ b/packages/backend/built/migration/1655371960534-nsfw-detection-2.js
@@ -0,0 +1,13 @@
+export class nsfwDetection21655371960534 {
+ name = "nsfwDetection21655371960534";
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TYPE "public"."meta_sensitiveimagedetectionsensitivity_enum" AS ENUM('medium', 'low', 'high')`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "sensitiveImageDetectionSensitivity" "public"."meta_sensitiveimagedetectionsensitivity_enum" NOT NULL DEFAULT 'medium'`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "disallowUploadWhenPredictedAsPorn" boolean NOT NULL DEFAULT false`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "disallowUploadWhenPredictedAsPorn"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "sensitiveImageDetectionSensitivity"`);
+ await queryRunner.query(`DROP TYPE "public"."meta_sensitiveimagedetectionsensitivity_enum"`);
+ }
+}
diff --git a/packages/backend/built/migration/1655388169582-nsfw-detection-3.js b/packages/backend/built/migration/1655388169582-nsfw-detection-3.js
new file mode 100644
index 0000000..251827c
--- /dev/null
+++ b/packages/backend/built/migration/1655388169582-nsfw-detection-3.js
@@ -0,0 +1,19 @@
+export class nsfwDetection31655388169582 {
+ name = "nsfwDetection31655388169582";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TYPE "public"."meta_sensitiveimagedetectionsensitivity_enum" RENAME TO "meta_sensitiveimagedetectionsensitivity_enum_old"`);
+ await queryRunner.query(`CREATE TYPE "public"."meta_sensitiveimagedetectionsensitivity_enum" AS ENUM('medium', 'low', 'high', 'veryLow', 'veryHigh')`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "sensitiveImageDetectionSensitivity" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "sensitiveImageDetectionSensitivity" TYPE "public"."meta_sensitiveimagedetectionsensitivity_enum" USING "sensitiveImageDetectionSensitivity"::"text"::"public"."meta_sensitiveimagedetectionsensitivity_enum"`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "sensitiveImageDetectionSensitivity" SET DEFAULT 'medium'`);
+ await queryRunner.query(`DROP TYPE "public"."meta_sensitiveimagedetectionsensitivity_enum_old"`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`CREATE TYPE "public"."meta_sensitiveimagedetectionsensitivity_enum_old" AS ENUM('medium', 'low', 'high')`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "sensitiveImageDetectionSensitivity" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "sensitiveImageDetectionSensitivity" TYPE "public"."meta_sensitiveimagedetectionsensitivity_enum_old" USING "sensitiveImageDetectionSensitivity"::"text"::"public"."meta_sensitiveimagedetectionsensitivity_enum_old"`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "sensitiveImageDetectionSensitivity" SET DEFAULT 'medium'`);
+ await queryRunner.query(`DROP TYPE "public"."meta_sensitiveimagedetectionsensitivity_enum"`);
+ await queryRunner.query(`ALTER TYPE "public"."meta_sensitiveimagedetectionsensitivity_enum_old" RENAME TO "meta_sensitiveimagedetectionsensitivity_enum"`);
+ }
+}
diff --git a/packages/backend/built/migration/1655393015659-nsfw-detection-4.js b/packages/backend/built/migration/1655393015659-nsfw-detection-4.js
new file mode 100644
index 0000000..c8944f2
--- /dev/null
+++ b/packages/backend/built/migration/1655393015659-nsfw-detection-4.js
@@ -0,0 +1,23 @@
+export class nsfwDetection41655393015659 {
+ name = "nsfwDetection41655393015659";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "sensitiveImageDetection"`);
+ await queryRunner.query(`DROP TYPE "public"."meta_sensitiveimagedetection_enum"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "sensitiveImageDetectionSensitivity"`);
+ await queryRunner.query(`DROP TYPE "public"."meta_sensitiveimagedetectionsensitivity_enum"`);
+ await queryRunner.query(`CREATE TYPE "public"."meta_sensitivemediadetection_enum" AS ENUM('none', 'all', 'local', 'remote')`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "sensitiveMediaDetection" "public"."meta_sensitivemediadetection_enum" NOT NULL DEFAULT 'none'`);
+ await queryRunner.query(`CREATE TYPE "public"."meta_sensitivemediadetectionsensitivity_enum" AS ENUM('medium', 'low', 'high', 'veryLow', 'veryHigh')`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "sensitiveMediaDetectionSensitivity" "public"."meta_sensitivemediadetectionsensitivity_enum" NOT NULL DEFAULT 'medium'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "sensitiveMediaDetectionSensitivity"`);
+ await queryRunner.query(`DROP TYPE "public"."meta_sensitivemediadetectionsensitivity_enum"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "sensitiveMediaDetection"`);
+ await queryRunner.query(`DROP TYPE "public"."meta_sensitivemediadetection_enum"`);
+ await queryRunner.query(`CREATE TYPE "public"."meta_sensitiveimagedetectionsensitivity_enum" AS ENUM('medium', 'low', 'high', 'veryLow', 'veryHigh')`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "sensitiveImageDetectionSensitivity" "public"."meta_sensitiveimagedetectionsensitivity_enum" NOT NULL DEFAULT 'medium'`);
+ await queryRunner.query(`CREATE TYPE "public"."meta_sensitiveimagedetection_enum" AS ENUM('none', 'all', 'local', 'remote')`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "sensitiveImageDetection" "public"."meta_sensitiveimagedetection_enum" NOT NULL DEFAULT 'none'`);
+ }
+}
diff --git a/packages/backend/built/migration/1655813815729-driveCapacityOverrideMb.js b/packages/backend/built/migration/1655813815729-driveCapacityOverrideMb.js
new file mode 100644
index 0000000..3e39977
--- /dev/null
+++ b/packages/backend/built/migration/1655813815729-driveCapacityOverrideMb.js
@@ -0,0 +1,11 @@
+export class driveCapacityOverrideMb1655813815729 {
+ name = "driveCapacityOverrideMb1655813815729";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" ADD "driveCapacityOverrideMb" integer`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."driveCapacityOverrideMb" IS 'Overrides user drive capacity limit'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`COMMENT ON COLUMN "user"."driveCapacityOverrideMb" IS 'Overrides user drive capacity limit'`);
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "driveCapacityOverrideMb"`);
+ }
+}
diff --git a/packages/backend/built/migration/1655918165614-user-ip.js b/packages/backend/built/migration/1655918165614-user-ip.js
new file mode 100644
index 0000000..f520ef8
--- /dev/null
+++ b/packages/backend/built/migration/1655918165614-user-ip.js
@@ -0,0 +1,15 @@
+export class userIp1655918165614 {
+ name = "userIp1655918165614";
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "user_ip" ("id" SERIAL NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "ip" character varying(128) NOT NULL, CONSTRAINT "PK_2c44ddfbf7c0464d028dcef325e" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_7f7f1c66f48e9a8e18a33bc515" ON "user_ip" ("userId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_361b500e06721013c124b7b6c5" ON "user_ip" ("userId", "ip") `);
+ await queryRunner.query(`ALTER TABLE "user_ip" ADD CONSTRAINT "FK_7f7f1c66f48e9a8e18a33bc5150" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_ip" DROP CONSTRAINT "FK_7f7f1c66f48e9a8e18a33bc5150"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_361b500e06721013c124b7b6c5"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_7f7f1c66f48e9a8e18a33bc515"`);
+ await queryRunner.query(`DROP TABLE "user_ip"`);
+ }
+}
diff --git a/packages/backend/built/migration/1656122560740-file-ip.js b/packages/backend/built/migration/1656122560740-file-ip.js
new file mode 100644
index 0000000..3369bd0
--- /dev/null
+++ b/packages/backend/built/migration/1656122560740-file-ip.js
@@ -0,0 +1,11 @@
+export class fileIp1656122560740 {
+ name = "fileIp1656122560740";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "drive_file" ADD "requestHeaders" jsonb DEFAULT '{}'`);
+ await queryRunner.query(`ALTER TABLE "drive_file" ADD "requestIp" character varying(128)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "requestIp"`);
+ await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "requestHeaders"`);
+ }
+}
diff --git a/packages/backend/built/migration/1656251734807-nsfw-detection-5.js b/packages/backend/built/migration/1656251734807-nsfw-detection-5.js
new file mode 100644
index 0000000..ced1473
--- /dev/null
+++ b/packages/backend/built/migration/1656251734807-nsfw-detection-5.js
@@ -0,0 +1,31 @@
+export class nsfwDetection51656251734807 {
+ name = "nsfwDetection51656251734807";
+ async up(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_fc2d74a6d7d8b11292a851d8f8"`);
+ await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "forceIsSensitive"`);
+ await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "predictedIsSensitive"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "forceIsSensitiveWhenPredicted"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "disallowUploadWhenPredictedAsPorn"`);
+ await queryRunner.query(`ALTER TABLE "drive_file" ADD "maybeSensitive" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."maybeSensitive" IS 'Whether the DriveFile is NSFW. (predict)'`);
+ await queryRunner.query(`ALTER TABLE "drive_file" ADD "maybePorn" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "setSensitiveFlagAutomatically" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "autoSensitive" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`CREATE INDEX "IDX_3b33dff77bb64b23c88151d23e" ON "drive_file" ("maybeSensitive") `);
+ await queryRunner.query(`CREATE INDEX "IDX_8bdcd3dd2bddb78014999a16ce" ON "drive_file" ("maybePorn") `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_8bdcd3dd2bddb78014999a16ce"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_3b33dff77bb64b23c88151d23e"`);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "autoSensitive"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "setSensitiveFlagAutomatically"`);
+ await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "maybePorn"`);
+ await queryRunner.query(`COMMENT ON COLUMN "drive_file"."maybeSensitive" IS 'Whether the DriveFile is NSFW. (predict)'`);
+ await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "maybeSensitive"`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "disallowUploadWhenPredictedAsPorn" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "forceIsSensitiveWhenPredicted" boolean NOT NULL DEFAULT true`);
+ await queryRunner.query(`ALTER TABLE "drive_file" ADD "predictedIsSensitive" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "drive_file" ADD "forceIsSensitive" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`CREATE INDEX "IDX_fc2d74a6d7d8b11292a851d8f8" ON "drive_file" ("predictedIsSensitive") `);
+ }
+}
diff --git a/packages/backend/built/migration/1656328812281-ip-2.js b/packages/backend/built/migration/1656328812281-ip-2.js
new file mode 100644
index 0000000..2b03587
--- /dev/null
+++ b/packages/backend/built/migration/1656328812281-ip-2.js
@@ -0,0 +1,11 @@
+export class ip21656328812281 {
+ name = "ip21656328812281";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_ip" DROP CONSTRAINT "FK_7f7f1c66f48e9a8e18a33bc5150"`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "enableIpLogging" boolean NOT NULL DEFAULT false`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableIpLogging"`);
+ await queryRunner.query(`ALTER TABLE "user_ip" ADD CONSTRAINT "FK_7f7f1c66f48e9a8e18a33bc5150" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
+ }
+}
diff --git a/packages/backend/built/migration/1656408772602-nsfw-detection-6.js b/packages/backend/built/migration/1656408772602-nsfw-detection-6.js
new file mode 100644
index 0000000..419e4d1
--- /dev/null
+++ b/packages/backend/built/migration/1656408772602-nsfw-detection-6.js
@@ -0,0 +1,9 @@
+export class nsfwDetection61656408772602 {
+ name = "nsfwDetection61656408772602";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "enableSensitiveMediaDetectionForVideos" boolean NOT NULL DEFAULT false`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableSensitiveMediaDetectionForVideos"`);
+ }
+}
diff --git a/packages/backend/built/migration/1656772790599-user-moderation-note.js b/packages/backend/built/migration/1656772790599-user-moderation-note.js
new file mode 100644
index 0000000..6d3a28a
--- /dev/null
+++ b/packages/backend/built/migration/1656772790599-user-moderation-note.js
@@ -0,0 +1,9 @@
+export class userModerationNote1656772790599 {
+ name = "userModerationNote1656772790599";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "moderationNote" character varying(8192) NOT NULL DEFAULT ''`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "moderationNote"`);
+ }
+}
diff --git a/packages/backend/built/migration/1657346559800-active-email-validation.js b/packages/backend/built/migration/1657346559800-active-email-validation.js
new file mode 100644
index 0000000..1c05df7
--- /dev/null
+++ b/packages/backend/built/migration/1657346559800-active-email-validation.js
@@ -0,0 +1,9 @@
+export class activeEmailValidation1657346559800 {
+ name = "activeEmailValidation1657346559800";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "enableActiveEmailValidation" boolean NOT NULL DEFAULT true`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableActiveEmailValidation"`);
+ }
+}
diff --git a/packages/backend/built/migration/1658203170545firefish.js b/packages/backend/built/migration/1658203170545firefish.js
new file mode 100644
index 0000000..ef8d601
--- /dev/null
+++ b/packages/backend/built/migration/1658203170545firefish.js
@@ -0,0 +1,11 @@
+export class calckey1658203170545 {
+ name = "calckey1658203170545";
+ async up(queryRunner) {
+ await queryRunner.query(`UPDATE meta SET "repositoryUrl" = 'https://codeberg/firefish/firefish'`);
+ await queryRunner.query(`UPDATE meta SET "feedbackUrl" = 'https://codeberg/firefish/firefish/issues'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`UPDATE meta SET "repositoryUrl" = 'https://codeberg/firefish/firefish'`);
+ await queryRunner.query(`UPDATE meta SET "feedbackUrl" = 'https://codeberg/firefish/firefish/issues'`);
+ }
+}
diff --git a/packages/backend/built/migration/1658656633972-note-replies-function.js b/packages/backend/built/migration/1658656633972-note-replies-function.js
new file mode 100644
index 0000000..94fbf42
--- /dev/null
+++ b/packages/backend/built/migration/1658656633972-note-replies-function.js
@@ -0,0 +1,50 @@
+export class noteRepliesFunction1658656633972 {
+ name = "noteRepliesFunction1658656633972";
+ async up(queryRunner) {
+ await queryRunner.query(`
+ CREATE OR REPLACE FUNCTION note_replies(start_id varchar, max_depth integer, max_breadth integer) RETURNS TABLE (id VARCHAR) AS
+ $$
+ SELECT DISTINCT id FROM (
+ WITH RECURSIVE tree (id, ancestors, depth) AS (
+ SELECT start_id, '{}'::VARCHAR[], 0
+ UNION
+ SELECT
+ note.id,
+ CASE
+ WHEN note."replyId" = tree.id THEN tree.ancestors || note."replyId"
+ ELSE tree.ancestors || note."renoteId"
+ END,
+ depth + 1
+ FROM note, tree
+ WHERE (
+ note."replyId" = tree.id
+ OR
+ (
+ -- get renotes but not pure renotes
+ note."renoteId" = tree.id
+ AND
+ (
+ note.text IS NOT NULL
+ OR
+ CARDINALITY(note."fileIds") != 0
+ OR
+ note."hasPoll" = TRUE
+ )
+ )
+ ) AND depth < max_depth
+ )
+ SELECT
+ id,
+ -- apply the limit per node
+ row_number() OVER (PARTITION BY ancestors[array_upper(ancestors, 1)]) AS nth_child
+ FROM tree
+ WHERE depth > 0
+ ) AS recursive WHERE nth_child < max_breadth
+ $$
+ LANGUAGE SQL
+ `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP FUNCTION note_replies`);
+ }
+}
diff --git a/packages/backend/built/migration/1658939464003CustomMOTD.js b/packages/backend/built/migration/1658939464003CustomMOTD.js
new file mode 100644
index 0000000..eac0345
--- /dev/null
+++ b/packages/backend/built/migration/1658939464003CustomMOTD.js
@@ -0,0 +1,8 @@
+export class CustomMOTD1658939464003 {
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "customMOTD" character varying(256) array NOT NULL DEFAULT '{}'::varchar[]`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "customMOTD"`);
+ }
+}
diff --git a/packages/backend/built/migration/1658941974648CustomSplashIcons.js b/packages/backend/built/migration/1658941974648CustomSplashIcons.js
new file mode 100644
index 0000000..fce5eb7
--- /dev/null
+++ b/packages/backend/built/migration/1658941974648CustomSplashIcons.js
@@ -0,0 +1,8 @@
+export class CustomSplashIcons1658941974648 {
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "customSplashIcons" character varying(256) array NOT NULL DEFAULT '{}'::varchar[]`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "customSplashIcons"`);
+ }
+}
diff --git a/packages/backend/built/migration/1658981842728FixCalckey.js b/packages/backend/built/migration/1658981842728FixCalckey.js
new file mode 100644
index 0000000..0347b57
--- /dev/null
+++ b/packages/backend/built/migration/1658981842728FixCalckey.js
@@ -0,0 +1,13 @@
+export class FixFirefish1658981842728 {
+ name = "FixFirefish1658981842728";
+ async up(queryRunner) {
+ await queryRunner.query(`UPDATE "meta" SET "useStarForReactionFallback" = TRUE;`);
+ await queryRunner.query(`UPDATE "meta" SET "repositoryUrl" = 'https://codeberg/firefish/firefish'`);
+ await queryRunner.query(`UPDATE "meta" SET "feedbackUrl" = 'https://codeberg/firefish/firefish/issues'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`UPDATE "meta" SET "useStarForReactionFallback" = FALSE;`);
+ await queryRunner.query(`UPDATE "meta" SET "repositoryUrl" = 'https://codeberg/firefish/firefish'`);
+ await queryRunner.query(`UPDATE "meta" SET "feedbackUrl" = 'https://codeberg/firefish/firefish/issues'`);
+ }
+}
diff --git a/packages/backend/built/migration/1659042130648RecommendedTimeline.js b/packages/backend/built/migration/1659042130648RecommendedTimeline.js
new file mode 100644
index 0000000..e2d9e45
--- /dev/null
+++ b/packages/backend/built/migration/1659042130648RecommendedTimeline.js
@@ -0,0 +1,11 @@
+export class RecommendedTimeline1659042130648 {
+ name = "RecommendedTimeline1659042130648";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "disableRecommendedTimeline" boolean NOT NULL DEFAULT true`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "recommendedInstances" character varying(256) array NOT NULL DEFAULT '{}'::varchar[]`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "disableRecommendedTimeline"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "recommendedInstances"`);
+ }
+}
diff --git a/packages/backend/built/migration/1660068273737GuestTimeline.js b/packages/backend/built/migration/1660068273737GuestTimeline.js
new file mode 100644
index 0000000..b564882
--- /dev/null
+++ b/packages/backend/built/migration/1660068273737GuestTimeline.js
@@ -0,0 +1,9 @@
+export class GuestTimeline1660068273737 {
+ name = "GuestTimeline1660068273737";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "enableGuestTimeline" boolean NOT NULL DEFAULT false`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableGuestTimeline"`);
+ }
+}
diff --git a/packages/backend/built/migration/1665091090561-add-renote-muting.js b/packages/backend/built/migration/1665091090561-add-renote-muting.js
new file mode 100644
index 0000000..f361c31
--- /dev/null
+++ b/packages/backend/built/migration/1665091090561-add-renote-muting.js
@@ -0,0 +1,10 @@
+export class addRenoteMuting1665091090561 {
+ name = "addRenoteMuting1665091090561";
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "renote_muting" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "muteeId" character varying(32) NOT NULL, "muterId" character varying(32) NOT NULL, CONSTRAINT "PK_renoteMuting_id" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_renote_muting_createdAt" ON "muting" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_renote_muting_muteeId" ON "muting" ("muteeId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_renote_muting_muterId" ON "muting" ("muterId") `);
+ }
+ async down(queryRunner) {}
+}
diff --git a/packages/backend/built/migration/1668828368510PageDraft.js b/packages/backend/built/migration/1668828368510PageDraft.js
new file mode 100644
index 0000000..d60aaec
--- /dev/null
+++ b/packages/backend/built/migration/1668828368510PageDraft.js
@@ -0,0 +1,8 @@
+export class Page1668828368510 {
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "page" ADD "isPublic" boolean NOT NULL DEFAULT true`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "page" DROP COLUMN "isPublic"`);
+ }
+}
diff --git a/packages/backend/built/migration/1668831378728FixCalckeyAgain.js b/packages/backend/built/migration/1668831378728FixCalckeyAgain.js
new file mode 100644
index 0000000..64c6da7
--- /dev/null
+++ b/packages/backend/built/migration/1668831378728FixCalckeyAgain.js
@@ -0,0 +1,9 @@
+export class FixFirefishAgain1668831378728 {
+ name = "FixFirefishAgain1668831378728";
+ async up(queryRunner) {
+ await queryRunner.query(`UPDATE "meta" SET "useStarForReactionFallback" = TRUE`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`UPDATE "meta" SET "useStarForReactionFallback" = FALSE`);
+ }
+}
diff --git a/packages/backend/built/migration/1669138716634-whetherPushNotifyToSendReadMessage.js b/packages/backend/built/migration/1669138716634-whetherPushNotifyToSendReadMessage.js
new file mode 100644
index 0000000..7309d27
--- /dev/null
+++ b/packages/backend/built/migration/1669138716634-whetherPushNotifyToSendReadMessage.js
@@ -0,0 +1,9 @@
+export class whetherPushNotifyToSendReadMessage1669138716634 {
+ name = "whetherPushNotifyToSendReadMessage1669138716634";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "sw_subscription" ADD "sendReadMessage" boolean NOT NULL DEFAULT false`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "sw_subscription" DROP COLUMN "sendReadMessage"`);
+ }
+}
diff --git a/packages/backend/built/migration/1669288094000-AddMovedToAndKnownAs.js b/packages/backend/built/migration/1669288094000-AddMovedToAndKnownAs.js
new file mode 100644
index 0000000..9d857b3
--- /dev/null
+++ b/packages/backend/built/migration/1669288094000-AddMovedToAndKnownAs.js
@@ -0,0 +1,13 @@
+export class addMovedToAndKnownAs1669288094000 {
+ name = "addMovedToAndKnownAs1669288094000";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" ADD "movedToUri" character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "user" ADD "alsoKnownAs" TEXT`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."movedToUri" IS 'The URI of the new account of the User'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."alsoKnownAs" IS 'URIs the user is known as too'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "movedToUri"`);
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "alsoKnownAs"`);
+ }
+}
diff --git a/packages/backend/built/migration/1671199573000-AddFkAbuseUserReportTargetUserIdToUserId.js b/packages/backend/built/migration/1671199573000-AddFkAbuseUserReportTargetUserIdToUserId.js
new file mode 100644
index 0000000..b4b4eb2
--- /dev/null
+++ b/packages/backend/built/migration/1671199573000-AddFkAbuseUserReportTargetUserIdToUserId.js
@@ -0,0 +1,10 @@
+export class addFkAbuseUserReportTargetUserIdToUserId1671199573000 {
+ name = "addFkAbuseUserReportTargetUserIdToUserId1671199573000";
+ async up(queryRunner) {
+ await queryRunner.query(`DELETE FROM abuse_user_report WHERE NOT EXISTS (SELECT 1 FROM "user" WHERE "user"."id" = "abuse_user_report"."targetUserId")`);
+ await queryRunner.query(`ALTER TABLE abuse_user_report ADD CONSTRAINT fk_7f4e851a35d81b64dda28eee0 FOREIGN KEY ("targetUserId") REFERENCES "user"("id") ON DELETE CASCADE`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE abuse_user_report DROP CONSTRAINT fk_7f4e851a35d81b64dda28eee0`);
+ }
+}
diff --git a/packages/backend/built/migration/1671388343000-CalckeyRepoMove.js b/packages/backend/built/migration/1671388343000-CalckeyRepoMove.js
new file mode 100644
index 0000000..703e915
--- /dev/null
+++ b/packages/backend/built/migration/1671388343000-CalckeyRepoMove.js
@@ -0,0 +1,12 @@
+/* "FirefishRepoMove1671388343000" is a class that updates the "useStarForReactionFallback" column in
+the "meta" table to TRUE */ export class FirefishRepoMove1671388343000 {
+ name = "FirefishRepoMove1671388343000";
+ async up(queryRunner) {
+ await queryRunner.query(`UPDATE meta SET "repositoryUrl" = 'https://codeberg/firefish/firefish'`);
+ await queryRunner.query(`UPDATE meta SET "feedbackUrl" = 'https://codeberg/firefish/firefish/issues'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`UPDATE meta SET "repositoryUrl" = 'https://codeberg/firefish/firefish'`);
+ await queryRunner.query(`UPDATE meta SET "feedbackUrl" = 'https://codeberg/firefish/firefish/issues'`);
+ }
+}
diff --git a/packages/backend/built/migration/1672882664294-DefaultReaction.js b/packages/backend/built/migration/1672882664294-DefaultReaction.js
new file mode 100644
index 0000000..d30d340
--- /dev/null
+++ b/packages/backend/built/migration/1672882664294-DefaultReaction.js
@@ -0,0 +1,10 @@
+export class DefaultReaction1672882664294 {
+ name = "DefaultReaction1672882664294";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "defaultReaction" character varying(256) NOT NULL DEFAULT '⭐'`);
+ await queryRunner.query(`COMMENT ON COLUMN "meta"."defaultReaction" IS 'The fallback reaction for emoji reacts'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "defaultReaction"`);
+ }
+}
diff --git a/packages/backend/built/migration/1673336077243-PollChoiceLength.js b/packages/backend/built/migration/1673336077243-PollChoiceLength.js
new file mode 100644
index 0000000..df403bd
--- /dev/null
+++ b/packages/backend/built/migration/1673336077243-PollChoiceLength.js
@@ -0,0 +1,9 @@
+export class PollChoiceLength1673336077243 {
+ name = "PollChoiceLength1673336077243";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "poll" ALTER COLUMN "choices" TYPE character varying(256) array`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "poll" ALTER COLUMN "choices" TYPE character varying(128) array`);
+ }
+}
diff --git a/packages/backend/built/migration/1676093997212-AntennaInstances.js b/packages/backend/built/migration/1676093997212-AntennaInstances.js
new file mode 100644
index 0000000..6c90ddf
--- /dev/null
+++ b/packages/backend/built/migration/1676093997212-AntennaInstances.js
@@ -0,0 +1,15 @@
+export class AntennaInstances1676093997212 {
+ name = "AntennaInstances1676093997212";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TYPE "antenna_src_enum" ADD VALUE 'instances'`);
+ await queryRunner.query(`ALTER TABLE "antenna" ADD "instances" jsonb NOT NULL DEFAULT '[]'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DELETE FROM "antenna" WHERE "src" = 'instances'`);
+ await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "instances"`);
+ await queryRunner.query(`CREATE TYPE "public"."antenna_src_enum_old" AS ENUM('home', 'all', 'users', 'list', 'group')`);
+ await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "src" TYPE "public"."antenna_src_enum_old" USING "src"::"text"::"public"."antenna_src_enum_old"`);
+ await queryRunner.query(`DROP TYPE "public"."antenna_src_enum"`);
+ await queryRunner.query(`ALTER TYPE "public"."antenna_src_enum_old" RENAME TO "antenna_src_enum"`);
+ }
+}
diff --git a/packages/backend/built/migration/1677935903517-DriveComment.js b/packages/backend/built/migration/1677935903517-DriveComment.js
new file mode 100644
index 0000000..e5dfccd
--- /dev/null
+++ b/packages/backend/built/migration/1677935903517-DriveComment.js
@@ -0,0 +1,9 @@
+export class DriveComment1677935903517 {
+ name = "DriveComment1677935903517";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "drive_file" ALTER "comment" TYPE character varying(8192)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "drive_file" ALTER "comment" TYPE character varying(512)`);
+ }
+}
diff --git a/packages/backend/built/migration/1678426061773-tweak-varchar-length.js b/packages/backend/built/migration/1678426061773-tweak-varchar-length.js
new file mode 100644
index 0000000..5f13c31
--- /dev/null
+++ b/packages/backend/built/migration/1678426061773-tweak-varchar-length.js
@@ -0,0 +1,8 @@
+export class tweakVarcharLength1678426061773 {
+ name = "tweakVarcharLength1678426061773";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "smtpUser" TYPE character varying(1024)`, undefined);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "smtpPass" TYPE character varying(1024)`, undefined);
+ }
+ async down(queryRunner) {}
+}
diff --git a/packages/backend/built/migration/1678945242650-add-props-for-custom-emoji.js b/packages/backend/built/migration/1678945242650-add-props-for-custom-emoji.js
new file mode 100644
index 0000000..de9ebc7
--- /dev/null
+++ b/packages/backend/built/migration/1678945242650-add-props-for-custom-emoji.js
@@ -0,0 +1,9 @@
+export class addPropsForCustomEmoji1678945242650 {
+ name = "addPropsForCustomEmoji1678945242650";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "emoji" ADD "license" character varying(1024)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "license"`);
+ }
+}
diff --git a/packages/backend/built/migration/1679269929000-fix-repo.js b/packages/backend/built/migration/1679269929000-fix-repo.js
new file mode 100644
index 0000000..660650d
--- /dev/null
+++ b/packages/backend/built/migration/1679269929000-fix-repo.js
@@ -0,0 +1,11 @@
+export class FixRepo1679269929000 {
+ name = "FixRepo1679269929000";
+ async up(queryRunner) {
+ await queryRunner.query(`UPDATE meta SET "repositoryUrl" = 'https://codeberg.org/firefish/firefish'`);
+ await queryRunner.query(`UPDATE meta SET "feedbackUrl" = 'https://codeberg.org/firefish/firefish/issues'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`UPDATE meta SET "repositoryUrl" = 'https://codeberg.org/firefish/firefish'`);
+ await queryRunner.query(`UPDATE meta SET "feedbackUrl" = 'https://codeberg.org/firefish/firefish/issues'`);
+ }
+}
diff --git a/packages/backend/built/migration/1680375641101-clean-charts.js b/packages/backend/built/migration/1680375641101-clean-charts.js
new file mode 100644
index 0000000..10d1e9d
--- /dev/null
+++ b/packages/backend/built/migration/1680375641101-clean-charts.js
@@ -0,0 +1,17 @@
+export class CleanCharts1680375641101 {
+ constructor(){
+ this.name = "CleanCharts1680375641101";
+ }
+ async up(queryRunner) {
+ await queryRunner.query(`delete from __chart__hashtag where ___local_users = 0 and ___remote_users = 0;`);
+ await queryRunner.query(`delete from __chart_day__hashtag where ___local_users = 0 and ___remote_users = 0;`);
+ await queryRunner.query(`COMMIT;`);
+ await queryRunner.query(`vacuum __chart__hashtag;`);
+ await queryRunner.query(`vacuum __chart_day__hashtag;`);
+ await queryRunner.query(`COMMIT;`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`delete from __chart__hashtag where ___local_users = 0 and ___remote_users = 0;`);
+ await queryRunner.query(`delete from __chart_day__hashtag where ___local_users = 0 and ___remote_users = 0;`);
+ }
+}
diff --git a/packages/backend/built/migration/1680426269172-SpeakAsCat.js b/packages/backend/built/migration/1680426269172-SpeakAsCat.js
new file mode 100644
index 0000000..016992b
--- /dev/null
+++ b/packages/backend/built/migration/1680426269172-SpeakAsCat.js
@@ -0,0 +1,18 @@
+export class SpeakAsCat1680426269172 {
+ name = "SpeakAsCat1680426269172";
+ async up(queryRunner) {
+ await queryRunner.query(`
+ ALTER TABLE "user"
+ ADD "speakAsCat" boolean NOT NULL DEFAULT true
+ `);
+ await queryRunner.query(`
+ COMMENT ON COLUMN "user"."speakAsCat"
+ IS 'Whether to speak as a cat if isCat.'
+ `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`
+ ALTER TABLE "user" DROP COLUMN "speakAsCat"
+ `);
+ }
+}
diff --git a/packages/backend/built/migration/1682753227899-NoteEdit.js b/packages/backend/built/migration/1682753227899-NoteEdit.js
new file mode 100644
index 0000000..16a27ed
--- /dev/null
+++ b/packages/backend/built/migration/1682753227899-NoteEdit.js
@@ -0,0 +1,51 @@
+export class NoteEdit1682753227899 {
+ name = "NoteEdit1682753227899";
+ async up(queryRunner) {
+ await queryRunner.query(`
+ CREATE TABLE "note_edit" (
+ "id" character varying(32) NOT NULL,
+ "noteId" character varying(32) NOT NULL,
+ "text" text,
+ "cw" character varying(512),
+ "fileIds" character varying(32) array NOT NULL DEFAULT '{}',
+ "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
+ CONSTRAINT "PK_736fc6e0d4e222ecc6f82058e08" PRIMARY KEY ("id")
+ )
+ `);
+ await queryRunner.query(`
+ COMMENT ON COLUMN "note_edit"."noteId" IS 'The ID of note.'
+ `);
+ await queryRunner.query(`
+ COMMENT ON COLUMN "note_edit"."updatedAt" IS 'The updated date of the Note.'
+ `);
+ await queryRunner.query(`
+ CREATE INDEX "IDX_702ad5ae993a672e4fbffbcd38" ON "note_edit" ("noteId")
+ `);
+ await queryRunner.query(`
+ ALTER TABLE "note"
+ ADD "updatedAt" TIMESTAMP WITH TIME ZONE
+ `);
+ await queryRunner.query(`
+ COMMENT ON COLUMN "note"."updatedAt" IS 'The updated date of the Note.'
+ `);
+ await queryRunner.query(`
+ ALTER TABLE "note_edit"
+ ADD CONSTRAINT "FK_702ad5ae993a672e4fbffbcd38c"
+ FOREIGN KEY ("noteId")
+ REFERENCES "note"("id")
+ ON DELETE CASCADE
+ ON UPDATE NO ACTION
+ `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`
+ ALTER TABLE "note_edit" DROP CONSTRAINT "FK_702ad5ae993a672e4fbffbcd38c"
+ `);
+ await queryRunner.query(`
+ ALTER TABLE "note" DROP COLUMN "updatedAt"
+ `);
+ await queryRunner.query(`
+ DROP TABLE "note_edit"
+ `);
+ }
+}
diff --git a/packages/backend/built/migration/1682777547198-LibreTranslate.js b/packages/backend/built/migration/1682777547198-LibreTranslate.js
new file mode 100644
index 0000000..f7a673e
--- /dev/null
+++ b/packages/backend/built/migration/1682777547198-LibreTranslate.js
@@ -0,0 +1,21 @@
+export class LibreTranslate1682777547198 {
+ name = "LibreTranslate1682777547198";
+ async up(queryRunner) {
+ await queryRunner.query(`
+ ALTER TABLE "meta"
+ ADD "libreTranslateApiUrl" character varying(512)
+ `);
+ await queryRunner.query(`
+ ALTER TABLE "meta"
+ ADD "libreTranslateApiKey" character varying(128)
+ `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`
+ ALTER TABLE "meta" DROP COLUMN "libreTranslateApiKey"
+ `);
+ await queryRunner.query(`
+ ALTER TABLE "meta" DROP COLUMN "libreTranslateApiUrl"
+ `);
+ }
+}
diff --git a/packages/backend/built/migration/1682891890317-InstanceSilence.js b/packages/backend/built/migration/1682891890317-InstanceSilence.js
new file mode 100644
index 0000000..494216d
--- /dev/null
+++ b/packages/backend/built/migration/1682891890317-InstanceSilence.js
@@ -0,0 +1,9 @@
+export class InstanceSilence1682891890317 {
+ name = "InstanceSilence1682891890317";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "silencedHosts" character varying(256) array NOT NULL DEFAULT '{}'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "silencedHosts"`);
+ }
+}
diff --git a/packages/backend/built/migration/1682891891317-AddHiddenPosts.js b/packages/backend/built/migration/1682891891317-AddHiddenPosts.js
new file mode 100644
index 0000000..871bc91
--- /dev/null
+++ b/packages/backend/built/migration/1682891891317-AddHiddenPosts.js
@@ -0,0 +1,9 @@
+export class AddHiddenPosts1682891891317 {
+ name = "AddHiddenPosts1682891891317";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TYPE note_visibility_enum ADD VALUE IF NOT EXISTS 'hidden'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TYPE note_visibility_enum REMOVE VALUE IF EXISTS 'hidden'`);
+ }
+}
diff --git a/packages/backend/built/migration/1683682889948-PreventAiLearning.js b/packages/backend/built/migration/1683682889948-PreventAiLearning.js
new file mode 100644
index 0000000..e468696
--- /dev/null
+++ b/packages/backend/built/migration/1683682889948-PreventAiLearning.js
@@ -0,0 +1,9 @@
+export class PreventAiLearning1683682889948 {
+ name = "PreventAiLearning1683682889948";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "preventAiLearning" boolean NOT NULL DEFAULT true`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "preventAiLearning"`);
+ }
+}
diff --git a/packages/backend/built/migration/1683980686995-ExperimentalFeatures.js b/packages/backend/built/migration/1683980686995-ExperimentalFeatures.js
new file mode 100644
index 0000000..0fba096
--- /dev/null
+++ b/packages/backend/built/migration/1683980686995-ExperimentalFeatures.js
@@ -0,0 +1,14 @@
+export class ExperimentalFeatures1683980686995 {
+ name = "ExperimentalFeatures1683980686995";
+ async up(queryRunner) {
+ await queryRunner.query(`
+ ALTER TABLE "meta"
+ ADD "experimentalFeatures" jsonb NOT NULL DEFAULT '{}'
+ `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`
+ ALTER TABLE "meta" DROP COLUMN "experimentalFeatures"
+ `);
+ }
+}
diff --git a/packages/backend/built/migration/1684206886988-remove-showTimelineReplies.js b/packages/backend/built/migration/1684206886988-remove-showTimelineReplies.js
new file mode 100644
index 0000000..8ad35b9
--- /dev/null
+++ b/packages/backend/built/migration/1684206886988-remove-showTimelineReplies.js
@@ -0,0 +1,9 @@
+export class RemoveShowTimelineReplies1684206886988 {
+ name = "RemoveShowTimelineReplies1684206886988";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "showTimelineReplies"`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" ADD "showTimelineReplies" boolean NOT NULL DEFAULT false`);
+ }
+}
diff --git a/packages/backend/built/migration/1684494870830-EmojiSize.js b/packages/backend/built/migration/1684494870830-EmojiSize.js
new file mode 100644
index 0000000..605d9ac
--- /dev/null
+++ b/packages/backend/built/migration/1684494870830-EmojiSize.js
@@ -0,0 +1,13 @@
+export class EmojiSize1684494870830 {
+ name = "EmojiSize1684494870830";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "emoji" ADD "width" integer`);
+ await queryRunner.query(`COMMENT ON COLUMN "emoji"."width" IS 'Image width'`);
+ await queryRunner.query(`ALTER TABLE "emoji" ADD "height" integer`);
+ await queryRunner.query(`COMMENT ON COLUMN "emoji"."height" IS 'Image height'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "height"`);
+ await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "width"`);
+ }
+}
diff --git a/packages/backend/built/migration/1688280713783-add-meta-options.js b/packages/backend/built/migration/1688280713783-add-meta-options.js
new file mode 100644
index 0000000..db3e276
--- /dev/null
+++ b/packages/backend/built/migration/1688280713783-add-meta-options.js
@@ -0,0 +1,11 @@
+export class AddMetaOptions1688280713783 {
+ name = "AddMetaOptions1688280713783";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "enableServerMachineStats" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "enableIdenticonGeneration" boolean NOT NULL DEFAULT true`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableIdenticonGeneration"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableServerMachineStats"`);
+ }
+}
diff --git a/packages/backend/built/migration/1688845537045-announcement-popup.js b/packages/backend/built/migration/1688845537045-announcement-popup.js
new file mode 100644
index 0000000..cb29b50
--- /dev/null
+++ b/packages/backend/built/migration/1688845537045-announcement-popup.js
@@ -0,0 +1,11 @@
+export class AnnouncementPopup1688845537045 {
+ name = "AnnouncementPopup1688845537045";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "announcement" ADD "showPopup" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "announcement" ADD "isGoodNews" boolean NOT NULL DEFAULT false`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "announcement" DROP COLUMN "isGoodNews"`);
+ await queryRunner.query(`ALTER TABLE "announcement" DROP COLUMN "showPopup"`);
+ }
+}
diff --git a/packages/backend/built/migration/1689136347561-donation-link.js b/packages/backend/built/migration/1689136347561-donation-link.js
new file mode 100644
index 0000000..dd84163
--- /dev/null
+++ b/packages/backend/built/migration/1689136347561-donation-link.js
@@ -0,0 +1,9 @@
+export class DonationLink1689136347561 {
+ name = "DonationLink1689136347561";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "donationLink" character varying(256)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "DonationLink1689136347561"`);
+ }
+}
diff --git a/packages/backend/built/migration/1689739513827-firefish-repo.js b/packages/backend/built/migration/1689739513827-firefish-repo.js
new file mode 100644
index 0000000..d2dc482
--- /dev/null
+++ b/packages/backend/built/migration/1689739513827-firefish-repo.js
@@ -0,0 +1,11 @@
+export class FirefishRepo1689739513827 {
+ name = "FirefishRepo1689739513827";
+ async up(queryRunner) {
+ await queryRunner.query(`UPDATE meta SET "repositoryUrl" = 'https://codeberg.org/firefish/firefish'`);
+ await queryRunner.query(`UPDATE meta SET "feedbackUrl" = 'https://codeberg.org/firefish/firefish/issues'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`UPDATE meta SET "repositoryUrl" = 'https://codeberg.org/calckey/calckey'`);
+ await queryRunner.query(`UPDATE meta SET "feedbackUrl" = 'https://codeberg.org/calckey/calckey/firefish/firefish/issues'`);
+ }
+}
diff --git a/packages/backend/built/migration/1689965609061-iceshrimp-repo.js b/packages/backend/built/migration/1689965609061-iceshrimp-repo.js
new file mode 100644
index 0000000..80cd8e9
--- /dev/null
+++ b/packages/backend/built/migration/1689965609061-iceshrimp-repo.js
@@ -0,0 +1,11 @@
+export class IceshrimpRepo1689965609061 {
+ name = "IceshrimpRepo1689965609061";
+ async up(queryRunner) {
+ await queryRunner.query(`UPDATE meta SET "repositoryUrl" = 'https://iceshrimp.dev/iceshrimp/iceshrimp'`);
+ await queryRunner.query(`UPDATE meta SET "feedbackUrl" = 'https://iceshrimp.dev/iceshrimp/iceshrimp/issues'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`UPDATE meta SET "repositoryUrl" = 'https://codeberg.org/firefish/firefish'`);
+ await queryRunner.query(`UPDATE meta SET "feedbackUrl" = 'https://codeberg.org/firefish/firefish/issues'`);
+ }
+}
diff --git a/packages/backend/built/migration/1695747439252-drop-reversi.js b/packages/backend/built/migration/1695747439252-drop-reversi.js
new file mode 100644
index 0000000..c18b755
--- /dev/null
+++ b/packages/backend/built/migration/1695747439252-drop-reversi.js
@@ -0,0 +1,24 @@
+export class DropReversi1695747439252 {
+ name = "DropReversi1695747439252";
+ async up(queryRunner) {
+ await queryRunner.query(`DROP TABLE IF EXISTS "reversi_game"`);
+ await queryRunner.query(`DROP TABLE IF EXISTS "reversi_matching"`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "reversi_game" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "startedAt" TIMESTAMP WITH TIME ZONE, "user1Id" character varying(32) NOT NULL, "user2Id" character varying(32) NOT NULL, "user1Accepted" boolean NOT NULL DEFAULT false, "user2Accepted" boolean NOT NULL DEFAULT false, "black" integer, "isStarted" boolean NOT NULL DEFAULT false, "isEnded" boolean NOT NULL DEFAULT false, "winnerId" character varying(32), "surrendered" character varying(32), "logs" jsonb NOT NULL DEFAULT '[]', "map" character varying(64) array NOT NULL, "bw" character varying(32) NOT NULL, "isLlotheo" boolean NOT NULL DEFAULT false, "canPutEverywhere" boolean NOT NULL DEFAULT false, "loopedBoard" boolean NOT NULL DEFAULT false, "form1" jsonb DEFAULT null, "form2" jsonb DEFAULT null, "crc32" character varying(32), CONSTRAINT "PK_76b30eeba71b1193ad7c5311c3f" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_b46ec40746efceac604142be1c" ON "reversi_game" ("createdAt") `);
+ await queryRunner.query(`CREATE TABLE "reversi_matching" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "parentId" character varying(32) NOT NULL, "childId" character varying(32) NOT NULL, CONSTRAINT "PK_880bd0afbab232f21c8b9d146cf" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_b604d92d6c7aec38627f6eaf16" ON "reversi_matching" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_3b25402709dd9882048c2bbade" ON "reversi_matching" ("parentId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_e247b23a3c9b45f89ec1299d06" ON "reversi_matching" ("childId") `);
+ await queryRunner.query(`ALTER TABLE "reversi_game" ADD CONSTRAINT "FK_f7467510c60a45ce5aca6292743" FOREIGN KEY ("user1Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "reversi_game" ADD CONSTRAINT "FK_6649a4e8c5d5cf32fb03b5da9f6" FOREIGN KEY ("user2Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "reversi_matching" ADD CONSTRAINT "FK_3b25402709dd9882048c2bbade0" FOREIGN KEY ("parentId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "reversi_matching" ADD CONSTRAINT "FK_e247b23a3c9b45f89ec1299d066" FOREIGN KEY ("childId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."createdAt" IS 'The created date of the ReversiGame.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."startedAt" IS 'The started date of the ReversiGame.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."form1" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."form2" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "reversi_matching"."createdAt" IS 'The created date of the ReversiMatching.'`);
+ }
+}
diff --git a/packages/backend/built/migration/1695748502971-index-note-url.js b/packages/backend/built/migration/1695748502971-index-note-url.js
new file mode 100644
index 0000000..f369b80
--- /dev/null
+++ b/packages/backend/built/migration/1695748502971-index-note-url.js
@@ -0,0 +1,9 @@
+export class IndexNoteUrl1695748502971 {
+ name = "IndexNoteUrl1695748502971";
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_note_url" ON "note" ("url") `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_note_url"`);
+ }
+}
diff --git a/packages/backend/built/migration/1695748874491-drop-ads.js b/packages/backend/built/migration/1695748874491-drop-ads.js
new file mode 100644
index 0000000..24b26d9
--- /dev/null
+++ b/packages/backend/built/migration/1695748874491-drop-ads.js
@@ -0,0 +1,14 @@
+export class DropAds1695748874491 {
+ name = "DropAds1695748874491";
+ async up(queryRunner) {
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_2da24ce20ad209f1d9dc032457"`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_1129c2ef687fc272df040bafaa"`);
+ await queryRunner.query(`DROP TABLE IF EXISTS "ad"`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "ad" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "expiresAt" TIMESTAMP WITH TIME ZONE NOT NULL, "place" character varying(32) NOT NULL, "priority" character varying(32) NOT NULL, "url" character varying(1024) NOT NULL, "imageUrl" character varying(1024) NOT NULL, "memo" character varying(8192) NOT NULL, CONSTRAINT "PK_0193d5ef09746e88e9ea92c634d" PRIMARY KEY ("id")); COMMENT ON COLUMN "ad"."createdAt" IS 'The created date of the Ad.'; COMMENT ON COLUMN "ad"."expiresAt" IS 'The expired date of the Ad.'`);
+ await queryRunner.query(`CREATE INDEX "IDX_1129c2ef687fc272df040bafaa" ON "ad" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX "IDX_2da24ce20ad209f1d9dc032457" ON "ad" ("expiresAt") `);
+ await queryRunner.query(`ALTER TABLE "ad" ADD "ratio" integer NOT NULL DEFAULT '1'`);
+ }
+}
diff --git a/packages/backend/built/migration/1695749386779-instance-account-domain-cleanup.js b/packages/backend/built/migration/1695749386779-instance-account-domain-cleanup.js
new file mode 100644
index 0000000..6e62b21
--- /dev/null
+++ b/packages/backend/built/migration/1695749386779-instance-account-domain-cleanup.js
@@ -0,0 +1,11 @@
+export class InstanceAccountDomainCleanup1695749386779 {
+ name = "InstanceAccountDomainCleanup1695749386779";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN IF EXISTS "accountDomain"`);
+ await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN IF EXISTS "account_domain"`);
+ }
+ async down(queryRunner) {
+ // This migration is only here to ensure consistent state if upgrading from certain dev branch commits, skipping the final TypeORM migration.
+ // As such, there is no need to revert it.
+ }
+}
diff --git a/packages/backend/built/migration/1695749948350-move-antenna-to-cache.js b/packages/backend/built/migration/1695749948350-move-antenna-to-cache.js
new file mode 100644
index 0000000..0aecc2d
--- /dev/null
+++ b/packages/backend/built/migration/1695749948350-move-antenna-to-cache.js
@@ -0,0 +1,45 @@
+export class MoveAntennaToCache1695749948350 {
+ name = "MoveAntennaToCache1695749948350";
+ async up(queryRunner) {
+ const tableExists = await queryRunner.query(`SELECT EXISTS ( SELECT 1 FROM pg_tables WHERE tablename = 'antenna_note' ) AS table_existence`).then((p)=>!!p[0]['table_existence']);
+ if (!tableExists) {
+ console.log('Skipping migration ("antenna_note" table does not exist)');
+ return;
+ }
+ const skipCopy = process.env.ANTENNA_MIGRATION_SKIP === 'true';
+ let readLimit = parseInt(process.env.ANTENNA_MIGRATION_READ_LIMIT ?? "10000", 10) ?? 10000;
+ if (skipCopy) {
+ console.log('ANTENNA_MIGRATION_SKIP = true, skipping antenna note migration');
+ } else {
+ const { redisClient } = await import("../db/redis.js");
+ const total = await queryRunner.query(`SELECT COUNT(1) FROM "antenna_note"`).then((p)=>p[0]['count']);
+ console.log(`Copying ${total} entries in "antenna_note", please hang tight!`);
+ let remaining = total;
+ let query = `SELECT "id", "noteId", "antennaId" FROM "antenna_note" ORDER BY "id" ASC LIMIT ${readLimit}`;
+ while(remaining > 0){
+ let res = await queryRunner.query(query);
+ if (res.length === 0) break;
+ remaining -= res.length;
+ for (const hit of res){
+ redisClient.xadd(`antennaTimeline:${hit.antennaId}`, "MAXLEN", "~", "200", "*", "note", hit.noteId);
+ }
+ console.log(`Copied ${total - remaining}/${total} notes to cache.`);
+ query = `SELECT "id", "noteId", "antennaId" FROM "antenna_note" WHERE "id" > '${res.at(-1).id}' ORDER BY "id" ASC LIMIT ${Math.min(readLimit, remaining)}`;
+ }
+ redisClient.quit();
+ }
+ await queryRunner.query(`DROP TABLE IF EXISTS "antenna_note"`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`CREATE TABLE IF NOT EXISTS "antenna_note" ("id" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, "antennaId" character varying(32) NOT NULL, CONSTRAINT "PK_fb28d94d0989a3872df19fd6ef8" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_bd0397be22147e17210940e125" ON "antenna_note" ("noteId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_0d775946662d2575dfd2068a5f" ON "antenna_note" ("antennaId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_335a0bf3f904406f9ef3dd51c2" ON "antenna_note" ("noteId", "antennaId") `);
+ await queryRunner.query(`ALTER TABLE "antenna_note" ADD CONSTRAINT "FK_bd0397be22147e17210940e125b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna_note" ADD CONSTRAINT "FK_0d775946662d2575dfd2068a5f5" FOREIGN KEY ("antennaId") REFERENCES "antenna"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
+ await queryRunner.query(`ALTER TABLE "antenna_note" ADD "read" boolean NOT NULL DEFAULT false`, undefined);
+ await queryRunner.query(`CREATE INDEX "IDX_9937ea48d7ae97ffb4f3f063a4" ON "antenna_note" ("read") `, undefined);
+ await queryRunner.query(`COMMENT ON COLUMN "antenna_note"."noteId" IS 'The note ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "antenna_note"."antennaId" IS 'The antenna ID.'`);
+ }
+}
diff --git a/packages/backend/built/migration/1695861526125-index-note-userid.js b/packages/backend/built/migration/1695861526125-index-note-userid.js
new file mode 100644
index 0000000..5d0ac5a
--- /dev/null
+++ b/packages/backend/built/migration/1695861526125-index-note-userid.js
@@ -0,0 +1,9 @@
+export class IndexNoteUserId1695861526125 {
+ name = "IndexNoteUserId1695861526125";
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE INDEX "IDX_note_userId_id" ON "note" ("userId", "id")`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_note_userId_id"`);
+ }
+}
diff --git a/packages/backend/built/migration/1697216726757-auto-generate-vapid-keys.js b/packages/backend/built/migration/1697216726757-auto-generate-vapid-keys.js
new file mode 100644
index 0000000..4a6f74c
--- /dev/null
+++ b/packages/backend/built/migration/1697216726757-auto-generate-vapid-keys.js
@@ -0,0 +1,24 @@
+import push from "web-push";
+export class AutoGenerateVapidKeys1697216726757 {
+ // Based on FoundKey's 1668374092227-forceEnablePush.js
+ name = 'AutoGenerateVapidKeys1697216726757';
+ async up(queryRunner) {
+ // set VAPID keys if not yet set
+ const { publicKey, privateKey } = push.generateVAPIDKeys();
+ await queryRunner.query(`UPDATE "meta" SET "swPublicKey" = $1, "swPrivateKey" = $2 WHERE "swPublicKey" IS NULL OR "swPrivateKey" IS NULL`, [
+ publicKey,
+ privateKey
+ ]);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableServiceWorker"`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "swPublicKey" SET NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "swPrivateKey" SET NOT NULL`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "swPrivateKey" DROP NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "swPublicKey" DROP NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "enableServiceWorker" boolean NOT NULL DEFAULT false`);
+ // since VAPID keys are set and the service worker may have been enabled before, make sure it is now enabled
+ await queryRunner.query(`UPDATE "meta" SET "enableServiceWorker" = true`);
+ // can't unset the VAPID keys because we do not know if we set them in the migration
+ }
+}
diff --git a/packages/backend/built/migration/1697226201723-add-oauth-tables.js b/packages/backend/built/migration/1697226201723-add-oauth-tables.js
new file mode 100644
index 0000000..9b85bd4
--- /dev/null
+++ b/packages/backend/built/migration/1697226201723-add-oauth-tables.js
@@ -0,0 +1,21 @@
+export class AddOAuthTables1697226201723 {
+ name = 'AddOAuthTables1697226201723';
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "oauth_app" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "clientId" character varying(64) NOT NULL, "clientSecret" character varying(64) NOT NULL, "name" character varying(128) NOT NULL, "website" character varying(256), "scopes" character varying(64) array NOT NULL, "redirectUris" character varying(64) array NOT NULL, CONSTRAINT "PK_3256b97c0a3ee2d67240805dca4" PRIMARY KEY ("id")); COMMENT ON COLUMN "oauth_app"."createdAt" IS 'The created date of the OAuth application'; COMMENT ON COLUMN "oauth_app"."clientId" IS 'The client id of the OAuth application'; COMMENT ON COLUMN "oauth_app"."clientSecret" IS 'The client secret of the OAuth application'; COMMENT ON COLUMN "oauth_app"."name" IS 'The name of the OAuth application'; COMMENT ON COLUMN "oauth_app"."website" IS 'The website of the OAuth application'; COMMENT ON COLUMN "oauth_app"."scopes" IS 'The scopes requested by the OAuth application'; COMMENT ON COLUMN "oauth_app"."redirectUris" IS 'The redirect URIs of the OAuth application'`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_65b61f406c811241e1315a2f82" ON "oauth_app" ("clientId") `);
+ await queryRunner.query(`CREATE TABLE "oauth_token" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "appId" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "code" character varying(64) NOT NULL, "token" character varying(64) NOT NULL, "active" boolean NOT NULL, "scopes" character varying(64) array NOT NULL, "redirectUri" character varying(64) NOT NULL, CONSTRAINT "PK_7e6a25a3cc4395d1658f5b89c73" PRIMARY KEY ("id")); COMMENT ON COLUMN "oauth_token"."createdAt" IS 'The created date of the OAuth token'; COMMENT ON COLUMN "oauth_token"."code" IS 'The auth code for the OAuth token'; COMMENT ON COLUMN "oauth_token"."token" IS 'The OAuth token'; COMMENT ON COLUMN "oauth_token"."active" IS 'Whether or not the token has been activated'; COMMENT ON COLUMN "oauth_token"."scopes" IS 'The scopes requested by the OAuth token'; COMMENT ON COLUMN "oauth_token"."redirectUri" IS 'The redirect URI of the OAuth token'`);
+ await queryRunner.query(`CREATE INDEX "IDX_dc5fe174a8b59025055f0ec136" ON "oauth_token" ("code") `);
+ await queryRunner.query(`CREATE INDEX "IDX_2cbeb4b389444bcf4379ef4273" ON "oauth_token" ("token") `);
+ await queryRunner.query(`ALTER TABLE "oauth_token" ADD CONSTRAINT "FK_6d3ef28ea647b1449ba79690874" FOREIGN KEY ("appId") REFERENCES "oauth_app"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "oauth_token" ADD CONSTRAINT "FK_f6b4b1ac66b753feab5d831ba04" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "oauth_token" DROP CONSTRAINT "FK_f6b4b1ac66b753feab5d831ba04"`);
+ await queryRunner.query(`ALTER TABLE "oauth_token" DROP CONSTRAINT "FK_6d3ef28ea647b1449ba79690874"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_2cbeb4b389444bcf4379ef4273"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_dc5fe174a8b59025055f0ec136"`);
+ await queryRunner.query(`DROP TABLE "oauth_token"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_65b61f406c811241e1315a2f82"`);
+ await queryRunner.query(`DROP TABLE "oauth_app"`);
+ }
+}
diff --git a/packages/backend/built/migration/1697246035867-increase-oauth-redirecturis-length.js b/packages/backend/built/migration/1697246035867-increase-oauth-redirecturis-length.js
new file mode 100644
index 0000000..fa2cff3
--- /dev/null
+++ b/packages/backend/built/migration/1697246035867-increase-oauth-redirecturis-length.js
@@ -0,0 +1,9 @@
+export class IncreaseOAuthRedirecturisLength1697246035867 {
+ name = 'IncreaseOAuthRedirecturisLength1697246035867';
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "oauth_app" ALTER "redirectUris" TYPE character varying(512) array`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "oauth_app" ALTER "redirectUris" TYPE character varying(64) array`);
+ }
+}
diff --git a/packages/backend/built/migration/1697286869039-increase-oauth-token-redirecturis-length.js b/packages/backend/built/migration/1697286869039-increase-oauth-token-redirecturis-length.js
new file mode 100644
index 0000000..a155c9b
--- /dev/null
+++ b/packages/backend/built/migration/1697286869039-increase-oauth-token-redirecturis-length.js
@@ -0,0 +1,9 @@
+export class IncreaseOauthTokenRedirecturisLength1697286869039 {
+ name = 'IncreaseOauthTokenRedirecturisLength1697286869039';
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "oauth_token" ALTER "redirectUri" TYPE character varying(512)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "oauth_token" ALTER "redirectUri" TYPE character varying(64)`);
+ }
+}
diff --git a/packages/backend/built/migration/1697289658422-resync-with-orm.js b/packages/backend/built/migration/1697289658422-resync-with-orm.js
new file mode 100644
index 0000000..1aa9c4c
--- /dev/null
+++ b/packages/backend/built/migration/1697289658422-resync-with-orm.js
@@ -0,0 +1,102 @@
+export class ResyncWithOrm1697289658422 {
+ name = 'ResyncWithOrm1697289658422';
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT IF EXISTS "fk_7f4e851a35d81b64dda28eee0"`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_renote_muting_createdAt"`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_renote_muting_muteeId"`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_renote_muting_muterId"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "useStarForReactionFallback"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableGuestTimeline"`);
+ await queryRunner.query(`COMMENT ON COLUMN "notification"."isRead" IS 'Whether the notification was read.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "meta"."defaultReaction" IS NULL`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "secureMode" SET NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "privateMode" SET NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "allowedHosts" SET NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "pinnedPages" SET DEFAULT '{/featured,/channels,/explore,/pages,/about-iceshrimp}'`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "repositoryUrl" SET DEFAULT 'https://iceshrimp.dev/iceshrimp/iceshrimp'`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "feedbackUrl" SET DEFAULT 'https://iceshrimp.dev/iceshrimp/iceshrimp/issues/new'`);
+ await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."createdAt" IS 'The created date of the Muting.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."muteeId" IS 'The mutee user ID.'`);
+ await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."muterId" IS 'The muter user ID.'`);
+ await queryRunner.query(`ALTER TABLE "poll" DROP CONSTRAINT IF EXISTS "FK_da851e06d0dfe2ef397d8b1bf1b"`);
+ await queryRunner.query(`ALTER TABLE "poll" DROP CONSTRAINT IF EXISTS "UQ_da851e06d0dfe2ef397d8b1bf1b"`);
+ await queryRunner.query(`ALTER TYPE "public"."poll_notevisibility_enum" RENAME TO "poll_notevisibility_enum_old"`);
+ await queryRunner.query(`CREATE TYPE "public"."poll_notevisibility_enum" AS ENUM('public', 'home', 'followers', 'specified', 'hidden')`);
+ await queryRunner.query(`ALTER TABLE "poll" ALTER COLUMN "noteVisibility" TYPE "public"."poll_notevisibility_enum" USING "noteVisibility"::"text"::"public"."poll_notevisibility_enum"`);
+ await queryRunner.query(`DROP TYPE "public"."poll_notevisibility_enum_old"`);
+ await queryRunner.query(`ALTER TABLE "user_keypair" DROP CONSTRAINT IF EXISTS "FK_f4853eb41ab722fe05f81cedeb6"`);
+ await queryRunner.query(`ALTER TABLE "user_keypair" DROP CONSTRAINT IF EXISTS "UQ_f4853eb41ab722fe05f81cedeb6"`);
+ await queryRunner.query(`ALTER TABLE "user_publickey" DROP CONSTRAINT IF EXISTS "FK_10c146e4b39b443ede016f6736d"`);
+ await queryRunner.query(`ALTER TABLE "user_publickey" DROP CONSTRAINT IF EXISTS "UQ_10c146e4b39b443ede016f6736d"`);
+ await queryRunner.query(`ALTER TABLE "page" ALTER COLUMN "isPublic" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT IF EXISTS "FK_51cb79b5555effaf7d69ba1cff9"`);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT IF EXISTS "UQ_51cb79b5555effaf7d69ba1cff9"`);
+ await queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT IF EXISTS "FK_e263909ca4fe5d57f8d4230dd5c"`);
+ await queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT IF EXISTS "UQ_e263909ca4fe5d57f8d4230dd5c"`);
+ await queryRunner.query(`ALTER TABLE "renote_muting" DROP CONSTRAINT IF EXISTS "FK_7eac97594bcac5ffcf2068089b6"`);
+ await queryRunner.query(`ALTER TABLE "renote_muting" DROP CONSTRAINT IF EXISTS "FK_7aa72a5fe76019bfe8e5e0e8b7d"`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT IF EXISTS "FK_a9021cc2e1feb5f72d3db6e9f5f"`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_d1259a2c2b7bb413ff449e8711" ON "renote_muting" ("createdAt") `);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_7eac97594bcac5ffcf2068089b" ON "renote_muting" ("muteeId") `);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_7aa72a5fe76019bfe8e5e0e8b7" ON "renote_muting" ("muterId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_0d801c609cec4e9eb4b6b4490c" ON "renote_muting" ("muterId", "muteeId") `);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_a9021cc2e1feb5f72d3db6e9f5" ON "abuse_user_report" ("targetUserId") `);
+ await queryRunner.query(`DELETE FROM "renote_muting" WHERE NOT EXISTS (select 1 from "user" where "user"."id" = "renote_muting"."muterId")`);
+ await queryRunner.query(`DELETE FROM "renote_muting" WHERE NOT EXISTS (select 1 from "user" where "user"."id" = "renote_muting"."muteeId")`);
+ await queryRunner.query(`ALTER TABLE "renote_muting" ADD CONSTRAINT "FK_7eac97594bcac5ffcf2068089b6" FOREIGN KEY ("muteeId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "renote_muting" ADD CONSTRAINT "FK_7aa72a5fe76019bfe8e5e0e8b7d" FOREIGN KEY ("muterId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f" FOREIGN KEY ("targetUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_publickey" ADD CONSTRAINT "FK_10c146e4b39b443ede016f6736d" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c"`);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9"`);
+ await queryRunner.query(`ALTER TABLE "user_publickey" DROP CONSTRAINT "FK_10c146e4b39b443ede016f6736d"`);
+ await queryRunner.query(`ALTER TABLE "user_keypair" DROP CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6"`);
+ await queryRunner.query(`ALTER TABLE "poll" DROP CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b"`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f"`);
+ await queryRunner.query(`ALTER TABLE "renote_muting" DROP CONSTRAINT "FK_7aa72a5fe76019bfe8e5e0e8b7d"`);
+ await queryRunner.query(`ALTER TABLE "renote_muting" DROP CONSTRAINT "FK_7eac97594bcac5ffcf2068089b6"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_a9021cc2e1feb5f72d3db6e9f5"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_0d801c609cec4e9eb4b6b4490c"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_7aa72a5fe76019bfe8e5e0e8b7"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_7eac97594bcac5ffcf2068089b"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_d1259a2c2b7bb413ff449e8711"`);
+ await queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "UQ_e263909ca4fe5d57f8d4230dd5c" UNIQUE ("noteId")`);
+ await queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "UQ_51cb79b5555effaf7d69ba1cff9" UNIQUE ("userId")`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "page" ALTER COLUMN "isPublic" SET DEFAULT true`);
+ await queryRunner.query(`ALTER TABLE "user_publickey" ADD CONSTRAINT "UQ_10c146e4b39b443ede016f6736d" UNIQUE ("userId")`);
+ await queryRunner.query(`ALTER TABLE "user_publickey" ADD CONSTRAINT "FK_10c146e4b39b443ede016f6736d" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "UQ_f4853eb41ab722fe05f81cedeb6" UNIQUE ("userId")`);
+ await queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`CREATE TYPE "public"."poll_notevisibility_enum_old" AS ENUM('public', 'home', 'followers', 'specified')`);
+ await queryRunner.query(`ALTER TABLE "poll" ALTER COLUMN "noteVisibility" TYPE "public"."poll_notevisibility_enum_old" USING "noteVisibility"::"text"::"public"."poll_notevisibility_enum_old"`);
+ await queryRunner.query(`DROP TYPE "public"."poll_notevisibility_enum"`);
+ await queryRunner.query(`ALTER TYPE "public"."poll_notevisibility_enum_old" RENAME TO "poll_notevisibility_enum"`);
+ await queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "UQ_da851e06d0dfe2ef397d8b1bf1b" UNIQUE ("noteId")`);
+ await queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."muterId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."muteeId" IS NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."createdAt" IS NULL`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "feedbackUrl" SET DEFAULT 'https://codeberg.org/firefish/firefish/issues'`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "repositoryUrl" SET DEFAULT 'https://codeberg.org/firefish/firefish'`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "pinnedPages" SET DEFAULT '{/featured,/channels,/explore,/pages,/about-misskey}'`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "allowedHosts" DROP NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "privateMode" DROP NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "secureMode" DROP NOT NULL`);
+ await queryRunner.query(`COMMENT ON COLUMN "meta"."defaultReaction" IS 'The fallback reaction for emoji reacts'`);
+ await queryRunner.query(`COMMENT ON COLUMN "notification"."isRead" IS 'Whether the Notification is read.'`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "enableGuestTimeline" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "useStarForReactionFallback" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`CREATE INDEX "IDX_renote_muting_muterId" ON "muting" ("muterId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_renote_muting_muteeId" ON "muting" ("muteeId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_renote_muting_createdAt" ON "muting" ("createdAt") `);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "fk_7f4e851a35d81b64dda28eee0" FOREIGN KEY ("targetUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+}
diff --git a/packages/backend/built/migration/1697302438587-add-user-profile-mentions.js b/packages/backend/built/migration/1697302438587-add-user-profile-mentions.js
new file mode 100644
index 0000000..15254d7
--- /dev/null
+++ b/packages/backend/built/migration/1697302438587-add-user-profile-mentions.js
@@ -0,0 +1,9 @@
+export class AddUserProfileMentions1697302438587 {
+ name = 'AddUserProfileMentions1697302438587';
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "mentions" jsonb NOT NULL DEFAULT '[]'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "mentions"`);
+ }
+}
diff --git a/packages/backend/built/migration/1697649475796-secure-mode-defaults.js b/packages/backend/built/migration/1697649475796-secure-mode-defaults.js
new file mode 100644
index 0000000..e452012
--- /dev/null
+++ b/packages/backend/built/migration/1697649475796-secure-mode-defaults.js
@@ -0,0 +1,9 @@
+export class SecureModeDefaults1697649475796 {
+ name = 'SecureModeDefaults1697649475796';
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "secureMode" SET DEFAULT true`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "secureMode" SET DEFAULT false`);
+ }
+}
diff --git a/packages/backend/built/migration/1697663824168-remote-nsfw-detection.js b/packages/backend/built/migration/1697663824168-remote-nsfw-detection.js
new file mode 100644
index 0000000..1744443
--- /dev/null
+++ b/packages/backend/built/migration/1697663824168-remote-nsfw-detection.js
@@ -0,0 +1,29 @@
+export class RemoveNsfwDetection1697663824168 {
+ name = 'RemoveNsfwDetection1697663824168';
+ async up(queryRunner) {
+ await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_3b33dff77bb64b23c88151d23e"`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_8bdcd3dd2bddb78014999a16ce"`);
+ await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN IF EXISTS "maybeSensitive"`);
+ await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN IF EXISTS "maybePorn"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN IF EXISTS "sensitiveMediaDetection"`);
+ await queryRunner.query(`DROP TYPE IF EXISTS "public"."meta_sensitivemediadetection_enum"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN IF EXISTS "sensitiveMediaDetectionSensitivity"`);
+ await queryRunner.query(`DROP TYPE IF EXISTS "public"."meta_sensitivemediadetectionsensitivity_enum"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN IF EXISTS "setSensitiveFlagAutomatically"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN IF EXISTS "enableSensitiveMediaDetectionForVideos"`);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN IF EXISTS "autoSensitive"`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "autoSensitive" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "enableSensitiveMediaDetectionForVideos" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "setSensitiveFlagAutomatically" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`CREATE TYPE "public"."meta_sensitivemediadetectionsensitivity_enum" AS ENUM('medium', 'low', 'high', 'veryLow', 'veryHigh')`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "sensitiveMediaDetectionSensitivity" "public"."meta_sensitivemediadetectionsensitivity_enum" NOT NULL DEFAULT 'medium'`);
+ await queryRunner.query(`CREATE TYPE "public"."meta_sensitivemediadetection_enum" AS ENUM('none', 'all', 'local', 'remote')`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "sensitiveMediaDetection" "public"."meta_sensitivemediadetection_enum" NOT NULL DEFAULT 'none'`);
+ await queryRunner.query(`ALTER TABLE "drive_file" ADD "maybePorn" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "drive_file" ADD "maybeSensitive" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`CREATE INDEX "IDX_8bdcd3dd2bddb78014999a16ce" ON "drive_file" ("maybePorn") `);
+ await queryRunner.query(`CREATE INDEX "IDX_3b33dff77bb64b23c88151d23e" ON "drive_file" ("maybeSensitive") `);
+ }
+}
diff --git a/packages/backend/built/migration/1697665612162-remove-proxy-account.js b/packages/backend/built/migration/1697665612162-remove-proxy-account.js
new file mode 100644
index 0000000..d3186fd
--- /dev/null
+++ b/packages/backend/built/migration/1697665612162-remove-proxy-account.js
@@ -0,0 +1,11 @@
+export class RemoveProxyAccount1697665612162 {
+ name = 'RemoveProxyAccount1697665612162';
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP CONSTRAINT IF EXISTS "FK_ab1bc0c1e209daa77b8e8d212ad"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN IF EXISTS "proxyAccountId"`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "proxyAccountId" character varying(32)`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD CONSTRAINT "FK_ab1bc0c1e209daa77b8e8d212ad" FOREIGN KEY ("proxyAccountId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
+ }
+}
diff --git a/packages/backend/built/migration/1697730891701-remove-unfollowed-users-from-lists.js b/packages/backend/built/migration/1697730891701-remove-unfollowed-users-from-lists.js
new file mode 100644
index 0000000..1f58d11
--- /dev/null
+++ b/packages/backend/built/migration/1697730891701-remove-unfollowed-users-from-lists.js
@@ -0,0 +1,15 @@
+// This migration is currently inactive.
+// It will be activated in the next stable release after the first one that includes this file,
+// to make sure users have enough time to migrate their unfollowed list members to follows.
+/*
+import { MigrationInterface, QueryRunner } from "typeorm"
+
+export class RemoveUnfollowedUsersFromLists1697730891701 implements MigrationInterface {
+ name = "RemoveUnfollowedUsersFromLists1697730891701";
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(`DELETE FROM "user_list_joining" USING "user_list_joining" AS "member" INNER JOIN "user_list" "list" ON "member"."userListId" = "list"."id" WHERE "user_list_joining"."id" = "member"."id" AND "member"."userId" <> "list"."userId" AND "member"."userId" NOT IN (SELECT "followeeId" FROM "following" WHERE "following"."followerId" = "list"."userId")`);
+ }
+
+ public async down(_queryRunner: QueryRunner): Promise {}
+}
+ */
\ No newline at end of file
diff --git a/packages/backend/built/migration/1697733603329-user-list-options.js b/packages/backend/built/migration/1697733603329-user-list-options.js
new file mode 100644
index 0000000..d26ab08
--- /dev/null
+++ b/packages/backend/built/migration/1697733603329-user-list-options.js
@@ -0,0 +1,11 @@
+export class UserListOptions1697733603329 {
+ name = 'UserListOptions1697733603329';
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_list" ADD "hideFromHomeTl" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_list"."hideFromHomeTl" IS 'Whether posts from list members should be hidden from the home timeline.'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`COMMENT ON COLUMN "user_list"."hideFromHomeTl" IS 'Whether posts from list members should be hidden from the home timeline.'`);
+ await queryRunner.query(`ALTER TABLE "user_list" DROP COLUMN "hideFromHomeTl"`);
+ }
+}
diff --git a/packages/backend/built/migration/1700331070890-note-text-fts-idx.js b/packages/backend/built/migration/1700331070890-note-text-fts-idx.js
new file mode 100644
index 0000000..eb26975
--- /dev/null
+++ b/packages/backend/built/migration/1700331070890-note-text-fts-idx.js
@@ -0,0 +1,16 @@
+export class NoteTextFtsIdx1700331070890 {
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE EXTENSION IF NOT EXISTS pg_trgm`);
+ const total = await queryRunner.query(`SELECT COUNT(*) FROM "note"`);
+ if (total && total.length > 0) {
+ const count = BigInt(total[0].count);
+ console.log(`Indexing the "note" table for full text search, please hang tight!`);
+ console.log(`You have ${count} notes in your database. This process will take an estimated ${count / 1000000n * 45n} seconds, though the exact duration depends on your hardware configuration.`);
+ }
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "note_text_fts_idx" ON "note" USING gin ("text" gin_trgm_ops)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX IF EXISTS "note_text_fts_idx"`);
+ await queryRunner.query(`DROP EXTENSION IF EXISTS pg_trgm`);
+ }
+}
diff --git a/packages/backend/built/migration/1700517975122-drive-file-url.js b/packages/backend/built/migration/1700517975122-drive-file-url.js
new file mode 100644
index 0000000..a2fe8b3
--- /dev/null
+++ b/packages/backend/built/migration/1700517975122-drive-file-url.js
@@ -0,0 +1,27 @@
+export class UserAvatarBannerRefactor1700517975122 {
+ name = 'UserAvatarBannerRefactor1700517975122';
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" ADD "avatarUrl" character varying(512)`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."avatarUrl" IS 'The URL of the avatar DriveFile'`);
+ await queryRunner.query(`ALTER TABLE "user" ADD "avatarBlurhash" character varying(128)`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."avatarBlurhash" IS 'The blurhash of the avatar DriveFile'`);
+ await queryRunner.query(`ALTER TABLE "user" ADD "bannerUrl" character varying(512)`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."bannerUrl" IS 'The URL of the banner DriveFile'`);
+ await queryRunner.query(`ALTER TABLE "user" ADD "bannerBlurhash" character varying(128)`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."bannerBlurhash" IS 'The blurhash of the banner DriveFile'`);
+ await queryRunner.query(`UPDATE "user" SET "avatarUrl" = (SELECT COALESCE("thumbnailUrl", "webpublicUrl", "url") FROM "drive_file" WHERE "id" = "user"."avatarId") WHERE "avatarId" IS NOT NULL`);
+ await queryRunner.query(`UPDATE "user" SET "avatarBlurhash" = (SELECT "blurhash" FROM "drive_file" WHERE "id" = "user"."avatarId") WHERE "avatarId" IS NOT NULL`);
+ await queryRunner.query(`UPDATE "user" SET "bannerUrl" = (SELECT COALESCE("webpublicUrl", "url") FROM "drive_file" WHERE "id" = "user"."bannerId") WHERE "bannerId" IS NOT NULL`);
+ await queryRunner.query(`UPDATE "user" SET "bannerBlurhash" = (SELECT "blurhash" FROM "drive_file" WHERE "id" = "user"."bannerId") WHERE "bannerId" IS NOT NULL`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`COMMENT ON COLUMN "user"."bannerBlurhash" IS 'The blurhash of the banner DriveFile'`);
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "bannerBlurhash"`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."bannerUrl" IS 'The URL of the banner DriveFile'`);
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "bannerUrl"`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."avatarBlurhash" IS 'The blurhash of the avatar DriveFile'`);
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarBlurhash"`);
+ await queryRunner.query(`COMMENT ON COLUMN "user"."avatarUrl" IS 'The URL of the avatar DriveFile'`);
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarUrl"`);
+ }
+}
diff --git a/packages/backend/built/migration/1700623165718-add-note-userhost-id-idx.js b/packages/backend/built/migration/1700623165718-add-note-userhost-id-idx.js
new file mode 100644
index 0000000..82b07f6
--- /dev/null
+++ b/packages/backend/built/migration/1700623165718-add-note-userhost-id-idx.js
@@ -0,0 +1,9 @@
+export class AddNoteIdUserhostIdx1700623165718 {
+ name = 'AddNoteIdUserhostIdx1700623165718';
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE INDEX "IDX_note_id_userHost" ON "note" ("id", "userHost") `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_note_id_userHost"`);
+ }
+}
diff --git a/packages/backend/built/migration/1700686908916-add-autofollowed-account.js b/packages/backend/built/migration/1700686908916-add-autofollowed-account.js
new file mode 100644
index 0000000..579fc94
--- /dev/null
+++ b/packages/backend/built/migration/1700686908916-add-autofollowed-account.js
@@ -0,0 +1,9 @@
+export class AddAutofollowedAccount1700686908916 {
+ name = "AddAutofollowedAccount1700686908916";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "autofollowedAccount" character varying(128)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "autofollowedAccount"`);
+ }
+}
diff --git a/packages/backend/built/migration/1700962939886-add-html-cache.js b/packages/backend/built/migration/1700962939886-add-html-cache.js
new file mode 100644
index 0000000..72692ce
--- /dev/null
+++ b/packages/backend/built/migration/1700962939886-add-html-cache.js
@@ -0,0 +1,15 @@
+export class AddHtmlCache1700962939886 {
+ name = 'AddHtmlCache1700962939886';
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "html_note_cache_entry" ("noteId" character varying(32) NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE, "content" text, CONSTRAINT "PK_6ef86ec901b2017cbe82d3a8286" PRIMARY KEY ("noteId"))`);
+ await queryRunner.query(`CREATE TABLE "html_user_cache_entry" ("userId" character varying(32) NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE, "bio" text, "fields" jsonb NOT NULL DEFAULT '[]', CONSTRAINT "PK_920b9474e3c9cae3f3c37c057e1" PRIMARY KEY ("userId"))`);
+ await queryRunner.query(`ALTER TABLE "html_note_cache_entry" ADD CONSTRAINT "FK_6ef86ec901b2017cbe82d3a8286" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "html_user_cache_entry" ADD CONSTRAINT "FK_920b9474e3c9cae3f3c37c057e1" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "html_user_cache_entry" DROP CONSTRAINT "FK_920b9474e3c9cae3f3c37c057e1"`);
+ await queryRunner.query(`ALTER TABLE "html_note_cache_entry" DROP CONSTRAINT "FK_6ef86ec901b2017cbe82d3a8286"`);
+ await queryRunner.query(`DROP TABLE "html_user_cache_entry"`);
+ await queryRunner.query(`DROP TABLE "html_note_cache_entry"`);
+ }
+}
diff --git a/packages/backend/built/migration/1701069578019-remove-twitter-integration.js b/packages/backend/built/migration/1701069578019-remove-twitter-integration.js
new file mode 100644
index 0000000..c9497d2
--- /dev/null
+++ b/packages/backend/built/migration/1701069578019-remove-twitter-integration.js
@@ -0,0 +1,13 @@
+export class RemoveTwitterIntegration1701069578019 {
+ name = "RemoveTwitterIntegration1701069578019";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN IF EXISTS "enableTwitterIntegration"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN IF EXISTS "twitterConsumerKey"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN IF EXISTS "twitterConsumerSecret"`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD COLUMN IF NOT EXISTS "enableTwitterIntegration" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD COLUMN IF NOT EXISTS "twitterConsumerKey" character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD COLUMN IF NOT EXISTS "twitterConsumerSecret" character varying(128)`);
+ }
+}
diff --git a/packages/backend/built/migration/1701108527387-rework-hard-mutes.js b/packages/backend/built/migration/1701108527387-rework-hard-mutes.js
new file mode 100644
index 0000000..68f724a
--- /dev/null
+++ b/packages/backend/built/migration/1701108527387-rework-hard-mutes.js
@@ -0,0 +1,23 @@
+export class ReworkHardMutes1701108527387 {
+ name = 'ReworkHardMutes1701108527387';
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "muted_note" DROP CONSTRAINT "FK_d8e07aa18c2d64e86201601aec1"`);
+ await queryRunner.query(`ALTER TABLE "muted_note" DROP CONSTRAINT "FK_70ab9786313d78e4201d81cdb89"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_a8c6bfd637d3f1d67a27c48e27"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_636e977ff90b23676fb5624b25"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_d8e07aa18c2d64e86201601aec"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_70ab9786313d78e4201d81cdb8"`);
+ await queryRunner.query(`DROP TABLE "muted_note"`);
+ await queryRunner.query(`DROP TYPE "public"."muted_note_reason_enum"`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`CREATE TYPE "public"."muted_note_reason_enum" AS ENUM('word', 'manual', 'spam', 'other')`);
+ await queryRunner.query(`CREATE TABLE "muted_note" ("id" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "reason" "public"."muted_note_reason_enum" NOT NULL, CONSTRAINT "PK_897e2eff1c0b9b64e55ca1418a4" PRIMARY KEY ("id")); COMMENT ON COLUMN "muted_note"."noteId" IS 'The note ID.'; COMMENT ON COLUMN "muted_note"."userId" IS 'The user ID.'; COMMENT ON COLUMN "muted_note"."reason" IS 'The reason of the MutedNote.'`);
+ await queryRunner.query(`CREATE INDEX "IDX_70ab9786313d78e4201d81cdb8" ON "muted_note" ("noteId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_d8e07aa18c2d64e86201601aec" ON "muted_note" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_636e977ff90b23676fb5624b25" ON "muted_note" ("reason") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a8c6bfd637d3f1d67a27c48e27" ON "muted_note" ("noteId", "userId") `);
+ await queryRunner.query(`ALTER TABLE "muted_note" ADD CONSTRAINT "FK_70ab9786313d78e4201d81cdb89" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "muted_note" ADD CONSTRAINT "FK_d8e07aa18c2d64e86201601aec1" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+}
diff --git a/packages/backend/built/migration/1701118152149-increase-host-char-limit.js b/packages/backend/built/migration/1701118152149-increase-host-char-limit.js
new file mode 100644
index 0000000..3a7e20d
--- /dev/null
+++ b/packages/backend/built/migration/1701118152149-increase-host-char-limit.js
@@ -0,0 +1,43 @@
+export class IncreaseHostCharLimit1701118152149 {
+ name = 'IncreaseHostCharLimit1701118152149';
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "drive_file" ALTER COLUMN "userHost" TYPE character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "host" TYPE character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "userHost" TYPE character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "user_publickey" ALTER COLUMN "keyId" TYPE character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "emoji" ALTER COLUMN "host" TYPE character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "userHost" TYPE character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "replyUserHost" TYPE character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "renoteUserHost" TYPE character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "host" TYPE character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "iconUrl" TYPE character varying(4096)`);
+ await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "faviconUrl" TYPE character varying(4096)`);
+ await queryRunner.query(`ALTER TABLE "poll" ALTER COLUMN "userHost" TYPE character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ALTER COLUMN "targetUserHost" TYPE character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ALTER COLUMN "reporterHost" TYPE character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "following" ALTER COLUMN "followeeHost" TYPE character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "following" ALTER COLUMN "followerHost" TYPE character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "follow_request" ALTER COLUMN "followeeHost" TYPE character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "follow_request" ALTER COLUMN "followerHost" TYPE character varying(512)`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "drive_file" ALTER COLUMN "userHost" TYPE character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "host" TYPE character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "userHost" TYPE character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "user_publickey" ALTER COLUMN "keyId" TYPE character varying(256)`);
+ await queryRunner.query(`ALTER TABLE "emoji" ALTER COLUMN "host" TYPE character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "userHost" TYPE character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "replyUserHost" TYPE character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "renoteUserHost" TYPE character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "host" TYPE character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "iconUrl" TYPE character varying(256)`);
+ await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "faviconUrl" TYPE character varying(256)`);
+ await queryRunner.query(`ALTER TABLE "poll" ALTER COLUMN "userHost" TYPE character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ALTER COLUMN "targetUserHost" TYPE character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "abuse_user_report" ALTER COLUMN "reporterHost" TYPE character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "following" ALTER COLUMN "followeeHost" TYPE character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "following" ALTER COLUMN "followerHost" TYPE character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "follow_request" ALTER COLUMN "followeeHost" TYPE character varying(128)`);
+ await queryRunner.query(`ALTER TABLE "follow_request" ALTER COLUMN "followerHost" TYPE character varying(128)`);
+ }
+}
diff --git a/packages/backend/built/migration/1702680809638-add-note-createdat-userid-idx.js b/packages/backend/built/migration/1702680809638-add-note-createdat-userid-idx.js
new file mode 100644
index 0000000..caf4900
--- /dev/null
+++ b/packages/backend/built/migration/1702680809638-add-note-createdat-userid-idx.js
@@ -0,0 +1,9 @@
+export class AddNoteCreatedatUseridIdx1702680809638 {
+ name = 'AddNoteCreatedatUseridIdx1702680809638';
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_note_createdAt_userId" ON "note" ("createdAt", "userId")`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_note_createdAt_userId"`);
+ }
+}
diff --git a/packages/backend/built/migration/1702744857694-user-issuspended-idx.js b/packages/backend/built/migration/1702744857694-user-issuspended-idx.js
new file mode 100644
index 0000000..ea2e394
--- /dev/null
+++ b/packages/backend/built/migration/1702744857694-user-issuspended-idx.js
@@ -0,0 +1,9 @@
+export class UserIssuspendedIdx1702744857694 {
+ name = 'UserIssuspendedIdx1702744857694';
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_8977c6037a7bc2cb0c84b6d4db" ON "user" ("isSuspended")`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_8977c6037a7bc2cb0c84b6d4db"`);
+ }
+}
diff --git a/packages/backend/built/migration/1705528046452-federated-bite.js b/packages/backend/built/migration/1705528046452-federated-bite.js
new file mode 100644
index 0000000..a0d62f6
--- /dev/null
+++ b/packages/backend/built/migration/1705528046452-federated-bite.js
@@ -0,0 +1,42 @@
+export class FederatedBite1705528046452 {
+ name = 'FederatedBite1705528046452';
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TYPE "public"."bite_targettype_enum" AS ENUM('user', 'bite')`);
+ await queryRunner.query(`CREATE TABLE "bite" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "uri" character varying(512), "userId" character varying(32) NOT NULL, "targetType" "public"."bite_targettype_enum" NOT NULL, "targetUserId" character varying(32), "targetBiteId" character varying(32), "replied" boolean NOT NULL DEFAULT true, CONSTRAINT "CHK_c3a20c5756ccff3133f8927500" CHECK ("targetUserId" IS NOT NULL OR "targetBiteId" IS NOT NULL), CONSTRAINT "PK_1887f3f621a4a7655a1b78bfd66" PRIMARY KEY ("id")); COMMENT ON COLUMN "bite"."uri" IS 'null if local'`);
+ await queryRunner.query(`ALTER TABLE "notification" ADD "biteId" character varying(32)`);
+ await queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum" RENAME TO "user_profile_mutingnotificationtypes_enum_old"`);
+ await queryRunner.query(`CREATE TYPE "public"."user_profile_mutingnotificationtypes_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'bite')`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" TYPE "public"."user_profile_mutingnotificationtypes_enum"[] USING "mutingNotificationTypes"::"text"::"public"."user_profile_mutingnotificationtypes_enum"[]`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`);
+ await queryRunner.query(`DROP TYPE "public"."user_profile_mutingnotificationtypes_enum_old"`);
+ await queryRunner.query(`ALTER TABLE "bite" ADD CONSTRAINT "FK_8d00aa79e157364ac1f60c15098" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "bite" ADD CONSTRAINT "FK_a646fbbeb6efa2531c75fec46b9" FOREIGN KEY ("targetUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "bite" ADD CONSTRAINT "FK_5d5f68610583f2e0b6785d3c0e9" FOREIGN KEY ("targetBiteId") REFERENCES "bite"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_c54844158c1eead7042e7ca4c83" FOREIGN KEY ("biteId") REFERENCES "bite"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TYPE "public"."notification_type_enum" RENAME TO "notification_type_enum_old"`);
+ await queryRunner.query(`CREATE TYPE "public"."notification_type_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'bite')`);
+ await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum" USING "type"::"text"::"public"."notification_type_enum"`);
+ await queryRunner.query(`DROP TYPE "public"."notification_type_enum_old"`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DELETE FROM "notification" WHERE "biteId" IS NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_c54844158c1eead7042e7ca4c83"`);
+ await queryRunner.query(`ALTER TABLE "bite" DROP CONSTRAINT "FK_5d5f68610583f2e0b6785d3c0e9"`);
+ await queryRunner.query(`ALTER TABLE "bite" DROP CONSTRAINT "FK_a646fbbeb6efa2531c75fec46b9"`);
+ await queryRunner.query(`ALTER TABLE "bite" DROP CONSTRAINT "FK_8d00aa79e157364ac1f60c15098"`);
+ await queryRunner.query(`CREATE TYPE "public"."user_profile_mutingnotificationtypes_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" TYPE "public"."user_profile_mutingnotificationtypes_enum_old"[] USING "mutingNotificationTypes"::"text"::"public"."user_profile_mutingnotificationtypes_enum_old"[]`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`);
+ await queryRunner.query(`DROP TYPE "public"."user_profile_mutingnotificationtypes_enum"`);
+ await queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum_old" RENAME TO "user_profile_mutingnotificationtypes_enum"`);
+ await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "biteId"`);
+ await queryRunner.query(`DROP TABLE "bite"`);
+ await queryRunner.query(`DROP TYPE "public"."bite_targettype_enum"`);
+ await queryRunner.query(`CREATE TYPE "public"."notification_type_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`);
+ await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum_old" USING "type"::"text"::"public"."notification_type_enum_old"`);
+ await queryRunner.query(`DROP TYPE "public"."notification_type_enum"`);
+ await queryRunner.query(`ALTER TYPE "public"."notification_type_enum_old" RENAME TO "notification_type_enum"`);
+ }
+}
diff --git a/packages/backend/built/migration/1722204953558-bite-notification-index.js b/packages/backend/built/migration/1722204953558-bite-notification-index.js
new file mode 100644
index 0000000..d4926ca
--- /dev/null
+++ b/packages/backend/built/migration/1722204953558-bite-notification-index.js
@@ -0,0 +1,9 @@
+export class BiteNotificationIndex1722204953558 {
+ name = 'BiteNotificationIndex1722204953558';
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE INDEX "IDX_c54844158c1eead7042e7ca4c8" ON "notification" ("biteId") `);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_c54844158c1eead7042e7ca4c8"`);
+ }
+}
diff --git a/packages/backend/built/migration/1751700000000-add-glyph-for-custom-emoji.js b/packages/backend/built/migration/1751700000000-add-glyph-for-custom-emoji.js
new file mode 100644
index 0000000..0ff10f1
--- /dev/null
+++ b/packages/backend/built/migration/1751700000000-add-glyph-for-custom-emoji.js
@@ -0,0 +1,9 @@
+export class addGlyphForCustomEmoji1751700000000 {
+ name = "addGlyphForCustomEmoji1751700000000";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "emoji" ADD "glyph" boolean NOT NULL DEFAULT false`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "glyph"`);
+ }
+}
diff --git a/packages/backend/built/migration/1763080186006-bite-compatibility.js b/packages/backend/built/migration/1763080186006-bite-compatibility.js
new file mode 100644
index 0000000..e9aac44
--- /dev/null
+++ b/packages/backend/built/migration/1763080186006-bite-compatibility.js
@@ -0,0 +1,25 @@
+export class BiteCompatibility1763080186006 {
+ name = 'BiteCompatibility1763080186006';
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "bite" DROP CONSTRAINT "CHK_c3a20c5756ccff3133f8927500"`);
+ await queryRunner.query(`ALTER TABLE "bite" DROP COLUMN "targetType"`);
+ await queryRunner.query(`DROP TYPE "public"."bite_targettype_enum"`);
+ await queryRunner.query(`ALTER TABLE "bite" ADD "userHost" character varying(512)`);
+ await queryRunner.query(`COMMENT ON COLUMN "bite"."userHost" IS '[Denormalized]'`);
+ await queryRunner.query(`ALTER TABLE "bite" ADD "targetNoteId" character varying(32)`);
+ await queryRunner.query(`ALTER TABLE "bite" ADD CONSTRAINT "CHK_f0e178a8942af8cf564ac5a60e" CHECK ("targetUserId" IS NOT NULL OR "targetBiteId" IS NOT NULL OR "targetNoteId" IS NOT NULL)`);
+ await queryRunner.query(`ALTER TABLE "bite" ADD CONSTRAINT "FK_4710076fcd98193b0ec8d7f68de" FOREIGN KEY ("targetNoteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`UPDATE "bite" SET "userHost" = "user"."host" FROM "user" WHERE "user"."id" = "bite"."userId"`); // populate userHost
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "bite" DROP CONSTRAINT "FK_4710076fcd98193b0ec8d7f68de"`);
+ await queryRunner.query(`ALTER TABLE "bite" DROP CONSTRAINT "CHK_f0e178a8942af8cf564ac5a60e"`);
+ await queryRunner.query(`ALTER TABLE "bite" DROP COLUMN "targetNoteId"`);
+ await queryRunner.query(`COMMENT ON COLUMN "bite"."userHost" IS '[Denormalized]'`);
+ await queryRunner.query(`ALTER TABLE "bite" DROP COLUMN "userHost"`);
+ await queryRunner.query(`CREATE TYPE "public"."bite_targettype_enum" AS ENUM('user', 'bite')`);
+ await queryRunner.query(`DELETE FROM "bite"`); // it's not easy to determine targetType in our migration, so we just clear all bites
+ await queryRunner.query(`ALTER TABLE "bite" ADD "targetType" "public"."bite_targettype_enum" NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "bite" ADD CONSTRAINT "CHK_c3a20c5756ccff3133f8927500" CHECK ((("targetUserId" IS NOT NULL) OR ("targetBiteId" IS NOT NULL)))`);
+ }
+}
diff --git a/packages/backend/built/migration/1763167719064-pronouns.js b/packages/backend/built/migration/1763167719064-pronouns.js
new file mode 100644
index 0000000..384aba5
--- /dev/null
+++ b/packages/backend/built/migration/1763167719064-pronouns.js
@@ -0,0 +1,11 @@
+export class Pronouns1763167719064 {
+ name = 'Pronouns1763167719064';
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "pronouns" jsonb NOT NULL DEFAULT '{}'`);
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."pronouns" IS 'Language map of user pronouns'`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`COMMENT ON COLUMN "user_profile"."pronouns" IS 'Language map of user pronouns'`);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "pronouns"`);
+ }
+}
diff --git a/packages/backend/built/migration/1769148205847-FEP-044f.js b/packages/backend/built/migration/1769148205847-FEP-044f.js
new file mode 100644
index 0000000..fab562c
--- /dev/null
+++ b/packages/backend/built/migration/1769148205847-FEP-044f.js
@@ -0,0 +1,21 @@
+export class FEP044f1769148205847 {
+ name = 'FEP044f1769148205847';
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TYPE "public"."interaction_stamp_type_enum" AS ENUM('quote')`);
+ await queryRunner.query(`CREATE TABLE "interaction_stamp" ("id" character varying(32) NOT NULL, "type" "public"."interaction_stamp_type_enum" NOT NULL, "targetNoteId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, CONSTRAINT "PK_655e4f52463bd63c126bf8a21bf" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_966faaedd04f5bc0e4e7f23b51" ON "interaction_stamp" ("noteId", "targetNoteId") `);
+ await queryRunner.query(`ALTER TABLE "note" ADD "quoteAuthorization" character varying(512)`);
+ await queryRunner.query(`ALTER TABLE "note" ADD "canQuote" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "interaction_stamp" ADD CONSTRAINT "FK_5b41539ebe68913e845d32b1a14" FOREIGN KEY ("targetNoteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "interaction_stamp" ADD CONSTRAINT "FK_f22ecf4fa523296288dfff84c40" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "interaction_stamp" DROP CONSTRAINT "FK_f22ecf4fa523296288dfff84c40"`);
+ await queryRunner.query(`ALTER TABLE "interaction_stamp" DROP CONSTRAINT "FK_5b41539ebe68913e845d32b1a14"`);
+ await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "canQuote"`);
+ await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "quoteAuthorization"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_966faaedd04f5bc0e4e7f23b51"`);
+ await queryRunner.query(`DROP TABLE "interaction_stamp"`);
+ await queryRunner.query(`DROP TYPE "public"."interaction_stamp_type_enum"`);
+ }
+}
diff --git a/packages/backend/built/migration/1770193373959-biteControls.js b/packages/backend/built/migration/1770193373959-biteControls.js
new file mode 100644
index 0000000..f5b2ba9
--- /dev/null
+++ b/packages/backend/built/migration/1770193373959-biteControls.js
@@ -0,0 +1,12 @@
+export class BiteControls1770193373959 {
+ name = 'BiteControls1770193373959';
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TYPE "public"."user_canbite_enum" AS ENUM('anyone', 'followers', 'nobody')`);
+ await queryRunner.query(`ALTER TABLE "user" ADD "canBite" "public"."user_canbite_enum" NOT NULL DEFAULT 'nobody'`);
+ await queryRunner.query(`UPDATE "user" SET "canBite" = 'followers' WHERE HOST IS NULL`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "canBite"`);
+ await queryRunner.query(`DROP TYPE "public"."user_canbite_enum"`);
+ }
+}
diff --git a/packages/backend/built/migration/1783148000000-user-emojis.js b/packages/backend/built/migration/1783148000000-user-emojis.js
new file mode 100644
index 0000000..926af40
--- /dev/null
+++ b/packages/backend/built/migration/1783148000000-user-emojis.js
@@ -0,0 +1,15 @@
+export class UserEmojis1783148000000 {
+ name = "UserEmojis1783148000000";
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "user_emoji" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "name" character varying(128) NOT NULL, "userId" character varying(32) NOT NULL, "originalUrl" character varying(512) NOT NULL, "publicUrl" character varying(512) NOT NULL DEFAULT '', "type" character varying(64), "width" integer, "height" integer, CONSTRAINT "PK_6cae1a4f12c059a9f04f4fc6335" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_f60ba1b11d0c9d945ad6093339" ON "user_emoji" ("name")`);
+ await queryRunner.query(`CREATE INDEX "IDX_800e361f3e733b7c1682234ee0" ON "user_emoji" ("userId")`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_41a4ec233a7201def6c470d3c9" ON "user_emoji" ("name", "userId")`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_41a4ec233a7201def6c470d3c9"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_800e361f3e733b7c1682234ee0"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_f60ba1b11d0c9d945ad6093339"`);
+ await queryRunner.query(`DROP TABLE "user_emoji"`);
+ }
+}
diff --git a/packages/backend/built/migration/1783149000000-frozen-friends-yume-features.js b/packages/backend/built/migration/1783149000000-frozen-friends-yume-features.js
new file mode 100644
index 0000000..79ed4f3
--- /dev/null
+++ b/packages/backend/built/migration/1783149000000-frozen-friends-yume-features.js
@@ -0,0 +1,25 @@
+export class FrozenFriendsYumeFeatures1783149000000 {
+ name = "FrozenFriendsYumeFeatures1783149000000";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_emoji" ADD "glyph" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "allowCalls" boolean NOT NULL DEFAULT true`);
+ await queryRunner.query(`ALTER TABLE "user_profile" ADD "symbolFileId" character varying(32)`);
+ await queryRunner.query(`ALTER TABLE "user_group" ADD "username" character varying(64)`);
+ await queryRunner.query(`ALTER TABLE "user_group" ADD "allowCalls" boolean NOT NULL DEFAULT true`);
+ await queryRunner.query(`ALTER TABLE "user_group" ADD "iconFileId" character varying(32)`);
+ await queryRunner.query(`ALTER TABLE "user_group" ADD "symbolFileId" character varying(32)`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_user_group_username" ON "user_group" ("username") WHERE "username" IS NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "note" ADD "viewCount" integer NOT NULL DEFAULT 0`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "viewCount"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_user_group_username"`);
+ await queryRunner.query(`ALTER TABLE "user_group" DROP COLUMN "symbolFileId"`);
+ await queryRunner.query(`ALTER TABLE "user_group" DROP COLUMN "iconFileId"`);
+ await queryRunner.query(`ALTER TABLE "user_group" DROP COLUMN "allowCalls"`);
+ await queryRunner.query(`ALTER TABLE "user_group" DROP COLUMN "username"`);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "symbolFileId"`);
+ await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "allowCalls"`);
+ await queryRunner.query(`ALTER TABLE "user_emoji" DROP COLUMN "glyph"`);
+ }
+}
diff --git a/packages/backend/built/migration/1783150000000-group-emojis-and-calls.js b/packages/backend/built/migration/1783150000000-group-emojis-and-calls.js
new file mode 100644
index 0000000..54aa7ce
--- /dev/null
+++ b/packages/backend/built/migration/1783150000000-group-emojis-and-calls.js
@@ -0,0 +1,15 @@
+export class GroupEmojisAndCalls1783150000000 {
+ name = "GroupEmojisAndCalls1783150000000";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_emoji" ALTER COLUMN "userId" DROP NOT NULL`);
+ await queryRunner.query(`ALTER TABLE "user_emoji" ADD "userGroupId" character varying(32)`);
+ await queryRunner.query(`CREATE INDEX "IDX_user_emoji_userGroupId" ON "user_emoji" ("userGroupId")`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_user_emoji_name_userGroupId" ON "user_emoji" ("name", "userGroupId") WHERE "userGroupId" IS NOT NULL`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_user_emoji_name_userGroupId"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_user_emoji_userGroupId"`);
+ await queryRunner.query(`ALTER TABLE "user_emoji" DROP COLUMN "userGroupId"`);
+ await queryRunner.query(`ALTER TABLE "user_emoji" ALTER COLUMN "userId" SET NOT NULL`);
+ }
+}
diff --git a/packages/backend/built/migration/1783151000000-call-blocking.js b/packages/backend/built/migration/1783151000000-call-blocking.js
new file mode 100644
index 0000000..2e29b5e
--- /dev/null
+++ b/packages/backend/built/migration/1783151000000-call-blocking.js
@@ -0,0 +1,21 @@
+export class CallBlocking1783151000000 {
+ name = "CallBlocking1783151000000";
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "call_blocking" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "blockeeId" character varying(32) NOT NULL, "blockerId" character varying(32) NOT NULL, CONSTRAINT "PK_call_blocking_id" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_call_blocking_createdAt" ON "call_blocking" ("createdAt")`);
+ await queryRunner.query(`CREATE INDEX "IDX_call_blocking_blockeeId" ON "call_blocking" ("blockeeId")`);
+ await queryRunner.query(`CREATE INDEX "IDX_call_blocking_blockerId" ON "call_blocking" ("blockerId")`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_call_blocking_blocker_blockee" ON "call_blocking" ("blockerId", "blockeeId")`);
+ await queryRunner.query(`ALTER TABLE "call_blocking" ADD CONSTRAINT "FK_call_blocking_blockee" FOREIGN KEY ("blockeeId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "call_blocking" ADD CONSTRAINT "FK_call_blocking_blocker" FOREIGN KEY ("blockerId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "call_blocking" DROP CONSTRAINT "FK_call_blocking_blocker"`);
+ await queryRunner.query(`ALTER TABLE "call_blocking" DROP CONSTRAINT "FK_call_blocking_blockee"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_call_blocking_blocker_blockee"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_call_blocking_blockerId"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_call_blocking_blockeeId"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_call_blocking_createdAt"`);
+ await queryRunner.query(`DROP TABLE "call_blocking"`);
+ }
+}
diff --git a/packages/backend/built/migration/1783309000000-allow-calls-default-off.js b/packages/backend/built/migration/1783309000000-allow-calls-default-off.js
new file mode 100644
index 0000000..ff6b411
--- /dev/null
+++ b/packages/backend/built/migration/1783309000000-allow-calls-default-off.js
@@ -0,0 +1,9 @@
+export class AllowCallsDefaultOff1783309000000 {
+ name = "AllowCallsDefaultOff1783309000000";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "allowCalls" SET DEFAULT false`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "allowCalls" SET DEFAULT true`);
+ }
+}
diff --git a/packages/backend/built/migration/1783309100000-drive-file-allow-download.js b/packages/backend/built/migration/1783309100000-drive-file-allow-download.js
new file mode 100644
index 0000000..bc60d8c
--- /dev/null
+++ b/packages/backend/built/migration/1783309100000-drive-file-allow-download.js
@@ -0,0 +1,11 @@
+export class DriveFileAllowDownload1783309100000 {
+ name = "DriveFileAllowDownload1783309100000";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "drive_file" ADD "allowDownload" boolean NOT NULL DEFAULT true`);
+ await queryRunner.query(`CREATE INDEX "IDX_drive_file_allow_download" ON "drive_file" ("allowDownload")`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_drive_file_allow_download"`);
+ await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "allowDownload"`);
+ }
+}
diff --git a/packages/backend/built/migration/1783650000000-restore-reversi.js b/packages/backend/built/migration/1783650000000-restore-reversi.js
new file mode 100644
index 0000000..4e2c8db
--- /dev/null
+++ b/packages/backend/built/migration/1783650000000-restore-reversi.js
@@ -0,0 +1,15 @@
+export class RestoreReversi1783650000000 {
+ name = "RestoreReversi1783650000000";
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE IF NOT EXISTS "reversi_game" ("id" character varying(32) NOT NULL, "startedAt" TIMESTAMP WITH TIME ZONE, "endedAt" TIMESTAMP WITH TIME ZONE, "user1Id" character varying(32) NOT NULL, "user2Id" character varying(32) NOT NULL, "user1Ready" boolean NOT NULL DEFAULT false, "user2Ready" boolean NOT NULL DEFAULT false, "black" integer, "isStarted" boolean NOT NULL DEFAULT false, "isEnded" boolean NOT NULL DEFAULT false, "winnerId" character varying(32), "surrenderedUserId" character varying(32), "timeoutUserId" character varying(32), "timeLimitForEachTurn" smallint NOT NULL DEFAULT '90', "logs" jsonb NOT NULL DEFAULT '[]', "map" character varying(64) array NOT NULL, "bw" character varying(32) NOT NULL, "noIrregularRules" boolean NOT NULL DEFAULT false, "isLlotheo" boolean NOT NULL DEFAULT false, "canPutEverywhere" boolean NOT NULL DEFAULT false, "loopedBoard" boolean NOT NULL DEFAULT false, "form1" jsonb DEFAULT null, "form2" jsonb DEFAULT null, "crc32" character varying(32), CONSTRAINT "PK_76b30eeba71b1193ad7c5311c3f" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE TABLE IF NOT EXISTS "reversi_matching" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "parentId" character varying(32) NOT NULL, "childId" character varying(32) NOT NULL, CONSTRAINT "PK_880bd0afbab232f21c8b9d146cf" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_3b25402709dd9882048c2bbade" ON "reversi_matching" ("parentId")`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_e247b23a3c9b45f89ec1299d06" ON "reversi_matching" ("childId")`);
+ await queryRunner.query(`DO $$ BEGIN ALTER TABLE "reversi_game" ADD CONSTRAINT "FK_reversi_game_user1" FOREIGN KEY ("user1Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION; EXCEPTION WHEN duplicate_object THEN NULL; END $$`);
+ await queryRunner.query(`DO $$ BEGIN ALTER TABLE "reversi_game" ADD CONSTRAINT "FK_reversi_game_user2" FOREIGN KEY ("user2Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION; EXCEPTION WHEN duplicate_object THEN NULL; END $$`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP TABLE IF EXISTS "reversi_matching"`);
+ await queryRunner.query(`DROP TABLE IF EXISTS "reversi_game"`);
+ }
+}
diff --git a/packages/backend/built/migration/1783660000000-restore-shogi.js b/packages/backend/built/migration/1783660000000-restore-shogi.js
new file mode 100644
index 0000000..35cb737
--- /dev/null
+++ b/packages/backend/built/migration/1783660000000-restore-shogi.js
@@ -0,0 +1,11 @@
+export class RestoreShogi1783660000000 {
+ name = "RestoreShogi1783660000000";
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE IF NOT EXISTS "shogi_game" ("id" character varying(32) NOT NULL, "startedAt" TIMESTAMP WITH TIME ZONE, "endedAt" TIMESTAMP WITH TIME ZONE, "user1Id" character varying(32) NOT NULL, "user2Id" character varying(32) NOT NULL, "user1Ready" boolean NOT NULL DEFAULT false, "user2Ready" boolean NOT NULL DEFAULT false, "sente" integer, "isStarted" boolean NOT NULL DEFAULT false, "isEnded" boolean NOT NULL DEFAULT false, "winnerId" character varying(32), "surrenderedUserId" character varying(32), "sfen" text NOT NULL, "logs" jsonb NOT NULL DEFAULT '[]', CONSTRAINT "PK_shogi_game" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`DO $$ BEGIN ALTER TABLE "shogi_game" ADD CONSTRAINT "FK_shogi_game_user1" FOREIGN KEY ("user1Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION; EXCEPTION WHEN duplicate_object THEN NULL; END $$`);
+ await queryRunner.query(`DO $$ BEGIN ALTER TABLE "shogi_game" ADD CONSTRAINT "FK_shogi_game_user2" FOREIGN KEY ("user2Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION; EXCEPTION WHEN duplicate_object THEN NULL; END $$`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP TABLE IF EXISTS "shogi_game"`);
+ }
+}
diff --git a/packages/backend/built/migration/1783700000000-scheduled-notes.js b/packages/backend/built/migration/1783700000000-scheduled-notes.js
new file mode 100644
index 0000000..8692088
--- /dev/null
+++ b/packages/backend/built/migration/1783700000000-scheduled-notes.js
@@ -0,0 +1,14 @@
+export class ScheduledNotes1783700000000 {
+ name = "ScheduledNotes1783700000000";
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE IF NOT EXISTS "scheduled_note" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "scheduledAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "status" character varying(16) NOT NULL DEFAULT 'scheduled', "data" jsonb NOT NULL, "noteId" character varying(32), "error" text, CONSTRAINT "PK_scheduled_note" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_scheduled_note_createdAt" ON "scheduled_note" ("createdAt")`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_scheduled_note_scheduledAt" ON "scheduled_note" ("scheduledAt")`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_scheduled_note_userId" ON "scheduled_note" ("userId")`);
+ await queryRunner.query(`DO $$ BEGIN ALTER TABLE "scheduled_note" ADD CONSTRAINT "FK_scheduled_note_user" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION; EXCEPTION WHEN duplicate_object THEN NULL; END $$`);
+ await queryRunner.query(`DO $$ BEGIN ALTER TABLE "scheduled_note" ADD CONSTRAINT "FK_scheduled_note_note" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE SET NULL ON UPDATE NO ACTION; EXCEPTION WHEN duplicate_object THEN NULL; END $$`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP TABLE IF EXISTS "scheduled_note"`);
+ }
+}
diff --git a/packages/backend/built/migration/1783701000000-group-actor-actions.js b/packages/backend/built/migration/1783701000000-group-actor-actions.js
new file mode 100644
index 0000000..aaeaf16
--- /dev/null
+++ b/packages/backend/built/migration/1783701000000-group-actor-actions.js
@@ -0,0 +1,79 @@
+export class GroupActorActions1783701000000 {
+ name = "GroupActorActions1783701000000";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "note" ADD COLUMN IF NOT EXISTS "groupId" character varying(32)`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_note_groupId" ON "note" ("groupId")`);
+ await queryRunner.query(`DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'FK_note_groupId') THEN ALTER TABLE "note" ADD CONSTRAINT "FK_note_groupId" FOREIGN KEY ("groupId") REFERENCES "user_group"("id") ON DELETE SET NULL ON UPDATE NO ACTION; END IF; END $$`);
+ await queryRunner.query(`ALTER TABLE "note_reaction" ADD COLUMN IF NOT EXISTS "groupId" character varying(32)`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_note_reaction_groupId" ON "note_reaction" ("groupId")`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_note_reaction_userId_noteId"`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_2ad0dfa7c5f662a9568d503f6c"`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_ad0c221b25672daf2df320a817"`);
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_note_reaction_user_note_personal" ON "note_reaction" ("userId", "noteId") WHERE "groupId" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_note_reaction_group_note" ON "note_reaction" ("groupId", "noteId") WHERE "groupId" IS NOT NULL`);
+ await queryRunner.query(`DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'FK_note_reaction_groupId') THEN ALTER TABLE "note_reaction" ADD CONSTRAINT "FK_note_reaction_groupId" FOREIGN KEY ("groupId") REFERENCES "user_group"("id") ON DELETE CASCADE ON UPDATE NO ACTION; END IF; END $$`);
+ await queryRunner.query(`ALTER TABLE "note_favorite" ADD COLUMN IF NOT EXISTS "groupId" character varying(32)`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_note_favorite_groupId" ON "note_favorite" ("groupId")`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_note_favorite_userId_noteId"`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_2f9b01a470a2b5d20a87f7520e"`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_0f4fb9ad355f3effff221ef245"`);
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_note_favorite_user_note_personal" ON "note_favorite" ("userId", "noteId") WHERE "groupId" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_note_favorite_group_note" ON "note_favorite" ("groupId", "noteId") WHERE "groupId" IS NOT NULL`);
+ await queryRunner.query(`DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'FK_note_favorite_groupId') THEN ALTER TABLE "note_favorite" ADD CONSTRAINT "FK_note_favorite_groupId" FOREIGN KEY ("groupId") REFERENCES "user_group"("id") ON DELETE CASCADE ON UPDATE NO ACTION; END IF; END $$`);
+ await queryRunner.query(`ALTER TABLE "muting" ADD COLUMN IF NOT EXISTS "groupId" character varying(32)`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_muting_groupId" ON "muting" ("groupId")`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_1eb9d9824a630321a29fd3b290"`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_muting_muterId_muteeId"`);
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_muting_user_mutee_personal" ON "muting" ("muterId", "muteeId") WHERE "groupId" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_muting_group_mutee" ON "muting" ("groupId", "muteeId") WHERE "groupId" IS NOT NULL`);
+ await queryRunner.query(`DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'FK_muting_groupId') THEN ALTER TABLE "muting" ADD CONSTRAINT "FK_muting_groupId" FOREIGN KEY ("groupId") REFERENCES "user_group"("id") ON DELETE CASCADE ON UPDATE NO ACTION; END IF; END $$`);
+ await queryRunner.query(`ALTER TABLE "blocking" ADD COLUMN IF NOT EXISTS "groupId" character varying(32)`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_blocking_groupId" ON "blocking" ("groupId")`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_98a1bc5cb30dfd159de056549f"`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_blocking_blockerId_blockeeId"`);
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_blocking_user_blockee_personal" ON "blocking" ("blockerId", "blockeeId") WHERE "groupId" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_blocking_group_blockee" ON "blocking" ("groupId", "blockeeId") WHERE "groupId" IS NOT NULL`);
+ await queryRunner.query(`DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'FK_blocking_groupId') THEN ALTER TABLE "blocking" ADD CONSTRAINT "FK_blocking_groupId" FOREIGN KEY ("groupId") REFERENCES "user_group"("id") ON DELETE CASCADE ON UPDATE NO ACTION; END IF; END $$`);
+ await queryRunner.query(`ALTER TABLE "call_blocking" ADD COLUMN IF NOT EXISTS "groupId" character varying(32)`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_call_blocking_groupId" ON "call_blocking" ("groupId")`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "public"."IDX_call_blocking_blocker_blockee"`);
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_call_blocking_user_blockee_personal" ON "call_blocking" ("blockerId", "blockeeId") WHERE "groupId" IS NULL`);
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_call_blocking_group_blockee" ON "call_blocking" ("groupId", "blockeeId") WHERE "groupId" IS NOT NULL`);
+ await queryRunner.query(`DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'FK_call_blocking_groupId') THEN ALTER TABLE "call_blocking" ADD CONSTRAINT "FK_call_blocking_groupId" FOREIGN KEY ("groupId") REFERENCES "user_group"("id") ON DELETE CASCADE ON UPDATE NO ACTION; END IF; END $$`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "call_blocking" DROP CONSTRAINT "FK_call_blocking_groupId"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_call_blocking_group_blockee"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_call_blocking_user_blockee_personal"`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_call_blocking_blocker_blockee" ON "call_blocking" ("blockerId", "blockeeId")`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_call_blocking_groupId"`);
+ await queryRunner.query(`ALTER TABLE "call_blocking" DROP COLUMN "groupId"`);
+ await queryRunner.query(`ALTER TABLE "blocking" DROP CONSTRAINT "FK_blocking_groupId"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_blocking_group_blockee"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_blocking_user_blockee_personal"`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_98a1bc5cb30dfd159de056549f" ON "blocking" ("blockerId", "blockeeId")`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_blocking_groupId"`);
+ await queryRunner.query(`ALTER TABLE "blocking" DROP COLUMN "groupId"`);
+ await queryRunner.query(`ALTER TABLE "muting" DROP CONSTRAINT "FK_muting_groupId"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_muting_group_mutee"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_muting_user_mutee_personal"`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_1eb9d9824a630321a29fd3b290" ON "muting" ("muterId", "muteeId")`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_muting_groupId"`);
+ await queryRunner.query(`ALTER TABLE "muting" DROP COLUMN "groupId"`);
+ await queryRunner.query(`ALTER TABLE "note_favorite" DROP CONSTRAINT "FK_note_favorite_groupId"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_note_favorite_group_note"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_note_favorite_user_note_personal"`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_note_favorite_userId_noteId" ON "note_favorite" ("userId", "noteId")`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_note_favorite_groupId"`);
+ await queryRunner.query(`ALTER TABLE "note_favorite" DROP COLUMN "groupId"`);
+ await queryRunner.query(`ALTER TABLE "note_reaction" DROP CONSTRAINT "FK_note_reaction_groupId"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_note_reaction_group_note"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_note_reaction_user_note_personal"`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_note_reaction_userId_noteId" ON "note_reaction" ("userId", "noteId")`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_note_reaction_groupId"`);
+ await queryRunner.query(`ALTER TABLE "note_reaction" DROP COLUMN "groupId"`);
+ await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_note_groupId"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_note_groupId"`);
+ await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "groupId"`);
+ }
+}
diff --git a/packages/backend/built/migration/1783800000000-memoriet.js b/packages/backend/built/migration/1783800000000-memoriet.js
new file mode 100644
index 0000000..6421882
--- /dev/null
+++ b/packages/backend/built/migration/1783800000000-memoriet.js
@@ -0,0 +1,15 @@
+export class Memoriet1783800000000 {
+ name = "Memoriet1783800000000";
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE IF NOT EXISTS "memoriet" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, "expiresAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_memoriet" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_memoriet_createdAt" ON "memoriet" ("createdAt")`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_memoriet_userId" ON "memoriet" ("userId")`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_memoriet_noteId" ON "memoriet" ("noteId")`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_memoriet_expiresAt" ON "memoriet" ("expiresAt")`);
+ await queryRunner.query(`DO $$ BEGIN ALTER TABLE "memoriet" ADD CONSTRAINT "FK_memoriet_user" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION; EXCEPTION WHEN duplicate_object THEN NULL; END $$`);
+ await queryRunner.query(`DO $$ BEGIN ALTER TABLE "memoriet" ADD CONSTRAINT "FK_memoriet_note" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION; EXCEPTION WHEN duplicate_object THEN NULL; END $$`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP TABLE IF EXISTS "memoriet"`);
+ }
+}
diff --git a/packages/backend/built/migration/1783801000000-memoriet-text-layers-and-views.js b/packages/backend/built/migration/1783801000000-memoriet-text-layers-and-views.js
new file mode 100644
index 0000000..9ff4df2
--- /dev/null
+++ b/packages/backend/built/migration/1783801000000-memoriet-text-layers-and-views.js
@@ -0,0 +1,18 @@
+export class MemorietTextLayersAndViews1783801000000 {
+ name = "MemorietTextLayersAndViews1783801000000";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "memoriet" ADD COLUMN IF NOT EXISTS "textLayers" jsonb NOT NULL DEFAULT '[]'::jsonb`);
+ await queryRunner.query(`CREATE TABLE IF NOT EXISTS "memoriet_view" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "viewedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "memorietId" character varying(32) NOT NULL, "viewerId" character varying(32) NOT NULL, CONSTRAINT "PK_memoriet_view" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_memoriet_view_createdAt" ON "memoriet_view" ("createdAt")`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_memoriet_view_viewedAt" ON "memoriet_view" ("viewedAt")`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_memoriet_view_memorietId" ON "memoriet_view" ("memorietId")`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_memoriet_view_viewerId" ON "memoriet_view" ("viewerId")`);
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_memoriet_view_memorietId_viewerId" ON "memoriet_view" ("memorietId", "viewerId")`);
+ await queryRunner.query(`DO $$ BEGIN ALTER TABLE "memoriet_view" ADD CONSTRAINT "FK_memoriet_view_memoriet" FOREIGN KEY ("memorietId") REFERENCES "memoriet"("id") ON DELETE CASCADE ON UPDATE NO ACTION; EXCEPTION WHEN duplicate_object THEN NULL; END $$`);
+ await queryRunner.query(`DO $$ BEGIN ALTER TABLE "memoriet_view" ADD CONSTRAINT "FK_memoriet_view_viewer" FOREIGN KEY ("viewerId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION; EXCEPTION WHEN duplicate_object THEN NULL; END $$`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP TABLE IF EXISTS "memoriet_view"`);
+ await queryRunner.query(`ALTER TABLE "memoriet" DROP COLUMN IF EXISTS "textLayers"`);
+ }
+}
diff --git a/packages/backend/built/migration/1783820000000-user-badges.js b/packages/backend/built/migration/1783820000000-user-badges.js
new file mode 100644
index 0000000..fd45161
--- /dev/null
+++ b/packages/backend/built/migration/1783820000000-user-badges.js
@@ -0,0 +1,11 @@
+export class UserBadges1783820000000 {
+ name = "UserBadges1783820000000";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "isVerified" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "minorBadges" character varying(1) array NOT NULL DEFAULT '{}'::varchar[]`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN IF EXISTS "minorBadges"`);
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN IF EXISTS "isVerified"`);
+ }
+}
diff --git a/packages/backend/built/migration/1783821000000-verified-badge-requests.js b/packages/backend/built/migration/1783821000000-verified-badge-requests.js
new file mode 100644
index 0000000..82e6ace
--- /dev/null
+++ b/packages/backend/built/migration/1783821000000-verified-badge-requests.js
@@ -0,0 +1,15 @@
+export class VerifiedBadgeRequests1783821000000 {
+ name = "VerifiedBadgeRequests1783821000000";
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE IF NOT EXISTS "verified_badge_request" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "resolvedAt" TIMESTAMP WITH TIME ZONE, "userId" character varying(32) NOT NULL, "resolverId" character varying(32), "status" character varying(16) NOT NULL DEFAULT 'pending', "comment" character varying(2048) NOT NULL DEFAULT '', CONSTRAINT "PK_verified_badge_request" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_verified_badge_request_createdAt" ON "verified_badge_request" ("createdAt")`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_verified_badge_request_userId" ON "verified_badge_request" ("userId")`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_verified_badge_request_status" ON "verified_badge_request" ("status")`);
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_verified_badge_request_user_pending" ON "verified_badge_request" ("userId") WHERE "status" = 'pending'`);
+ await queryRunner.query(`DO $$ BEGIN ALTER TABLE "verified_badge_request" ADD CONSTRAINT "FK_verified_badge_request_user" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION; EXCEPTION WHEN duplicate_object THEN NULL; END $$`);
+ await queryRunner.query(`DO $$ BEGIN ALTER TABLE "verified_badge_request" ADD CONSTRAINT "FK_verified_badge_request_resolver" FOREIGN KEY ("resolverId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION; EXCEPTION WHEN duplicate_object THEN NULL; END $$`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP TABLE IF EXISTS "verified_badge_request"`);
+ }
+}
diff --git a/packages/backend/built/migration/1783900000000-lua4frozen-database-files.js b/packages/backend/built/migration/1783900000000-lua4frozen-database-files.js
new file mode 100644
index 0000000..30b5e48
--- /dev/null
+++ b/packages/backend/built/migration/1783900000000-lua4frozen-database-files.js
@@ -0,0 +1,13 @@
+export class Lua4FrozenDatabaseFiles1783900000000 {
+ name = "Lua4FrozenDatabaseFiles1783900000000";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "drive_file" ADD COLUMN IF NOT EXISTS "isDatabase" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_drive_file_is_database" ON "drive_file" ("isDatabase")`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD COLUMN IF NOT EXISTS "lua4frozenDatabaseCapacityMb" integer NOT NULL DEFAULT 16`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN IF EXISTS "lua4frozenDatabaseCapacityMb"`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_drive_file_is_database"`);
+ await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN IF EXISTS "isDatabase"`);
+ }
+}
diff --git a/packages/backend/built/migration/1784000000000-ad-service-credits.js b/packages/backend/built/migration/1784000000000-ad-service-credits.js
new file mode 100644
index 0000000..6fe51ab
--- /dev/null
+++ b/packages/backend/built/migration/1784000000000-ad-service-credits.js
@@ -0,0 +1,19 @@
+export class adServiceCredits1784000000000 {
+ name = "adServiceCredits1784000000000";
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "promo_note" ADD COLUMN IF NOT EXISTS "totalCredits" integer NOT NULL DEFAULT 0`);
+ await queryRunner.query(`ALTER TABLE "promo_note" ADD COLUMN IF NOT EXISTS "remainingCredits" integer NOT NULL DEFAULT 0`);
+ await queryRunner.query(`ALTER TABLE "promo_read" ADD COLUMN IF NOT EXISTS "readDay" character varying(10)`);
+ await queryRunner.query(`UPDATE "promo_read" SET "readDay" = to_char("createdAt" AT TIME ZONE 'UTC', 'YYYY-MM-DD') WHERE "readDay" IS NULL`);
+ await queryRunner.query(`ALTER TABLE "promo_read" ALTER COLUMN "readDay" SET NOT NULL`);
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_2882b8a1a07c7d281a98b6db16"`);
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_promo_read_user_note_day" ON "promo_read" ("userId", "noteId", "readDay")`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX IF EXISTS "IDX_promo_read_user_note_day"`);
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_2882b8a1a07c7d281a98b6db16" ON "promo_read" ("userId", "noteId")`);
+ await queryRunner.query(`ALTER TABLE "promo_read" DROP COLUMN IF EXISTS "readDay"`);
+ await queryRunner.query(`ALTER TABLE "promo_note" DROP COLUMN IF EXISTS "remainingCredits"`);
+ await queryRunner.query(`ALTER TABLE "promo_note" DROP COLUMN IF EXISTS "totalCredits"`);
+ }
+}
diff --git a/packages/backend/built/migration/1784100000000-memoriet-archive.js b/packages/backend/built/migration/1784100000000-memoriet-archive.js
new file mode 100644
index 0000000..39e0a7d
--- /dev/null
+++ b/packages/backend/built/migration/1784100000000-memoriet-archive.js
@@ -0,0 +1,12 @@
+export class MemorietArchive1784100000000 {
+ name = "MemorietArchive1784100000000";
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE IF NOT EXISTS "memoriet_archive" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "deletedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "text" text, "cw" character varying(128), "fileIds" character varying(32) array NOT NULL DEFAULT '{}', "textLayers" jsonb NOT NULL DEFAULT '[]'::jsonb, "visibility" character varying(32) NOT NULL DEFAULT 'home', CONSTRAINT "PK_memoriet_archive" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_memoriet_archive_createdAt" ON "memoriet_archive" ("createdAt")`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_memoriet_archive_deletedAt" ON "memoriet_archive" ("deletedAt")`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_memoriet_archive_userId" ON "memoriet_archive" ("userId")`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP TABLE IF EXISTS "memoriet_archive"`);
+ }
+}
diff --git a/packages/backend/built/migration/1784200000000-plans.js b/packages/backend/built/migration/1784200000000-plans.js
new file mode 100644
index 0000000..aa0ce9d
--- /dev/null
+++ b/packages/backend/built/migration/1784200000000-plans.js
@@ -0,0 +1,19 @@
+export class Plans1784200000000 {
+ name = "Plans1784200000000";
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE IF NOT EXISTS "plan" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE, "name" character varying(128) NOT NULL, "icon" character varying(64) NOT NULL, "description" character varying(512) NOT NULL DEFAULT '', CONSTRAINT "PK_plan" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_plan_name" ON "plan" ("name")`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_plan_createdAt" ON "plan" ("createdAt")`);
+ await queryRunner.query(`CREATE TABLE IF NOT EXISTS "user_plan" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "planId" character varying(32) NOT NULL, CONSTRAINT "PK_user_plan" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_plan_user_plan" ON "user_plan" ("userId", "planId")`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_user_plan_createdAt" ON "user_plan" ("createdAt")`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_user_plan_userId" ON "user_plan" ("userId")`);
+ await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_user_plan_planId" ON "user_plan" ("planId")`);
+ await queryRunner.query(`DO $$ BEGIN ALTER TABLE "user_plan" ADD CONSTRAINT "FK_user_plan_user" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION; EXCEPTION WHEN duplicate_object THEN NULL; END $$`);
+ await queryRunner.query(`DO $$ BEGIN ALTER TABLE "user_plan" ADD CONSTRAINT "FK_user_plan_plan" FOREIGN KEY ("planId") REFERENCES "plan"("id") ON DELETE CASCADE ON UPDATE NO ACTION; EXCEPTION WHEN duplicate_object THEN NULL; END $$`);
+ }
+ async down(queryRunner) {
+ await queryRunner.query(`DROP TABLE IF EXISTS "user_plan"`);
+ await queryRunner.query(`DROP TABLE IF EXISTS "plan"`);
+ }
+}
diff --git a/packages/backend/built/misc/acct.js b/packages/backend/built/misc/acct.js
new file mode 100644
index 0000000..1857c30
--- /dev/null
+++ b/packages/backend/built/misc/acct.js
@@ -0,0 +1,11 @@
+export function parse(acct) {
+ if (acct.startsWith("@")) acct = acct.slice(1);
+ const split = acct.split("@", 2);
+ return {
+ username: split[0],
+ host: split[1] || null
+ };
+}
+export function toString(acct) {
+ return acct.host == null ? acct.username : `${acct.username}@${acct.host}`;
+}
diff --git a/packages/backend/built/misc/antenna-cache.js b/packages/backend/built/misc/antenna-cache.js
new file mode 100644
index 0000000..55f3157
--- /dev/null
+++ b/packages/backend/built/misc/antenna-cache.js
@@ -0,0 +1,30 @@
+import { Antennas } from "../models/index.js";
+import { subscriber } from "../db/redis.js";
+let antennasFetched = false;
+let antennas = [];
+export async function getAntennas() {
+ if (!antennasFetched) {
+ antennas = await Antennas.find();
+ antennasFetched = true;
+ }
+ return antennas;
+}
+subscriber.on("message", async (_, data)=>{
+ const obj = JSON.parse(data);
+ if (obj.channel === "internal") {
+ const { type, body } = obj.message;
+ switch(type){
+ case "antennaCreated":
+ antennas.push(body);
+ break;
+ case "antennaUpdated":
+ antennas[antennas.findIndex((a)=>a.id === body.id)] = body;
+ break;
+ case "antennaDeleted":
+ antennas = antennas.filter((a)=>a.id !== body.id);
+ break;
+ default:
+ break;
+ }
+ }
+});
diff --git a/packages/backend/built/misc/api-permissions.js b/packages/backend/built/misc/api-permissions.js
new file mode 100644
index 0000000..cac62b3
--- /dev/null
+++ b/packages/backend/built/misc/api-permissions.js
@@ -0,0 +1,34 @@
+export const kinds = [
+ "read:account",
+ "write:account",
+ "read:blocks",
+ "write:blocks",
+ "read:drive",
+ "write:drive",
+ "read:favorites",
+ "write:favorites",
+ "read:following",
+ "write:following",
+ "read:messaging",
+ "write:messaging",
+ "read:mutes",
+ "write:mutes",
+ "write:notes",
+ "read:notifications",
+ "write:notifications",
+ "read:reactions",
+ "write:reactions",
+ "write:votes",
+ "read:pages",
+ "write:pages",
+ "write:page-likes",
+ "read:page-likes",
+ "read:user-groups",
+ "write:user-groups",
+ "read:channels",
+ "write:channels",
+ "read:gallery",
+ "write:gallery",
+ "read:gallery-likes",
+ "write:gallery-likes"
+]; // IF YOU ADD KINDS(PERMISSIONS), YOU MUST ADD TRANSLATIONS (under _permissions).
diff --git a/packages/backend/built/misc/app-lock.js b/packages/backend/built/misc/app-lock.js
new file mode 100644
index 0000000..5d2b251
--- /dev/null
+++ b/packages/backend/built/misc/app-lock.js
@@ -0,0 +1,21 @@
+import { redisClient } from "../db/redis.js";
+import { promisify } from "node:util";
+import redisLock from "redis-lock";
+/**
+ * Retry delay (ms) for lock acquisition
+ */ const retryDelay = 100;
+const lock = redisClient ? promisify(redisLock(redisClient, retryDelay)) : async ()=>()=>{};
+/**
+ * Get AP Object lock
+ * @param uri AP object ID
+ * @param timeout Lock timeout (ms), The timeout releases previous lock.
+ * @returns Unlock function
+ */ export function getApLock(uri, timeout = 30 * 1000) {
+ return lock(`ap-object:${uri}`, timeout);
+}
+export function getFetchInstanceMetadataLock(host, timeout = 30 * 1000) {
+ return lock(`instance:${host}`, timeout);
+}
+export function getChartInsertLock(lockKey, timeout = 30 * 1000) {
+ return lock(`chart-insert:${lockKey}`, timeout);
+}
diff --git a/packages/backend/built/misc/before-shutdown.js b/packages/backend/built/misc/before-shutdown.js
new file mode 100644
index 0000000..01b5719
--- /dev/null
+++ b/packages/backend/built/misc/before-shutdown.js
@@ -0,0 +1,75 @@
+// https://gist.github.com/nfantone/1eaa803772025df69d07f4dbf5df7e58
+"use strict";
+/**
+ * @callback BeforeShutdownListener
+ * @param {string} [signalOrEvent] The exit signal or event name received on the process.
+ */ /**
+ * System signals the app will listen to initiate shutdown.
+ * @const {string[]}
+ */ const SHUTDOWN_SIGNALS = [
+ "SIGINT",
+ "SIGTERM"
+];
+/**
+ * Time in milliseconds to wait before forcing shutdown.
+ * @const {number}
+ */ const SHUTDOWN_TIMEOUT = 15000;
+/**
+ * A queue of listener callbacks to execute before shutting
+ * down the process.
+ * @type {BeforeShutdownListener[]}
+ */ const shutdownListeners = [];
+/**
+ * Listen for signals and execute given `fn` function once.
+ * @param {string[]} signals System signals to listen to.
+ * @param {function(string)} fn Function to execute on shutdown.
+ */ const processOnce = (signals, fn)=>{
+ for (const sig of signals){
+ process.once(sig, fn);
+ }
+};
+/**
+ * Sets a forced shutdown mechanism that will exit the process after `timeout` milliseconds.
+ * @param {number} timeout Time to wait before forcing shutdown (milliseconds)
+ */ const forceExitAfter = (timeout)=>()=>{
+ setTimeout(()=>{
+ // Force shutdown after timeout
+ console.warn(`Could not close resources gracefully after ${timeout}ms: forcing shutdown`);
+ return process.exit(1);
+ }, timeout).unref();
+ };
+/**
+ * Main process shutdown handler. Will invoke every previously registered async shutdown listener
+ * in the queue and exit with a code of `0`. Any `Promise` rejections from any listener will
+ * be logged out as a warning, but won't prevent other callbacks from executing.
+ * @param {string} signalOrEvent The exit signal or event name received on the process.
+ */ async function shutdownHandler(signalOrEvent) {
+ if (process.env.NODE_ENV === "test") return process.exit(0);
+ console.warn(`Shutting down: received [${signalOrEvent}] signal`);
+ for (const listener of shutdownListeners){
+ try {
+ await listener(signalOrEvent);
+ } catch (err) {
+ if (err instanceof Error) {
+ console.warn(`A shutdown handler failed before completing with: ${err.message || err}`);
+ }
+ }
+ }
+ return process.exit(0);
+}
+/**
+ * Registers a new shutdown listener to be invoked before exiting
+ * the main process. Listener handlers are guaranteed to be called in the order
+ * they were registered.
+ * @param {BeforeShutdownListener} listener The shutdown listener to register.
+ * @returns {BeforeShutdownListener} Echoes back the supplied `listener`.
+ */ export function beforeShutdown(listener) {
+ shutdownListeners.push(listener);
+ return listener;
+}
+// Register shutdown callback that kills the process after `SHUTDOWN_TIMEOUT` milliseconds
+// This prevents custom shutdown handlers from hanging the process indefinitely
+processOnce(SHUTDOWN_SIGNALS, forceExitAfter(SHUTDOWN_TIMEOUT));
+// Register process shutdown callback
+// Will listen to incoming signal events and execute all registered handlers in the stack
+processOnce(SHUTDOWN_SIGNALS, shutdownHandler);
diff --git a/packages/backend/built/misc/cache.js b/packages/backend/built/misc/cache.js
new file mode 100644
index 0000000..06b4e83
--- /dev/null
+++ b/packages/backend/built/misc/cache.js
@@ -0,0 +1,101 @@
+import { redisClient } from "../db/redis.js";
+import { encode, decode } from "msgpackr";
+import config from "../config/index.js";
+export class Cache {
+ ttl;
+ prefix;
+ constructor(name, ttlSeconds){
+ this.ttl = ttlSeconds;
+ this.prefix = `cache:${name}`;
+ }
+ prefixedKey(key) {
+ return key ? `${this.prefix}:${key}` : this.prefix;
+ }
+ async set(key, value, transaction) {
+ const _key = this.prefixedKey(key);
+ const _value = Buffer.from(encode(value));
+ const commander = transaction ?? redisClient;
+ await commander.set(_key, _value, "EX", this.ttl);
+ }
+ async get(key, renew = false) {
+ const _key = this.prefixedKey(key);
+ const cached = await redisClient.getBuffer(_key);
+ if (cached === null) return undefined;
+ if (renew) await redisClient.expire(_key, this.ttl);
+ return decode(cached);
+ }
+ async getAll(renew = false) {
+ const finalPrefix = `${config.redis.prefix}:${this.prefix}:`;
+ const keys = (await redisClient.keys(`${finalPrefix}*`)).map((p)=>p.substring(finalPrefix.length));
+ const prefixedKeys = keys.map((p)=>this.prefixedKey(p));
+ const map = new Map();
+ if (keys.length === 0) {
+ return map;
+ }
+ const values = await redisClient.mgetBuffer(prefixedKeys);
+ for (const [i, key] of keys.entries()){
+ const val = values[i];
+ if (val !== null) {
+ map.set(key, decode(val));
+ }
+ }
+ if (renew) {
+ const trans = redisClient.multi();
+ for (const key of map.keys()){
+ trans.expire(this.prefixedKey(key), this.ttl);
+ }
+ await trans.exec();
+ }
+ return map;
+ }
+ async delete(...keys) {
+ if (keys.length > 0) {
+ const _keys = keys.map((p)=>this.prefixedKey(p));
+ await redisClient.del(_keys);
+ }
+ }
+ /**
+ * Returns if cached value exists. Otherwise, calls fetcher and caches.
+ * Overwrites cached value if invalidated by the optional validator.
+ */ async fetch(key, fetcher, renew = false, validator) {
+ const cachedValue = await this.get(key, renew);
+ if (cachedValue !== undefined) {
+ if (validator) {
+ if (validator(cachedValue)) {
+ // Cache HIT
+ return cachedValue;
+ }
+ } else {
+ // Cache HIT
+ return cachedValue;
+ }
+ }
+ // Cache MISS
+ const value = await fetcher();
+ await this.set(key, value);
+ return value;
+ }
+ /**
+ * Returns if cached value exists. Otherwise, calls fetcher and caches if the fetcher returns a value.
+ * Overwrites cached value if invalidated by the optional validator.
+ */ async fetchMaybe(key, fetcher, renew = false, validator) {
+ const cachedValue = await this.get(key, renew);
+ if (cachedValue !== undefined) {
+ if (validator) {
+ if (validator(cachedValue)) {
+ // Cache HIT
+ return cachedValue;
+ }
+ } else {
+ // Cache HIT
+ return cachedValue;
+ }
+ }
+ // Cache MISS
+ const value = await fetcher();
+ if (value !== undefined) {
+ await this.set(key, value);
+ }
+ return value;
+ }
+}
diff --git a/packages/backend/built/misc/captcha.js b/packages/backend/built/misc/captcha.js
new file mode 100644
index 0000000..1ea9dac
--- /dev/null
+++ b/packages/backend/built/misc/captcha.js
@@ -0,0 +1,44 @@
+import fetch from "node-fetch";
+import { URLSearchParams } from "node:url";
+import { getAgentByUrl } from "./fetch.js";
+import config from "../config/index.js";
+export async function verifyRecaptcha(secret, response) {
+ const result = await getCaptchaResponse("https://www.recaptcha.net/recaptcha/api/siteverify", secret, response).catch((e)=>{
+ throw new Error(`recaptcha-request-failed: ${e.message}`);
+ });
+ if (result.success !== true) {
+ const errorCodes = result["error-codes"] ? result["error-codes"]?.join(", ") : "";
+ throw new Error(`recaptcha-failed: ${errorCodes}`);
+ }
+}
+export async function verifyHcaptcha(secret, response) {
+ const result = await getCaptchaResponse("https://hcaptcha.com/siteverify", secret, response).catch((e)=>{
+ throw new Error(`hcaptcha-request-failed: ${e.message}`);
+ });
+ if (result.success !== true) {
+ const errorCodes = result["error-codes"] ? result["error-codes"]?.join(", ") : "";
+ throw new Error(`hcaptcha-failed: ${errorCodes}`);
+ }
+}
+async function getCaptchaResponse(url, secret, response) {
+ const params = new URLSearchParams({
+ secret,
+ response
+ });
+ const res = await fetch(url, {
+ method: "POST",
+ body: params,
+ headers: {
+ "User-Agent": config.userAgent
+ },
+ // TODO
+ //timeout: 10 * 1000,
+ agent: getAgentByUrl
+ }).catch((e)=>{
+ throw new Error(`${e.message || e}`);
+ });
+ if (!res.ok) {
+ throw new Error(`${res.status}`);
+ }
+ return await res.json();
+}
diff --git a/packages/backend/built/misc/check-hit-antenna.js b/packages/backend/built/misc/check-hit-antenna.js
new file mode 100644
index 0000000..c970a9a
--- /dev/null
+++ b/packages/backend/built/misc/check-hit-antenna.js
@@ -0,0 +1,69 @@
+import { UserListJoinings, UserGroupJoinings, Blockings } from "../models/index.js";
+import { getFullApAccount } from "./convert-host.js";
+import * as Acct from "./acct.js";
+import { Cache } from "./cache.js";
+const blockingCache = new Cache("blocking", 60 * 5);
+// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている
+/**
+ * noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい
+ */ export async function checkHitAntenna(antenna, note, noteUser, noteUserFollowers, antennaUserFollowing) {
+ if (note.visibility === "specified") return false;
+ if (note.visibility === "home") return false;
+ // アンテナ作成者がノート作成者にブロックされていたらスキップ
+ const blockings = await blockingCache.fetch(noteUser.id, ()=>Blockings.findBy({
+ blockerId: noteUser.id
+ }).then((res)=>res.map((x)=>x.blockeeId)));
+ if (blockings.some((blocking)=>blocking === antenna.userId)) return false;
+ if (note.visibility === "followers") {
+ if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false;
+ if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false;
+ }
+ if (!antenna.withReplies && note.replyId != null) return false;
+ if (antenna.src === "home") {
+ if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false;
+ if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false;
+ } else if (antenna.src === "list") {
+ const listUsers = (await UserListJoinings.findBy({
+ userListId: antenna.userListId
+ })).map((x)=>x.userId);
+ if (!listUsers.includes(note.userId)) return false;
+ } else if (antenna.src === "group") {
+ const joining = await UserGroupJoinings.findOneByOrFail({
+ id: antenna.userGroupJoiningId
+ });
+ const groupUsers = (await UserGroupJoinings.findBy({
+ userGroupId: joining.userGroupId
+ })).map((x)=>x.userId);
+ if (!groupUsers.includes(note.userId)) return false;
+ } else if (antenna.src === "users") {
+ const accts = antenna.users.map((x)=>{
+ const { username, host } = Acct.parse(x);
+ return getFullApAccount(username, host).toLowerCase();
+ });
+ if (!accts.includes(getFullApAccount(noteUser.username, noteUser.host).toLowerCase())) return false;
+ } else if (antenna.src === "instances") {
+ const instances = antenna.instances.filter((x)=>x !== "").map((host)=>{
+ return host.toLowerCase();
+ });
+ if (!instances.includes(noteUser.host?.toLowerCase() ?? "")) return false;
+ }
+ const keywords = antenna.keywords// Clean up
+ .map((xs)=>xs.filter((x)=>x !== "")).filter((xs)=>xs.length > 0);
+ if (keywords.length > 0) {
+ if (note.text == null) return false;
+ const matched = keywords.some((and)=>and.every((keyword)=>antenna.caseSensitive ? note.text.includes(keyword) || note.cw?.includes(keyword) : note.text.toLowerCase().includes(keyword.toLowerCase()) || note.cw?.toLowerCase().includes(keyword.toLowerCase())));
+ if (!matched) return false;
+ }
+ const excludeKeywords = antenna.excludeKeywords// Clean up
+ .map((xs)=>xs.filter((x)=>x !== "")).filter((xs)=>xs.length > 0);
+ if (excludeKeywords.length > 0) {
+ if (note.text == null) return false;
+ const matched = excludeKeywords.some((and)=>and.every((keyword)=>antenna.caseSensitive ? note.text.includes(keyword) || note.cw?.includes(keyword) : note.text.toLowerCase().includes(keyword.toLowerCase()) || note.cw?.toLowerCase().includes(keyword.toLowerCase())));
+ if (matched) return false;
+ }
+ if (antenna.withFile) {
+ if (note.fileIds && note.fileIds.length === 0) return false;
+ }
+ // TODO: eval expression
+ return true;
+}
diff --git a/packages/backend/built/misc/check-word-mute.js b/packages/backend/built/misc/check-word-mute.js
new file mode 100644
index 0000000..8cbe6bd
--- /dev/null
+++ b/packages/backend/built/misc/check-word-mute.js
@@ -0,0 +1,36 @@
+import RE2 from "re2";
+function checkWordMute(note, mutedWords) {
+ if (note == null) return false;
+ let text = `${note.cw ?? ""} ${note.text ?? ""}`;
+ if (note.files != null) text += ` ${note.files.map((f)=>f.comment ?? "").join(" ")}`;
+ text = text.trim().toLowerCase();
+ if (text === "") return false;
+ for (const mutePattern of mutedWords){
+ if (Array.isArray(mutePattern)) {
+ // Clean up
+ const keywords = mutePattern.filter((keyword)=>keyword !== "");
+ if (keywords.length > 0 && keywords.every((keyword)=>text.includes(keyword.toLowerCase()))) return true;
+ } else {
+ // represents RegExp
+ const regexp = mutePattern.match(/^\/(.+)\/(.*)$/);
+ // This should never happen due to input sanitisation.
+ if (!regexp) {
+ console.warn(`Found invalid regex in word mutes: ${mutePattern}`);
+ continue;
+ }
+ try {
+ if (new RE2(regexp[1], regexp[2]).test(text)) return true;
+ } catch (err) {
+ // This should never happen due to input sanitisation.
+ }
+ }
+ }
+ return false;
+}
+export async function getWordHardMute(note, me, mutedWords) {
+ // 自分自身
+ if (me && note.userId === me.id) return false;
+ if (mutedWords.length <= 0) return false;
+ if (note.isFiltered) return true;
+ return checkWordMute(note, mutedWords) || checkWordMute(note.reply, mutedWords) || checkWordMute(note.renote, mutedWords);
+}
diff --git a/packages/backend/built/misc/checked-fetch.js b/packages/backend/built/misc/checked-fetch.js
new file mode 100644
index 0000000..0924367
--- /dev/null
+++ b/packages/backend/built/misc/checked-fetch.js
@@ -0,0 +1,72 @@
+import * as http from "node:http";
+import * as https from "node:https";
+import net from "node:net";
+import { HttpProxyAgent, HttpsProxyAgent } from "hpagent";
+import config from "../config/index.js";
+import IPCIDR from "ip-cidr";
+import PrivateIp from "private-ip";
+function isPrivateIp(ip) {
+ for (const net of config.allowedPrivateNetworks || []){
+ const cidr = new IPCIDR(net);
+ if (cidr.contains(ip)) {
+ return false;
+ }
+ }
+ return PrivateIp(ip);
+}
+function checkConnection(socket) {
+ if (socket instanceof net.Socket) {
+ const address = socket.remoteAddress;
+ if (process.env.NODE_ENV === 'production') {
+ if (address && IPCIDR.isValidAddress(address) && isPrivateIp(address)) {
+ socket.destroy(new Error(`Blocked address: ${address}`));
+ }
+ }
+ } else {
+ throw "Tried to check connection for type that isn't net.Socket";
+ }
+}
+export class CheckedHttpAgent extends http.Agent {
+ createConnection(options, callback) {
+ const socket = super.createConnection(options, callback ? (err, stream)=>{
+ if (stream) checkConnection(stream);
+ callback(err, stream);
+ } : undefined)?.on('connect', ()=>{
+ socket && checkConnection(socket);
+ });
+ return socket;
+ }
+}
+export class CheckedHttpsAgent extends https.Agent {
+ createConnection(options, callback) {
+ const socket = super.createConnection(options, callback ? (err, stream)=>{
+ if (stream) checkConnection(stream);
+ callback(err, stream);
+ } : undefined)?.on('connect', ()=>{
+ socket && checkConnection(socket);
+ });
+ return socket;
+ }
+}
+export class CheckedHttpProxyAgent extends HttpProxyAgent {
+ createConnection(options, callback) {
+ const socket = super.createConnection(options, callback ? (err, stream)=>{
+ if (stream) checkConnection(stream);
+ callback(err, stream);
+ } : undefined)?.on('connect', ()=>{
+ socket && checkConnection(socket);
+ });
+ return socket;
+ }
+}
+export class CheckedHttpsProxyAgent extends HttpsProxyAgent {
+ createConnection(options, callback) {
+ const socket = super.createConnection(options, callback ? (err, stream)=>{
+ if (stream) checkConnection(stream);
+ callback(err, stream);
+ } : undefined)?.on('connect', ()=>{
+ socket && checkConnection(socket);
+ });
+ return socket;
+ }
+}
diff --git a/packages/backend/built/misc/clone.js b/packages/backend/built/misc/clone.js
new file mode 100644
index 0000000..3289d97
--- /dev/null
+++ b/packages/backend/built/misc/clone.js
@@ -0,0 +1,15 @@
+// structredCloneが遅いため
+// SEE: http://var.blog.jp/archives/86038606.html
+export function deepClone(x) {
+ if (typeof x === "object") {
+ if (x === null) return x;
+ if (Array.isArray(x)) return x.map(deepClone);
+ const obj = {};
+ for (const [k, v] of Object.entries(x)){
+ obj[k] = deepClone(v);
+ }
+ return obj;
+ } else {
+ return x;
+ }
+}
diff --git a/packages/backend/built/misc/content-disposition.js b/packages/backend/built/misc/content-disposition.js
new file mode 100644
index 0000000..c3cccd4
--- /dev/null
+++ b/packages/backend/built/misc/content-disposition.js
@@ -0,0 +1,8 @@
+import cd from "content-disposition";
+export function contentDisposition(type, filename) {
+ const fallback = filename.replace(/[^\w.-]/g, "_");
+ return cd(filename, {
+ type,
+ fallback
+ });
+}
diff --git a/packages/backend/built/misc/convert-host.js b/packages/backend/built/misc/convert-host.js
new file mode 100644
index 0000000..1864c23
--- /dev/null
+++ b/packages/backend/built/misc/convert-host.js
@@ -0,0 +1,21 @@
+import { URL } from "node:url";
+import config from "../config/index.js";
+import punycode from "punycode/";
+export function getFullApAccount(username, host) {
+ return host ? `${username}@${toPuny(host)}` : `${username}@${toPuny(config.domain)}`;
+}
+export function isSelfHost(host) {
+ if (host == null) return true;
+ return toPuny(config.domain) === toPuny(host) || toPuny(config.host) === toPuny(host);
+}
+export function extractDbHost(uri) {
+ const url = new URL(uri);
+ return toPuny(url.hostname);
+}
+export function toPuny(host) {
+ return punycode.toASCII(host.toLowerCase());
+}
+export function toPunyNullable(host) {
+ if (host == null) return null;
+ return punycode.toASCII(host.toLowerCase());
+}
diff --git a/packages/backend/built/misc/convert-milliseconds.js b/packages/backend/built/misc/convert-milliseconds.js
new file mode 100644
index 0000000..d38533c
--- /dev/null
+++ b/packages/backend/built/misc/convert-milliseconds.js
@@ -0,0 +1,15 @@
+export function convertMilliseconds(ms) {
+ let seconds = Math.round(ms / 1000);
+ let minutes = Math.round(seconds / 60);
+ let hours = Math.round(minutes / 60);
+ const days = Math.round(hours / 24);
+ seconds %= 60;
+ minutes %= 60;
+ hours %= 24;
+ const result = [];
+ if (days > 0) result.push(`${days} day(s)`);
+ if (hours > 0) result.push(`${hours} hour(s)`);
+ if (minutes > 0) result.push(`${minutes} minute(s)`);
+ if (seconds > 0) result.push(`${seconds} second(s)`);
+ return result.join(", ");
+}
diff --git a/packages/backend/built/misc/count-same-renotes.js b/packages/backend/built/misc/count-same-renotes.js
new file mode 100644
index 0000000..7e93649
--- /dev/null
+++ b/packages/backend/built/misc/count-same-renotes.js
@@ -0,0 +1,23 @@
+import { Notes } from "../models/index.js";
+export async function countSameRenotes(userId, renoteId, excludeNoteId, groupId) {
+ // 指定したユーザーの指定したノートのリノートがいくつあるか数える
+ const query = Notes.createQueryBuilder("note").where("note.userId = :userId", {
+ userId
+ }).andWhere("note.renoteId = :renoteId", {
+ renoteId
+ });
+ if (groupId) {
+ query.andWhere("note.groupId = :groupId", {
+ groupId
+ });
+ } else {
+ query.andWhere("note.groupId IS NULL");
+ }
+ // 指定した投稿を除く
+ if (excludeNoteId) {
+ query.andWhere("note.id != :excludeNoteId", {
+ excludeNoteId
+ });
+ }
+ return await query.getCount();
+}
diff --git a/packages/backend/built/misc/create-temp.js b/packages/backend/built/misc/create-temp.js
new file mode 100644
index 0000000..49dfa26
--- /dev/null
+++ b/packages/backend/built/misc/create-temp.js
@@ -0,0 +1,25 @@
+import * as tmp from "tmp";
+export function createTemp() {
+ return new Promise((res, rej)=>{
+ tmp.file((e, path, fd, cleanup)=>{
+ if (e) return rej(e);
+ res([
+ path,
+ cleanup
+ ]);
+ });
+ });
+}
+export function createTempDir() {
+ return new Promise((res, rej)=>{
+ tmp.dir({
+ unsafeCleanup: true
+ }, (e, path, cleanup)=>{
+ if (e) return rej(e);
+ res([
+ path,
+ cleanup
+ ]);
+ });
+ });
+}
diff --git a/packages/backend/built/misc/detect-url-mime.js b/packages/backend/built/misc/detect-url-mime.js
new file mode 100644
index 0000000..35328c2
--- /dev/null
+++ b/packages/backend/built/misc/detect-url-mime.js
@@ -0,0 +1,13 @@
+import { createTemp } from "./create-temp.js";
+import { downloadUrl } from "./download-url.js";
+import { detectType } from "./get-file-info.js";
+export async function detectUrlMime(url) {
+ const [path, cleanup] = await createTemp();
+ try {
+ await downloadUrl(url, path);
+ const { mime } = await detectType(path);
+ return mime;
+ } finally{
+ cleanup();
+ }
+}
diff --git a/packages/backend/built/misc/download-text-file.js b/packages/backend/built/misc/download-text-file.js
new file mode 100644
index 0000000..bb26df1
--- /dev/null
+++ b/packages/backend/built/misc/download-text-file.js
@@ -0,0 +1,19 @@
+import * as fs from "node:fs";
+import * as util from "node:util";
+import Logger from "../services/logger.js";
+import { createTemp } from "./create-temp.js";
+import { downloadUrl } from "./download-url.js";
+const logger = new Logger("download-text-file");
+export async function downloadTextFile(url) {
+ // Create temp file
+ const [path, cleanup] = await createTemp();
+ logger.info(`Temp file is ${path}`);
+ try {
+ // write content at URL to temp file
+ await downloadUrl(url, path);
+ const text = await util.promisify(fs.readFile)(path, "utf8");
+ return text;
+ } finally{
+ cleanup();
+ }
+}
diff --git a/packages/backend/built/misc/download-url.js b/packages/backend/built/misc/download-url.js
new file mode 100644
index 0000000..62bdaeb
--- /dev/null
+++ b/packages/backend/built/misc/download-url.js
@@ -0,0 +1,63 @@
+import * as fs from "node:fs";
+import * as stream from "node:stream";
+import * as util from "node:util";
+import got, * as Got from "got";
+import { httpAgent, httpsAgent, StatusError } from "./fetch.js";
+import config from "../config/index.js";
+import chalk from "chalk";
+import Logger from "../services/logger.js";
+const pipeline = util.promisify(stream.pipeline);
+export async function downloadUrl(url, path) {
+ const logger = new Logger("download");
+ logger.info(`Downloading ${chalk.cyan(url)} ...`);
+ const timeout = 30 * 1000;
+ const operationTimeout = 60 * 1000;
+ const maxSize = config.maxFileSize || 262144000;
+ const req = got.stream(url, {
+ headers: {
+ "User-Agent": config.userAgent,
+ Host: new URL(url).hostname
+ },
+ timeout: {
+ lookup: timeout,
+ connect: timeout,
+ secureConnect: timeout,
+ socket: timeout,
+ response: timeout,
+ send: timeout,
+ request: operationTimeout
+ },
+ agent: {
+ http: httpAgent,
+ https: httpsAgent
+ },
+ http2: false,
+ retry: {
+ limit: 0
+ }
+ }).on("response", (res)=>{
+ const contentLength = res.headers["content-length"];
+ if (contentLength != null) {
+ const size = Number(contentLength);
+ if (size > maxSize) {
+ logger.warn(`maxSize exceeded (${size} > ${maxSize}) on response`);
+ req.destroy();
+ }
+ }
+ }).on("downloadProgress", (progress)=>{
+ if (progress.transferred > maxSize) {
+ logger.warn(`maxSize exceeded (${progress.transferred} > ${maxSize}) on downloadProgress`);
+ req.destroy();
+ }
+ });
+ try {
+ await pipeline(req, fs.createWriteStream(path));
+ } catch (e) {
+ if (e instanceof Got.HTTPError) {
+ throw new StatusError(`${e.response.statusCode} ${e.response.statusMessage}`, e.response.statusCode, e.response.statusMessage);
+ } else {
+ throw e;
+ }
+ }
+ logger.succ(`Download finished: ${chalk.cyan(url)}`);
+}
diff --git a/packages/backend/built/misc/emoji-meta.js b/packages/backend/built/misc/emoji-meta.js
new file mode 100644
index 0000000..8f25875
--- /dev/null
+++ b/packages/backend/built/misc/emoji-meta.js
@@ -0,0 +1,49 @@
+import probeImageSize from "probe-image-size";
+import { Mutex } from "redis-semaphore";
+import { FILE_TYPE_BROWSERSAFE } from "../const.js";
+import Logger from "../services/logger.js";
+import { Cache } from "./cache.js";
+import { redisClient } from "../db/redis.js";
+const cache = new Cache("emojiMeta", 60 * 10); // once every 10 minutes for the same url
+const logger = new Logger("emoji");
+export async function getEmojiSize(url) {
+ let attempted = true;
+ const lock = new Mutex(redisClient, "getEmojiSize");
+ await lock.acquire();
+ try {
+ attempted = await cache.get(url) === true;
+ if (!attempted) {
+ await cache.set(url, true);
+ }
+ } finally{
+ await lock.release();
+ }
+ if (attempted) {
+ logger.warn(`Attempt limit exceeded: ${url}`);
+ throw new Error("Too many attempts");
+ }
+ try {
+ logger.debug(`Retrieving emoji size from ${url}`);
+ const { width, height, mime } = await probeImageSize(url, {
+ timeout: 5000
+ });
+ if (!(mime.startsWith("image/") && FILE_TYPE_BROWSERSAFE.includes(mime))) {
+ throw new Error("Unsupported image type");
+ }
+ return {
+ width,
+ height
+ };
+ } catch (e) {
+ throw new Error(`Unable to retrieve metadata: ${e}`);
+ }
+}
+export function getNormalSize({ width, height }, orientation) {
+ return (orientation || 0) >= 5 ? {
+ width: height,
+ height: width
+ } : {
+ width,
+ height
+ };
+}
diff --git a/packages/backend/built/misc/emoji-regex.js b/packages/backend/built/misc/emoji-regex.js
new file mode 100644
index 0000000..f696f83
--- /dev/null
+++ b/packages/backend/built/misc/emoji-regex.js
@@ -0,0 +1,4 @@
+import twemoji from "@twemoji/parser/dist/lib/regex.js";
+const twemojiRegex = twemoji.default;
+export const emojiRegex = new RegExp(`(${twemojiRegex.source})`);
+export const emojiRegexAtStartToEnd = new RegExp(`^(${twemojiRegex.source})$`);
diff --git a/packages/backend/built/misc/extract-custom-emojis-from-mfm.js b/packages/backend/built/misc/extract-custom-emojis-from-mfm.js
new file mode 100644
index 0000000..5b55928
--- /dev/null
+++ b/packages/backend/built/misc/extract-custom-emojis-from-mfm.js
@@ -0,0 +1,24 @@
+import * as mfm from "mfm-js";
+import { unique } from "../prelude/array.js";
+const wrappedEmojiRegex = /([:;])([^:;\s]{1,100})\1/g;
+const glyphEmojiRegex = /;([^:;\s]{1,100});/g;
+export function extractCustomEmojisFromMfm(nodes) {
+ const emojiNodes = mfm.extract(nodes, (node)=>{
+ return node.type === "emojiCode" && node.props.name.length <= 100;
+ });
+ const glyphNames = mfm.extract(nodes, (node)=>node.type === "text").flatMap((node)=>Array.from(node.props.text.matchAll(glyphEmojiRegex), (match)=>match[1]));
+ return unique([
+ ...emojiNodes.map((x)=>x.props.name),
+ ...glyphNames
+ ]);
+}
+export function extractCustomEmojiNamesFromText(texts) {
+ const emojis = [];
+ for (const text of texts){
+ if (!text) continue;
+ for (const match of text.matchAll(wrappedEmojiRegex)){
+ emojis.push(match[2]);
+ }
+ }
+ return unique(emojis);
+}
diff --git a/packages/backend/built/misc/extract-custom-emojis-from-mfm.test.js b/packages/backend/built/misc/extract-custom-emojis-from-mfm.test.js
new file mode 100644
index 0000000..5be92c2
--- /dev/null
+++ b/packages/backend/built/misc/extract-custom-emojis-from-mfm.test.js
@@ -0,0 +1,19 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import * as mfm from "mfm-js";
+import { extractCustomEmojiNamesFromText, extractCustomEmojisFromMfm } from "./extract-custom-emojis-from-mfm.js";
+test("extractCustomEmojiNamesFromText includes glyph syntax", ()=>{
+ assert.deepEqual(extractCustomEmojiNamesFromText([
+ "plain :blobcat: and ;glyph; and ;glyph@example.com;"
+ ]), [
+ "blobcat",
+ "glyph",
+ "glyph@example.com"
+ ]);
+});
+test("extractCustomEmojisFromMfm includes glyph syntax inside text nodes", ()=>{
+ assert.deepEqual(extractCustomEmojisFromMfm(mfm.parse("hello :blobcat: ;glyph;")), [
+ "blobcat",
+ "glyph"
+ ]);
+});
diff --git a/packages/backend/built/misc/extract-group-mentions.js b/packages/backend/built/misc/extract-group-mentions.js
new file mode 100644
index 0000000..148ec0f
--- /dev/null
+++ b/packages/backend/built/misc/extract-group-mentions.js
@@ -0,0 +1,42 @@
+import { UserGroupJoinings, UserGroups, Users } from "../models/index.js";
+import { In } from "typeorm";
+const GROUP_MENTION = /(^|[^\w@])@@([a-zA-Z0-9_]{1,64})\b/g;
+const WRAPPED_ASSET = /[:;][^:;\s]{1,100}[:;]/g;
+export function extractGroupMentionNames(texts) {
+ const names = new Set();
+ for (const text of texts){
+ if (!text) continue;
+ const sanitized = text.replace(WRAPPED_ASSET, (match)=>" ".repeat(match.length));
+ for (const match of sanitized.matchAll(GROUP_MENTION)){
+ names.add(match[2].toLowerCase());
+ }
+ }
+ return [
+ ...names
+ ];
+}
+export async function extractGroupMentionedUsers(texts) {
+ const names = extractGroupMentionNames(texts);
+ if (names.length === 0) return [];
+ const groups = await UserGroups.find({
+ where: names.map((username)=>({
+ username
+ }))
+ });
+ const mentionedUserIds = new Set();
+ for (const group of groups){
+ mentionedUserIds.add(group.userId);
+ const joinings = await UserGroupJoinings.findBy({
+ userGroupId: group.id
+ });
+ for (const joining of joinings){
+ mentionedUserIds.add(joining.userId);
+ }
+ }
+ if (mentionedUserIds.size === 0) return [];
+ return await Users.findBy({
+ id: In([
+ ...mentionedUserIds
+ ])
+ });
+}
diff --git a/packages/backend/built/misc/extract-hashtags.js b/packages/backend/built/misc/extract-hashtags.js
new file mode 100644
index 0000000..adfb346
--- /dev/null
+++ b/packages/backend/built/misc/extract-hashtags.js
@@ -0,0 +1,7 @@
+import * as mfm from "mfm-js";
+import { unique } from "../prelude/array.js";
+export function extractHashtags(nodes) {
+ const hashtagNodes = mfm.extract(nodes, (node)=>node.type === "hashtag");
+ const hashtags = unique(hashtagNodes.map((x)=>x.props.hashtag));
+ return hashtags;
+}
diff --git a/packages/backend/built/misc/extract-mentions.js b/packages/backend/built/misc/extract-mentions.js
new file mode 100644
index 0000000..e9a2ada
--- /dev/null
+++ b/packages/backend/built/misc/extract-mentions.js
@@ -0,0 +1,22 @@
+// test is located in test/extract-mentions
+export function extractMentions(nodes) {
+ const mentions = [];
+ collectMentions(nodes, mentions);
+ return mentions;
+}
+function collectMentions(nodes, mentions) {
+ for(let i = 0; i < nodes.length; i++){
+ const node = nodes[i];
+ if (node.type === "mention" && !isWrappedUserEmojiMention(nodes, i)) {
+ mentions.push(node.props);
+ }
+ if ("children" in node && Array.isArray(node.children)) {
+ collectMentions(node.children, mentions);
+ }
+ }
+}
+function isWrappedUserEmojiMention(nodes, index) {
+ const previous = nodes[index - 1];
+ const next = nodes[index + 1];
+ return previous?.type === "text" && next?.type === "text" && previous.props.text.endsWith(":") && next.props.text.startsWith(":");
+}
diff --git a/packages/backend/built/misc/extract-mentions.test.js b/packages/backend/built/misc/extract-mentions.test.js
new file mode 100644
index 0000000..e26365c
--- /dev/null
+++ b/packages/backend/built/misc/extract-mentions.test.js
@@ -0,0 +1,29 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import * as mfm from "mfm-js";
+import { extractMentions } from "./extract-mentions.js";
+test("extractMentions keeps regular mentions", ()=>{
+ const mentions = extractMentions(mfm.parse("hello @alice and @bob@example.com"));
+ assert.deepEqual(mentions, [
+ {
+ username: "alice",
+ host: null,
+ acct: "@alice"
+ },
+ {
+ username: "bob",
+ host: "example.com",
+ acct: "@bob@example.com"
+ }
+ ]);
+});
+test("extractMentions ignores user icon syntax", ()=>{
+ const mentions = extractMentions(mfm.parse("regular @alice icon :@alice: remote :@bob@example.com:"));
+ assert.deepEqual(mentions, [
+ {
+ username: "alice",
+ host: null,
+ acct: "@alice"
+ }
+ ]);
+});
diff --git a/packages/backend/built/misc/fetch-meta.js b/packages/backend/built/misc/fetch-meta.js
new file mode 100644
index 0000000..0cfca18
--- /dev/null
+++ b/packages/backend/built/misc/fetch-meta.js
@@ -0,0 +1,62 @@
+import push from "web-push";
+import { Metas } from "../models/index.js";
+let cache;
+export function metaToPugArgs(meta) {
+ let motd = [
+ "Loading..."
+ ];
+ if (meta.customMOTD.length > 0) {
+ motd = meta.customMOTD;
+ }
+ let splashIconUrl = meta.iconUrl;
+ if (meta.customSplashIcons.length > 0) {
+ splashIconUrl = meta.customSplashIcons[Math.floor(Math.random() * meta.customSplashIcons.length)];
+ }
+ return {
+ img: meta.bannerUrl,
+ title: meta.name || "FrozenFriendsYume",
+ instanceName: meta.name || "FrozenFriendsYume",
+ desc: meta.description,
+ icon: meta.iconUrl,
+ splashIcon: splashIconUrl,
+ themeColor: meta.themeColor,
+ randomMOTD: motd[Math.floor(Math.random() * motd.length)],
+ privateMode: meta.privateMode
+ };
+}
+export function fetchMetaSync() {
+ return cache;
+}
+export async function fetchMeta(noCache = false) {
+ if (!noCache && cache) return cache;
+ // New IDs are prioritized because multiple records may have been created due to past bugs.
+ const meta = await Metas.findOne({
+ where: {},
+ order: {
+ id: "DESC"
+ }
+ });
+ if (meta) {
+ cache = meta;
+ return meta;
+ }
+ const { publicKey, privateKey } = push.generateVAPIDKeys();
+ const data = {
+ id: "x",
+ swPublicKey: publicKey,
+ swPrivateKey: privateKey
+ };
+ // If fetchMeta is called at the same time when meta is empty, this part may be called at the same time, so use fail-safe upsert.
+ await Metas.upsert(data, [
+ "id"
+ ]);
+ cache = await Metas.findOneByOrFail({
+ id: data.id
+ });
+ return cache;
+}
+setInterval(()=>{
+ fetchMeta(true).then((meta)=>{
+ cache = meta;
+ });
+}, 1000 * 10);
diff --git a/packages/backend/built/misc/fetch.js b/packages/backend/built/misc/fetch.js
new file mode 100644
index 0000000..2131009
--- /dev/null
+++ b/packages/backend/built/misc/fetch.js
@@ -0,0 +1,141 @@
+import CacheableLookup from "cacheable-lookup";
+import fetch from "node-fetch";
+import config from "../config/index.js";
+import { CheckedHttpAgent, CheckedHttpProxyAgent, CheckedHttpsAgent, CheckedHttpsProxyAgent } from "./checked-fetch.js";
+export async function getJson(url, accept = "application/json, */*", timeout = 10000, headers) {
+ const res = await getResponse({
+ url,
+ method: "GET",
+ headers: Object.assign({
+ "User-Agent": config.userAgent,
+ Accept: accept
+ }, headers || {}),
+ timeout
+ });
+ return await res.json();
+}
+export async function getJsonActivity(url, accept = "application/activity+json, application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", timeout = 10000, headers) {
+ const res = await getResponse({
+ url,
+ method: "GET",
+ headers: Object.assign({
+ "User-Agent": config.userAgent,
+ Accept: accept
+ }, headers || {}),
+ timeout
+ });
+ const contentType = res.headers.get('content-type');
+ if (contentType == null || contentType !== 'application/activity+json' && !contentType.startsWith('application/activity+json;') && (!contentType.startsWith('application/ld+json;') || !contentType.includes('profile="https://www.w3.org/ns/activitystreams"'))) {
+ throw new Error(`getJsonActivity response had unexpected content-type: ${contentType}`);
+ }
+ return {
+ finalUrl: res.url,
+ content: await res.json()
+ };
+}
+export async function getHtml(url, accept = "text/html, */*", timeout = 10000, headers) {
+ const res = await getResponse({
+ url,
+ method: "GET",
+ headers: Object.assign({
+ "User-Agent": config.userAgent,
+ Accept: accept
+ }, headers || {}),
+ timeout
+ });
+ return await res.text();
+}
+export async function getResponse(args) {
+ const timeout = args.timeout || 10 * 1000;
+ const controller = new AbortController();
+ setTimeout(()=>{
+ controller.abort();
+ }, timeout * 6);
+ const res = await fetch(args.url, {
+ method: args.method,
+ headers: args.headers,
+ body: args.body,
+ timeout,
+ size: 10 * 1024 * 1024,
+ agent: getAgentByUrl,
+ signal: controller.signal,
+ redirect: args.redirect
+ });
+ if (args.redirect === "manual" && [
+ 301,
+ 302,
+ 307,
+ 308
+ ].includes(res.status)) {
+ return res;
+ }
+ if (!res.ok) {
+ throw new StatusError(`${res.status} ${res.statusText}`, res.status, res.statusText);
+ }
+ return res;
+}
+const cache = new CacheableLookup({
+ maxTtl: 3600,
+ errorTtl: 30,
+ lookup: false
+});
+/**
+ * Get http non-proxy agent
+ */ const _http = new CheckedHttpAgent({
+ keepAlive: true,
+ keepAliveMsecs: 30 * 1000,
+ lookup: cache.lookup
+});
+/**
+ * Get https non-proxy agent
+ */ const _https = new CheckedHttpsAgent({
+ keepAlive: true,
+ keepAliveMsecs: 30 * 1000,
+ lookup: cache.lookup
+});
+const maxSockets = Math.max(256, config.deliverJobConcurrency || 128);
+/**
+ * Get http proxy or non-proxy agent
+ */ export const httpAgent = config.proxy ? new CheckedHttpProxyAgent({
+ keepAlive: true,
+ keepAliveMsecs: 30 * 1000,
+ maxSockets,
+ maxFreeSockets: 256,
+ scheduling: "lifo",
+ proxy: config.proxy
+}) : _http;
+/**
+ * Get https proxy or non-proxy agent
+ */ export const httpsAgent = config.proxy ? new CheckedHttpsProxyAgent({
+ keepAlive: true,
+ keepAliveMsecs: 30 * 1000,
+ maxSockets,
+ maxFreeSockets: 256,
+ scheduling: "lifo",
+ proxy: config.proxy
+}) : _https;
+/**
+ * Get agent by URL
+ * @param url URL
+ * @param bypassProxy Allways bypass proxy
+ */ export function getAgentByUrl(url, bypassProxy = false) {
+ if (bypassProxy || (config.proxyBypassHosts || []).includes(url.hostname)) {
+ return url.protocol === "http:" ? _http : _https;
+ } else {
+ return url.protocol === "http:" ? httpAgent : httpsAgent;
+ }
+}
+export class StatusError extends Error {
+ statusCode;
+ statusMessage;
+ isClientError;
+ isRetryable;
+ constructor(message, statusCode, statusMessage){
+ super(message);
+ this.name = "StatusError";
+ this.statusCode = statusCode;
+ this.statusMessage = statusMessage;
+ this.isClientError = typeof this.statusCode === "number" && this.statusCode >= 400 && this.statusCode < 500;
+ this.isRetryable = this.isClientError && this.statusCode != 429;
+ }
+}
diff --git a/packages/backend/built/misc/gen-id.js b/packages/backend/built/misc/gen-id.js
new file mode 100644
index 0000000..f1234e6
--- /dev/null
+++ b/packages/backend/built/misc/gen-id.js
@@ -0,0 +1,23 @@
+import { init, createId } from "@paralleldrive/cuid2";
+import config from "../config/index.js";
+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) {
+ 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()}`;
+}
diff --git a/packages/backend/built/misc/gen-identicon.js b/packages/backend/built/misc/gen-identicon.js
new file mode 100644
index 0000000..9b1ded3
--- /dev/null
+++ b/packages/backend/built/misc/gen-identicon.js
@@ -0,0 +1,199 @@
+/**
+ * Identicon generator
+ * https://en.wikipedia.org/wiki/Identicon
+ */ import * as p from "pureimage";
+import gen from "random-seed";
+const size = 128; // px
+const n = 5; // resolution
+const margin = size / 4;
+const colors = [
+ [
+ "#eb6f92",
+ "#b4637a"
+ ],
+ [
+ "#f6c177",
+ "#ea9d34"
+ ],
+ [
+ "#ebbcba",
+ "#d7827e"
+ ],
+ [
+ "#9ccfd8",
+ "#56949f"
+ ],
+ [
+ "#c4a7e7",
+ "#907aa9"
+ ],
+ [
+ "#eb6f92",
+ "#f6c177"
+ ],
+ [
+ "#eb6f92",
+ "#ebbcba"
+ ],
+ [
+ "#eb6f92",
+ "#31748f"
+ ],
+ [
+ "#eb6f92",
+ "#9ccfd8"
+ ],
+ [
+ "#eb6f92",
+ "#c4a7e7"
+ ],
+ [
+ "#f6c177",
+ "#eb6f92"
+ ],
+ [
+ "#f6c177",
+ "#ebbcba"
+ ],
+ [
+ "#f6c177",
+ "#31748f"
+ ],
+ [
+ "#f6c177",
+ "#9ccfd8"
+ ],
+ [
+ "#f6c177",
+ "#c4a7e7"
+ ],
+ [
+ "#ebbcba",
+ "#eb6f92"
+ ],
+ [
+ "#ebbcba",
+ "#f6c177"
+ ],
+ [
+ "#ebbcba",
+ "#31748f"
+ ],
+ [
+ "#ebbcba",
+ "#9ccfd8"
+ ],
+ [
+ "#ebbcba",
+ "#c4a7e7"
+ ],
+ [
+ "#31748f",
+ "#eb6f92"
+ ],
+ [
+ "#31748f",
+ "#f6c177"
+ ],
+ [
+ "#31748f",
+ "#ebbcba"
+ ],
+ [
+ "#31748f",
+ "#9ccfd8"
+ ],
+ [
+ "#31748f",
+ "#c4a7e7"
+ ],
+ [
+ "#9ccfd8",
+ "#eb6f92"
+ ],
+ [
+ "#9ccfd8",
+ "#f6c177"
+ ],
+ [
+ "#9ccfd8",
+ "#ebbcba"
+ ],
+ [
+ "#9ccfd8",
+ "#31748f"
+ ],
+ [
+ "#9ccfd8",
+ "#c4a7e7"
+ ],
+ [
+ "#c4a7e7",
+ "#eb6f92"
+ ],
+ [
+ "#c4a7e7",
+ "#f6c177"
+ ],
+ [
+ "#c4a7e7",
+ "#ebbcba"
+ ],
+ [
+ "#c4a7e7",
+ "#31748f"
+ ],
+ [
+ "#c4a7e7",
+ "#9ccfd8"
+ ]
+];
+const actualSize = size - margin * 2;
+const cellSize = actualSize / n;
+const sideN = Math.floor(n / 2);
+/**
+ * Generate buffer of an identicon by seed
+ */ export function genIdenticon(seed, stream) {
+ const rand = gen.create(seed);
+ const canvas = p.make(size, size, undefined);
+ const ctx = canvas.getContext("2d");
+ const bgColors = colors[rand(colors.length)];
+ const bg = ctx.createLinearGradient(0, 0, size, size);
+ bg.addColorStop(0, bgColors[0]);
+ bg.addColorStop(1, bgColors[1]);
+ ctx.fillStyle = bg;
+ ctx.beginPath();
+ ctx.fillRect(0, 0, size, size);
+ ctx.fillStyle = "#ffffff";
+ // side bitmap (filled by false)
+ const side = new Array(sideN);
+ for(let i = 0; i < side.length; i++){
+ side[i] = new Array(n).fill(false);
+ }
+ // 1*n (filled by false)
+ const center = new Array(n).fill(false);
+ for(let x = 0; x < side.length; x++){
+ for(let y = 0; y < side[x].length; y++){
+ side[x][y] = rand(3) === 0;
+ }
+ }
+ for(let i = 0; i < center.length; i++){
+ center[i] = rand(3) === 0;
+ }
+ // Draw
+ for(let x = 0; x < n; x++){
+ for(let y = 0; y < n; y++){
+ const isXCenter = x === (n - 1) / 2;
+ if (isXCenter && !center[y]) continue;
+ const isLeftSide = x < (n - 1) / 2;
+ if (isLeftSide && !side[x][y]) continue;
+ const isRightSide = x > (n - 1) / 2;
+ if (isRightSide && !side[sideN - (x - sideN)][y]) continue;
+ const actualX = margin + cellSize * x;
+ const actualY = margin + cellSize * y;
+ ctx.beginPath();
+ ctx.fillRect(actualX, actualY, cellSize, cellSize);
+ }
+ }
+ return p.encodePNGToStream(canvas, stream);
+}
diff --git a/packages/backend/built/misc/gen-key-pair.js b/packages/backend/built/misc/gen-key-pair.js
new file mode 100644
index 0000000..65da86a
--- /dev/null
+++ b/packages/backend/built/misc/gen-key-pair.js
@@ -0,0 +1,33 @@
+import * as crypto from "node:crypto";
+import * as util from "node:util";
+const generateKeyPair = util.promisify(crypto.generateKeyPair);
+export async function genRsaKeyPair(modulusLength = 2048) {
+ return await generateKeyPair("rsa", {
+ modulusLength,
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem"
+ },
+ privateKeyEncoding: {
+ type: "pkcs8",
+ format: "pem",
+ cipher: undefined,
+ passphrase: undefined
+ }
+ });
+}
+export async function genEcKeyPair(namedCurve = "prime256v1") {
+ return await generateKeyPair("ec", {
+ namedCurve,
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem"
+ },
+ privateKeyEncoding: {
+ type: "pkcs8",
+ format: "pem",
+ cipher: undefined,
+ passphrase: undefined
+ }
+ });
+}
diff --git a/packages/backend/built/misc/get-file-info.js b/packages/backend/built/misc/get-file-info.js
new file mode 100644
index 0000000..41e3dd2
--- /dev/null
+++ b/packages/backend/built/misc/get-file-info.js
@@ -0,0 +1,168 @@
+import * as fs from "node:fs";
+import * as crypto from "node:crypto";
+import * as stream from "node:stream";
+import * as util from "node:util";
+import { fileTypeFromFile } from "file-type";
+import probeImageSize from "probe-image-size";
+import isSvg from "is-svg";
+import sharp from "sharp";
+import { encode } from "blurhash";
+const pipeline = util.promisify(stream.pipeline);
+const TYPE_OCTET_STREAM = {
+ mime: "application/octet-stream",
+ ext: null
+};
+const TYPE_SVG = {
+ mime: "image/svg+xml",
+ ext: "svg"
+};
+/**
+ * Get file information
+ */ export async function getFileInfo(path) {
+ const warnings = [];
+ const size = await getFileSize(path);
+ const md5 = await calcHash(path);
+ let type = await detectType(path);
+ // image dimensions
+ let width;
+ let height;
+ let orientation;
+ if ([
+ "image/jpeg",
+ "image/gif",
+ "image/png",
+ "image/apng",
+ "image/webp",
+ "image/bmp",
+ "image/tiff",
+ "image/svg+xml",
+ "image/vnd.adobe.photoshop",
+ "image/avif"
+ ].includes(type.mime)) {
+ const imageSize = await detectImageSize(path).catch((e)=>{
+ warnings.push(`detectImageSize failed: ${e}`);
+ return undefined;
+ });
+ // うまく判定できない画像は octet-stream にする
+ if (!imageSize) {
+ warnings.push("cannot detect image dimensions");
+ type = TYPE_OCTET_STREAM;
+ } else if (imageSize.wUnits === "px") {
+ width = imageSize.width;
+ height = imageSize.height;
+ orientation = imageSize.orientation;
+ // 制限を超えている画像は octet-stream にする
+ if (imageSize.width > 16383 || imageSize.height > 16383) {
+ warnings.push("image dimensions exceeds limits");
+ type = TYPE_OCTET_STREAM;
+ }
+ } else {
+ warnings.push(`unsupported unit type: ${imageSize.wUnits}`);
+ }
+ }
+ let blurhash;
+ if ([
+ "image/jpeg",
+ "image/gif",
+ "image/png",
+ "image/apng",
+ "image/webp",
+ "image/svg+xml",
+ "image/avif"
+ ].includes(type.mime)) {
+ blurhash = await getBlurhash(path).catch((e)=>{
+ warnings.push(`getBlurhash failed: ${e}`);
+ return undefined;
+ });
+ }
+ return {
+ size,
+ md5,
+ type,
+ width,
+ height,
+ orientation,
+ blurhash,
+ warnings
+ };
+}
+function exists(path) {
+ return fs.promises.access(path).then(()=>true, ()=>false);
+}
+/**
+ * Detect MIME Type and extension
+ */ export async function detectType(path) {
+ // Check 0 byte
+ const fileSize = await getFileSize(path);
+ if (fileSize === 0) {
+ return TYPE_OCTET_STREAM;
+ }
+ const type = await fileTypeFromFile(path);
+ if (type) {
+ // XMLはSVGかもしれない
+ if (type.mime === "application/xml" && await checkSvg(path)) {
+ return TYPE_SVG;
+ }
+ return {
+ mime: type.mime,
+ ext: type.ext
+ };
+ }
+ // 種類が不明でもSVGかもしれない
+ if (await checkSvg(path)) {
+ return TYPE_SVG;
+ }
+ // それでも種類が不明なら application/octet-stream にする
+ return TYPE_OCTET_STREAM;
+}
+/**
+ * Check the file is SVG or not
+ */ export async function checkSvg(path) {
+ try {
+ const size = await getFileSize(path);
+ if (size > 1 * 1024 * 1024) return false;
+ return isSvg(fs.readFileSync(path));
+ } catch {
+ return false;
+ }
+}
+/**
+ * Get file size
+ */ export async function getFileSize(path) {
+ const getStat = util.promisify(fs.stat);
+ return (await getStat(path)).size;
+}
+/**
+ * Calculate MD5 hash
+ */ async function calcHash(path) {
+ const hash = crypto.createHash("md5").setEncoding("hex");
+ await pipeline(fs.createReadStream(path), hash);
+ return hash.read();
+}
+/**
+ * Detect dimensions of image
+ */ async function detectImageSize(path) {
+ const readable = fs.createReadStream(path);
+ const imageSize = await probeImageSize(readable);
+ readable.destroy();
+ return imageSize;
+}
+/**
+ * Calculate average color of image
+ */ function getBlurhash(path) {
+ return new Promise((resolve, reject)=>{
+ sharp(path).raw().ensureAlpha().resize(64, 64, {
+ fit: "inside"
+ }).toBuffer((err, buffer, info)=>{
+ if (err) return reject(err);
+ let { width, height } = info;
+ let hash;
+ try {
+ hash = encode(new Uint8ClampedArray(buffer), width, height, 7, 7);
+ } catch (e) {
+ return reject(e);
+ }
+ resolve(hash);
+ });
+ });
+}
diff --git a/packages/backend/built/misc/get-ip-hash.js b/packages/backend/built/misc/get-ip-hash.js
new file mode 100644
index 0000000..a46b6aa
--- /dev/null
+++ b/packages/backend/built/misc/get-ip-hash.js
@@ -0,0 +1,22 @@
+import IPCIDR from "ip-cidr";
+import net from "node:net";
+function normalizeIp(ip) {
+ let normalized = ip.split(",")[0]?.trim();
+ if (!normalized) return null;
+ if (normalized.startsWith("[") && normalized.includes("]")) {
+ normalized = normalized.slice(1, normalized.indexOf("]"));
+ }
+ if (net.isIP(normalized)) return normalized;
+ const ipv4WithPort = normalized.match(/^(\d{1,3}(?:\.\d{1,3}){3}):\d+$/);
+ if (ipv4WithPort && net.isIPv4(ipv4WithPort[1])) return ipv4WithPort[1];
+ return null;
+}
+export function getIpHash(ip) {
+ const normalized = normalizeIp(ip);
+ if (!normalized) return "ip-invalid";
+ // because a single person may control many IPv6 addresses,
+ // only a /64 subnet prefix of any IP will be taken into account.
+ // (this means for IPv4 the entire address is used)
+ const prefix = IPCIDR.createAddress(normalized).mask(64);
+ return `ip-${BigInt(`0b${prefix}`).toString(36)}`;
+}
diff --git a/packages/backend/built/misc/get-note-summary.js b/packages/backend/built/misc/get-note-summary.js
new file mode 100644
index 0000000..7e237c7
--- /dev/null
+++ b/packages/backend/built/misc/get-note-summary.js
@@ -0,0 +1,43 @@
+/**
+ * 投稿を表す文字列を取得します。
+ * @param {*} note (packされた)投稿
+ */ export const getNoteSummary = (note)=>{
+ if (note.deletedAt) {
+ return "❌";
+ }
+ let summary = "";
+ // 本文
+ if (note.cw != null) {
+ summary += note.cw;
+ } else {
+ summary += note.text ? note.text : "";
+ }
+ // ファイルが添付されているとき
+ if ((note.files || []).length !== 0) {
+ const len = note.files?.length;
+ summary += ` 📎${len !== 1 ? ` (${len})` : ""}`;
+ }
+ // 投票が添付されているとき
+ if (note.poll) {
+ summary += " 📊";
+ }
+ /*
+ // 返信のとき
+ if (note.replyId) {
+ if (note.reply) {
+ summary += `\n\nRE: ${getNoteSummary(note.reply)}`;
+ } else {
+ summary += '\n\nRE: ...';
+ }
+ }
+
+ // Renoteのとき
+ if (note.renoteId) {
+ if (note.renote) {
+ summary += `\n\nRN: ${getNoteSummary(note.renote)}`;
+ } else {
+ summary += '\n\nRN: ...';
+ }
+ }
+ */ return summary.trim();
+};
diff --git a/packages/backend/built/misc/get-reaction-emoji.js b/packages/backend/built/misc/get-reaction-emoji.js
new file mode 100644
index 0000000..1ee5d0e
--- /dev/null
+++ b/packages/backend/built/misc/get-reaction-emoji.js
@@ -0,0 +1,28 @@
+export default function(reaction) {
+ switch(reaction){
+ case "like":
+ return "👍";
+ case "love":
+ return "❤️";
+ case "laugh":
+ return "😆";
+ case "hmm":
+ return "🤔";
+ case "surprise":
+ return "😮";
+ case "congrats":
+ return "🎉";
+ case "angry":
+ return "💢";
+ case "confused":
+ return "😥";
+ case "rip":
+ return "😇";
+ case "pudding":
+ return "🍮";
+ case "star":
+ return "⭐";
+ default:
+ return reaction;
+ }
+}
diff --git a/packages/backend/built/misc/hard-limits.js b/packages/backend/built/misc/hard-limits.js
new file mode 100644
index 0000000..47f7f7d
--- /dev/null
+++ b/packages/backend/built/misc/hard-limits.js
@@ -0,0 +1,14 @@
+// If you change DB_* values, you must also change the DB schema.
+/**
+ * Maximum note text length that can be stored in DB.
+ * Surrogate pairs count as one
+ *
+ * NOTE: this can hypothetically be pushed further
+ * (up to 250000000), but will likely cause truncations
+ * and incompatibilities with other servers,
+ * as well as potential performance issues.
+ */ export const DB_MAX_NOTE_TEXT_LENGTH = 100000;
+/**
+ * Maximum image description length that can be stored in DB.
+ * Surrogate pairs count as one
+ */ export const DB_MAX_IMAGE_COMMENT_LENGTH = 8192;
diff --git a/packages/backend/built/misc/i18n.js b/packages/backend/built/misc/i18n.js
new file mode 100644
index 0000000..a0920c1
--- /dev/null
+++ b/packages/backend/built/misc/i18n.js
@@ -0,0 +1,25 @@
+export class I18n {
+ locale;
+ constructor(locale){
+ this.locale = locale;
+ //#region BIND
+ this.t = this.t.bind(this);
+ //#endregion
+ }
+ // string にしているのは、ドット区切りでのパス指定を許可するため
+ // なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも
+ t(key, args) {
+ try {
+ let str = key.split(".").reduce((o, i)=>o[i], this.locale);
+ if (args) {
+ for (const [k, v] of Object.entries(args)){
+ str = str.replace(`{${k}}`, v);
+ }
+ }
+ return str;
+ } catch (e) {
+ console.warn(`missing localization '${key}'`);
+ return key;
+ }
+ }
+}
diff --git a/packages/backend/built/misc/id/aid.js b/packages/backend/built/misc/id/aid.js
new file mode 100644
index 0000000..d84cadf
--- /dev/null
+++ b/packages/backend/built/misc/id/aid.js
@@ -0,0 +1,19 @@
+// AID
+// 長さ8の[2000年1月1日からの経過ミリ秒をbase36でエンコードしたもの] + 長さ2の[ノイズ文字列]
+import * as crypto from "node:crypto";
+const TIME2000 = 946684800000;
+let counter = crypto.randomBytes(2).readUInt16LE(0);
+function getTime(time) {
+ time = time - TIME2000;
+ if (time < 0) time = 0;
+ return time.toString(36).padStart(8, "0");
+}
+function getNoise() {
+ return counter.toString(36).padStart(2, "0").slice(-2);
+}
+export function genAid(date) {
+ const t = date.getTime();
+ if (isNaN(t)) throw "Failed to create AID: Invalid Date";
+ counter++;
+ return getTime(t) + getNoise();
+}
diff --git a/packages/backend/built/misc/id/meid.js b/packages/backend/built/misc/id/meid.js
new file mode 100644
index 0000000..fa9746e
--- /dev/null
+++ b/packages/backend/built/misc/id/meid.js
@@ -0,0 +1,19 @@
+const CHARS = "0123456789abcdef";
+function getTime(time) {
+ if (time < 0) time = 0;
+ if (time === 0) {
+ return CHARS[0];
+ }
+ time += 0x800000000000;
+ return time.toString(16).padStart(12, CHARS[0]);
+}
+function getRandom() {
+ let str = "";
+ for(let i = 0; i < 12; i++){
+ str += CHARS[Math.floor(Math.random() * CHARS.length)];
+ }
+ return str;
+}
+export function genMeid(date) {
+ return getTime(date.getTime()) + getRandom();
+}
diff --git a/packages/backend/built/misc/id/meidg.js b/packages/backend/built/misc/id/meidg.js
new file mode 100644
index 0000000..47d3163
--- /dev/null
+++ b/packages/backend/built/misc/id/meidg.js
@@ -0,0 +1,21 @@
+const CHARS = "0123456789abcdef";
+// 4bit Fixed hex value 'g'
+// 44bit UNIX Time ms in Hex
+// 48bit Random value in Hex
+function getTime(time) {
+ if (time < 0) time = 0;
+ if (time === 0) {
+ return CHARS[0];
+ }
+ return time.toString(16).padStart(11, CHARS[0]);
+}
+function getRandom() {
+ let str = "";
+ for(let i = 0; i < 12; i++){
+ str += CHARS[Math.floor(Math.random() * CHARS.length)];
+ }
+ return str;
+}
+export function genMeidg(date) {
+ return `g${getTime(date.getTime())}${getRandom()}`;
+}
diff --git a/packages/backend/built/misc/id/object-id.js b/packages/backend/built/misc/id/object-id.js
new file mode 100644
index 0000000..b075e72
--- /dev/null
+++ b/packages/backend/built/misc/id/object-id.js
@@ -0,0 +1,19 @@
+const CHARS = "0123456789abcdef";
+function getTime(time) {
+ if (time < 0) time = 0;
+ if (time === 0) {
+ return CHARS[0];
+ }
+ time = Math.floor(time / 1000);
+ return time.toString(16).padStart(8, CHARS[0]);
+}
+function getRandom() {
+ let str = "";
+ for(let i = 0; i < 16; i++){
+ str += CHARS[Math.floor(Math.random() * CHARS.length)];
+ }
+ return str;
+}
+export function genObjectId(date) {
+ return getTime(date.getTime()) + getRandom();
+}
diff --git a/packages/backend/built/misc/identifiable-error.js b/packages/backend/built/misc/identifiable-error.js
new file mode 100644
index 0000000..a1615a7
--- /dev/null
+++ b/packages/backend/built/misc/identifiable-error.js
@@ -0,0 +1,11 @@
+/**
+ * ID付きエラー
+ */ export class IdentifiableError extends Error {
+ message;
+ id;
+ constructor(id, message){
+ super(message);
+ this.message = message || "";
+ this.id = id;
+ }
+}
diff --git a/packages/backend/built/misc/is-duplicate-key-value-error.js b/packages/backend/built/misc/is-duplicate-key-value-error.js
new file mode 100644
index 0000000..b449cdc
--- /dev/null
+++ b/packages/backend/built/misc/is-duplicate-key-value-error.js
@@ -0,0 +1,4 @@
+export function isDuplicateKeyValueError(e) {
+ const nodeError = e;
+ return nodeError.code === "23505";
+}
diff --git a/packages/backend/built/misc/is-filtered.js b/packages/backend/built/misc/is-filtered.js
new file mode 100644
index 0000000..4b4cdf4
--- /dev/null
+++ b/packages/backend/built/misc/is-filtered.js
@@ -0,0 +1,19 @@
+import { getWordHardMute } from "./check-word-mute.js";
+import { Cache } from "./cache.js";
+import { unique } from "../prelude/array.js";
+import config from "../config/index.js";
+import { UserProfiles } from "../models/index.js";
+const filteredNoteCache = new Cache("filteredNote", config.wordMuteCache?.ttlSeconds ?? 60 * 60 * 24);
+const mutedWordsCache = new Cache("mutedWords", 60 * 5);
+export async function isFiltered(note, user, profile) {
+ if (!user) return false;
+ if (profile === undefined) profile = {
+ mutedWords: await mutedWordsCache.fetch(user.id, async ()=>UserProfiles.findOneBy({
+ userId: user.id
+ }).then((p)=>p?.mutedWords ?? []))
+ };
+ if (!profile || profile.mutedWords.length < 1) return false;
+ const ts = note.updatedAt ?? note.createdAt;
+ const identifier = (typeof ts === "string" ? new Date(ts) : ts)?.getTime() ?? '0';
+ return filteredNoteCache.fetch(`${note.id}:${identifier}:${user.id}`, ()=>getWordHardMute(note, user, unique(profile.mutedWords)));
+}
diff --git a/packages/backend/built/misc/is-instance-muted.js b/packages/backend/built/misc/is-instance-muted.js
new file mode 100644
index 0000000..a9e1296
--- /dev/null
+++ b/packages/backend/built/misc/is-instance-muted.js
@@ -0,0 +1,10 @@
+export function isInstanceMuted(note, mutedInstances) {
+ if (mutedInstances.has(note?.user?.host ?? "")) return true;
+ if (mutedInstances.has(note?.reply?.user?.host ?? "")) return true;
+ if (mutedInstances.has(note?.renote?.user?.host ?? "")) return true;
+ return false;
+}
+export function isUserFromMutedInstance(notif, mutedInstances) {
+ if (mutedInstances.has(notif?.user?.host ?? "")) return true;
+ return false;
+}
diff --git a/packages/backend/built/misc/is-mime-image.js b/packages/backend/built/misc/is-mime-image.js
new file mode 100644
index 0000000..4937c48
--- /dev/null
+++ b/packages/backend/built/misc/is-mime-image.js
@@ -0,0 +1,15 @@
+import { FILE_TYPE_BROWSERSAFE } from "../const.js";
+const dictionary = {
+ "safe-file": FILE_TYPE_BROWSERSAFE,
+ "sharp-convertible-image": [
+ "image/jpeg",
+ "image/png",
+ "image/gif",
+ "image/apng",
+ "image/vnd.mozilla.apng",
+ "image/webp",
+ "image/svg+xml",
+ "image/avif"
+ ]
+};
+export const isMimeImage = (mime, type)=>dictionary[type].includes(mime);
diff --git a/packages/backend/built/misc/is-quote.js b/packages/backend/built/misc/is-quote.js
new file mode 100644
index 0000000..96b2bd9
--- /dev/null
+++ b/packages/backend/built/misc/is-quote.js
@@ -0,0 +1,3 @@
+export default function(note) {
+ return note.renoteId != null && (note.text != null || note.hasPoll || note.fileIds != null && note.fileIds.length > 0);
+}
diff --git a/packages/backend/built/misc/is-user-related.js b/packages/backend/built/misc/is-user-related.js
new file mode 100644
index 0000000..2f6edfe
--- /dev/null
+++ b/packages/backend/built/misc/is-user-related.js
@@ -0,0 +1,7 @@
+export function isUserRelated(note, ids) {
+ if (ids.has(note.userId)) return true; // note author is muted
+ if (note.mentions?.some((user)=>ids.has(user))) return true; // any of mentioned users are muted
+ if (note.reply && isUserRelated(note.reply, ids)) return true; // also check reply target
+ if (note.renote && isUserRelated(note.renote, ids)) return true; // also check renote target
+ return false;
+}
diff --git a/packages/backend/built/misc/keypair-store.js b/packages/backend/built/misc/keypair-store.js
new file mode 100644
index 0000000..3ee4f58
--- /dev/null
+++ b/packages/backend/built/misc/keypair-store.js
@@ -0,0 +1,8 @@
+import { UserKeypairs } from "../models/index.js";
+import { Cache } from "./cache.js";
+const cache = new Cache("keypairStore", 60 * 30);
+export async function getUserKeypair(userId) {
+ return await cache.fetch(userId, ()=>UserKeypairs.findOneByOrFail({
+ userId: userId
+ }), true);
+}
diff --git a/packages/backend/built/misc/langmap.js b/packages/backend/built/misc/langmap.js
new file mode 100644
index 0000000..d4022c7
--- /dev/null
+++ b/packages/backend/built/misc/langmap.js
@@ -0,0 +1,666 @@
+// TODO: sharedに置いてフロントエンドのと統合したい
+export const langmap = {
+ ach: {
+ nativeName: "Lwo"
+ },
+ ady: {
+ nativeName: "Адыгэбзэ"
+ },
+ af: {
+ nativeName: "Afrikaans"
+ },
+ "af-NA": {
+ nativeName: "Afrikaans (Namibia)"
+ },
+ "af-ZA": {
+ nativeName: "Afrikaans (South Africa)"
+ },
+ ak: {
+ nativeName: "Tɕɥi"
+ },
+ ar: {
+ nativeName: "العربية"
+ },
+ "ar-AR": {
+ nativeName: "العربية"
+ },
+ "ar-MA": {
+ nativeName: "العربية"
+ },
+ "ar-SA": {
+ nativeName: "العربية (السعودية)"
+ },
+ "ay-BO": {
+ nativeName: "Aymar aru"
+ },
+ az: {
+ nativeName: "Azərbaycan dili"
+ },
+ "az-AZ": {
+ nativeName: "Azərbaycan dili"
+ },
+ "be-BY": {
+ nativeName: "Беларуская"
+ },
+ bg: {
+ nativeName: "Български"
+ },
+ "bg-BG": {
+ nativeName: "Български"
+ },
+ bn: {
+ nativeName: "বাংলা"
+ },
+ "bn-IN": {
+ nativeName: "বাংলা (ভারত)"
+ },
+ "bn-BD": {
+ nativeName: "বাংলা(বাংলাদেশ)"
+ },
+ br: {
+ nativeName: "Brezhoneg"
+ },
+ "bs-BA": {
+ nativeName: "Bosanski"
+ },
+ ca: {
+ nativeName: "Català"
+ },
+ "ca-ES": {
+ nativeName: "Català"
+ },
+ cak: {
+ nativeName: "Maya Kaqchikel"
+ },
+ "ck-US": {
+ nativeName: "ᏣᎳᎩ (tsalagi)"
+ },
+ cs: {
+ nativeName: "Čeština"
+ },
+ "cs-CZ": {
+ nativeName: "Čeština"
+ },
+ cy: {
+ nativeName: "Cymraeg"
+ },
+ "cy-GB": {
+ nativeName: "Cymraeg"
+ },
+ da: {
+ nativeName: "Dansk"
+ },
+ "da-DK": {
+ nativeName: "Dansk"
+ },
+ de: {
+ nativeName: "Deutsch"
+ },
+ "de-AT": {
+ nativeName: "Deutsch (Österreich)"
+ },
+ "de-DE": {
+ nativeName: "Deutsch (Deutschland)"
+ },
+ "de-CH": {
+ nativeName: "Deutsch (Schweiz)"
+ },
+ dsb: {
+ nativeName: "Dolnoserbšćina"
+ },
+ el: {
+ nativeName: "Ελληνικά"
+ },
+ "el-GR": {
+ nativeName: "Ελληνικά"
+ },
+ en: {
+ nativeName: "English"
+ },
+ "en-GB": {
+ nativeName: "English (UK)"
+ },
+ "en-AU": {
+ nativeName: "English (Australia)"
+ },
+ "en-CA": {
+ nativeName: "English (Canada)"
+ },
+ "en-IE": {
+ nativeName: "English (Ireland)"
+ },
+ "en-IN": {
+ nativeName: "English (India)"
+ },
+ "en-PI": {
+ nativeName: "English (Pirate)"
+ },
+ "en-SG": {
+ nativeName: "English (Singapore)"
+ },
+ "en-UD": {
+ nativeName: "English (Upside Down)"
+ },
+ "en-US": {
+ nativeName: "English (US)"
+ },
+ "en-ZA": {
+ nativeName: "English (South Africa)"
+ },
+ "en@pirate": {
+ nativeName: "English (Pirate)"
+ },
+ eo: {
+ nativeName: "Esperanto"
+ },
+ "eo-EO": {
+ nativeName: "Esperanto"
+ },
+ es: {
+ nativeName: "Español"
+ },
+ "es-AR": {
+ nativeName: "Español (Argentine)"
+ },
+ "es-419": {
+ nativeName: "Español (Latinoamérica)"
+ },
+ "es-CL": {
+ nativeName: "Español (Chile)"
+ },
+ "es-CO": {
+ nativeName: "Español (Colombia)"
+ },
+ "es-EC": {
+ nativeName: "Español (Ecuador)"
+ },
+ "es-ES": {
+ nativeName: "Español (España)"
+ },
+ "es-LA": {
+ nativeName: "Español (Latinoamérica)"
+ },
+ "es-NI": {
+ nativeName: "Español (Nicaragua)"
+ },
+ "es-MX": {
+ nativeName: "Español (México)"
+ },
+ "es-US": {
+ nativeName: "Español (Estados Unidos)"
+ },
+ "es-VE": {
+ nativeName: "Español (Venezuela)"
+ },
+ et: {
+ nativeName: "eesti keel"
+ },
+ "et-EE": {
+ nativeName: "Eesti (Estonia)"
+ },
+ eu: {
+ nativeName: "Euskara"
+ },
+ "eu-ES": {
+ nativeName: "Euskara"
+ },
+ fa: {
+ nativeName: "فارسی"
+ },
+ "fa-IR": {
+ nativeName: "فارسی"
+ },
+ "fb-LT": {
+ nativeName: "Leet Speak"
+ },
+ ff: {
+ nativeName: "Fulah"
+ },
+ fi: {
+ nativeName: "Suomi"
+ },
+ "fi-FI": {
+ nativeName: "Suomi"
+ },
+ fo: {
+ nativeName: "Føroyskt"
+ },
+ "fo-FO": {
+ nativeName: "Føroyskt (Færeyjar)"
+ },
+ fr: {
+ nativeName: "Français"
+ },
+ "fr-CA": {
+ nativeName: "Français (Canada)"
+ },
+ "fr-FR": {
+ nativeName: "Français (France)"
+ },
+ "fr-BE": {
+ nativeName: "Français (Belgique)"
+ },
+ "fr-CH": {
+ nativeName: "Français (Suisse)"
+ },
+ "fy-NL": {
+ nativeName: "Frysk"
+ },
+ ga: {
+ nativeName: "Gaeilge"
+ },
+ "ga-IE": {
+ nativeName: "Gaeilge"
+ },
+ gd: {
+ nativeName: "Gàidhlig"
+ },
+ gl: {
+ nativeName: "Galego"
+ },
+ "gl-ES": {
+ nativeName: "Galego"
+ },
+ "gn-PY": {
+ nativeName: "Avañe'ẽ"
+ },
+ "gu-IN": {
+ nativeName: "ગુજરાતી"
+ },
+ gv: {
+ nativeName: "Gaelg"
+ },
+ "gx-GR": {
+ nativeName: "Ἑλληνική ἀρχαία"
+ },
+ he: {
+ nativeName: "עברית"
+ },
+ "he-IL": {
+ nativeName: "עברית"
+ },
+ hi: {
+ nativeName: "हिन्दी"
+ },
+ "hi-IN": {
+ nativeName: "हिन्दी"
+ },
+ hr: {
+ nativeName: "Hrvatski"
+ },
+ "hr-HR": {
+ nativeName: "Hrvatski"
+ },
+ hsb: {
+ nativeName: "Hornjoserbšćina"
+ },
+ ht: {
+ nativeName: "Kreyòl"
+ },
+ hu: {
+ nativeName: "Magyar"
+ },
+ "hu-HU": {
+ nativeName: "Magyar"
+ },
+ hy: {
+ nativeName: "Հայերեն"
+ },
+ "hy-AM": {
+ nativeName: "Հայերեն (Հայաստան)"
+ },
+ id: {
+ nativeName: "Bahasa Indonesia"
+ },
+ "id-ID": {
+ nativeName: "Bahasa Indonesia"
+ },
+ is: {
+ nativeName: "Íslenska"
+ },
+ "is-IS": {
+ nativeName: "Íslenska (Iceland)"
+ },
+ it: {
+ nativeName: "Italiano"
+ },
+ "it-IT": {
+ nativeName: "Italiano"
+ },
+ ja: {
+ nativeName: "日本語"
+ },
+ "ja-JP": {
+ nativeName: "日本語 (日本)"
+ },
+ "jv-ID": {
+ nativeName: "Basa Jawa"
+ },
+ "ka-GE": {
+ nativeName: "ქართული"
+ },
+ "kk-KZ": {
+ nativeName: "Қазақша"
+ },
+ km: {
+ nativeName: "ភាសាខ្មែរ"
+ },
+ kl: {
+ nativeName: "kalaallisut"
+ },
+ "km-KH": {
+ nativeName: "ភាសាខ្មែរ"
+ },
+ kab: {
+ nativeName: "Taqbaylit"
+ },
+ kn: {
+ nativeName: "ಕನ್ನಡ"
+ },
+ "kn-IN": {
+ nativeName: "ಕನ್ನಡ (India)"
+ },
+ ko: {
+ nativeName: "한국어"
+ },
+ "ko-KR": {
+ nativeName: "한국어 (한국)"
+ },
+ "ku-TR": {
+ nativeName: "Kurdî"
+ },
+ kw: {
+ nativeName: "Kernewek"
+ },
+ la: {
+ nativeName: "Latin"
+ },
+ "la-VA": {
+ nativeName: "Latin"
+ },
+ lb: {
+ nativeName: "Lëtzebuergesch"
+ },
+ "li-NL": {
+ nativeName: "Lèmbörgs"
+ },
+ lt: {
+ nativeName: "Lietuvių"
+ },
+ "lt-LT": {
+ nativeName: "Lietuvių"
+ },
+ lv: {
+ nativeName: "Latviešu"
+ },
+ "lv-LV": {
+ nativeName: "Latviešu"
+ },
+ mai: {
+ nativeName: "मैथिली, মৈথিলী"
+ },
+ "mg-MG": {
+ nativeName: "Malagasy"
+ },
+ mk: {
+ nativeName: "Македонски"
+ },
+ "mk-MK": {
+ nativeName: "Македонски (Македонски)"
+ },
+ ml: {
+ nativeName: "മലയാളം"
+ },
+ "ml-IN": {
+ nativeName: "മലയാളം"
+ },
+ "mn-MN": {
+ nativeName: "Монгол"
+ },
+ mr: {
+ nativeName: "मराठी"
+ },
+ "mr-IN": {
+ nativeName: "मराठी"
+ },
+ ms: {
+ nativeName: "Bahasa Melayu"
+ },
+ "ms-MY": {
+ nativeName: "Bahasa Melayu"
+ },
+ mt: {
+ nativeName: "Malti"
+ },
+ "mt-MT": {
+ nativeName: "Malti"
+ },
+ my: {
+ nativeName: "ဗမာစကာ"
+ },
+ no: {
+ nativeName: "Norsk"
+ },
+ nb: {
+ nativeName: "Norsk (bokmål)"
+ },
+ "nb-NO": {
+ nativeName: "Norsk (bokmål)"
+ },
+ ne: {
+ nativeName: "नेपाली"
+ },
+ "ne-NP": {
+ nativeName: "नेपाली"
+ },
+ nl: {
+ nativeName: "Nederlands"
+ },
+ "nl-BE": {
+ nativeName: "Nederlands (België)"
+ },
+ "nl-NL": {
+ nativeName: "Nederlands (Nederland)"
+ },
+ "nn-NO": {
+ nativeName: "Norsk (nynorsk)"
+ },
+ oc: {
+ nativeName: "Occitan"
+ },
+ "or-IN": {
+ nativeName: "ଓଡ଼ିଆ"
+ },
+ pa: {
+ nativeName: "ਪੰਜਾਬੀ"
+ },
+ "pa-IN": {
+ nativeName: "ਪੰਜਾਬੀ (ਭਾਰਤ ਨੂੰ)"
+ },
+ pl: {
+ nativeName: "Polski"
+ },
+ "pl-PL": {
+ nativeName: "Polski"
+ },
+ "ps-AF": {
+ nativeName: "پښتو"
+ },
+ pt: {
+ nativeName: "Português"
+ },
+ "pt-BR": {
+ nativeName: "Português (Brasil)"
+ },
+ "pt-PT": {
+ nativeName: "Português (Portugal)"
+ },
+ "qu-PE": {
+ nativeName: "Qhichwa"
+ },
+ "rm-CH": {
+ nativeName: "Rumantsch"
+ },
+ ro: {
+ nativeName: "Română"
+ },
+ "ro-RO": {
+ nativeName: "Română"
+ },
+ ru: {
+ nativeName: "Русский"
+ },
+ "ru-RU": {
+ nativeName: "Русский"
+ },
+ "sa-IN": {
+ nativeName: "संस्कृतम्"
+ },
+ "se-NO": {
+ nativeName: "Davvisámegiella"
+ },
+ sh: {
+ nativeName: "српскохрватски"
+ },
+ "si-LK": {
+ nativeName: "සිංහල"
+ },
+ sk: {
+ nativeName: "Slovenčina"
+ },
+ "sk-SK": {
+ nativeName: "Slovenčina (Slovakia)"
+ },
+ sl: {
+ nativeName: "Slovenščina"
+ },
+ "sl-SI": {
+ nativeName: "Slovenščina"
+ },
+ "so-SO": {
+ nativeName: "Soomaaliga"
+ },
+ sq: {
+ nativeName: "Shqip"
+ },
+ "sq-AL": {
+ nativeName: "Shqip"
+ },
+ sr: {
+ nativeName: "Српски"
+ },
+ "sr-RS": {
+ nativeName: "Српски (Serbia)"
+ },
+ su: {
+ nativeName: "Basa Sunda"
+ },
+ sv: {
+ nativeName: "Svenska"
+ },
+ "sv-SE": {
+ nativeName: "Svenska"
+ },
+ sw: {
+ nativeName: "Kiswahili"
+ },
+ "sw-KE": {
+ nativeName: "Kiswahili"
+ },
+ ta: {
+ nativeName: "தமிழ்"
+ },
+ "ta-IN": {
+ nativeName: "தமிழ்"
+ },
+ te: {
+ nativeName: "తెలుగు"
+ },
+ "te-IN": {
+ nativeName: "తెలుగు"
+ },
+ tg: {
+ nativeName: "забо́ни тоҷикӣ́"
+ },
+ "tg-TJ": {
+ nativeName: "тоҷикӣ"
+ },
+ th: {
+ nativeName: "ภาษาไทย"
+ },
+ "th-TH": {
+ nativeName: "ภาษาไทย (ประเทศไทย)"
+ },
+ fil: {
+ nativeName: "Filipino"
+ },
+ tlh: {
+ nativeName: "tlhIngan-Hol"
+ },
+ tr: {
+ nativeName: "Türkçe"
+ },
+ "tr-TR": {
+ nativeName: "Türkçe"
+ },
+ "tt-RU": {
+ nativeName: "татарча"
+ },
+ uk: {
+ nativeName: "Українська"
+ },
+ "uk-UA": {
+ nativeName: "Українська"
+ },
+ ur: {
+ nativeName: "اردو"
+ },
+ "ur-PK": {
+ nativeName: "اردو"
+ },
+ uz: {
+ nativeName: "O'zbek"
+ },
+ "uz-UZ": {
+ nativeName: "O'zbek"
+ },
+ vi: {
+ nativeName: "Tiếng Việt"
+ },
+ "vi-VN": {
+ nativeName: "Tiếng Việt"
+ },
+ "xh-ZA": {
+ nativeName: "isiXhosa"
+ },
+ yi: {
+ nativeName: "ייִדיש"
+ },
+ "yi-DE": {
+ nativeName: "ייִדיש (German)"
+ },
+ zh: {
+ nativeName: "中文"
+ },
+ "zh-Hans": {
+ nativeName: "中文简体"
+ },
+ "zh-Hant": {
+ nativeName: "中文繁體"
+ },
+ "zh-CN": {
+ nativeName: "中文(中国大陆)"
+ },
+ "zh-HK": {
+ nativeName: "中文(香港)"
+ },
+ "zh-SG": {
+ nativeName: "中文(新加坡)"
+ },
+ "zh-TW": {
+ nativeName: "中文(台灣)"
+ },
+ "zu-ZA": {
+ nativeName: "isiZulu"
+ }
+};
diff --git a/packages/backend/built/misc/normalize-for-search.js b/packages/backend/built/misc/normalize-for-search.js
new file mode 100644
index 0000000..aa17132
--- /dev/null
+++ b/packages/backend/built/misc/normalize-for-search.js
@@ -0,0 +1,6 @@
+export function normalizeForSearch(tag) {
+ // ref.
+ // - https://analytics-note.xyz/programming/unicode-normalization-forms/
+ // - https://maku77.github.io/js/string/normalize.html
+ return tag.normalize("NFKC").toLowerCase();
+}
diff --git a/packages/backend/built/misc/nyaize.js b/packages/backend/built/misc/nyaize.js
new file mode 100644
index 0000000..b70b08a
--- /dev/null
+++ b/packages/backend/built/misc/nyaize.js
@@ -0,0 +1,7 @@
+export function nyaize(text) {
+ return text// ja-JP
+ .replaceAll("な", "にゃ").replaceAll("ナ", "ニャ").replaceAll("ナ", "ニャ")// en-US
+ .replace(/(?<=n)a/gi, (x)=>x === "A" ? "YA" : "ya").replace(/(?<=morn)ing/gi, (x)=>x === "ING" ? "YAN" : "yan").replace(/(?<=every)one/gi, (x)=>x === "ONE" ? "NYAN" : "nyan").replace(/non(?=[bcdfghjklmnpqrstvwxyz])/gi, (x)=>x === "NON" ? "NYAN" : "nyan")// ko-KR
+ .replace(/[나-낳]/g, (match)=>String.fromCharCode(match.charCodeAt(0) + "냐".charCodeAt(0) - "나".charCodeAt(0))).replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, "다냥").replace(/(야(?=\?))|(야$)|(야(?= ))/gm, "냥")// el-GR
+ .replaceAll("να", "νια").replaceAll("ΝΑ", "ΝΙΑ").replaceAll("Να", "Νια");
+}
diff --git a/packages/backend/built/misc/password.js b/packages/backend/built/misc/password.js
new file mode 100644
index 0000000..c273913
--- /dev/null
+++ b/packages/backend/built/misc/password.js
@@ -0,0 +1,13 @@
+import bcrypt from "bcryptjs";
+import * as argon2 from "argon2";
+export async function hashPassword(password) {
+ return argon2.hash(password);
+}
+export async function comparePassword(password, hash) {
+ if (isOldAlgorithm(hash)) return bcrypt.compare(password, hash);
+ return argon2.verify(hash, password);
+}
+export function isOldAlgorithm(hash) {
+ // bcrypt hashes start with $2[ab]$
+ return hash.startsWith("$2");
+}
diff --git a/packages/backend/built/misc/populate-emojis.js b/packages/backend/built/misc/populate-emojis.js
new file mode 100644
index 0000000..e20a5c1
--- /dev/null
+++ b/packages/backend/built/misc/populate-emojis.js
@@ -0,0 +1,285 @@
+import { In, IsNull } from "typeorm";
+import { DriveFiles, Emojis, UserEmojis, UserGroups, UserProfiles, Users } from "../models/index.js";
+import { Cache } from "./cache.js";
+import { isSelfHost, toPunyNullable } from "./convert-host.js";
+import { decodeReaction } from "./reaction-lib.js";
+import config from "../config/index.js";
+import { query } from "../prelude/url.js";
+import { redisClient } from "../db/redis.js";
+import { resolveUser } from "../remote/resolve-user.js";
+const cache = new Cache("populateEmojis", 60 * 60 * 12);
+const userEmojiCache = new Cache("populateUserEmojis", 60 * 60 * 12);
+function normalizeHost(src, noteUserHost) {
+ // クエリに使うホスト
+ let host = src === "." ? null // .はローカルホスト (ここがマッチするのはリアクションのみ)
+ : src === undefined ? noteUserHost // ノートなどでホスト省略表記の場合はローカルホスト (ここがリアクションにマッチすることはない)
+ : isSelfHost(src) ? null // 自ホスト指定
+ : src || noteUserHost; // 指定されたホスト || ノートなどの所有者のホスト (こっちがリアクションにマッチすることはない)
+ host = toPunyNullable(host);
+ return host;
+}
+function parseEmojiStr(emojiName, noteUserHost) {
+ // emojiName may be of the form `emoji@host`, turn it into a suitable form
+ const match = emojiName.split("@");
+ const name = match[0];
+ const host = toPunyNullable(normalizeHost(match[1], noteUserHost));
+ return {
+ name,
+ host
+ };
+}
+function proxiedUrl(url, host) {
+ if (host == null) return url;
+ return `${config.url}/proxy/${encodeURIComponent(new URL(url).pathname)}?${query({
+ url
+ })}`;
+}
+function proxiedGlyphUrl(url, host) {
+ if (host == null) return url;
+ return `${config.url}/proxy/${encodeURIComponent(new URL(url).pathname)}?${query({
+ url,
+ glyph: "1"
+ })}`;
+}
+function parseUserIconEmoji(emojiName) {
+ const match = emojiName.match(/^@([^@:\s]+)(?:@([^@:\s]+))?$/);
+ if (!match) return null;
+ return {
+ username: match[1],
+ host: toPunyNullable(normalizeHost(match[2], null))
+ };
+}
+function parseGroupSymbolEmoji(emojiName) {
+ const match = emojiName.match(/^@@([a-zA-Z0-9_]{1,64})$/);
+ if (!match) return null;
+ return {
+ username: match[1].toLowerCase()
+ };
+}
+function parseGroupEmoji(emojiName) {
+ const match = emojiName.match(/^([a-z0-9_]{1,64})@@([a-zA-Z0-9_]{1,64})$/);
+ if (!match) return null;
+ return {
+ name: match[1],
+ username: match[2].toLowerCase()
+ };
+}
+function parseUserEmoji(emojiName) {
+ const parts = emojiName.split("@");
+ if (parts.length !== 2 && parts.length !== 3) return null;
+ if (!parts[0] || !parts[1]) return null;
+ return {
+ name: parts[0],
+ username: parts[1],
+ host: toPunyNullable(normalizeHost(parts[2], null))
+ };
+}
+async function findUserByAcct(username, host) {
+ const user = await Users.findOneBy({
+ usernameLower: username.toLowerCase(),
+ host: host ?? IsNull()
+ });
+ if (user) return user;
+ if (host == null) return null;
+ return resolveUser(username, host).catch(()=>null);
+}
+async function populateUserIconEmoji(emojiName) {
+ const parsed = parseUserIconEmoji(emojiName);
+ if (!parsed) return null;
+ const user = await findUserByAcct(parsed.username, parsed.host);
+ if (!user) return null;
+ const profile = await UserProfiles.findOneBy({
+ userId: user.id
+ });
+ if (profile?.symbolFileId) {
+ const symbol = await DriveFiles.findOneBy({
+ id: profile.symbolFileId
+ });
+ if (symbol) {
+ const symbolUrl = symbol.webpublicUrl ?? symbol.url;
+ return {
+ name: emojiName,
+ url: proxiedUrl(symbolUrl, user.host),
+ glyph: true,
+ glyphUrl: proxiedGlyphUrl(symbol.url, user.host),
+ width: null,
+ height: null
+ };
+ }
+ }
+ const avatarUrl = user.avatarUrl ?? await Users.getAvatarUrl(user);
+ return {
+ name: emojiName,
+ url: proxiedUrl(avatarUrl, user.host),
+ glyph: false,
+ glyphUrl: null,
+ width: null,
+ height: null
+ };
+}
+async function populateUserEmoji(emojiName) {
+ const parsed = parseUserEmoji(emojiName);
+ if (!parsed) return null;
+ const user = await findUserByAcct(parsed.username, parsed.host);
+ if (!user) return null;
+ const userEmoji = await UserEmojis.findOneBy({
+ name: parsed.name,
+ userId: user.id
+ });
+ if (!userEmoji) return null;
+ const emojiUrl = userEmoji.publicUrl || userEmoji.originalUrl;
+ return {
+ name: emojiName,
+ url: proxiedUrl(emojiUrl, user.host),
+ glyph: userEmoji.glyph,
+ glyphUrl: userEmoji.glyph ? proxiedGlyphUrl(userEmoji.originalUrl, user.host) : null,
+ width: userEmoji.width,
+ height: userEmoji.height
+ };
+}
+async function populateGroupSymbolEmoji(emojiName) {
+ const parsed = parseGroupSymbolEmoji(emojiName);
+ if (!parsed) return null;
+ const group = await UserGroups.findOneBy({
+ username: parsed.username
+ });
+ if (!group?.symbolFileId && !group?.iconFileId) return null;
+ const file = await DriveFiles.findOneBy({
+ id: group.symbolFileId ?? group.iconFileId
+ });
+ if (!file) return null;
+ const symbolUrl = file.webpublicUrl ?? file.url;
+ return {
+ name: emojiName,
+ url: proxiedUrl(symbolUrl, null),
+ glyph: true,
+ glyphUrl: proxiedGlyphUrl(file.url, null),
+ width: null,
+ height: null
+ };
+}
+async function populateGroupEmoji(emojiName) {
+ const parsed = parseGroupEmoji(emojiName);
+ if (!parsed) return null;
+ const group = await UserGroups.findOneBy({
+ username: parsed.username
+ });
+ if (!group) return null;
+ const groupEmoji = await UserEmojis.findOneBy({
+ name: parsed.name,
+ userGroupId: group.id
+ });
+ if (!groupEmoji) return null;
+ const emojiUrl = groupEmoji.publicUrl || groupEmoji.originalUrl;
+ return {
+ name: emojiName,
+ url: proxiedUrl(emojiUrl, null),
+ glyph: groupEmoji.glyph,
+ glyphUrl: groupEmoji.glyph ? proxiedGlyphUrl(groupEmoji.originalUrl, null) : null,
+ width: groupEmoji.width,
+ height: groupEmoji.height
+ };
+}
+/**
+ * 添付用絵文字情報を解決する
+ * @param emojiName ノートやユーザープロフィールに添付された、またはリアクションのカスタム絵文字名 (:は含めない, リアクションでローカルホストの場合は@.を付ける (これはdecodeReactionで可能))
+ * @param noteUserHost ノートやユーザープロフィールの所有者のホスト
+ * @returns 絵文字情報, nullは未マッチを意味する
+ */ export async function populateEmoji(emojiName, noteUserHost) {
+ const { name, host } = parseEmojiStr(emojiName, noteUserHost);
+ if (name == null) return null;
+ const queryOrNull = async ()=>await Emojis.findOneBy({
+ name,
+ host: host ?? IsNull()
+ }) || null;
+ const cacheKey = `${name} ${host}`;
+ let emoji = await cache.fetch(cacheKey, queryOrNull);
+ if (emoji && !(emoji.width && emoji.height)) {
+ emoji = await queryOrNull();
+ await cache.set(cacheKey, emoji);
+ }
+ if (emoji == null) return null;
+ const isLocal = emoji.host == null;
+ const emojiUrl = emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl してるのは後方互換性のため
+ const url = proxiedUrl(emojiUrl, isLocal ? null : emoji.host);
+ return {
+ name: emojiName,
+ url,
+ glyph: emoji.glyph,
+ glyphUrl: emoji.glyph ? proxiedGlyphUrl(emoji.originalUrl, isLocal ? null : emoji.host) : null,
+ width: emoji.width,
+ height: emoji.height
+ };
+}
+export async function populateEmojiOrUserEmoji(emojiName, noteUserHost) {
+ const emoji = await populateEmoji(emojiName, noteUserHost);
+ if (emoji) return emoji;
+ const userEmoji = await userEmojiCache.fetchMaybe(`user ${emojiName}`, async ()=>await populateUserIconEmoji(emojiName) ?? await populateUserEmoji(emojiName) ?? await populateGroupSymbolEmoji(emojiName) ?? await populateGroupEmoji(emojiName) ?? undefined, false, (cached)=>cached != null);
+ return userEmoji ?? null;
+}
+export async function clearUserEmojiCache(name, username, host) {
+ const keys = new Set([
+ `user ${name}@${username}`
+ ]);
+ if (host) {
+ keys.add(`user ${name}@${username}@${host}`);
+ } else {
+ keys.add(`user ${name}@${username}@${config.host}`);
+ }
+ await userEmojiCache.delete(...keys);
+}
+export async function clearGroupEmojiCache(name, groupUsername) {
+ if (!groupUsername) return;
+ await userEmojiCache.delete(`user ${name}@@${groupUsername}`, `user @@${groupUsername}`);
+}
+/**
+ * 複数の添付用絵文字情報を解決する (キャシュ付き, 存在しないものは結果から除外される)
+ */ export async function populateEmojis(emojiNames, noteUserHost) {
+ const emojis = await Promise.all(emojiNames.map((x)=>populateEmojiOrUserEmoji(x, noteUserHost)));
+ return emojis.filter((x)=>x != null);
+}
+export function aggregateNoteEmojis(notes) {
+ let emojis = [];
+ for (const note of notes){
+ emojis = emojis.concat(note.emojis.map((e)=>parseEmojiStr(e, note.userHost)));
+ if (note.renote) {
+ emojis = emojis.concat(note.renote.emojis.map((e)=>parseEmojiStr(e, note.renote.userHost)));
+ if (note.renote.user) {
+ emojis = emojis.concat(note.renote.user.emojis.map((e)=>parseEmojiStr(e, note.renote.userHost)));
+ }
+ }
+ const customReactions = Object.keys(note.reactions).map((x)=>decodeReaction(x)).filter((x)=>x.name != null);
+ emojis = emojis.concat(customReactions);
+ if (note.user) {
+ emojis = emojis.concat(note.user.emojis.map((e)=>parseEmojiStr(e, note.userHost)));
+ }
+ }
+ return emojis.filter((x)=>x.name != null);
+}
+/**
+ * 与えられた絵文字のリストをデータベースから取得し、キャッシュに追加します
+ */ export async function prefetchEmojis(emojis) {
+ const notCachedEmojis = emojis.filter(async (emoji)=>!await cache.get(`${emoji.name} ${emoji.host}`));
+ const emojisQuery = [];
+ const hosts = new Set(notCachedEmojis.map((e)=>e.host));
+ for (const host of hosts){
+ emojisQuery.push({
+ name: In(notCachedEmojis.filter((e)=>e.host === host).map((e)=>e.name)),
+ host: host ?? IsNull()
+ });
+ }
+ const _emojis = emojisQuery.length > 0 ? await Emojis.find({
+ where: emojisQuery,
+ select: [
+ "name",
+ "host",
+ "originalUrl",
+ "publicUrl"
+ ]
+ }) : [];
+ const trans = redisClient.multi();
+ for (const emoji of _emojis){
+ cache.set(`${emoji.name} ${emoji.host}`, emoji, trans);
+ }
+ await trans.exec();
+}
diff --git a/packages/backend/built/misc/post.js b/packages/backend/built/misc/post.js
new file mode 100644
index 0000000..9db9865
--- /dev/null
+++ b/packages/backend/built/misc/post.js
@@ -0,0 +1,15 @@
+export function parse(acct) {
+ return {
+ text: acct.text,
+ cw: acct.cw,
+ localOnly: acct.localOnly,
+ createdAt: new Date(acct.createdAt)
+ };
+}
+export function toJson(acct) {
+ return ({
+ text: acct.text,
+ cw: acct.cw,
+ localOnly: acct.localOnly
+ }).toString();
+}
diff --git a/packages/backend/built/misc/process-masto-notes.js b/packages/backend/built/misc/process-masto-notes.js
new file mode 100644
index 0000000..16c66f3
--- /dev/null
+++ b/packages/backend/built/misc/process-masto-notes.js
@@ -0,0 +1,122 @@
+import * as fs from "node:fs";
+import Logger from "../services/logger.js";
+import { createTemp, createTempDir } from "./create-temp.js";
+import { downloadUrl } from "./download-url.js";
+import { addFile } from "../services/drive/add-file.js";
+import { Users } from "../models/index.js";
+import * as tar from "tar-stream";
+import gunzip from "gunzip-maybe";
+import decompress from "decompress";
+import * as Path from "node:path";
+const logger = new Logger("process-masto-notes");
+export async function processMastoNotes(fn, url, uid) {
+ // Create temp file
+ const [path, cleanup] = await createTemp();
+ const [unzipPath, unzipCleanup] = await createTempDir();
+ logger.info(`Temp file is ${path}`);
+ try {
+ // write content at URL to temp file
+ await downloadUrl(url, path);
+ return await processMastoFile(fn, path, unzipPath, uid);
+ } finally{
+ cleanup();
+ //unzipCleanup();
+ }
+}
+function processMastoFile(fn, path, dir, uid) {
+ return new Promise(async (resolve, reject)=>{
+ const user = await Users.findOneBy({
+ id: uid
+ });
+ try {
+ logger.info(`Start unzip ${path}`);
+ fn.endsWith("tar.gz") ? await unzipTarGz(path, dir) : await unzipZip(path, dir);
+ logger.info(`Unzip to ${dir}`);
+ const outbox = JSON.parse(fs.readFileSync(`${dir}/outbox.json`));
+ for (const note of outbox.orderedItems){
+ // Skip if attachment is undefined or not iterable
+ if (note.object.attachment == null || !note.object.attachment[Symbol.iterator]) {
+ continue;
+ }
+ for (const attachment of note.object.attachment){
+ const url = attachment.url.replaceAll("..", "");
+ if (url.indexOf("\0") !== -1) {
+ logger.error(`Found Poison Null Bytes Attack: ${url}`);
+ reject();
+ return;
+ }
+ try {
+ const fpath = Path.resolve(`${dir}${url}`);
+ if (!fpath.startsWith(dir)) {
+ logger.error(`Found Path Attack: ${url}`);
+ reject();
+ return;
+ }
+ logger.info(fpath);
+ const driveFile = await addFile({
+ user: user,
+ path: fpath
+ });
+ attachment.driveFile = driveFile;
+ } catch (e) {
+ logger.error(`Skipped adding file to drive: ${url}`);
+ }
+ }
+ }
+ resolve(outbox);
+ } catch (e) {
+ logger.error(`Error on extract masto note package: ${fn}`);
+ reject(e);
+ }
+ });
+}
+function createFileDir(fn) {
+ if (!fs.existsSync(fn)) {
+ fs.mkdirSync(fn, {
+ recursive: true
+ });
+ fs.rmdirSync(fn);
+ }
+}
+function unzipZip(fn, dir) {
+ return new Promise(async (resolve, reject)=>{
+ try {
+ decompress(fn, dir).then((files)=>{
+ resolve(files);
+ });
+ } catch (e) {
+ reject();
+ }
+ });
+}
+function unzipTarGz(fn, dir) {
+ return new Promise(async (resolve, reject)=>{
+ const onErr = (err)=>{
+ logger.error(`pipe broken: ${err}`);
+ reject();
+ };
+ try {
+ const extract = tar.extract().on("error", onErr);
+ dir = dir.endsWith("/") ? dir : dir + "/";
+ const ls = [];
+ extract.on("entry", function(header, stream, next) {
+ try {
+ ls.push(dir + header.name);
+ createFileDir(dir + header.name);
+ stream.on("error", onErr).pipe(fs.createWriteStream(dir + header.name)).on("error", onErr);
+ next();
+ } catch (e) {
+ logger.error(`create dir error:${e}`);
+ reject();
+ }
+ });
+ extract.on("finish", function() {
+ resolve(ls);
+ });
+ fs.createReadStream(fn).on("error", onErr).pipe(gunzip()).on("error", onErr).pipe(extract).on("error", onErr);
+ } catch (e) {
+ logger.error(`unzipTarGz error: ${e}`);
+ reject();
+ }
+ });
+}
diff --git a/packages/backend/built/misc/reaction-lib.js b/packages/backend/built/misc/reaction-lib.js
new file mode 100644
index 0000000..a6b87f4
--- /dev/null
+++ b/packages/backend/built/misc/reaction-lib.js
@@ -0,0 +1,184 @@
+import { emojiRegex } from "./emoji-regex.js";
+import { fetchMeta } from "./fetch-meta.js";
+import { Emojis, UserEmojis, UserGroups, Users } from "../models/index.js";
+import { toPunyNullable } from "./convert-host.js";
+import { IsNull } from "typeorm";
+import { resolveUser } from "../remote/resolve-user.js";
+const legacies = new Map([
+ [
+ "like",
+ "👍"
+ ],
+ [
+ "love",
+ "❤️"
+ ],
+ [
+ "laugh",
+ "😆"
+ ],
+ [
+ "hmm",
+ "🤔"
+ ],
+ [
+ "surprise",
+ "😮"
+ ],
+ [
+ "congrats",
+ "🎉"
+ ],
+ [
+ "angry",
+ "💢"
+ ],
+ [
+ "confused",
+ "😥"
+ ],
+ [
+ "rip",
+ "😇"
+ ],
+ [
+ "pudding",
+ "🍮"
+ ],
+ [
+ "star",
+ "⭐"
+ ]
+]);
+export async function getFallbackReaction() {
+ const meta = await fetchMeta();
+ return meta.defaultReaction;
+}
+export function convertLegacyReactions(reactions) {
+ const _reactions = new Map();
+ const decodedReactions = new Map();
+ for(const reaction in reactions){
+ if (reactions[reaction] <= 0) continue;
+ let decodedReaction;
+ if (decodedReactions.has(reaction)) {
+ decodedReaction = decodedReactions.get(reaction);
+ } else {
+ decodedReaction = decodeReaction(reaction);
+ decodedReactions.set(reaction, decodedReaction);
+ }
+ let emoji = legacies.get(decodedReaction.reaction);
+ if (emoji) {
+ _reactions.set(emoji, (_reactions.get(emoji) || 0) + reactions[reaction]);
+ } else {
+ _reactions.set(reaction, (_reactions.get(reaction) || 0) + reactions[reaction]);
+ }
+ }
+ const _reactions2 = new Map();
+ for (const [reaction, count] of _reactions){
+ const decodedReaction = decodedReactions.get(reaction);
+ _reactions2.set(decodedReaction.reaction, count);
+ }
+ return Object.fromEntries(_reactions2);
+}
+export async function toDbReaction(reaction, reacterHost, recurse = true) {
+ if (!reaction) return await getFallbackReaction();
+ reacterHost = toPunyNullable(reacterHost);
+ // Convert string-type reactions to unicode
+ const emoji = legacies.get(reaction) || (reaction === "♥️" ? "❤️" : null);
+ if (emoji) return emoji;
+ // Allow unicode reactions
+ const match = emojiRegex.exec(reaction);
+ if (match) {
+ const unicode = match[0];
+ return unicode;
+ }
+ const custom = reaction.match(/^:([^:\s]+):$/);
+ if (custom) {
+ const decoded = decodeReaction(reaction);
+ if (decoded.name) {
+ const groupCustom = decoded.name.match(/^([a-z0-9_]{1,64})@@([a-zA-Z0-9_]{1,64})$/);
+ if (groupCustom) {
+ const group = await UserGroups.findOneBy({
+ username: groupCustom[2].toLowerCase()
+ });
+ const groupEmoji = group ? await UserEmojis.findOneBy({
+ name: groupCustom[1],
+ userGroupId: group.id,
+ glyph: false
+ }) : null;
+ if (groupEmoji) return `:${groupCustom[1]}@@${group.username}:`;
+ }
+ const emoji = await Emojis.findOneBy({
+ host: decoded.host ?? reacterHost ?? IsNull(),
+ name: decoded.name
+ });
+ if (emoji) return emoji.host ? `:${emoji.name}@${emoji.host}:` : `:${emoji.name}:`;
+ }
+ if (await isUserReaction(reaction)) return reaction;
+ }
+ return recurse && reacterHost == null && reaction !== null ? await toDbReaction(`:${reaction}:`, reacterHost, false) : await getFallbackReaction();
+}
+export function decodeReaction(str) {
+ const custom = str.match(/^:([^:\s]+):$/);
+ if (custom) {
+ const body = custom[1];
+ const parts = body.split("@");
+ const name = parts[0];
+ const host = parts.length === 2 ? parts[1] || null : null;
+ return {
+ reaction: host ? `:${name}@${host}:` : str,
+ name,
+ host
+ };
+ }
+ return {
+ reaction: str,
+ name: undefined,
+ host: undefined
+ };
+}
+async function isUserReaction(reaction) {
+ const body = reaction.slice(1, -1);
+ const icon = body.match(/^@([^@:\s]+)(?:@([^@:\s]+))?$/);
+ if (icon) {
+ return await findUser(icon[1], icon[2] ?? null) != null;
+ }
+ if (/^@@[a-zA-Z0-9_]{1,64}$/.test(body)) return false;
+ const groupCustom = body.match(/^([a-z0-9_]{1,64})@@([a-zA-Z0-9_]{1,64})$/);
+ if (groupCustom) {
+ const group = await UserGroups.findOneBy({
+ username: groupCustom[2].toLowerCase()
+ });
+ if (!group) return false;
+ return await UserEmojis.findOneBy({
+ name: groupCustom[1],
+ userGroupId: group.id,
+ glyph: false
+ }) != null;
+ }
+ const parts = body.split("@");
+ if (parts.length !== 2 && parts.length !== 3) return false;
+ const [name, username, host] = parts;
+ if (!name || !username) return false;
+ const user = await findUser(username, host ?? null);
+ if (!user) return false;
+ return await UserEmojis.findOneBy({
+ name,
+ userId: user.id
+ }) != null;
+}
+async function findUser(username, host) {
+ const normalizedHost = toPunyNullable(host);
+ const user = await Users.findOneBy({
+ usernameLower: username.toLowerCase(),
+ host: normalizedHost ?? IsNull()
+ });
+ if (user) return user;
+ if (normalizedHost == null) return null;
+ return resolveUser(username, normalizedHost).catch(()=>null);
+}
+export function convertLegacyReaction(reaction) {
+ const decoded = decodeReaction(reaction).reaction;
+ if (legacies.has(decoded)) return legacies.get(decoded);
+ return decoded;
+}
diff --git a/packages/backend/built/misc/safe-for-sql.js b/packages/backend/built/misc/safe-for-sql.js
new file mode 100644
index 0000000..9aea91a
--- /dev/null
+++ b/packages/backend/built/misc/safe-for-sql.js
@@ -0,0 +1,3 @@
+export function safeForSql(text) {
+ return !/[\0\x08\x09\x1a\n\r"'\\\%]/g.test(text);
+}
diff --git a/packages/backend/built/misc/schema.js b/packages/backend/built/misc/schema.js
new file mode 100644
index 0000000..a7fd828
--- /dev/null
+++ b/packages/backend/built/misc/schema.js
@@ -0,0 +1,60 @@
+import { packedUserLiteSchema, packedUserDetailedNotMeOnlySchema, packedMeDetailedOnlySchema, packedUserDetailedNotMeSchema, packedMeDetailedSchema, packedUserDetailedSchema, packedUserSchema } from "../models/schema/user.js";
+import { packedNoteSchema } from "../models/schema/note.js";
+import { packedUserListSchema } from "../models/schema/user-list.js";
+import { packedAppSchema } from "../models/schema/app.js";
+import { packedMessagingMessageSchema } from "../models/schema/messaging-message.js";
+import { packedNotificationSchema } from "../models/schema/notification.js";
+import { packedDriveFileSchema } from "../models/schema/drive-file.js";
+import { packedDriveFolderSchema } from "../models/schema/drive-folder.js";
+import { packedFollowingSchema } from "../models/schema/following.js";
+import { packedMutingSchema } from "../models/schema/muting.js";
+import { packedRenoteMutingSchema } from "../models/schema/renote-muting.js";
+import { packedBlockingSchema } from "../models/schema/blocking.js";
+import { packedNoteReactionSchema } from "../models/schema/note-reaction.js";
+import { packedHashtagSchema } from "../models/schema/hashtag.js";
+import { packedPageSchema } from "../models/schema/page.js";
+import { packedUserGroupSchema } from "../models/schema/user-group.js";
+import { packedNoteFavoriteSchema } from "../models/schema/note-favorite.js";
+import { packedChannelSchema } from "../models/schema/channel.js";
+import { packedAntennaSchema } from "../models/schema/antenna.js";
+import { packedClipSchema } from "../models/schema/clip.js";
+import { packedFederationInstanceSchema } from "../models/schema/federation-instance.js";
+import { packedQueueCountSchema } from "../models/schema/queue.js";
+import { packedGalleryPostSchema } from "../models/schema/gallery-post.js";
+import { packedEmojiSchema } from "../models/schema/emoji.js";
+import { packedNoteEdit } from "../models/schema/note-edit.js";
+import { packedBiteSchema } from "../models/schema/bite.js";
+export const refs = {
+ UserLite: packedUserLiteSchema,
+ UserDetailedNotMeOnly: packedUserDetailedNotMeOnlySchema,
+ MeDetailedOnly: packedMeDetailedOnlySchema,
+ UserDetailedNotMe: packedUserDetailedNotMeSchema,
+ MeDetailed: packedMeDetailedSchema,
+ UserDetailed: packedUserDetailedSchema,
+ User: packedUserSchema,
+ UserList: packedUserListSchema,
+ UserGroup: packedUserGroupSchema,
+ App: packedAppSchema,
+ MessagingMessage: packedMessagingMessageSchema,
+ Note: packedNoteSchema,
+ NoteEdit: packedNoteEdit,
+ NoteReaction: packedNoteReactionSchema,
+ NoteFavorite: packedNoteFavoriteSchema,
+ Notification: packedNotificationSchema,
+ DriveFile: packedDriveFileSchema,
+ DriveFolder: packedDriveFolderSchema,
+ Following: packedFollowingSchema,
+ Muting: packedMutingSchema,
+ RenoteMuting: packedRenoteMutingSchema,
+ Blocking: packedBlockingSchema,
+ Hashtag: packedHashtagSchema,
+ Page: packedPageSchema,
+ Channel: packedChannelSchema,
+ QueueCount: packedQueueCountSchema,
+ Antenna: packedAntennaSchema,
+ Clip: packedClipSchema,
+ FederationInstance: packedFederationInstanceSchema,
+ GalleryPost: packedGalleryPostSchema,
+ Emoji: packedEmojiSchema,
+ Bite: packedBiteSchema
+};
diff --git a/packages/backend/built/misc/secure-rndstr.js b/packages/backend/built/misc/secure-rndstr.js
new file mode 100644
index 0000000..18db71b
--- /dev/null
+++ b/packages/backend/built/misc/secure-rndstr.js
@@ -0,0 +1,13 @@
+import * as crypto from "node:crypto";
+const charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+export function secureRndstr(length = 32) {
+ let str = "";
+ for(let i = 0; i < length; i++){
+ let rand = Math.floor(crypto.randomBytes(1).readUInt8(0) / 0xff * charset.length);
+ if (rand === charset.length) {
+ rand = charset.length - 1;
+ }
+ str += charset.charAt(rand);
+ }
+ return str;
+}
diff --git a/packages/backend/built/misc/should-block-instance.js b/packages/backend/built/misc/should-block-instance.js
new file mode 100644
index 0000000..b0ae2d6
--- /dev/null
+++ b/packages/backend/built/misc/should-block-instance.js
@@ -0,0 +1,21 @@
+import { fetchMeta } from "./fetch-meta.js";
+/**
+ * Returns whether a specific host (punycoded) should be blocked.
+ *
+ * @param host punycoded instance host
+ * @param meta a resolved Meta table
+ * @returns whether the given host should be blocked
+ */ export async function shouldBlockInstance(host, meta) {
+ const { blockedHosts } = meta ?? await fetchMeta();
+ return blockedHosts.some((blockedHost)=>host === blockedHost || host.endsWith(`.${blockedHost}`));
+}
+/**
+ * Returns whether a specific host (punycoded) should be limited.
+ *
+ * @param host punycoded instance host
+ * @param meta a resolved Meta table
+ * @returns whether the given host should be limited
+ */ export async function shouldSilenceInstance(host, meta) {
+ const { silencedHosts } = meta ?? await fetchMeta();
+ return silencedHosts.some((silencedHost)=>host === silencedHost || host.endsWith(`.${silencedHost}`));
+}
diff --git a/packages/backend/built/misc/show-machine-info.js b/packages/backend/built/misc/show-machine-info.js
new file mode 100644
index 0000000..02a83de
--- /dev/null
+++ b/packages/backend/built/misc/show-machine-info.js
@@ -0,0 +1,11 @@
+import * as os from "node:os";
+import sysUtils from "systeminformation";
+export async function showMachineInfo(parentLogger) {
+ const logger = parentLogger.createSubLogger("machine");
+ logger.debug(`Hostname: ${os.hostname()}`);
+ logger.debug(`Platform: ${process.platform} Arch: ${process.arch}`);
+ const mem = await sysUtils.mem();
+ const totalmem = (mem.total / 1024 / 1024 / 1024).toFixed(1);
+ const availmem = (mem.available / 1024 / 1024 / 1024).toFixed(1);
+ logger.debug(`CPU: ${os.cpus().length} core MEM: ${totalmem}GB (available: ${availmem}GB)`);
+}
diff --git a/packages/backend/built/misc/skipped-instances.js b/packages/backend/built/misc/skipped-instances.js
new file mode 100644
index 0000000..d44ba6e
--- /dev/null
+++ b/packages/backend/built/misc/skipped-instances.js
@@ -0,0 +1,46 @@
+import { Brackets } from "typeorm";
+import { fetchMeta } from "./fetch-meta.js";
+import { Instances } from "../models/index.js";
+import { DAY } from "../const.js";
+import { shouldBlockInstance } from "./should-block-instance.js";
+// Threshold from last contact after which an instance will be considered
+// "dead" and should no longer get activities delivered to it.
+const deadThreshold = 7 * DAY;
+/**
+ * Returns the subset of hosts which should be skipped.
+ *
+ * @param hosts array of punycoded instance hosts
+ * @returns array of punycoed instance hosts that should be skipped (subset of hosts parameter)
+ */ export async function skippedInstances(hosts) {
+ // first check for blocked instances since that info may already be in memory
+ const meta = await fetchMeta();
+ const shouldSkip = await Promise.all(hosts.map((host)=>shouldBlockInstance(host, meta)));
+ const skipped = hosts.filter((_, i)=>shouldSkip[i]);
+ // if possible return early and skip accessing the database
+ if (skipped.length === hosts.length) return hosts;
+ const deadTime = new Date(Date.now() - deadThreshold);
+ return skipped.concat(await Instances.createQueryBuilder("instance").where("instance.host in (:...hosts)", {
+ // don't check hosts again that we already know are suspended
+ // also avoids adding duplicates to the list
+ hosts: hosts.filter((host)=>!skipped.includes(host))
+ }).andWhere(new Brackets((qb)=>{
+ qb.where("instance.isSuspended").orWhere(new Brackets((qb)=>{
+ qb.where("instance.isNotResponding").andWhere("instance.lastCommunicatedAt < :deadTime", {
+ deadTime
+ });
+ }));
+ })).select("host").getRawMany());
+}
+/**
+ * Returns whether a specific host (punycoded) should be skipped.
+ * Convenience wrapper around skippedInstances which should only be used if there is a single host to check.
+ * If you have multiple hosts, consider using skippedInstances instead to do a bulk check.
+ *
+ * @param host punycoded instance host
+ * @returns whether the given host should be skipped
+ */ export async function shouldSkipInstance(host) {
+ const skipped = await skippedInstances([
+ host
+ ]);
+ return skipped.length > 0;
+}
diff --git a/packages/backend/built/misc/sql-like-escape.js b/packages/backend/built/misc/sql-like-escape.js
new file mode 100644
index 0000000..6eecdc2
--- /dev/null
+++ b/packages/backend/built/misc/sql-like-escape.js
@@ -0,0 +1,3 @@
+export function sqlLikeEscape(s) {
+ return s.replace(/([%_\\])/g, "\\$1");
+}
diff --git a/packages/backend/built/misc/sql-regex-escape.js b/packages/backend/built/misc/sql-regex-escape.js
new file mode 100644
index 0000000..5a78415
--- /dev/null
+++ b/packages/backend/built/misc/sql-regex-escape.js
@@ -0,0 +1,3 @@
+export function sqlRegexEscape(s) {
+ return s.replace(/([!$()*+.:<=>?[\\\]^{|}-])/g, "\\$1");
+}
diff --git a/packages/backend/built/misc/truncate.js b/packages/backend/built/misc/truncate.js
new file mode 100644
index 0000000..914ebe3
--- /dev/null
+++ b/packages/backend/built/misc/truncate.js
@@ -0,0 +1,8 @@
+import { substring } from "stringz";
+export function truncate(input, size) {
+ if (!input) {
+ return input;
+ } else {
+ return substring(input, 0, size);
+ }
+}
diff --git a/packages/backend/built/misc/webhook-cache.js b/packages/backend/built/misc/webhook-cache.js
new file mode 100644
index 0000000..23dfc50
--- /dev/null
+++ b/packages/backend/built/misc/webhook-cache.js
@@ -0,0 +1,43 @@
+import { Webhooks } from "../models/index.js";
+import { subscriber } from "../db/redis.js";
+let webhooksFetched = false;
+let webhooks = [];
+export async function getActiveWebhooks() {
+ if (!webhooksFetched) {
+ webhooks = await Webhooks.findBy({
+ active: true
+ });
+ webhooksFetched = true;
+ }
+ return webhooks;
+}
+subscriber.on("message", async (_, data)=>{
+ const obj = JSON.parse(data);
+ if (obj.channel === "internal") {
+ const { type, body } = obj.message;
+ switch(type){
+ case "webhookCreated":
+ if (body.active) {
+ webhooks.push(body);
+ }
+ break;
+ case "webhookUpdated":
+ if (body.active) {
+ const i = webhooks.findIndex((a)=>a.id === body.id);
+ if (i > -1) {
+ webhooks[i] = body;
+ } else {
+ webhooks.push(body);
+ }
+ } else {
+ webhooks = webhooks.filter((a)=>a.id !== body.id);
+ }
+ break;
+ case "webhookDeleted":
+ webhooks = webhooks.filter((a)=>a.id !== body.id);
+ break;
+ default:
+ break;
+ }
+ }
+});
diff --git a/packages/backend/built/models/entities/abuse-user-report.js b/packages/backend/built/models/entities/abuse-user-report.js
new file mode 100644
index 0000000..bebe9ae
--- /dev/null
+++ b/packages/backend/built/models/entities/abuse-user-report.js
@@ -0,0 +1,117 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let AbuseUserReport = class AbuseUserReport {
+ id;
+ createdAt;
+ targetUserId;
+ targetUser;
+ reporterId;
+ reporter;
+ assigneeId;
+ assignee;
+ resolved;
+ forwarded;
+ comment;
+ //#region Denormalized fields
+ targetUserHost;
+ reporterHost;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], AbuseUserReport.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the AbuseUserReport."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], AbuseUserReport.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], AbuseUserReport.prototype, "targetUserId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], AbuseUserReport.prototype, "targetUser", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], AbuseUserReport.prototype, "reporterId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], AbuseUserReport.prototype, "reporter", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], AbuseUserReport.prototype, "assigneeId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "SET NULL"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], AbuseUserReport.prototype, "assignee", void 0);
+_ts_decorate([
+ Index(),
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], AbuseUserReport.prototype, "resolved", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], AbuseUserReport.prototype, "forwarded", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 2048
+ }),
+ _ts_metadata("design:type", String)
+], AbuseUserReport.prototype, "comment", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], AbuseUserReport.prototype, "targetUserHost", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], AbuseUserReport.prototype, "reporterHost", void 0);
+AbuseUserReport = _ts_decorate([
+ Entity()
+], AbuseUserReport);
diff --git a/packages/backend/built/models/entities/access-token.js b/packages/backend/built/models/entities/access-token.js
new file mode 100644
index 0000000..8c85863
--- /dev/null
+++ b/packages/backend/built/models/entities/access-token.js
@@ -0,0 +1,132 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, PrimaryColumn, Index, Column, ManyToOne, JoinColumn } from "typeorm";
+import { User } from "./user.js";
+import { App } from "./app.js";
+import { id } from "../id.js";
+export let AccessToken = class AccessToken {
+ id;
+ createdAt;
+ lastUsedAt;
+ token;
+ session;
+ hash;
+ userId;
+ user;
+ appId;
+ app;
+ name;
+ description;
+ iconUrl;
+ permission;
+ fetched;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], AccessToken.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the AccessToken."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], AccessToken.prototype, "createdAt", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], AccessToken.prototype, "lastUsedAt", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 128
+ }),
+ _ts_metadata("design:type", String)
+], AccessToken.prototype, "token", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], AccessToken.prototype, "session", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 128
+ }),
+ _ts_metadata("design:type", String)
+], AccessToken.prototype, "hash", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], AccessToken.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], AccessToken.prototype, "user", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], AccessToken.prototype, "appId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>App, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], AccessToken.prototype, "app", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], AccessToken.prototype, "name", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], AccessToken.prototype, "description", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], AccessToken.prototype, "iconUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 64,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], AccessToken.prototype, "permission", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], AccessToken.prototype, "fetched", void 0);
+AccessToken = _ts_decorate([
+ Entity()
+], AccessToken);
diff --git a/packages/backend/built/models/entities/announcement-read.js b/packages/backend/built/models/entities/announcement-read.js
new file mode 100644
index 0000000..808e251
--- /dev/null
+++ b/packages/backend/built/models/entities/announcement-read.js
@@ -0,0 +1,64 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { Announcement } from "./announcement.js";
+import { id } from "../id.js";
+export let AnnouncementRead = class AnnouncementRead {
+ id;
+ createdAt;
+ userId;
+ user;
+ announcementId;
+ announcement;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], AnnouncementRead.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the AnnouncementRead."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], AnnouncementRead.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], AnnouncementRead.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], AnnouncementRead.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], AnnouncementRead.prototype, "announcementId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Announcement, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], AnnouncementRead.prototype, "announcement", void 0);
+AnnouncementRead = _ts_decorate([
+ Entity(),
+ Index([
+ "userId",
+ "announcementId"
+ ], {
+ unique: true
+ })
+], AnnouncementRead);
diff --git a/packages/backend/built/models/entities/announcement.js b/packages/backend/built/models/entities/announcement.js
new file mode 100644
index 0000000..119f012
--- /dev/null
+++ b/packages/backend/built/models/entities/announcement.js
@@ -0,0 +1,85 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, Index, Column, PrimaryColumn } from "typeorm";
+import { id } from "../id.js";
+export let Announcement = class Announcement {
+ id;
+ createdAt;
+ updatedAt;
+ text;
+ title;
+ imageUrl;
+ showPopup;
+ isGoodNews;
+ constructor(data){
+ if (data == null) return;
+ for (const [k, v] of Object.entries(data)){
+ this[k] = v;
+ }
+ }
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Announcement.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the Announcement."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], Announcement.prototype, "createdAt", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The updated date of the Announcement.",
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Announcement.prototype, "updatedAt", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 8192,
+ nullable: false
+ }),
+ _ts_metadata("design:type", String)
+], Announcement.prototype, "text", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ nullable: false
+ }),
+ _ts_metadata("design:type", String)
+], Announcement.prototype, "title", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 1024,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Announcement.prototype, "imageUrl", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Announcement.prototype, "showPopup", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Announcement.prototype, "isGoodNews", void 0);
+Announcement = _ts_decorate([
+ Entity(),
+ _ts_metadata("design:type", Function),
+ _ts_metadata("design:paramtypes", [
+ typeof Partial === "undefined" ? Object : Partial
+ ])
+], Announcement);
diff --git a/packages/backend/built/models/entities/antenna.js b/packages/backend/built/models/entities/antenna.js
new file mode 100644
index 0000000..3856e39
--- /dev/null
+++ b/packages/backend/built/models/entities/antenna.js
@@ -0,0 +1,164 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+import { UserList } from "./user-list.js";
+import { UserGroupJoining } from "./user-group-joining.js";
+export let Antenna = class Antenna {
+ id;
+ createdAt;
+ userId;
+ user;
+ name;
+ src;
+ userListId;
+ userList;
+ userGroupJoiningId;
+ userGroupJoining;
+ users;
+ instances;
+ keywords;
+ excludeKeywords;
+ caseSensitive;
+ withReplies;
+ withFile;
+ expression;
+ notify;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Antenna.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the Antenna."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], Antenna.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The owner ID."
+ }),
+ _ts_metadata("design:type", Object)
+], Antenna.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Antenna.prototype, "user", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ comment: "The name of the Antenna."
+ }),
+ _ts_metadata("design:type", String)
+], Antenna.prototype, "name", void 0);
+_ts_decorate([
+ Column("enum", {
+ enum: [
+ "home",
+ "all",
+ "users",
+ "list",
+ "group",
+ "instances"
+ ]
+ }),
+ _ts_metadata("design:type", String)
+], Antenna.prototype, "src", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Antenna.prototype, "userListId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>UserList, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Antenna.prototype, "userList", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Antenna.prototype, "userGroupJoiningId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>UserGroupJoining, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Antenna.prototype, "userGroupJoining", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 1024,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Antenna.prototype, "users", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: []
+ }),
+ _ts_metadata("design:type", Array)
+], Antenna.prototype, "instances", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: []
+ }),
+ _ts_metadata("design:type", Array)
+], Antenna.prototype, "keywords", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: []
+ }),
+ _ts_metadata("design:type", Array)
+], Antenna.prototype, "excludeKeywords", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Antenna.prototype, "caseSensitive", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Antenna.prototype, "withReplies", void 0);
+_ts_decorate([
+ Column("boolean"),
+ _ts_metadata("design:type", Boolean)
+], Antenna.prototype, "withFile", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 2048,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Antenna.prototype, "expression", void 0);
+_ts_decorate([
+ Column("boolean"),
+ _ts_metadata("design:type", Boolean)
+], Antenna.prototype, "notify", void 0);
+Antenna = _ts_decorate([
+ Entity()
+], Antenna);
diff --git a/packages/backend/built/models/entities/app.js b/packages/backend/built/models/entities/app.js
new file mode 100644
index 0000000..574954f
--- /dev/null
+++ b/packages/backend/built/models/entities/app.js
@@ -0,0 +1,91 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, PrimaryColumn, Column, Index, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let App = class App {
+ id;
+ createdAt;
+ userId;
+ user;
+ secret;
+ name;
+ description;
+ permission;
+ callbackUrl;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], App.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the App."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], App.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "The owner ID."
+ }),
+ _ts_metadata("design:type", Object)
+], App.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "SET NULL",
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], App.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 64,
+ comment: "The secret key of the App."
+ }),
+ _ts_metadata("design:type", String)
+], App.prototype, "secret", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ comment: "The name of the App."
+ }),
+ _ts_metadata("design:type", String)
+], App.prototype, "name", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ comment: "The description of the App."
+ }),
+ _ts_metadata("design:type", String)
+], App.prototype, "description", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 64,
+ array: true,
+ comment: "The permission of the App."
+ }),
+ _ts_metadata("design:type", Array)
+], App.prototype, "permission", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "The callbackUrl of the App."
+ }),
+ _ts_metadata("design:type", Object)
+], App.prototype, "callbackUrl", void 0);
+App = _ts_decorate([
+ Entity()
+], App);
diff --git a/packages/backend/built/models/entities/attestation-challenge.js b/packages/backend/built/models/entities/attestation-challenge.js
new file mode 100644
index 0000000..e6ad3e3
--- /dev/null
+++ b/packages/backend/built/models/entities/attestation-challenge.js
@@ -0,0 +1,70 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, JoinColumn, Column, ManyToOne, Index } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let AttestationChallenge = class AttestationChallenge {
+ id;
+ userId;
+ user;
+ challenge;
+ createdAt;
+ registrationChallenge;
+ constructor(data){
+ if (data == null) return;
+ for (const [k, v] of Object.entries(data)){
+ this[k] = v;
+ }
+ }
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], AttestationChallenge.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", Object)
+], AttestationChallenge.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], AttestationChallenge.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 64,
+ comment: "Hex-encoded sha256 hash of the challenge."
+ }),
+ _ts_metadata("design:type", String)
+], AttestationChallenge.prototype, "challenge", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The date challenge was created for expiry purposes."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], AttestationChallenge.prototype, "createdAt", void 0);
+_ts_decorate([
+ Column("boolean", {
+ comment: "Indicates that the challenge is only for registration purposes if true to prevent the challenge for being used as authentication.",
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], AttestationChallenge.prototype, "registrationChallenge", void 0);
+AttestationChallenge = _ts_decorate([
+ Entity(),
+ _ts_metadata("design:type", Function),
+ _ts_metadata("design:paramtypes", [
+ typeof Partial === "undefined" ? Object : Partial
+ ])
+], AttestationChallenge);
diff --git a/packages/backend/built/models/entities/auth-session.js b/packages/backend/built/models/entities/auth-session.js
new file mode 100644
index 0000000..6871870
--- /dev/null
+++ b/packages/backend/built/models/entities/auth-session.js
@@ -0,0 +1,68 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, PrimaryColumn, Index, Column, ManyToOne, JoinColumn } from "typeorm";
+import { User } from "./user.js";
+import { App } from "./app.js";
+import { id } from "../id.js";
+export let AuthSession = class AuthSession {
+ id;
+ createdAt;
+ token;
+ userId;
+ user;
+ appId;
+ app;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], AuthSession.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the AuthSession."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], AuthSession.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 128
+ }),
+ _ts_metadata("design:type", String)
+], AuthSession.prototype, "token", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], AuthSession.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE",
+ nullable: true
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], AuthSession.prototype, "user", void 0);
+_ts_decorate([
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], AuthSession.prototype, "appId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>App, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], AuthSession.prototype, "app", void 0);
+AuthSession = _ts_decorate([
+ Entity()
+], AuthSession);
diff --git a/packages/backend/built/models/entities/bite.js b/packages/backend/built/models/entities/bite.js
new file mode 100644
index 0000000..7440d15
--- /dev/null
+++ b/packages/backend/built/models/entities/bite.js
@@ -0,0 +1,114 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Check, Column, Entity, ManyToOne, PrimaryColumn } from "typeorm";
+import { id } from "../id.js";
+import { User } from "./user.js";
+import { Note } from "./note.js";
+export let Bite = class Bite {
+ id;
+ createdAt;
+ uri;
+ userId;
+ user;
+ userHost;
+ targetUserId;
+ targetUser;
+ targetBiteId;
+ targetBite;
+ targetNoteId;
+ targetNote;
+ replied;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Bite.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone"),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], Bite.prototype, "createdAt", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "null if local"
+ }),
+ _ts_metadata("design:type", Object)
+], Bite.prototype, "uri", void 0);
+_ts_decorate([
+ Column(id()),
+ _ts_metadata("design:type", String)
+], Bite.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne(()=>User, {
+ onDelete: "CASCADE"
+ }),
+ _ts_metadata("design:type", Object)
+], Bite.prototype, "user", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", String)
+], Bite.prototype, "userHost", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Bite.prototype, "targetUserId", void 0);
+_ts_decorate([
+ ManyToOne(()=>User, {
+ onDelete: "CASCADE",
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Bite.prototype, "targetUser", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Bite.prototype, "targetBiteId", void 0);
+_ts_decorate([
+ ManyToOne(()=>Bite, {
+ onDelete: "CASCADE",
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Bite.prototype, "targetBite", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Bite.prototype, "targetNoteId", void 0);
+_ts_decorate([
+ ManyToOne(()=>Note, {
+ onDelete: "CASCADE",
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Bite.prototype, "targetNote", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: true
+ }),
+ _ts_metadata("design:type", Boolean)
+], Bite.prototype, "replied", void 0);
+Bite = _ts_decorate([
+ Entity(),
+ Check(`"targetUserId" IS NOT NULL OR "targetBiteId" IS NOT NULL OR "targetNoteId" IS NOT NULL`)
+], Bite);
diff --git a/packages/backend/built/models/entities/blocking.js b/packages/backend/built/models/entities/blocking.js
new file mode 100644
index 0000000..4988ad7
--- /dev/null
+++ b/packages/backend/built/models/entities/blocking.js
@@ -0,0 +1,96 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { UserGroup } from "./user-group.js";
+import { id } from "../id.js";
+export let Blocking = class Blocking {
+ id;
+ createdAt;
+ blockeeId;
+ blockee;
+ groupId;
+ group;
+ blockerId;
+ blocker;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Blocking.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the Blocking."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], Blocking.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The blockee user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], Blocking.prototype, "blockeeId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Blocking.prototype, "blockee", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Blocking.prototype, "groupId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>UserGroup, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Blocking.prototype, "group", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The blocker user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], Blocking.prototype, "blockerId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Blocking.prototype, "blocker", void 0);
+Blocking = _ts_decorate([
+ Entity(),
+ Index([
+ "blockerId",
+ "blockeeId"
+ ], {
+ unique: true,
+ where: '"groupId" IS NULL'
+ }),
+ Index([
+ "groupId",
+ "blockeeId"
+ ], {
+ unique: true,
+ where: '"groupId" IS NOT NULL'
+ })
+], Blocking);
diff --git a/packages/backend/built/models/entities/call-blocking.js b/packages/backend/built/models/entities/call-blocking.js
new file mode 100644
index 0000000..b7d8f51
--- /dev/null
+++ b/packages/backend/built/models/entities/call-blocking.js
@@ -0,0 +1,96 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { UserGroup } from "./user-group.js";
+import { id } from "../id.js";
+export let CallBlocking = class CallBlocking {
+ id;
+ createdAt;
+ blockeeId;
+ blockee;
+ groupId;
+ group;
+ blockerId;
+ blocker;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], CallBlocking.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the CallBlocking."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], CallBlocking.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The call-blocked user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], CallBlocking.prototype, "blockeeId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], CallBlocking.prototype, "blockee", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], CallBlocking.prototype, "groupId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>UserGroup, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], CallBlocking.prototype, "group", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The call-blocking user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], CallBlocking.prototype, "blockerId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], CallBlocking.prototype, "blocker", void 0);
+CallBlocking = _ts_decorate([
+ Entity(),
+ Index([
+ "blockerId",
+ "blockeeId"
+ ], {
+ unique: true,
+ where: '"groupId" IS NULL'
+ }),
+ Index([
+ "groupId",
+ "blockeeId"
+ ], {
+ unique: true,
+ where: '"groupId" IS NOT NULL'
+ })
+], CallBlocking);
diff --git a/packages/backend/built/models/entities/channel-following.js b/packages/backend/built/models/entities/channel-following.js
new file mode 100644
index 0000000..1b6dfc8
--- /dev/null
+++ b/packages/backend/built/models/entities/channel-following.js
@@ -0,0 +1,71 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+import { Channel } from "./channel.js";
+export let ChannelFollowing = class ChannelFollowing {
+ id;
+ createdAt;
+ followeeId;
+ followee;
+ followerId;
+ follower;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], ChannelFollowing.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the ChannelFollowing."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], ChannelFollowing.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The followee channel ID."
+ }),
+ _ts_metadata("design:type", Object)
+], ChannelFollowing.prototype, "followeeId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Channel, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], ChannelFollowing.prototype, "followee", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The follower user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], ChannelFollowing.prototype, "followerId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], ChannelFollowing.prototype, "follower", void 0);
+ChannelFollowing = _ts_decorate([
+ Entity(),
+ Index([
+ "followerId",
+ "followeeId"
+ ], {
+ unique: true
+ })
+], ChannelFollowing);
diff --git a/packages/backend/built/models/entities/channel-note-pining.js b/packages/backend/built/models/entities/channel-note-pining.js
new file mode 100644
index 0000000..61e5f16
--- /dev/null
+++ b/packages/backend/built/models/entities/channel-note-pining.js
@@ -0,0 +1,63 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { Note } from "./note.js";
+import { Channel } from "./channel.js";
+import { id } from "../id.js";
+export let ChannelNotePining = class ChannelNotePining {
+ id;
+ createdAt;
+ channelId;
+ channel;
+ noteId;
+ note;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], ChannelNotePining.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the ChannelNotePining."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], ChannelNotePining.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], ChannelNotePining.prototype, "channelId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Channel, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], ChannelNotePining.prototype, "channel", void 0);
+_ts_decorate([
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], ChannelNotePining.prototype, "noteId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], ChannelNotePining.prototype, "note", void 0);
+ChannelNotePining = _ts_decorate([
+ Entity(),
+ Index([
+ "channelId",
+ "noteId"
+ ], {
+ unique: true
+ })
+], ChannelNotePining);
diff --git a/packages/backend/built/models/entities/channel.js b/packages/backend/built/models/entities/channel.js
new file mode 100644
index 0000000..d226e60
--- /dev/null
+++ b/packages/backend/built/models/entities/channel.js
@@ -0,0 +1,109 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+import { DriveFile } from "./drive-file.js";
+export let Channel = class Channel {
+ id;
+ createdAt;
+ lastNotedAt;
+ userId;
+ user;
+ name;
+ description;
+ bannerId;
+ banner;
+ notesCount;
+ usersCount;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Channel.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the Channel."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], Channel.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Channel.prototype, "lastNotedAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "The owner ID."
+ }),
+ _ts_metadata("design:type", Object)
+], Channel.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "SET NULL"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Channel.prototype, "user", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ comment: "The name of the Channel."
+ }),
+ _ts_metadata("design:type", String)
+], Channel.prototype, "name", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 2048,
+ nullable: true,
+ comment: "The description of the Channel."
+ }),
+ _ts_metadata("design:type", Object)
+], Channel.prototype, "description", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "The ID of banner Channel."
+ }),
+ _ts_metadata("design:type", Object)
+], Channel.prototype, "bannerId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>DriveFile, {
+ onDelete: "SET NULL"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Channel.prototype, "banner", void 0);
+_ts_decorate([
+ Index(),
+ Column("integer", {
+ default: 0,
+ comment: "The count of notes."
+ }),
+ _ts_metadata("design:type", Number)
+], Channel.prototype, "notesCount", void 0);
+_ts_decorate([
+ Index(),
+ Column("integer", {
+ default: 0,
+ comment: "The count of users."
+ }),
+ _ts_metadata("design:type", Number)
+], Channel.prototype, "usersCount", void 0);
+Channel = _ts_decorate([
+ Entity()
+], Channel);
diff --git a/packages/backend/built/models/entities/clip-note.js b/packages/backend/built/models/entities/clip-note.js
new file mode 100644
index 0000000..95c48f9
--- /dev/null
+++ b/packages/backend/built/models/entities/clip-note.js
@@ -0,0 +1,63 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, Index, JoinColumn, Column, ManyToOne, PrimaryColumn } from "typeorm";
+import { Note } from "./note.js";
+import { Clip } from "./clip.js";
+import { id } from "../id.js";
+export let ClipNote = class ClipNote {
+ id;
+ noteId;
+ note;
+ clipId;
+ clip;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], ClipNote.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The note ID."
+ }),
+ _ts_metadata("design:type", Object)
+], ClipNote.prototype, "noteId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], ClipNote.prototype, "note", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The clip ID."
+ }),
+ _ts_metadata("design:type", Object)
+], ClipNote.prototype, "clipId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Clip, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], ClipNote.prototype, "clip", void 0);
+ClipNote = _ts_decorate([
+ Entity(),
+ Index([
+ "noteId",
+ "clipId"
+ ], {
+ unique: true
+ })
+], ClipNote);
diff --git a/packages/backend/built/models/entities/clip.js b/packages/backend/built/models/entities/clip.js
new file mode 100644
index 0000000..bdc23c3
--- /dev/null
+++ b/packages/backend/built/models/entities/clip.js
@@ -0,0 +1,70 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let Clip = class Clip {
+ id;
+ createdAt;
+ userId;
+ user;
+ name;
+ isPublic;
+ description;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Clip.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the Clip."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], Clip.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The owner ID."
+ }),
+ _ts_metadata("design:type", Object)
+], Clip.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Clip.prototype, "user", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ comment: "The name of the Clip."
+ }),
+ _ts_metadata("design:type", String)
+], Clip.prototype, "name", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Clip.prototype, "isPublic", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 2048,
+ nullable: true,
+ comment: "The description of the Clip."
+ }),
+ _ts_metadata("design:type", Object)
+], Clip.prototype, "description", void 0);
+Clip = _ts_decorate([
+ Entity()
+], Clip);
diff --git a/packages/backend/built/models/entities/drive-file.js b/packages/backend/built/models/entities/drive-file.js
new file mode 100644
index 0000000..8023e2f
--- /dev/null
+++ b/packages/backend/built/models/entities/drive-file.js
@@ -0,0 +1,286 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { id } from "../id.js";
+import { User } from "./user.js";
+import { DriveFolder } from "./drive-folder.js";
+import { DB_MAX_IMAGE_COMMENT_LENGTH } from "../../misc/hard-limits.js";
+export let DriveFile = class DriveFile {
+ id;
+ createdAt;
+ userId;
+ user;
+ userHost;
+ md5;
+ name;
+ type;
+ size;
+ comment;
+ blurhash;
+ properties;
+ storedInternal;
+ url;
+ thumbnailUrl;
+ webpublicUrl;
+ webpublicType;
+ accessKey;
+ thumbnailAccessKey;
+ webpublicAccessKey;
+ uri;
+ src;
+ folderId;
+ folder;
+ isSensitive;
+ allowDownload;
+ isDatabase;
+ /**
+ * 外部の(信頼されていない)URLへの直リンクか否か
+ */ isLink;
+ requestHeaders;
+ requestIp;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], DriveFile.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the DriveFile."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], DriveFile.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "The owner ID."
+ }),
+ _ts_metadata("design:type", Object)
+], DriveFile.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "SET NULL"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], DriveFile.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "The host of owner. It will be null if the user in local."
+ }),
+ _ts_metadata("design:type", Object)
+], DriveFile.prototype, "userHost", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 32,
+ comment: "The MD5 hash of the DriveFile."
+ }),
+ _ts_metadata("design:type", String)
+], DriveFile.prototype, "md5", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ comment: "The file name of the DriveFile."
+ }),
+ _ts_metadata("design:type", String)
+], DriveFile.prototype, "name", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 128,
+ comment: "The content type (MIME) of the DriveFile."
+ }),
+ _ts_metadata("design:type", String)
+], DriveFile.prototype, "type", void 0);
+_ts_decorate([
+ Column("integer", {
+ comment: "The file size (bytes) of the DriveFile."
+ }),
+ _ts_metadata("design:type", Number)
+], DriveFile.prototype, "size", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: DB_MAX_IMAGE_COMMENT_LENGTH,
+ nullable: true,
+ comment: "The comment of the DriveFile."
+ }),
+ _ts_metadata("design:type", Object)
+], DriveFile.prototype, "comment", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true,
+ comment: "The BlurHash string."
+ }),
+ _ts_metadata("design:type", Object)
+], DriveFile.prototype, "blurhash", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: {},
+ comment: "The any properties of the DriveFile. For example, it includes image width/height."
+ }),
+ _ts_metadata("design:type", Object)
+], DriveFile.prototype, "properties", void 0);
+_ts_decorate([
+ Column("boolean"),
+ _ts_metadata("design:type", Boolean)
+], DriveFile.prototype, "storedInternal", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ comment: "The URL of the DriveFile."
+ }),
+ _ts_metadata("design:type", String)
+], DriveFile.prototype, "url", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "The URL of the thumbnail of the DriveFile."
+ }),
+ _ts_metadata("design:type", Object)
+], DriveFile.prototype, "thumbnailUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "The URL of the webpublic of the DriveFile."
+ }),
+ _ts_metadata("design:type", Object)
+], DriveFile.prototype, "webpublicUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], DriveFile.prototype, "webpublicType", void 0);
+_ts_decorate([
+ Index({
+ unique: true
+ }),
+ Column("varchar", {
+ length: 256,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], DriveFile.prototype, "accessKey", void 0);
+_ts_decorate([
+ Index({
+ unique: true
+ }),
+ Column("varchar", {
+ length: 256,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], DriveFile.prototype, "thumbnailAccessKey", void 0);
+_ts_decorate([
+ Index({
+ unique: true
+ }),
+ Column("varchar", {
+ length: 256,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], DriveFile.prototype, "webpublicAccessKey", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "The URI of the DriveFile. it will be null when the DriveFile is local."
+ }),
+ _ts_metadata("design:type", Object)
+], DriveFile.prototype, "uri", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], DriveFile.prototype, "src", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "The parent folder ID. If null, it means the DriveFile is located in root."
+ }),
+ _ts_metadata("design:type", Object)
+], DriveFile.prototype, "folderId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>DriveFolder, {
+ onDelete: "SET NULL"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], DriveFile.prototype, "folder", void 0);
+_ts_decorate([
+ Index(),
+ Column("boolean", {
+ default: false,
+ comment: "Whether the DriveFile is NSFW."
+ }),
+ _ts_metadata("design:type", Boolean)
+], DriveFile.prototype, "isSensitive", void 0);
+_ts_decorate([
+ Index(),
+ Column("boolean", {
+ default: true,
+ comment: "Whether the DriveFile can be downloaded by users other than its owner."
+ }),
+ _ts_metadata("design:type", Boolean)
+], DriveFile.prototype, "allowDownload", void 0);
+_ts_decorate([
+ Index(),
+ Column("boolean", {
+ default: false,
+ comment: "Whether the DriveFile is a Lua4Frozen database file."
+ }),
+ _ts_metadata("design:type", Boolean)
+], DriveFile.prototype, "isDatabase", void 0);
+_ts_decorate([
+ Index(),
+ Column("boolean", {
+ default: false,
+ comment: "Whether the DriveFile is direct link to remote server."
+ }),
+ _ts_metadata("design:type", Boolean)
+], DriveFile.prototype, "isLink", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: {},
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], DriveFile.prototype, "requestHeaders", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], DriveFile.prototype, "requestIp", void 0);
+DriveFile = _ts_decorate([
+ Entity(),
+ Index([
+ "userId",
+ "folderId",
+ "id"
+ ])
+], DriveFile);
diff --git a/packages/backend/built/models/entities/drive-folder.js b/packages/backend/built/models/entities/drive-folder.js
new file mode 100644
index 0000000..86f2d87
--- /dev/null
+++ b/packages/backend/built/models/entities/drive-folder.js
@@ -0,0 +1,74 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { JoinColumn, ManyToOne, Entity, PrimaryColumn, Index, Column } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let DriveFolder = class DriveFolder {
+ id;
+ createdAt;
+ name;
+ userId;
+ user;
+ parentId;
+ parent;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], DriveFolder.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the DriveFolder."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], DriveFolder.prototype, "createdAt", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ comment: "The name of the DriveFolder."
+ }),
+ _ts_metadata("design:type", String)
+], DriveFolder.prototype, "name", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "The owner ID."
+ }),
+ _ts_metadata("design:type", Object)
+], DriveFolder.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], DriveFolder.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "The parent folder ID. If null, it means the DriveFolder is located in root."
+ }),
+ _ts_metadata("design:type", Object)
+], DriveFolder.prototype, "parentId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>DriveFolder, {
+ onDelete: "SET NULL"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], DriveFolder.prototype, "parent", void 0);
+DriveFolder = _ts_decorate([
+ Entity()
+], DriveFolder);
diff --git a/packages/backend/built/models/entities/emoji.js b/packages/backend/built/models/entities/emoji.js
new file mode 100644
index 0000000..796c505
--- /dev/null
+++ b/packages/backend/built/models/entities/emoji.js
@@ -0,0 +1,132 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, Column } from "typeorm";
+import { id } from "../id.js";
+export let Emoji = class Emoji {
+ id;
+ updatedAt;
+ name;
+ host;
+ category;
+ originalUrl;
+ publicUrl;
+ uri;
+ // publicUrlの方のtypeが入る
+ // (mime)
+ type;
+ aliases;
+ license;
+ glyph;
+ width;
+ height;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Emoji.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Emoji.prototype, "updatedAt", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 128
+ }),
+ _ts_metadata("design:type", String)
+], Emoji.prototype, "name", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Emoji.prototype, "host", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Emoji.prototype, "category", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512
+ }),
+ _ts_metadata("design:type", String)
+], Emoji.prototype, "originalUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ default: ""
+ }),
+ _ts_metadata("design:type", String)
+], Emoji.prototype, "publicUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Emoji.prototype, "uri", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 64,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Emoji.prototype, "type", void 0);
+_ts_decorate([
+ Column("varchar", {
+ array: true,
+ length: 128,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Emoji.prototype, "aliases", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 1024,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Emoji.prototype, "license", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Emoji.prototype, "glyph", void 0);
+_ts_decorate([
+ Column("integer", {
+ nullable: true,
+ comment: "Image width"
+ }),
+ _ts_metadata("design:type", Object)
+], Emoji.prototype, "width", void 0);
+_ts_decorate([
+ Column("integer", {
+ nullable: true,
+ comment: "Image height"
+ }),
+ _ts_metadata("design:type", Object)
+], Emoji.prototype, "height", void 0);
+Emoji = _ts_decorate([
+ Entity(),
+ Index([
+ "name",
+ "host"
+ ], {
+ unique: true
+ })
+], Emoji);
diff --git a/packages/backend/built/models/entities/follow-request.js b/packages/backend/built/models/entities/follow-request.js
new file mode 100644
index 0000000..18bb5ba
--- /dev/null
+++ b/packages/backend/built/models/entities/follow-request.js
@@ -0,0 +1,133 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let FollowRequest = class FollowRequest {
+ id;
+ createdAt;
+ followeeId;
+ followee;
+ followerId;
+ follower;
+ requestId;
+ //#region Denormalized fields
+ followerHost;
+ followerInbox;
+ followerSharedInbox;
+ followeeHost;
+ followeeInbox;
+ followeeSharedInbox;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], FollowRequest.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the FollowRequest."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], FollowRequest.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The followee user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], FollowRequest.prototype, "followeeId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], FollowRequest.prototype, "followee", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The follower user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], FollowRequest.prototype, "followerId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], FollowRequest.prototype, "follower", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true,
+ comment: "id of Follow Activity."
+ }),
+ _ts_metadata("design:type", Object)
+], FollowRequest.prototype, "requestId", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], FollowRequest.prototype, "followerHost", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], FollowRequest.prototype, "followerInbox", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], FollowRequest.prototype, "followerSharedInbox", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], FollowRequest.prototype, "followeeHost", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], FollowRequest.prototype, "followeeInbox", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], FollowRequest.prototype, "followeeSharedInbox", void 0);
+FollowRequest = _ts_decorate([
+ Entity(),
+ Index([
+ "followerId",
+ "followeeId"
+ ], {
+ unique: true
+ })
+], FollowRequest);
diff --git a/packages/backend/built/models/entities/following.js b/packages/backend/built/models/entities/following.js
new file mode 100644
index 0000000..bf69d33
--- /dev/null
+++ b/packages/backend/built/models/entities/following.js
@@ -0,0 +1,127 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let Following = class Following {
+ id;
+ createdAt;
+ followeeId;
+ followee;
+ followerId;
+ follower;
+ //#region Denormalized fields
+ followerHost;
+ followerInbox;
+ followerSharedInbox;
+ followeeHost;
+ followeeInbox;
+ followeeSharedInbox;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Following.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the Following."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], Following.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The followee user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], Following.prototype, "followeeId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Following.prototype, "followee", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The follower user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], Following.prototype, "followerId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Following.prototype, "follower", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], Following.prototype, "followerHost", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], Following.prototype, "followerInbox", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], Following.prototype, "followerSharedInbox", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], Following.prototype, "followeeHost", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], Following.prototype, "followeeInbox", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], Following.prototype, "followeeSharedInbox", void 0);
+Following = _ts_decorate([
+ Entity(),
+ Index([
+ "followerId",
+ "followeeId"
+ ], {
+ unique: true
+ })
+], Following);
diff --git a/packages/backend/built/models/entities/gallery-like.js b/packages/backend/built/models/entities/gallery-like.js
new file mode 100644
index 0000000..e22db88
--- /dev/null
+++ b/packages/backend/built/models/entities/gallery-like.js
@@ -0,0 +1,61 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+import { GalleryPost } from "./gallery-post.js";
+export let GalleryLike = class GalleryLike {
+ id;
+ createdAt;
+ userId;
+ user;
+ postId;
+ post;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], GalleryLike.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone"),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], GalleryLike.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], GalleryLike.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], GalleryLike.prototype, "user", void 0);
+_ts_decorate([
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], GalleryLike.prototype, "postId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>GalleryPost, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], GalleryLike.prototype, "post", void 0);
+GalleryLike = _ts_decorate([
+ Entity(),
+ Index([
+ "userId",
+ "postId"
+ ], {
+ unique: true
+ })
+], GalleryLike);
diff --git a/packages/backend/built/models/entities/gallery-post.js b/packages/backend/built/models/entities/gallery-post.js
new file mode 100644
index 0000000..1a95a0b
--- /dev/null
+++ b/packages/backend/built/models/entities/gallery-post.js
@@ -0,0 +1,117 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let GalleryPost = class GalleryPost {
+ id;
+ createdAt;
+ updatedAt;
+ title;
+ description;
+ userId;
+ user;
+ fileIds;
+ isSensitive;
+ likedCount;
+ tags;
+ constructor(data){
+ if (data == null) return;
+ for (const [k, v] of Object.entries(data)){
+ this[k] = v;
+ }
+ }
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], GalleryPost.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the GalleryPost."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], GalleryPost.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The updated date of the GalleryPost."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], GalleryPost.prototype, "updatedAt", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256
+ }),
+ _ts_metadata("design:type", String)
+], GalleryPost.prototype, "title", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 2048,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], GalleryPost.prototype, "description", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The ID of author."
+ }),
+ _ts_metadata("design:type", Object)
+], GalleryPost.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], GalleryPost.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], GalleryPost.prototype, "fileIds", void 0);
+_ts_decorate([
+ Index(),
+ Column("boolean", {
+ default: false,
+ comment: "Whether the post is sensitive."
+ }),
+ _ts_metadata("design:type", Boolean)
+], GalleryPost.prototype, "isSensitive", void 0);
+_ts_decorate([
+ Index(),
+ Column("integer", {
+ default: 0
+ }),
+ _ts_metadata("design:type", Number)
+], GalleryPost.prototype, "likedCount", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 128,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], GalleryPost.prototype, "tags", void 0);
+GalleryPost = _ts_decorate([
+ Entity(),
+ _ts_metadata("design:type", Function),
+ _ts_metadata("design:paramtypes", [
+ typeof Partial === "undefined" ? Object : Partial
+ ])
+], GalleryPost);
diff --git a/packages/backend/built/models/entities/hashtag.js b/packages/backend/built/models/entities/hashtag.js
new file mode 100644
index 0000000..9320fbe
--- /dev/null
+++ b/packages/backend/built/models/entities/hashtag.js
@@ -0,0 +1,127 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, PrimaryColumn, Index, Column } from "typeorm";
+import { id } from "../id.js";
+export let Hashtag = class Hashtag {
+ id;
+ name;
+ mentionedUserIds;
+ mentionedUsersCount;
+ mentionedLocalUserIds;
+ mentionedLocalUsersCount;
+ mentionedRemoteUserIds;
+ mentionedRemoteUsersCount;
+ attachedUserIds;
+ attachedUsersCount;
+ attachedLocalUserIds;
+ attachedLocalUsersCount;
+ attachedRemoteUserIds;
+ attachedRemoteUsersCount;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Hashtag.prototype, "id", void 0);
+_ts_decorate([
+ Index({
+ unique: true
+ }),
+ Column("varchar", {
+ length: 128
+ }),
+ _ts_metadata("design:type", String)
+], Hashtag.prototype, "name", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ array: true
+ }),
+ _ts_metadata("design:type", Array)
+], Hashtag.prototype, "mentionedUserIds", void 0);
+_ts_decorate([
+ Index(),
+ Column("integer", {
+ default: 0
+ }),
+ _ts_metadata("design:type", Number)
+], Hashtag.prototype, "mentionedUsersCount", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ array: true
+ }),
+ _ts_metadata("design:type", Array)
+], Hashtag.prototype, "mentionedLocalUserIds", void 0);
+_ts_decorate([
+ Index(),
+ Column("integer", {
+ default: 0
+ }),
+ _ts_metadata("design:type", Number)
+], Hashtag.prototype, "mentionedLocalUsersCount", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ array: true
+ }),
+ _ts_metadata("design:type", Array)
+], Hashtag.prototype, "mentionedRemoteUserIds", void 0);
+_ts_decorate([
+ Index(),
+ Column("integer", {
+ default: 0
+ }),
+ _ts_metadata("design:type", Number)
+], Hashtag.prototype, "mentionedRemoteUsersCount", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ array: true
+ }),
+ _ts_metadata("design:type", Array)
+], Hashtag.prototype, "attachedUserIds", void 0);
+_ts_decorate([
+ Index(),
+ Column("integer", {
+ default: 0
+ }),
+ _ts_metadata("design:type", Number)
+], Hashtag.prototype, "attachedUsersCount", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ array: true
+ }),
+ _ts_metadata("design:type", Array)
+], Hashtag.prototype, "attachedLocalUserIds", void 0);
+_ts_decorate([
+ Index(),
+ Column("integer", {
+ default: 0
+ }),
+ _ts_metadata("design:type", Number)
+], Hashtag.prototype, "attachedLocalUsersCount", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ array: true
+ }),
+ _ts_metadata("design:type", Array)
+], Hashtag.prototype, "attachedRemoteUserIds", void 0);
+_ts_decorate([
+ Index(),
+ Column("integer", {
+ default: 0
+ }),
+ _ts_metadata("design:type", Number)
+], Hashtag.prototype, "attachedRemoteUsersCount", void 0);
+Hashtag = _ts_decorate([
+ Entity()
+], Hashtag);
diff --git a/packages/backend/built/models/entities/html-note-cache-entry.js b/packages/backend/built/models/entities/html-note-cache-entry.js
new file mode 100644
index 0000000..445fd00
--- /dev/null
+++ b/packages/backend/built/models/entities/html-note-cache-entry.js
@@ -0,0 +1,44 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, PrimaryColumn, Column, ManyToOne, JoinColumn } from "typeorm";
+import { id } from "../id.js";
+import { Note } from "./note.js";
+export let HtmlNoteCacheEntry = class HtmlNoteCacheEntry {
+ noteId;
+ note;
+ updatedAt;
+ content;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", Object)
+], HtmlNoteCacheEntry.prototype, "noteId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], HtmlNoteCacheEntry.prototype, "note", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], HtmlNoteCacheEntry.prototype, "updatedAt", void 0);
+_ts_decorate([
+ Column("text", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], HtmlNoteCacheEntry.prototype, "content", void 0);
+HtmlNoteCacheEntry = _ts_decorate([
+ Entity()
+], HtmlNoteCacheEntry);
diff --git a/packages/backend/built/models/entities/html-user-cache-entry.js b/packages/backend/built/models/entities/html-user-cache-entry.js
new file mode 100644
index 0000000..21b75c3
--- /dev/null
+++ b/packages/backend/built/models/entities/html-user-cache-entry.js
@@ -0,0 +1,51 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, PrimaryColumn, Column, ManyToOne, JoinColumn } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let HtmlUserCacheEntry = class HtmlUserCacheEntry {
+ userId;
+ user;
+ updatedAt;
+ bio;
+ fields;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", Object)
+], HtmlUserCacheEntry.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], HtmlUserCacheEntry.prototype, "user", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], HtmlUserCacheEntry.prototype, "updatedAt", void 0);
+_ts_decorate([
+ Column("text", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], HtmlUserCacheEntry.prototype, "bio", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: []
+ }),
+ _ts_metadata("design:type", Array)
+], HtmlUserCacheEntry.prototype, "fields", void 0);
+HtmlUserCacheEntry = _ts_decorate([
+ Entity()
+], HtmlUserCacheEntry);
diff --git a/packages/backend/built/models/entities/instance.js b/packages/backend/built/models/entities/instance.js
new file mode 100644
index 0000000..9776786
--- /dev/null
+++ b/packages/backend/built/models/entities/instance.js
@@ -0,0 +1,222 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, PrimaryColumn, Index, Column } from "typeorm";
+import { id } from "../id.js";
+export let Instance = class Instance {
+ id;
+ /**
+ * このインスタンスを捕捉した日時
+ */ caughtAt;
+ /**
+ * ホスト
+ */ host;
+ /**
+ * インスタンスのユーザー数
+ */ usersCount;
+ /**
+ * インスタンスの投稿数
+ */ notesCount;
+ /**
+ * このインスタンスのユーザーからフォローされている、自インスタンスのユーザーの数
+ */ followingCount;
+ /**
+ * このインスタンスのユーザーをフォローしている、自インスタンスのユーザーの数
+ */ followersCount;
+ /**
+ * 直近のリクエスト送信日時
+ */ latestRequestSentAt;
+ /**
+ * 直近のリクエスト送信時のHTTPステータスコード
+ */ latestStatus;
+ /**
+ * 直近のリクエスト受信日時
+ */ latestRequestReceivedAt;
+ /**
+ * このインスタンスと最後にやり取りした日時
+ */ lastCommunicatedAt;
+ /**
+ * このインスタンスと不通かどうか
+ */ isNotResponding;
+ /**
+ * このインスタンスへの配信を停止するか
+ */ isSuspended;
+ softwareName;
+ softwareVersion;
+ openRegistrations;
+ name;
+ description;
+ maintainerName;
+ maintainerEmail;
+ iconUrl;
+ faviconUrl;
+ themeColor;
+ infoUpdatedAt;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Instance.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The caught date of the Instance."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], Instance.prototype, "caughtAt", void 0);
+_ts_decorate([
+ Index({
+ unique: true
+ }),
+ Column("varchar", {
+ length: 512,
+ comment: "The host of the Instance."
+ }),
+ _ts_metadata("design:type", String)
+], Instance.prototype, "host", void 0);
+_ts_decorate([
+ Column("integer", {
+ default: 0,
+ comment: "The count of the users of the Instance."
+ }),
+ _ts_metadata("design:type", Number)
+], Instance.prototype, "usersCount", void 0);
+_ts_decorate([
+ Column("integer", {
+ default: 0,
+ comment: "The count of the notes of the Instance."
+ }),
+ _ts_metadata("design:type", Number)
+], Instance.prototype, "notesCount", void 0);
+_ts_decorate([
+ Column("integer", {
+ default: 0
+ }),
+ _ts_metadata("design:type", Number)
+], Instance.prototype, "followingCount", void 0);
+_ts_decorate([
+ Column("integer", {
+ default: 0
+ }),
+ _ts_metadata("design:type", Number)
+], Instance.prototype, "followersCount", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Instance.prototype, "latestRequestSentAt", void 0);
+_ts_decorate([
+ Column("integer", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Instance.prototype, "latestStatus", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Instance.prototype, "latestRequestReceivedAt", void 0);
+_ts_decorate([
+ Column("timestamp with time zone"),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], Instance.prototype, "lastCommunicatedAt", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Instance.prototype, "isNotResponding", void 0);
+_ts_decorate([
+ Index(),
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Instance.prototype, "isSuspended", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 64,
+ nullable: true,
+ comment: "The software of the Instance."
+ }),
+ _ts_metadata("design:type", Object)
+], Instance.prototype, "softwareName", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 64,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Instance.prototype, "softwareVersion", void 0);
+_ts_decorate([
+ Column("boolean", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Instance.prototype, "openRegistrations", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Instance.prototype, "name", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 4096,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Instance.prototype, "description", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Instance.prototype, "maintainerName", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Instance.prototype, "maintainerEmail", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 4096,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Instance.prototype, "iconUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 4096,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Instance.prototype, "faviconUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 64,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Instance.prototype, "themeColor", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Instance.prototype, "infoUpdatedAt", void 0);
+Instance = _ts_decorate([
+ Entity()
+], Instance);
diff --git a/packages/backend/built/models/entities/interaction-stamp.js b/packages/backend/built/models/entities/interaction-stamp.js
new file mode 100644
index 0000000..4ddc33c
--- /dev/null
+++ b/packages/backend/built/models/entities/interaction-stamp.js
@@ -0,0 +1,63 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from "typeorm";
+import { id } from "../id.js";
+import { Note } from "./note.js";
+export let InteractionStamp = class InteractionStamp {
+ id;
+ type;
+ targetNoteId;
+ targetNote;
+ noteId;
+ note;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], InteractionStamp.prototype, "id", void 0);
+_ts_decorate([
+ Column("enum", {
+ enum: [
+ "quote"
+ ]
+ }),
+ _ts_metadata("design:type", String)
+], InteractionStamp.prototype, "type", void 0);
+_ts_decorate([
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], InteractionStamp.prototype, "targetNoteId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], InteractionStamp.prototype, "targetNote", void 0);
+_ts_decorate([
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], InteractionStamp.prototype, "noteId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], InteractionStamp.prototype, "note", void 0);
+InteractionStamp = _ts_decorate([
+ Entity(),
+ Index([
+ "noteId",
+ "targetNoteId"
+ ], {
+ unique: true
+ })
+], InteractionStamp);
diff --git a/packages/backend/built/models/entities/memoriet-archive.js b/packages/backend/built/models/entities/memoriet-archive.js
new file mode 100644
index 0000000..3ba2f4f
--- /dev/null
+++ b/packages/backend/built/models/entities/memoriet-archive.js
@@ -0,0 +1,78 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Column, Entity, Index, PrimaryColumn } from "typeorm";
+import { id } from "../id.js";
+export let MemorietArchive = class MemorietArchive {
+ id;
+ createdAt;
+ deletedAt;
+ userId;
+ text;
+ cw;
+ fileIds;
+ textLayers;
+ visibility;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], MemorietArchive.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone"),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], MemorietArchive.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone"),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], MemorietArchive.prototype, "deletedAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], MemorietArchive.prototype, "userId", void 0);
+_ts_decorate([
+ Column("text", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], MemorietArchive.prototype, "text", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], MemorietArchive.prototype, "cw", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], MemorietArchive.prototype, "fileIds", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: []
+ }),
+ _ts_metadata("design:type", Array)
+], MemorietArchive.prototype, "textLayers", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 32,
+ default: "home"
+ }),
+ _ts_metadata("design:type", Object)
+], MemorietArchive.prototype, "visibility", void 0);
+MemorietArchive = _ts_decorate([
+ Entity("memoriet_archive")
+], MemorietArchive);
diff --git a/packages/backend/built/models/entities/memoriet-view.js b/packages/backend/built/models/entities/memoriet-view.js
new file mode 100644
index 0000000..95c7e53
--- /dev/null
+++ b/packages/backend/built/models/entities/memoriet-view.js
@@ -0,0 +1,69 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from "typeorm";
+import { id } from "../id.js";
+import { User } from "./user.js";
+import { Memoriet } from "./memoriet.js";
+export let MemorietView = class MemorietView {
+ id;
+ createdAt;
+ viewedAt;
+ memorietId;
+ memoriet;
+ viewerId;
+ viewer;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], MemorietView.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone"),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], MemorietView.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone"),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], MemorietView.prototype, "viewedAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], MemorietView.prototype, "memorietId", void 0);
+_ts_decorate([
+ ManyToOne(()=>Memoriet, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], MemorietView.prototype, "memoriet", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], MemorietView.prototype, "viewerId", void 0);
+_ts_decorate([
+ ManyToOne(()=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], MemorietView.prototype, "viewer", void 0);
+MemorietView = _ts_decorate([
+ Entity("memoriet_view"),
+ Index([
+ "memorietId",
+ "viewerId"
+ ], {
+ unique: true
+ })
+], MemorietView);
diff --git a/packages/backend/built/models/entities/memoriet.js b/packages/backend/built/models/entities/memoriet.js
new file mode 100644
index 0000000..44f9ffa
--- /dev/null
+++ b/packages/backend/built/models/entities/memoriet.js
@@ -0,0 +1,72 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from "typeorm";
+import { id } from "../id.js";
+import { User } from "./user.js";
+import { Note } from "./note.js";
+export let Memoriet = class Memoriet {
+ id;
+ createdAt;
+ userId;
+ user;
+ noteId;
+ note;
+ expiresAt;
+ textLayers;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Memoriet.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone"),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], Memoriet.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], Memoriet.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne(()=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Memoriet.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], Memoriet.prototype, "noteId", void 0);
+_ts_decorate([
+ ManyToOne(()=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Memoriet.prototype, "note", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Memoriet.prototype, "expiresAt", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: []
+ }),
+ _ts_metadata("design:type", Array)
+], Memoriet.prototype, "textLayers", void 0);
+Memoriet = _ts_decorate([
+ Entity("memoriet")
+], Memoriet);
diff --git a/packages/backend/built/models/entities/messaging-message.js b/packages/backend/built/models/entities/messaging-message.js
new file mode 100644
index 0000000..79fa0a6
--- /dev/null
+++ b/packages/backend/built/models/entities/messaging-message.js
@@ -0,0 +1,133 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { DriveFile } from "./drive-file.js";
+import { id } from "../id.js";
+import { UserGroup } from "./user-group.js";
+export let MessagingMessage = class MessagingMessage {
+ id;
+ createdAt;
+ userId;
+ user;
+ recipientId;
+ recipient;
+ groupId;
+ group;
+ text;
+ isRead;
+ uri;
+ reads;
+ fileId;
+ file;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], MessagingMessage.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the MessagingMessage."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], MessagingMessage.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The sender user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], MessagingMessage.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], MessagingMessage.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "The recipient user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], MessagingMessage.prototype, "recipientId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], MessagingMessage.prototype, "recipient", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "The recipient group ID."
+ }),
+ _ts_metadata("design:type", Object)
+], MessagingMessage.prototype, "groupId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>UserGroup, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], MessagingMessage.prototype, "group", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 4096,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], MessagingMessage.prototype, "text", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], MessagingMessage.prototype, "isRead", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], MessagingMessage.prototype, "uri", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], MessagingMessage.prototype, "reads", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], MessagingMessage.prototype, "fileId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>DriveFile, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], MessagingMessage.prototype, "file", void 0);
+MessagingMessage = _ts_decorate([
+ Entity()
+], MessagingMessage);
diff --git a/packages/backend/built/models/entities/meta.js b/packages/backend/built/models/entities/meta.js
new file mode 100644
index 0000000..611aee7
--- /dev/null
+++ b/packages/backend/built/models/entities/meta.js
@@ -0,0 +1,695 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, Column, PrimaryColumn } from "typeorm";
+import { id } from "../id.js";
+export let Meta = class Meta {
+ id;
+ name;
+ description;
+ /**
+ * メンテナの名前
+ */ maintainerName;
+ /**
+ * メンテナの連絡先
+ */ maintainerEmail;
+ disableRegistration;
+ disableLocalTimeline;
+ disableRecommendedTimeline;
+ disableGlobalTimeline;
+ defaultReaction;
+ langs;
+ pinnedUsers;
+ recommendedInstances;
+ customMOTD;
+ customSplashIcons;
+ hiddenTags;
+ blockedHosts;
+ silencedHosts;
+ secureMode;
+ privateMode;
+ allowedHosts;
+ pinnedPages;
+ pinnedClipId;
+ themeColor;
+ mascotImageUrl;
+ bannerUrl;
+ backgroundImageUrl;
+ logoImageUrl;
+ errorImageUrl;
+ iconUrl;
+ cacheRemoteFiles;
+ emailRequiredForSignup;
+ enableHcaptcha;
+ hcaptchaSiteKey;
+ hcaptchaSecretKey;
+ enableRecaptcha;
+ recaptchaSiteKey;
+ recaptchaSecretKey;
+ localDriveCapacityMb;
+ remoteDriveCapacityMb;
+ lua4frozenDatabaseCapacityMb;
+ summalyProxy;
+ enableEmail;
+ email;
+ smtpSecure;
+ smtpHost;
+ smtpPort;
+ smtpUser;
+ smtpPass;
+ swPublicKey;
+ swPrivateKey;
+ enableGithubIntegration;
+ githubClientId;
+ githubClientSecret;
+ enableDiscordIntegration;
+ discordClientId;
+ discordClientSecret;
+ deeplAuthKey;
+ deeplIsPro;
+ libreTranslateApiUrl;
+ libreTranslateApiKey;
+ ToSUrl;
+ repositoryUrl;
+ feedbackUrl;
+ defaultLightTheme;
+ defaultDarkTheme;
+ useObjectStorage;
+ objectStorageBucket;
+ objectStoragePrefix;
+ objectStorageBaseUrl;
+ objectStorageEndpoint;
+ objectStorageRegion;
+ objectStorageAccessKey;
+ objectStorageSecretKey;
+ objectStoragePort;
+ objectStorageUseSSL;
+ objectStorageUseProxy;
+ objectStorageSetPublicRead;
+ objectStorageS3ForcePathStyle;
+ enableIpLogging;
+ enableActiveEmailValidation;
+ experimentalFeatures;
+ enableServerMachineStats;
+ enableIdenticonGeneration;
+ donationLink;
+ autofollowedAccount;
+};
+_ts_decorate([
+ PrimaryColumn({
+ type: "varchar",
+ length: 32
+ }),
+ _ts_metadata("design:type", String)
+], Meta.prototype, "id", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "name", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 1024,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "description", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "maintainerName", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "maintainerEmail", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "disableRegistration", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "disableLocalTimeline", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: true
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "disableRecommendedTimeline", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "disableGlobalTimeline", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ default: "⭐"
+ }),
+ _ts_metadata("design:type", String)
+], Meta.prototype, "defaultReaction", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 64,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Meta.prototype, "langs", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Meta.prototype, "pinnedUsers", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Meta.prototype, "recommendedInstances", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Meta.prototype, "customMOTD", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Meta.prototype, "customSplashIcons", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Meta.prototype, "hiddenTags", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Meta.prototype, "blockedHosts", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Meta.prototype, "silencedHosts", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: true
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "secureMode", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "privateMode", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Meta.prototype, "allowedHosts", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ array: true,
+ default: "{/featured,/channels,/explore,/pages,/about-iceshrimp}"
+ }),
+ _ts_metadata("design:type", Array)
+], Meta.prototype, "pinnedPages", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "pinnedClipId", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "themeColor", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ default: "/twemoji/1f440.svg"
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "mascotImageUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "bannerUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "backgroundImageUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "logoImageUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ default: "/twemoji/1f480.svg"
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "errorImageUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "iconUrl", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "cacheRemoteFiles", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "emailRequiredForSignup", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "enableHcaptcha", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 64,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "hcaptchaSiteKey", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 64,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "hcaptchaSecretKey", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "enableRecaptcha", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 64,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "recaptchaSiteKey", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 64,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "recaptchaSecretKey", void 0);
+_ts_decorate([
+ Column("integer", {
+ default: 1024,
+ comment: "Drive capacity of a local user (MB)"
+ }),
+ _ts_metadata("design:type", Number)
+], Meta.prototype, "localDriveCapacityMb", void 0);
+_ts_decorate([
+ Column("integer", {
+ default: 32,
+ comment: "Drive capacity of a remote user (MB)"
+ }),
+ _ts_metadata("design:type", Number)
+], Meta.prototype, "remoteDriveCapacityMb", void 0);
+_ts_decorate([
+ Column("integer", {
+ default: 16,
+ comment: "Lua4Frozen database capacity of a local user (MB)"
+ }),
+ _ts_metadata("design:type", Number)
+], Meta.prototype, "lua4frozenDatabaseCapacityMb", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "summalyProxy", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "enableEmail", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "email", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "smtpSecure", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "smtpHost", void 0);
+_ts_decorate([
+ Column("integer", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "smtpPort", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 1024,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "smtpUser", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 1024,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "smtpPass", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: false
+ }),
+ _ts_metadata("design:type", String)
+], Meta.prototype, "swPublicKey", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: false
+ }),
+ _ts_metadata("design:type", String)
+], Meta.prototype, "swPrivateKey", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "enableGithubIntegration", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "githubClientId", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "githubClientSecret", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "enableDiscordIntegration", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "discordClientId", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "discordClientSecret", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "deeplAuthKey", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "deeplIsPro", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "libreTranslateApiUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "libreTranslateApiKey", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "ToSUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ default: "https://iceshrimp.dev/iceshrimp/iceshrimp",
+ nullable: false
+ }),
+ _ts_metadata("design:type", String)
+], Meta.prototype, "repositoryUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ default: "https://iceshrimp.dev/iceshrimp/iceshrimp/issues/new",
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "feedbackUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 8192,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "defaultLightTheme", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 8192,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "defaultDarkTheme", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "useObjectStorage", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "objectStorageBucket", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "objectStoragePrefix", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "objectStorageBaseUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "objectStorageEndpoint", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "objectStorageRegion", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "objectStorageAccessKey", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "objectStorageSecretKey", void 0);
+_ts_decorate([
+ Column("integer", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "objectStoragePort", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: true
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "objectStorageUseSSL", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: true
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "objectStorageUseProxy", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "objectStorageSetPublicRead", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: true
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "objectStorageS3ForcePathStyle", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "enableIpLogging", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: true
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "enableActiveEmailValidation", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: {}
+ }),
+ _ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
+], Meta.prototype, "experimentalFeatures", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "enableServerMachineStats", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: true
+ }),
+ _ts_metadata("design:type", Boolean)
+], Meta.prototype, "enableIdenticonGeneration", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "donationLink", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Meta.prototype, "autofollowedAccount", void 0);
+Meta = _ts_decorate([
+ Entity()
+], Meta);
diff --git a/packages/backend/built/models/entities/moderation-log.js b/packages/backend/built/models/entities/moderation-log.js
new file mode 100644
index 0000000..a172c8c
--- /dev/null
+++ b/packages/backend/built/models/entities/moderation-log.js
@@ -0,0 +1,55 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let ModerationLog = class ModerationLog {
+ id;
+ createdAt;
+ userId;
+ user;
+ type;
+ info;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], ModerationLog.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the ModerationLog."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], ModerationLog.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], ModerationLog.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], ModerationLog.prototype, "user", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128
+ }),
+ _ts_metadata("design:type", String)
+], ModerationLog.prototype, "type", void 0);
+_ts_decorate([
+ Column("jsonb"),
+ _ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
+], ModerationLog.prototype, "info", void 0);
+ModerationLog = _ts_decorate([
+ Entity()
+], ModerationLog);
diff --git a/packages/backend/built/models/entities/muting.js b/packages/backend/built/models/entities/muting.js
new file mode 100644
index 0000000..7719281
--- /dev/null
+++ b/packages/backend/built/models/entities/muting.js
@@ -0,0 +1,104 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { UserGroup } from "./user-group.js";
+import { id } from "../id.js";
+export let Muting = class Muting {
+ id;
+ createdAt;
+ expiresAt;
+ muteeId;
+ mutee;
+ groupId;
+ group;
+ muterId;
+ muter;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Muting.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the Muting."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], Muting.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Muting.prototype, "expiresAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The mutee user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], Muting.prototype, "muteeId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Muting.prototype, "mutee", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Muting.prototype, "groupId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>UserGroup, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Muting.prototype, "group", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The muter user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], Muting.prototype, "muterId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Muting.prototype, "muter", void 0);
+Muting = _ts_decorate([
+ Entity(),
+ Index([
+ "muterId",
+ "muteeId"
+ ], {
+ unique: true,
+ where: '"groupId" IS NULL'
+ }),
+ Index([
+ "groupId",
+ "muteeId"
+ ], {
+ unique: true,
+ where: '"groupId" IS NOT NULL'
+ })
+], Muting);
diff --git a/packages/backend/built/models/entities/note-edit.js b/packages/backend/built/models/entities/note-edit.js
new file mode 100644
index 0000000..d2c1f47
--- /dev/null
+++ b/packages/backend/built/models/entities/note-edit.js
@@ -0,0 +1,70 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, JoinColumn, Column, ManyToOne, PrimaryColumn, Index } from "typeorm";
+import { Note } from "./note.js";
+import { id } from "../id.js";
+export let NoteEdit = class NoteEdit {
+ id;
+ noteId;
+ note;
+ text;
+ cw;
+ fileIds;
+ updatedAt;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], NoteEdit.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The ID of note."
+ }),
+ _ts_metadata("design:type", Object)
+], NoteEdit.prototype, "noteId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], NoteEdit.prototype, "note", void 0);
+_ts_decorate([
+ Column("text", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], NoteEdit.prototype, "text", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], NoteEdit.prototype, "cw", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], NoteEdit.prototype, "fileIds", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The updated date of the Note."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], NoteEdit.prototype, "updatedAt", void 0);
+NoteEdit = _ts_decorate([
+ Entity()
+], NoteEdit);
diff --git a/packages/backend/built/models/entities/note-favorite.js b/packages/backend/built/models/entities/note-favorite.js
new file mode 100644
index 0000000..8a31f30
--- /dev/null
+++ b/packages/backend/built/models/entities/note-favorite.js
@@ -0,0 +1,89 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { Note } from "./note.js";
+import { User } from "./user.js";
+import { UserGroup } from "./user-group.js";
+import { id } from "../id.js";
+export let NoteFavorite = class NoteFavorite {
+ id;
+ createdAt;
+ userId;
+ user;
+ groupId;
+ group;
+ noteId;
+ note;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], NoteFavorite.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the NoteFavorite."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], NoteFavorite.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], NoteFavorite.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], NoteFavorite.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], NoteFavorite.prototype, "groupId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>UserGroup, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], NoteFavorite.prototype, "group", void 0);
+_ts_decorate([
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], NoteFavorite.prototype, "noteId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], NoteFavorite.prototype, "note", void 0);
+NoteFavorite = _ts_decorate([
+ Entity(),
+ Index([
+ "userId",
+ "noteId"
+ ], {
+ unique: true,
+ where: '"groupId" IS NULL'
+ }),
+ Index([
+ "groupId",
+ "noteId"
+ ], {
+ unique: true,
+ where: '"groupId" IS NOT NULL'
+ })
+], NoteFavorite);
diff --git a/packages/backend/built/models/entities/note-reaction.js b/packages/backend/built/models/entities/note-reaction.js
new file mode 100644
index 0000000..54bf620
--- /dev/null
+++ b/packages/backend/built/models/entities/note-reaction.js
@@ -0,0 +1,99 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { Note } from "./note.js";
+import { UserGroup } from "./user-group.js";
+import { id } from "../id.js";
+export let NoteReaction = class NoteReaction {
+ id;
+ createdAt;
+ userId;
+ user;
+ groupId;
+ group;
+ noteId;
+ note;
+ // TODO: 対象noteのuserIdを非正規化したい(「受け取ったリアクション一覧」のようなものを(JOIN無しで)実装したいため)
+ reaction;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], NoteReaction.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the NoteReaction."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], NoteReaction.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], NoteReaction.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], NoteReaction.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], NoteReaction.prototype, "groupId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>UserGroup, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], NoteReaction.prototype, "group", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], NoteReaction.prototype, "noteId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], NoteReaction.prototype, "note", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 260
+ }),
+ _ts_metadata("design:type", String)
+], NoteReaction.prototype, "reaction", void 0);
+NoteReaction = _ts_decorate([
+ Entity(),
+ Index([
+ "userId",
+ "noteId"
+ ], {
+ unique: true,
+ where: '"groupId" IS NULL'
+ }),
+ Index([
+ "groupId",
+ "noteId"
+ ], {
+ unique: true,
+ where: '"groupId" IS NOT NULL'
+ })
+], NoteReaction);
diff --git a/packages/backend/built/models/entities/note-thread-muting.js b/packages/backend/built/models/entities/note-thread-muting.js
new file mode 100644
index 0000000..fe0cc3f
--- /dev/null
+++ b/packages/backend/built/models/entities/note-thread-muting.js
@@ -0,0 +1,57 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let NoteThreadMuting = class NoteThreadMuting {
+ id;
+ createdAt;
+ userId;
+ user;
+ threadId;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], NoteThreadMuting.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {}),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], NoteThreadMuting.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id()
+ }),
+ _ts_metadata("design:type", Object)
+], NoteThreadMuting.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], NoteThreadMuting.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 256
+ }),
+ _ts_metadata("design:type", String)
+], NoteThreadMuting.prototype, "threadId", void 0);
+NoteThreadMuting = _ts_decorate([
+ Entity(),
+ Index([
+ "userId",
+ "threadId"
+ ], {
+ unique: true
+ })
+], NoteThreadMuting);
diff --git a/packages/backend/built/models/entities/note-unread.js b/packages/backend/built/models/entities/note-unread.js
new file mode 100644
index 0000000..533ff6f
--- /dev/null
+++ b/packages/backend/built/models/entities/note-unread.js
@@ -0,0 +1,93 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { Note } from "./note.js";
+import { id } from "../id.js";
+export let NoteUnread = class NoteUnread {
+ id;
+ userId;
+ user;
+ noteId;
+ note;
+ /**
+ * メンションか否か
+ */ isMentioned;
+ /**
+ * ダイレクト投稿か否か
+ */ isSpecified;
+ //#region Denormalized fields
+ noteUserId;
+ noteChannelId;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], NoteUnread.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], NoteUnread.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], NoteUnread.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], NoteUnread.prototype, "noteId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], NoteUnread.prototype, "note", void 0);
+_ts_decorate([
+ Index(),
+ Column("boolean"),
+ _ts_metadata("design:type", Boolean)
+], NoteUnread.prototype, "isMentioned", void 0);
+_ts_decorate([
+ Index(),
+ Column("boolean"),
+ _ts_metadata("design:type", Boolean)
+], NoteUnread.prototype, "isSpecified", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], NoteUnread.prototype, "noteUserId", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], NoteUnread.prototype, "noteChannelId", void 0);
+NoteUnread = _ts_decorate([
+ Entity(),
+ Index([
+ "userId",
+ "noteId"
+ ], {
+ unique: true
+ })
+], NoteUnread);
diff --git a/packages/backend/built/models/entities/note-watching.js b/packages/backend/built/models/entities/note-watching.js
new file mode 100644
index 0000000..40b40b3
--- /dev/null
+++ b/packages/backend/built/models/entities/note-watching.js
@@ -0,0 +1,81 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { Note } from "./note.js";
+import { id } from "../id.js";
+export let NoteWatching = class NoteWatching {
+ id;
+ createdAt;
+ userId;
+ user;
+ noteId;
+ note;
+ //#region Denormalized fields
+ noteUserId;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], NoteWatching.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the NoteWatching."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], NoteWatching.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The watcher ID."
+ }),
+ _ts_metadata("design:type", Object)
+], NoteWatching.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], NoteWatching.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The target Note ID."
+ }),
+ _ts_metadata("design:type", Object)
+], NoteWatching.prototype, "noteId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], NoteWatching.prototype, "note", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], NoteWatching.prototype, "noteUserId", void 0);
+NoteWatching = _ts_decorate([
+ Entity(),
+ Index([
+ "userId",
+ "noteId"
+ ], {
+ unique: true
+ })
+], NoteWatching);
diff --git a/packages/backend/built/models/entities/note.js b/packages/backend/built/models/entities/note.js
new file mode 100644
index 0000000..194eb1a
--- /dev/null
+++ b/packages/backend/built/models/entities/note.js
@@ -0,0 +1,410 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { UserGroup } from "./user-group.js";
+import { id } from "../id.js";
+import { noteVisibilities } from "../../types.js";
+import { Channel } from "./channel.js";
+export let Note = class Note {
+ id;
+ createdAt;
+ replyId;
+ reply;
+ renoteId;
+ renote;
+ threadId;
+ text;
+ name;
+ cw;
+ userId;
+ user;
+ groupId;
+ group;
+ localOnly;
+ renoteCount;
+ repliesCount;
+ viewCount;
+ reactions;
+ /**
+ * public ... 公開
+ * home ... ホームタイムライン(ユーザーページのタイムライン含む)のみに流す
+ * hidden ... only visible on profile (doesnt federate, like local only, but can be fetched via AP like home) <- for now only used for post imports
+ * followers ... フォロワーのみ
+ * specified ... visibleUserIds で指定したユーザーのみ
+ */ visibility;
+ uri;
+ url;
+ score;
+ fileIds;
+ attachedFileTypes;
+ visibleUserIds;
+ mentions;
+ mentionedRemoteUsers;
+ emojis;
+ tags;
+ hasPoll;
+ channelId;
+ channel;
+ quoteAuthorization;
+ canQuote;
+ //#region Denormalized fields
+ userHost;
+ replyUserId;
+ replyUserHost;
+ renoteUserId;
+ renoteUserHost;
+ updatedAt;
+ //#endregion
+ constructor(data){
+ if (data == null) return;
+ for (const [k, v] of Object.entries(data)){
+ this[k] = v;
+ }
+ }
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Note.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the Note."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], Note.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "The ID of reply target."
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "replyId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "reply", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "The ID of renote target."
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "renoteId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "renote", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 256,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "threadId", void 0);
+_ts_decorate([
+ Index('note_text_fts_idx', {
+ synchronize: false
+ }),
+ Column("text", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "text", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "name", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "cw", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The ID of author."
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "The ID of acting group."
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "groupId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>UserGroup, {
+ onDelete: "SET NULL"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "group", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Note.prototype, "localOnly", void 0);
+_ts_decorate([
+ Column("smallint", {
+ default: 0
+ }),
+ _ts_metadata("design:type", Number)
+], Note.prototype, "renoteCount", void 0);
+_ts_decorate([
+ Column("smallint", {
+ default: 0
+ }),
+ _ts_metadata("design:type", Number)
+], Note.prototype, "repliesCount", void 0);
+_ts_decorate([
+ Column("integer", {
+ default: 0
+ }),
+ _ts_metadata("design:type", Number)
+], Note.prototype, "viewCount", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: {}
+ }),
+ _ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
+], Note.prototype, "reactions", void 0);
+_ts_decorate([
+ Column("enum", {
+ enum: noteVisibilities
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "visibility", void 0);
+_ts_decorate([
+ Index({
+ unique: true
+ }),
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "The URI of a note. it will be null when the note is local."
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "uri", void 0);
+_ts_decorate([
+ Index("IDX_note_url"),
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "The human readable url of a note. it will be null when the note is local."
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "url", void 0);
+_ts_decorate([
+ Column("integer", {
+ default: 0,
+ select: false
+ }),
+ _ts_metadata("design:type", Number)
+], Note.prototype, "score", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Note.prototype, "fileIds", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 256,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Note.prototype, "attachedFileTypes", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Note.prototype, "visibleUserIds", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Note.prototype, "mentions", void 0);
+_ts_decorate([
+ Column("text", {
+ default: "[]"
+ }),
+ _ts_metadata("design:type", String)
+], Note.prototype, "mentionedRemoteUsers", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Note.prototype, "emojis", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 128,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Note.prototype, "tags", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Note.prototype, "hasPoll", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "The ID of source channel."
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "channelId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Channel, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "channel", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "quoteAuthorization", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Note.prototype, "canQuote", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "userHost", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "replyUserId", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "replyUserHost", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "renoteUserId", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "renoteUserHost", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ nullable: true,
+ comment: "The updated date of the Note."
+ }),
+ _ts_metadata("design:type", Object)
+], Note.prototype, "updatedAt", void 0);
+Note = _ts_decorate([
+ Entity(),
+ Index("IDX_NOTE_TAGS", {
+ synchronize: false
+ }),
+ Index("IDX_NOTE_MENTIONS", {
+ synchronize: false
+ }),
+ Index("IDX_NOTE_VISIBLE_USER_IDS", {
+ synchronize: false
+ }),
+ Index("IDX_note_userId_id", [
+ "userId",
+ "id"
+ ]),
+ Index("IDX_note_id_userHost", [
+ "id",
+ "userHost"
+ ]),
+ Index("IDX_note_createdAt_userId", [
+ "createdAt",
+ "userId"
+ ]),
+ _ts_metadata("design:type", Function),
+ _ts_metadata("design:paramtypes", [
+ typeof Partial === "undefined" ? Object : Partial
+ ])
+], Note);
diff --git a/packages/backend/built/models/entities/notification.js b/packages/backend/built/models/entities/notification.js
new file mode 100644
index 0000000..de056b5
--- /dev/null
+++ b/packages/backend/built/models/entities/notification.js
@@ -0,0 +1,240 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, Index, JoinColumn, ManyToOne, Column, PrimaryColumn } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+import { Note } from "./note.js";
+import { FollowRequest } from "./follow-request.js";
+import { UserGroupInvitation } from "./user-group-invitation.js";
+import { AccessToken } from "./access-token.js";
+import { notificationTypes } from "../../types.js";
+import { Bite } from "./bite.js";
+export let Notification = class Notification {
+ id;
+ createdAt;
+ /**
+ * Notification Recipient ID
+ */ notifieeId;
+ notifiee;
+ /**
+ * Notification sender (initiator)
+ */ notifierId;
+ notifier;
+ /**
+ * Notification types:
+ * follow - Follow request
+ * mention - User was referenced in a post.
+ * reply - A post that a user made (or was watching) has been replied to.
+ * renote - A post that a user made (or was watching) has been renoted.
+ * quote - A post that a user made (or was watching) has been quoted and renoted.
+ * reaction - (自分または自分がWatchしている)投稿にリアクションされた
+ * pollVote - (自分または自分がWatchしている)投稿のアンケートに投票された
+ * pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した
+ * receiveFollowRequest - フォローリクエストされた
+ * followRequestAccepted - A follow request has been accepted.
+ * groupInvited - グループに招待された
+ * app - App notifications.
+ */ type;
+ /**
+ * Whether the notification was read.
+ */ isRead;
+ noteId;
+ note;
+ followRequestId;
+ followRequest;
+ userGroupInvitationId;
+ userGroupInvitation;
+ reaction;
+ choice;
+ /**
+ * App notification body
+ */ customBody;
+ /**
+ * App notification header
+ * (If omitted, it is expected to be displayed with the app name)
+ */ customHeader;
+ /**
+ * App notification icon (URL)
+ * (If omitted, it is expected to be displayed as an app icon)
+ */ customIcon;
+ /**
+ * App notification app (token for)
+ */ appAccessTokenId;
+ appAccessToken;
+ biteId;
+ bite;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Notification.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the Notification."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], Notification.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The ID of recipient user of the Notification."
+ }),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "notifieeId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "notifiee", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "The ID of sender user of the Notification."
+ }),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "notifierId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "notifier", void 0);
+_ts_decorate([
+ Index(),
+ Column("enum", {
+ enum: notificationTypes,
+ comment: "The type of the Notification."
+ }),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "type", void 0);
+_ts_decorate([
+ Index(),
+ Column("boolean", {
+ default: false,
+ comment: "Whether the notification was read."
+ }),
+ _ts_metadata("design:type", Boolean)
+], Notification.prototype, "isRead", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "noteId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "note", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "followRequestId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>FollowRequest, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "followRequest", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "userGroupInvitationId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>UserGroupInvitation, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "userGroupInvitation", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "reaction", void 0);
+_ts_decorate([
+ Column("integer", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "choice", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 2048,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "customBody", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "customHeader", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 1024,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "customIcon", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "appAccessTokenId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>AccessToken, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "appAccessToken", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "biteId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Bite, {
+ onDelete: "CASCADE",
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Notification.prototype, "bite", void 0);
+Notification = _ts_decorate([
+ Entity()
+], Notification);
diff --git a/packages/backend/built/models/entities/oauth-app.js b/packages/backend/built/models/entities/oauth-app.js
new file mode 100644
index 0000000..b20ee5e
--- /dev/null
+++ b/packages/backend/built/models/entities/oauth-app.js
@@ -0,0 +1,82 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, PrimaryColumn, Column, Index } from "typeorm";
+import { id } from "../id.js";
+export let OAuthApp = class OAuthApp {
+ id;
+ createdAt;
+ clientId;
+ clientSecret;
+ name;
+ website;
+ scopes;
+ redirectUris;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], OAuthApp.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the OAuth application"
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], OAuthApp.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index({
+ unique: true
+ }),
+ Column("varchar", {
+ length: 64,
+ comment: "The client id of the OAuth application"
+ }),
+ _ts_metadata("design:type", String)
+], OAuthApp.prototype, "clientId", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 64,
+ comment: "The client secret of the OAuth application"
+ }),
+ _ts_metadata("design:type", String)
+], OAuthApp.prototype, "clientSecret", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ comment: "The name of the OAuth application"
+ }),
+ _ts_metadata("design:type", String)
+], OAuthApp.prototype, "name", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ nullable: true,
+ comment: "The website of the OAuth application"
+ }),
+ _ts_metadata("design:type", Object)
+], OAuthApp.prototype, "website", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 64,
+ array: true,
+ comment: "The scopes requested by the OAuth application"
+ }),
+ _ts_metadata("design:type", Array)
+], OAuthApp.prototype, "scopes", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ array: true,
+ comment: "The redirect URIs of the OAuth application"
+ }),
+ _ts_metadata("design:type", Array)
+], OAuthApp.prototype, "redirectUris", void 0);
+OAuthApp = _ts_decorate([
+ Entity('oauth_app')
+], OAuthApp);
diff --git a/packages/backend/built/models/entities/oauth-token.js b/packages/backend/built/models/entities/oauth-token.js
new file mode 100644
index 0000000..c9ac385
--- /dev/null
+++ b/packages/backend/built/models/entities/oauth-token.js
@@ -0,0 +1,98 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, PrimaryColumn, Column, Index, ManyToOne, JoinColumn } from "typeorm";
+import { id } from "../id.js";
+import { OAuthApp } from "./oauth-app.js";
+import { User } from "./user.js";
+export let OAuthToken = class OAuthToken {
+ id;
+ createdAt;
+ appId;
+ app;
+ userId;
+ user;
+ code;
+ token;
+ active;
+ scopes;
+ redirectUri;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], OAuthToken.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the OAuth token"
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], OAuthToken.prototype, "createdAt", void 0);
+_ts_decorate([
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], OAuthToken.prototype, "appId", void 0);
+_ts_decorate([
+ ManyToOne(()=>OAuthApp, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", typeof OAuthApp === "undefined" ? Object : OAuthApp)
+], OAuthToken.prototype, "app", void 0);
+_ts_decorate([
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], OAuthToken.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne(()=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", typeof User === "undefined" ? Object : User)
+], OAuthToken.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 64,
+ comment: "The auth code for the OAuth token"
+ }),
+ _ts_metadata("design:type", String)
+], OAuthToken.prototype, "code", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 64,
+ comment: "The OAuth token"
+ }),
+ _ts_metadata("design:type", String)
+], OAuthToken.prototype, "token", void 0);
+_ts_decorate([
+ Column("boolean", {
+ comment: "Whether or not the token has been activated"
+ }),
+ _ts_metadata("design:type", Boolean)
+], OAuthToken.prototype, "active", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 64,
+ array: true,
+ comment: "The scopes requested by the OAuth token"
+ }),
+ _ts_metadata("design:type", Array)
+], OAuthToken.prototype, "scopes", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ comment: "The redirect URI of the OAuth token"
+ }),
+ _ts_metadata("design:type", String)
+], OAuthToken.prototype, "redirectUri", void 0);
+OAuthToken = _ts_decorate([
+ Entity('oauth_token')
+], OAuthToken);
diff --git a/packages/backend/built/models/entities/page-like.js b/packages/backend/built/models/entities/page-like.js
new file mode 100644
index 0000000..3e83eaf
--- /dev/null
+++ b/packages/backend/built/models/entities/page-like.js
@@ -0,0 +1,61 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+import { Page } from "./page.js";
+export let PageLike = class PageLike {
+ id;
+ createdAt;
+ userId;
+ user;
+ pageId;
+ page;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], PageLike.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone"),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], PageLike.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], PageLike.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], PageLike.prototype, "user", void 0);
+_ts_decorate([
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], PageLike.prototype, "pageId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Page, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], PageLike.prototype, "page", void 0);
+PageLike = _ts_decorate([
+ Entity(),
+ Index([
+ "userId",
+ "pageId"
+ ], {
+ unique: true
+ })
+], PageLike);
diff --git a/packages/backend/built/models/entities/page.js b/packages/backend/built/models/entities/page.js
new file mode 100644
index 0000000..a49a103
--- /dev/null
+++ b/packages/backend/built/models/entities/page.js
@@ -0,0 +1,189 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+import { DriveFile } from "./drive-file.js";
+export let Page = class Page {
+ id;
+ createdAt;
+ updatedAt;
+ title;
+ name;
+ summary;
+ alignCenter;
+ isPublic;
+ hideTitleWhenPinned;
+ font;
+ userId;
+ user;
+ eyeCatchingImageId;
+ eyeCatchingImage;
+ content;
+ variables;
+ script;
+ /**
+ * public ... 公開
+ * followers ... フォロワーのみ
+ * specified ... visibleUserIds で指定したユーザーのみ
+ */ visibility;
+ visibleUserIds;
+ likedCount;
+ constructor(data){
+ if (data == null) return;
+ for (const [k, v] of Object.entries(data)){
+ this[k] = v;
+ }
+ }
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Page.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the Page."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], Page.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The updated date of the Page."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], Page.prototype, "updatedAt", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256
+ }),
+ _ts_metadata("design:type", String)
+], Page.prototype, "title", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 256
+ }),
+ _ts_metadata("design:type", String)
+], Page.prototype, "name", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Page.prototype, "summary", void 0);
+_ts_decorate([
+ Column("boolean"),
+ _ts_metadata("design:type", Boolean)
+], Page.prototype, "alignCenter", void 0);
+_ts_decorate([
+ Column("boolean"),
+ _ts_metadata("design:type", Boolean)
+], Page.prototype, "isPublic", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], Page.prototype, "hideTitleWhenPinned", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 32
+ }),
+ _ts_metadata("design:type", String)
+], Page.prototype, "font", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The ID of author."
+ }),
+ _ts_metadata("design:type", Object)
+], Page.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Page.prototype, "user", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Page.prototype, "eyeCatchingImageId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>DriveFile, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Page.prototype, "eyeCatchingImage", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: []
+ }),
+ _ts_metadata("design:type", Array)
+], Page.prototype, "content", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: []
+ }),
+ _ts_metadata("design:type", Array)
+], Page.prototype, "variables", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 16384,
+ default: ""
+ }),
+ _ts_metadata("design:type", String)
+], Page.prototype, "script", void 0);
+_ts_decorate([
+ Column("enum", {
+ enum: [
+ "public",
+ "followers",
+ "specified"
+ ]
+ }),
+ _ts_metadata("design:type", String)
+], Page.prototype, "visibility", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Page.prototype, "visibleUserIds", void 0);
+_ts_decorate([
+ Column("integer", {
+ default: 0
+ }),
+ _ts_metadata("design:type", Number)
+], Page.prototype, "likedCount", void 0);
+Page = _ts_decorate([
+ Entity(),
+ Index([
+ "userId",
+ "name"
+ ], {
+ unique: true
+ }),
+ _ts_metadata("design:type", Function),
+ _ts_metadata("design:paramtypes", [
+ typeof Partial === "undefined" ? Object : Partial
+ ])
+], Page);
diff --git a/packages/backend/built/models/entities/password-reset-request.js b/packages/backend/built/models/entities/password-reset-request.js
new file mode 100644
index 0000000..10efb4a
--- /dev/null
+++ b/packages/backend/built/models/entities/password-reset-request.js
@@ -0,0 +1,53 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, Column, ManyToOne, JoinColumn } from "typeorm";
+import { id } from "../id.js";
+import { User } from "./user.js";
+export let PasswordResetRequest = class PasswordResetRequest {
+ id;
+ createdAt;
+ token;
+ userId;
+ user;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], PasswordResetRequest.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone"),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], PasswordResetRequest.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index({
+ unique: true
+ }),
+ Column("varchar", {
+ length: 256
+ }),
+ _ts_metadata("design:type", String)
+], PasswordResetRequest.prototype, "token", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id()
+ }),
+ _ts_metadata("design:type", Object)
+], PasswordResetRequest.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], PasswordResetRequest.prototype, "user", void 0);
+PasswordResetRequest = _ts_decorate([
+ Entity()
+], PasswordResetRequest);
diff --git a/packages/backend/built/models/entities/plan.js b/packages/backend/built/models/entities/plan.js
new file mode 100644
index 0000000..1c1e501
--- /dev/null
+++ b/packages/backend/built/models/entities/plan.js
@@ -0,0 +1,61 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, Column } from "typeorm";
+import { id } from "../id.js";
+export let Plan = class Plan {
+ id;
+ createdAt;
+ updatedAt;
+ name;
+ icon;
+ description;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Plan.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the Plan."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], Plan.prototype, "createdAt", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Plan.prototype, "updatedAt", void 0);
+_ts_decorate([
+ Index({
+ unique: true
+ }),
+ Column("varchar", {
+ length: 128
+ }),
+ _ts_metadata("design:type", String)
+], Plan.prototype, "name", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 64
+ }),
+ _ts_metadata("design:type", String)
+], Plan.prototype, "icon", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ default: ""
+ }),
+ _ts_metadata("design:type", String)
+], Plan.prototype, "description", void 0);
+Plan = _ts_decorate([
+ Entity()
+], Plan);
diff --git a/packages/backend/built/models/entities/poll-vote.js b/packages/backend/built/models/entities/poll-vote.js
new file mode 100644
index 0000000..9eba0a5
--- /dev/null
+++ b/packages/backend/built/models/entities/poll-vote.js
@@ -0,0 +1,71 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { Note } from "./note.js";
+import { id } from "../id.js";
+export let PollVote = class PollVote {
+ id;
+ createdAt;
+ userId;
+ user;
+ noteId;
+ note;
+ choice;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], PollVote.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the PollVote."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], PollVote.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], PollVote.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], PollVote.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], PollVote.prototype, "noteId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], PollVote.prototype, "note", void 0);
+_ts_decorate([
+ Column("integer"),
+ _ts_metadata("design:type", Number)
+], PollVote.prototype, "choice", void 0);
+PollVote = _ts_decorate([
+ Entity(),
+ Index([
+ "userId",
+ "noteId",
+ "choice"
+ ], {
+ unique: true
+ })
+], PollVote);
diff --git a/packages/backend/built/models/entities/poll.js b/packages/backend/built/models/entities/poll.js
new file mode 100644
index 0000000..0fcd766
--- /dev/null
+++ b/packages/backend/built/models/entities/poll.js
@@ -0,0 +1,98 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from "typeorm";
+import { id } from "../id.js";
+import { Note } from "./note.js";
+import { noteVisibilities } from "../../types.js";
+export let Poll = class Poll {
+ noteId;
+ note;
+ expiresAt;
+ multiple;
+ choices;
+ votes;
+ //#region Denormalized fields
+ noteVisibility;
+ userId;
+ userHost;
+ //#endregion
+ constructor(data){
+ if (data == null) return;
+ for (const [k, v] of Object.entries(data)){
+ this[k] = v;
+ }
+ }
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", Object)
+], Poll.prototype, "noteId", void 0);
+_ts_decorate([
+ OneToOne((type)=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Poll.prototype, "note", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Poll.prototype, "expiresAt", void 0);
+_ts_decorate([
+ Column("boolean"),
+ _ts_metadata("design:type", Boolean)
+], Poll.prototype, "multiple", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Poll.prototype, "choices", void 0);
+_ts_decorate([
+ Column("integer", {
+ array: true
+ }),
+ _ts_metadata("design:type", Array)
+], Poll.prototype, "votes", void 0);
+_ts_decorate([
+ Column("enum", {
+ enum: noteVisibilities,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], Poll.prototype, "noteVisibility", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], Poll.prototype, "userId", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], Poll.prototype, "userHost", void 0);
+Poll = _ts_decorate([
+ Entity(),
+ _ts_metadata("design:type", Function),
+ _ts_metadata("design:paramtypes", [
+ typeof Partial === "undefined" ? Object : Partial
+ ])
+], Poll);
diff --git a/packages/backend/built/models/entities/promo-note.js b/packages/backend/built/models/entities/promo-note.js
new file mode 100644
index 0000000..46e89db
--- /dev/null
+++ b/packages/backend/built/models/entities/promo-note.js
@@ -0,0 +1,59 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from "typeorm";
+import { Note } from "./note.js";
+import { id } from "../id.js";
+export let PromoNote = class PromoNote {
+ noteId;
+ note;
+ expiresAt;
+ totalCredits;
+ remainingCredits;
+ //#region Denormalized fields
+ userId;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", Object)
+], PromoNote.prototype, "noteId", void 0);
+_ts_decorate([
+ OneToOne((type)=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], PromoNote.prototype, "note", void 0);
+_ts_decorate([
+ Column("timestamp with time zone"),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], PromoNote.prototype, "expiresAt", void 0);
+_ts_decorate([
+ Column("integer", {
+ default: 0
+ }),
+ _ts_metadata("design:type", Number)
+], PromoNote.prototype, "totalCredits", void 0);
+_ts_decorate([
+ Column("integer", {
+ default: 0
+ }),
+ _ts_metadata("design:type", Number)
+], PromoNote.prototype, "remainingCredits", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], PromoNote.prototype, "userId", void 0);
+PromoNote = _ts_decorate([
+ Entity()
+], PromoNote);
diff --git a/packages/backend/built/models/entities/promo-read.js b/packages/backend/built/models/entities/promo-read.js
new file mode 100644
index 0000000..47ed029
--- /dev/null
+++ b/packages/backend/built/models/entities/promo-read.js
@@ -0,0 +1,71 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { Note } from "./note.js";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let PromoRead = class PromoRead {
+ id;
+ createdAt;
+ userId;
+ user;
+ noteId;
+ note;
+ readDay;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], PromoRead.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the PromoRead."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], PromoRead.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], PromoRead.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], PromoRead.prototype, "user", void 0);
+_ts_decorate([
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], PromoRead.prototype, "noteId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], PromoRead.prototype, "note", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 10
+ }),
+ _ts_metadata("design:type", String)
+], PromoRead.prototype, "readDay", void 0);
+PromoRead = _ts_decorate([
+ Entity(),
+ Index([
+ "userId",
+ "noteId",
+ "readDay"
+ ], {
+ unique: true
+ })
+], PromoRead);
diff --git a/packages/backend/built/models/entities/registration-tickets.js b/packages/backend/built/models/entities/registration-tickets.js
new file mode 100644
index 0000000..6d8c6fa
--- /dev/null
+++ b/packages/backend/built/models/entities/registration-tickets.js
@@ -0,0 +1,36 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, Column } from "typeorm";
+import { id } from "../id.js";
+export let RegistrationTicket = class RegistrationTicket {
+ id;
+ createdAt;
+ code;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], RegistrationTicket.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone"),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], RegistrationTicket.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index({
+ unique: true
+ }),
+ Column("varchar", {
+ length: 64
+ }),
+ _ts_metadata("design:type", String)
+], RegistrationTicket.prototype, "code", void 0);
+RegistrationTicket = _ts_decorate([
+ Entity()
+], RegistrationTicket);
diff --git a/packages/backend/built/models/entities/registry-item.js b/packages/backend/built/models/entities/registry-item.js
new file mode 100644
index 0000000..bc1c911
--- /dev/null
+++ b/packages/backend/built/models/entities/registry-item.js
@@ -0,0 +1,90 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let RegistryItem = class RegistryItem {
+ id;
+ createdAt;
+ updatedAt;
+ userId;
+ user;
+ key;
+ value;
+ scope;
+ // サードパーティアプリに開放するときのためのカラム
+ domain;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], RegistryItem.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the RegistryItem."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], RegistryItem.prototype, "createdAt", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The updated date of the RegistryItem."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], RegistryItem.prototype, "updatedAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The owner ID."
+ }),
+ _ts_metadata("design:type", Object)
+], RegistryItem.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], RegistryItem.prototype, "user", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 1024,
+ comment: "The key of the RegistryItem."
+ }),
+ _ts_metadata("design:type", String)
+], RegistryItem.prototype, "key", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: {},
+ nullable: true,
+ comment: "The value of the RegistryItem."
+ }),
+ _ts_metadata("design:type", Object)
+], RegistryItem.prototype, "value", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 1024,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], RegistryItem.prototype, "scope", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 512,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], RegistryItem.prototype, "domain", void 0);
+RegistryItem = _ts_decorate([
+ Entity()
+], RegistryItem);
diff --git a/packages/backend/built/models/entities/relay.js b/packages/backend/built/models/entities/relay.js
new file mode 100644
index 0000000..e84e7a6
--- /dev/null
+++ b/packages/backend/built/models/entities/relay.js
@@ -0,0 +1,43 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, Column } from "typeorm";
+import { id } from "../id.js";
+export let Relay = class Relay {
+ id;
+ inbox;
+ status;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Relay.prototype, "id", void 0);
+_ts_decorate([
+ Index({
+ unique: true
+ }),
+ Column("varchar", {
+ length: 512,
+ nullable: false
+ }),
+ _ts_metadata("design:type", String)
+], Relay.prototype, "inbox", void 0);
+_ts_decorate([
+ Column("enum", {
+ enum: [
+ "requesting",
+ "accepted",
+ "rejected"
+ ]
+ }),
+ _ts_metadata("design:type", String)
+], Relay.prototype, "status", void 0);
+Relay = _ts_decorate([
+ Entity()
+], Relay);
diff --git a/packages/backend/built/models/entities/renote-muting.js b/packages/backend/built/models/entities/renote-muting.js
new file mode 100644
index 0000000..e83d053
--- /dev/null
+++ b/packages/backend/built/models/entities/renote-muting.js
@@ -0,0 +1,70 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { id } from "../id.js";
+import { User } from "./user.js";
+export let RenoteMuting = class RenoteMuting {
+ id;
+ createdAt;
+ muteeId;
+ mutee;
+ muterId;
+ muter;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], RenoteMuting.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the Muting."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], RenoteMuting.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The mutee user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], RenoteMuting.prototype, "muteeId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], RenoteMuting.prototype, "mutee", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The muter user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], RenoteMuting.prototype, "muterId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], RenoteMuting.prototype, "muter", void 0);
+RenoteMuting = _ts_decorate([
+ Entity(),
+ Index([
+ "muterId",
+ "muteeId"
+ ], {
+ unique: true
+ })
+], RenoteMuting);
diff --git a/packages/backend/built/models/entities/reversi-game.js b/packages/backend/built/models/entities/reversi-game.js
new file mode 100644
index 0000000..fe53bcd
--- /dev/null
+++ b/packages/backend/built/models/entities/reversi-game.js
@@ -0,0 +1,204 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from "typeorm";
+import { id } from "../id.js";
+import { User } from "./user.js";
+export let ReversiGame = class ReversiGame {
+ id;
+ startedAt;
+ endedAt;
+ user1Id;
+ user1;
+ user2Id;
+ user2;
+ user1Ready;
+ user2Ready;
+ black;
+ isStarted;
+ isEnded;
+ winnerId;
+ surrenderedUserId;
+ timeoutUserId;
+ timeLimitForEachTurn;
+ logs;
+ map;
+ bw;
+ noIrregularRules;
+ isLlotheo;
+ canPutEverywhere;
+ loopedBoard;
+ form1;
+ form2;
+ crc32;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], ReversiGame.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ nullable: true,
+ comment: "The started date of the ReversiGame."
+ }),
+ _ts_metadata("design:type", Object)
+], ReversiGame.prototype, "startedAt", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ nullable: true,
+ comment: "The ended date of the ReversiGame."
+ }),
+ _ts_metadata("design:type", Object)
+], ReversiGame.prototype, "endedAt", void 0);
+_ts_decorate([
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], ReversiGame.prototype, "user1Id", void 0);
+_ts_decorate([
+ ManyToOne(()=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], ReversiGame.prototype, "user1", void 0);
+_ts_decorate([
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], ReversiGame.prototype, "user2Id", void 0);
+_ts_decorate([
+ ManyToOne(()=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], ReversiGame.prototype, "user2", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], ReversiGame.prototype, "user1Ready", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], ReversiGame.prototype, "user2Ready", void 0);
+_ts_decorate([
+ Column("integer", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], ReversiGame.prototype, "black", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], ReversiGame.prototype, "isStarted", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], ReversiGame.prototype, "isEnded", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], ReversiGame.prototype, "winnerId", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], ReversiGame.prototype, "surrenderedUserId", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], ReversiGame.prototype, "timeoutUserId", void 0);
+_ts_decorate([
+ Column("smallint", {
+ default: 90
+ }),
+ _ts_metadata("design:type", Number)
+], ReversiGame.prototype, "timeLimitForEachTurn", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: []
+ }),
+ _ts_metadata("design:type", Array)
+], ReversiGame.prototype, "logs", void 0);
+_ts_decorate([
+ Column("varchar", {
+ array: true,
+ length: 64
+ }),
+ _ts_metadata("design:type", Array)
+], ReversiGame.prototype, "map", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 32
+ }),
+ _ts_metadata("design:type", String)
+], ReversiGame.prototype, "bw", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], ReversiGame.prototype, "noIrregularRules", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], ReversiGame.prototype, "isLlotheo", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], ReversiGame.prototype, "canPutEverywhere", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], ReversiGame.prototype, "loopedBoard", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ nullable: true,
+ default: null
+ }),
+ _ts_metadata("design:type", Object)
+], ReversiGame.prototype, "form1", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ nullable: true,
+ default: null
+ }),
+ _ts_metadata("design:type", Object)
+], ReversiGame.prototype, "form2", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 32,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], ReversiGame.prototype, "crc32", void 0);
+ReversiGame = _ts_decorate([
+ Entity("reversi_game")
+], ReversiGame);
diff --git a/packages/backend/built/models/entities/reversi-matching.js b/packages/backend/built/models/entities/reversi-matching.js
new file mode 100644
index 0000000..b588414
--- /dev/null
+++ b/packages/backend/built/models/entities/reversi-matching.js
@@ -0,0 +1,38 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Column, Entity, PrimaryColumn } from "typeorm";
+import { id } from "../id.js";
+export let ReversiMatching = class ReversiMatching {
+ id;
+ createdAt;
+ parentId;
+ childId;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], ReversiMatching.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the ReversiMatching."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], ReversiMatching.prototype, "createdAt", void 0);
+_ts_decorate([
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], ReversiMatching.prototype, "parentId", void 0);
+_ts_decorate([
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], ReversiMatching.prototype, "childId", void 0);
+ReversiMatching = _ts_decorate([
+ Entity("reversi_matching")
+], ReversiMatching);
diff --git a/packages/backend/built/models/entities/scheduled-note.js b/packages/backend/built/models/entities/scheduled-note.js
new file mode 100644
index 0000000..9e3e95c
--- /dev/null
+++ b/packages/backend/built/models/entities/scheduled-note.js
@@ -0,0 +1,85 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from "typeorm";
+import { id } from "../id.js";
+import { User } from "./user.js";
+import { Note } from "./note.js";
+export let ScheduledNote = class ScheduledNote {
+ id;
+ createdAt;
+ scheduledAt;
+ userId;
+ user;
+ status;
+ data;
+ noteId;
+ note;
+ error;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], ScheduledNote.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone"),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], ScheduledNote.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone"),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], ScheduledNote.prototype, "scheduledAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], ScheduledNote.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne(()=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], ScheduledNote.prototype, "user", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 16,
+ default: "scheduled"
+ }),
+ _ts_metadata("design:type", typeof ScheduledNoteStatus === "undefined" ? Object : ScheduledNoteStatus)
+], ScheduledNote.prototype, "status", void 0);
+_ts_decorate([
+ Column("jsonb"),
+ _ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
+], ScheduledNote.prototype, "data", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], ScheduledNote.prototype, "noteId", void 0);
+_ts_decorate([
+ ManyToOne(()=>Note, {
+ onDelete: "SET NULL"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], ScheduledNote.prototype, "note", void 0);
+_ts_decorate([
+ Column("text", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], ScheduledNote.prototype, "error", void 0);
+ScheduledNote = _ts_decorate([
+ Entity("scheduled_note")
+], ScheduledNote);
diff --git a/packages/backend/built/models/entities/shogi-game.js b/packages/backend/built/models/entities/shogi-game.js
new file mode 100644
index 0000000..740e21f
--- /dev/null
+++ b/packages/backend/built/models/entities/shogi-game.js
@@ -0,0 +1,125 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from "typeorm";
+import { id } from "../id.js";
+import { User } from "./user.js";
+export let ShogiGame = class ShogiGame {
+ id;
+ startedAt;
+ endedAt;
+ user1Id;
+ user1;
+ user2Id;
+ user2;
+ user1Ready;
+ user2Ready;
+ sente;
+ isStarted;
+ isEnded;
+ winnerId;
+ surrenderedUserId;
+ sfen;
+ logs;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], ShogiGame.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], ShogiGame.prototype, "startedAt", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], ShogiGame.prototype, "endedAt", void 0);
+_ts_decorate([
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], ShogiGame.prototype, "user1Id", void 0);
+_ts_decorate([
+ ManyToOne(()=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], ShogiGame.prototype, "user1", void 0);
+_ts_decorate([
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], ShogiGame.prototype, "user2Id", void 0);
+_ts_decorate([
+ ManyToOne(()=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], ShogiGame.prototype, "user2", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], ShogiGame.prototype, "user1Ready", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], ShogiGame.prototype, "user2Ready", void 0);
+_ts_decorate([
+ Column("integer", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], ShogiGame.prototype, "sente", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], ShogiGame.prototype, "isStarted", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], ShogiGame.prototype, "isEnded", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], ShogiGame.prototype, "winnerId", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], ShogiGame.prototype, "surrenderedUserId", void 0);
+_ts_decorate([
+ Column("text"),
+ _ts_metadata("design:type", String)
+], ShogiGame.prototype, "sfen", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: []
+ }),
+ _ts_metadata("design:type", typeof Array === "undefined" ? Object : Array)
+], ShogiGame.prototype, "logs", void 0);
+ShogiGame = _ts_decorate([
+ Entity("shogi_game")
+], ShogiGame);
diff --git a/packages/backend/built/models/entities/signin.js b/packages/backend/built/models/entities/signin.js
new file mode 100644
index 0000000..4d27ac1
--- /dev/null
+++ b/packages/backend/built/models/entities/signin.js
@@ -0,0 +1,60 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let Signin = class Signin {
+ id;
+ createdAt;
+ userId;
+ user;
+ ip;
+ headers;
+ success;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Signin.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the Signin."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], Signin.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], Signin.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Signin.prototype, "user", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128
+ }),
+ _ts_metadata("design:type", String)
+], Signin.prototype, "ip", void 0);
+_ts_decorate([
+ Column("jsonb"),
+ _ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
+], Signin.prototype, "headers", void 0);
+_ts_decorate([
+ Column("boolean"),
+ _ts_metadata("design:type", Boolean)
+], Signin.prototype, "success", void 0);
+Signin = _ts_decorate([
+ Entity()
+], Signin);
diff --git a/packages/backend/built/models/entities/sw-subscription.js b/packages/backend/built/models/entities/sw-subscription.js
new file mode 100644
index 0000000..0a91704
--- /dev/null
+++ b/packages/backend/built/models/entities/sw-subscription.js
@@ -0,0 +1,69 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let SwSubscription = class SwSubscription {
+ id;
+ createdAt;
+ userId;
+ user;
+ endpoint;
+ auth;
+ publickey;
+ sendReadMessage;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], SwSubscription.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone"),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], SwSubscription.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], SwSubscription.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], SwSubscription.prototype, "user", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512
+ }),
+ _ts_metadata("design:type", String)
+], SwSubscription.prototype, "endpoint", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256
+ }),
+ _ts_metadata("design:type", String)
+], SwSubscription.prototype, "auth", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128
+ }),
+ _ts_metadata("design:type", String)
+], SwSubscription.prototype, "publickey", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], SwSubscription.prototype, "sendReadMessage", void 0);
+SwSubscription = _ts_decorate([
+ Entity()
+], SwSubscription);
diff --git a/packages/backend/built/models/entities/used-username.js b/packages/backend/built/models/entities/used-username.js
new file mode 100644
index 0000000..d2fa4ba
--- /dev/null
+++ b/packages/backend/built/models/entities/used-username.js
@@ -0,0 +1,37 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Column } from "typeorm";
+export let UsedUsername = class UsedUsername {
+ username;
+ createdAt;
+ constructor(data){
+ if (data == null) return;
+ for (const [k, v] of Object.entries(data)){
+ this[k] = v;
+ }
+ }
+};
+_ts_decorate([
+ PrimaryColumn("varchar", {
+ length: 128
+ }),
+ _ts_metadata("design:type", String)
+], UsedUsername.prototype, "username", void 0);
+_ts_decorate([
+ Column("timestamp with time zone"),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], UsedUsername.prototype, "createdAt", void 0);
+UsedUsername = _ts_decorate([
+ Entity(),
+ _ts_metadata("design:type", Function),
+ _ts_metadata("design:paramtypes", [
+ typeof Partial === "undefined" ? Object : Partial
+ ])
+], UsedUsername);
diff --git a/packages/backend/built/models/entities/user-emoji.js b/packages/backend/built/models/entities/user-emoji.js
new file mode 100644
index 0000000..caf5d99
--- /dev/null
+++ b/packages/backend/built/models/entities/user-emoji.js
@@ -0,0 +1,110 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Column, Entity, Index, PrimaryColumn } from "typeorm";
+import { id } from "../id.js";
+export let UserEmoji = class UserEmoji {
+ id;
+ createdAt;
+ name;
+ userId;
+ userGroupId;
+ originalUrl;
+ publicUrl;
+ type;
+ glyph;
+ width;
+ height;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], UserEmoji.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone"),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], UserEmoji.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 128
+ }),
+ _ts_metadata("design:type", String)
+], UserEmoji.prototype, "name", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], UserEmoji.prototype, "userId", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], UserEmoji.prototype, "userGroupId", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512
+ }),
+ _ts_metadata("design:type", String)
+], UserEmoji.prototype, "originalUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ default: ""
+ }),
+ _ts_metadata("design:type", String)
+], UserEmoji.prototype, "publicUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 64,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], UserEmoji.prototype, "type", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], UserEmoji.prototype, "glyph", void 0);
+_ts_decorate([
+ Column("integer", {
+ nullable: true,
+ comment: "Image width"
+ }),
+ _ts_metadata("design:type", Object)
+], UserEmoji.prototype, "width", void 0);
+_ts_decorate([
+ Column("integer", {
+ nullable: true,
+ comment: "Image height"
+ }),
+ _ts_metadata("design:type", Object)
+], UserEmoji.prototype, "height", void 0);
+UserEmoji = _ts_decorate([
+ Entity(),
+ Index([
+ "name",
+ "userId"
+ ], {
+ unique: true
+ }),
+ Index([
+ "name",
+ "userGroupId"
+ ], {
+ unique: true
+ })
+], UserEmoji);
diff --git a/packages/backend/built/models/entities/user-group-invitation.js b/packages/backend/built/models/entities/user-group-invitation.js
new file mode 100644
index 0000000..f2d88d1
--- /dev/null
+++ b/packages/backend/built/models/entities/user-group-invitation.js
@@ -0,0 +1,70 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { UserGroup } from "./user-group.js";
+import { id } from "../id.js";
+export let UserGroupInvitation = class UserGroupInvitation {
+ id;
+ createdAt;
+ userId;
+ user;
+ userGroupId;
+ userGroup;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], UserGroupInvitation.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the UserGroupInvitation."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], UserGroupInvitation.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], UserGroupInvitation.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], UserGroupInvitation.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The group ID."
+ }),
+ _ts_metadata("design:type", Object)
+], UserGroupInvitation.prototype, "userGroupId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>UserGroup, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], UserGroupInvitation.prototype, "userGroup", void 0);
+UserGroupInvitation = _ts_decorate([
+ Entity(),
+ Index([
+ "userId",
+ "userGroupId"
+ ], {
+ unique: true
+ })
+], UserGroupInvitation);
diff --git a/packages/backend/built/models/entities/user-group-joining.js b/packages/backend/built/models/entities/user-group-joining.js
new file mode 100644
index 0000000..46b701a
--- /dev/null
+++ b/packages/backend/built/models/entities/user-group-joining.js
@@ -0,0 +1,70 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { UserGroup } from "./user-group.js";
+import { id } from "../id.js";
+export let UserGroupJoining = class UserGroupJoining {
+ id;
+ createdAt;
+ userId;
+ user;
+ userGroupId;
+ userGroup;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], UserGroupJoining.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the UserGroupJoining."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], UserGroupJoining.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], UserGroupJoining.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], UserGroupJoining.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The group ID."
+ }),
+ _ts_metadata("design:type", Object)
+], UserGroupJoining.prototype, "userGroupId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>UserGroup, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], UserGroupJoining.prototype, "userGroup", void 0);
+UserGroupJoining = _ts_decorate([
+ Entity(),
+ Index([
+ "userId",
+ "userGroupId"
+ ], {
+ unique: true
+ })
+], UserGroupJoining);
diff --git a/packages/backend/built/models/entities/user-group.js b/packages/backend/built/models/entities/user-group.js
new file mode 100644
index 0000000..88fd4e5
--- /dev/null
+++ b/packages/backend/built/models/entities/user-group.js
@@ -0,0 +1,105 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let UserGroup = class UserGroup {
+ id;
+ createdAt;
+ name;
+ username;
+ userId;
+ user;
+ isPrivate;
+ allowCalls;
+ iconFileId;
+ symbolFileId;
+ constructor(data){
+ if (data == null) return;
+ for (const [k, v] of Object.entries(data)){
+ this[k] = v;
+ }
+ }
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], UserGroup.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the UserGroup."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], UserGroup.prototype, "createdAt", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 256
+ }),
+ _ts_metadata("design:type", String)
+], UserGroup.prototype, "name", void 0);
+_ts_decorate([
+ Index({
+ unique: true
+ }),
+ Column("varchar", {
+ length: 64,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], UserGroup.prototype, "username", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The ID of owner."
+ }),
+ _ts_metadata("design:type", Object)
+], UserGroup.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], UserGroup.prototype, "user", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], UserGroup.prototype, "isPrivate", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: true
+ }),
+ _ts_metadata("design:type", Boolean)
+], UserGroup.prototype, "allowCalls", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], UserGroup.prototype, "iconFileId", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], UserGroup.prototype, "symbolFileId", void 0);
+UserGroup = _ts_decorate([
+ Entity(),
+ _ts_metadata("design:type", Function),
+ _ts_metadata("design:paramtypes", [
+ typeof Partial === "undefined" ? Object : Partial
+ ])
+], UserGroup);
diff --git a/packages/backend/built/models/entities/user-ip.js b/packages/backend/built/models/entities/user-ip.js
new file mode 100644
index 0000000..33774c5
--- /dev/null
+++ b/packages/backend/built/models/entities/user-ip.js
@@ -0,0 +1,45 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, Index, Column, PrimaryGeneratedColumn } from "typeorm";
+import { id } from "../id.js";
+export let UserIp = class UserIp {
+ id;
+ createdAt;
+ userId;
+ ip;
+};
+_ts_decorate([
+ PrimaryGeneratedColumn(),
+ _ts_metadata("design:type", String)
+], UserIp.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {}),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], UserIp.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], UserIp.prototype, "userId", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128
+ }),
+ _ts_metadata("design:type", String)
+], UserIp.prototype, "ip", void 0);
+UserIp = _ts_decorate([
+ Entity(),
+ Index([
+ "userId",
+ "ip"
+ ], {
+ unique: true
+ })
+], UserIp);
diff --git a/packages/backend/built/models/entities/user-keypair.js b/packages/backend/built/models/entities/user-keypair.js
new file mode 100644
index 0000000..eb40cce
--- /dev/null
+++ b/packages/backend/built/models/entities/user-keypair.js
@@ -0,0 +1,54 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, JoinColumn, Column, OneToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let UserKeypair = class UserKeypair {
+ userId;
+ user;
+ publicKey;
+ privateKey;
+ constructor(data){
+ if (data == null) return;
+ for (const [k, v] of Object.entries(data)){
+ this[k] = v;
+ }
+ }
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", Object)
+], UserKeypair.prototype, "userId", void 0);
+_ts_decorate([
+ OneToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], UserKeypair.prototype, "user", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 4096
+ }),
+ _ts_metadata("design:type", String)
+], UserKeypair.prototype, "publicKey", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 4096
+ }),
+ _ts_metadata("design:type", String)
+], UserKeypair.prototype, "privateKey", void 0);
+UserKeypair = _ts_decorate([
+ Entity(),
+ _ts_metadata("design:type", Function),
+ _ts_metadata("design:paramtypes", [
+ typeof Partial === "undefined" ? Object : Partial
+ ])
+], UserKeypair);
diff --git a/packages/backend/built/models/entities/user-list-joining.js b/packages/backend/built/models/entities/user-list-joining.js
new file mode 100644
index 0000000..c47676f
--- /dev/null
+++ b/packages/backend/built/models/entities/user-list-joining.js
@@ -0,0 +1,70 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { UserList } from "./user-list.js";
+import { id } from "../id.js";
+export let UserListJoining = class UserListJoining {
+ id;
+ createdAt;
+ userId;
+ user;
+ userListId;
+ userList;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], UserListJoining.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the UserListJoining."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], UserListJoining.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], UserListJoining.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], UserListJoining.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The list ID."
+ }),
+ _ts_metadata("design:type", Object)
+], UserListJoining.prototype, "userListId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>UserList, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], UserListJoining.prototype, "userList", void 0);
+UserListJoining = _ts_decorate([
+ Entity(),
+ Index([
+ "userId",
+ "userListId"
+ ], {
+ unique: true
+ })
+], UserListJoining);
diff --git a/packages/backend/built/models/entities/user-list.js b/packages/backend/built/models/entities/user-list.js
new file mode 100644
index 0000000..0467e38
--- /dev/null
+++ b/packages/backend/built/models/entities/user-list.js
@@ -0,0 +1,62 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let UserList = class UserList {
+ id;
+ createdAt;
+ userId;
+ user;
+ name;
+ hideFromHomeTl;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], UserList.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the UserList."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], UserList.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The owner ID."
+ }),
+ _ts_metadata("design:type", Object)
+], UserList.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], UserList.prototype, "user", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ comment: "The name of the UserList."
+ }),
+ _ts_metadata("design:type", String)
+], UserList.prototype, "name", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false,
+ comment: "Whether posts from list members should be hidden from the home timeline."
+ }),
+ _ts_metadata("design:type", Boolean)
+], UserList.prototype, "hideFromHomeTl", void 0);
+UserList = _ts_decorate([
+ Entity()
+], UserList);
diff --git a/packages/backend/built/models/entities/user-note-pining.js b/packages/backend/built/models/entities/user-note-pining.js
new file mode 100644
index 0000000..584b787
--- /dev/null
+++ b/packages/backend/built/models/entities/user-note-pining.js
@@ -0,0 +1,63 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { Note } from "./note.js";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let UserNotePining = class UserNotePining {
+ id;
+ createdAt;
+ userId;
+ user;
+ noteId;
+ note;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], UserNotePining.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the UserNotePinings."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], UserNotePining.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], UserNotePining.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], UserNotePining.prototype, "user", void 0);
+_ts_decorate([
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], UserNotePining.prototype, "noteId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Note, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], UserNotePining.prototype, "note", void 0);
+UserNotePining = _ts_decorate([
+ Entity(),
+ Index([
+ "userId",
+ "noteId"
+ ], {
+ unique: true
+ })
+], UserNotePining);
diff --git a/packages/backend/built/models/entities/user-pending.js b/packages/backend/built/models/entities/user-pending.js
new file mode 100644
index 0000000..3f0f2fa
--- /dev/null
+++ b/packages/backend/built/models/entities/user-pending.js
@@ -0,0 +1,57 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, Column } from "typeorm";
+import { id } from "../id.js";
+export let UserPending = class UserPending {
+ id;
+ createdAt;
+ code;
+ username;
+ email;
+ password;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], UserPending.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone"),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], UserPending.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index({
+ unique: true
+ }),
+ Column("varchar", {
+ length: 128
+ }),
+ _ts_metadata("design:type", String)
+], UserPending.prototype, "code", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128
+ }),
+ _ts_metadata("design:type", String)
+], UserPending.prototype, "username", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128
+ }),
+ _ts_metadata("design:type", String)
+], UserPending.prototype, "email", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128
+ }),
+ _ts_metadata("design:type", String)
+], UserPending.prototype, "password", void 0);
+UserPending = _ts_decorate([
+ Entity()
+], UserPending);
diff --git a/packages/backend/built/models/entities/user-plan.js b/packages/backend/built/models/entities/user-plan.js
new file mode 100644
index 0000000..d402e82
--- /dev/null
+++ b/packages/backend/built/models/entities/user-plan.js
@@ -0,0 +1,71 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { Plan } from "./plan.js";
+import { id } from "../id.js";
+export let UserPlan = class UserPlan {
+ id;
+ createdAt;
+ userId;
+ user;
+ planId;
+ plan;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], UserPlan.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the UserPlan."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], UserPlan.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The user ID."
+ }),
+ _ts_metadata("design:type", Object)
+], UserPlan.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], UserPlan.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The plan ID."
+ }),
+ _ts_metadata("design:type", Object)
+], UserPlan.prototype, "planId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>Plan, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], UserPlan.prototype, "plan", void 0);
+UserPlan = _ts_decorate([
+ Entity(),
+ Index([
+ "userId",
+ "planId"
+ ], {
+ unique: true
+ })
+], UserPlan);
diff --git a/packages/backend/built/models/entities/user-profile.js b/packages/backend/built/models/entities/user-profile.js
new file mode 100644
index 0000000..0aa2cb0
--- /dev/null
+++ b/packages/backend/built/models/entities/user-profile.js
@@ -0,0 +1,363 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from "typeorm";
+import { ffVisibility, notificationTypes } from "../../types.js";
+import { id } from "../id.js";
+import { User } from "./user.js";
+import { Page } from "./page.js";
+export let UserProfile = class UserProfile {
+ userId;
+ user;
+ location;
+ birthday;
+ description;
+ fields;
+ mentions;
+ lang;
+ url;
+ email;
+ emailVerifyCode;
+ emailVerified;
+ emailNotificationTypes;
+ publicReactions;
+ allowCalls;
+ symbolFileId;
+ ffVisibility;
+ twoFactorTempSecret;
+ twoFactorSecret;
+ twoFactorEnabled;
+ securityKeysAvailable;
+ usePasswordLessLogin;
+ password;
+ moderationNote;
+ // TODO: そのうち消す
+ clientData;
+ // TODO: そのうち消す
+ room;
+ autoAcceptFollowed;
+ noCrawle;
+ preventAiLearning;
+ alwaysMarkNsfw;
+ carefulBot;
+ injectFeaturedNote;
+ receiveAnnouncementEmail;
+ pinnedPageId;
+ pinnedPage;
+ integrations;
+ enableWordMute;
+ mutedWords;
+ mutedInstances;
+ mutingNotificationTypes;
+ pronouns;
+ //#region Denormalized fields
+ userHost;
+ //#endregion
+ constructor(data){
+ if (data == null) return;
+ for (const [k, v] of Object.entries(data)){
+ this[k] = v;
+ }
+ }
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", Object)
+], UserProfile.prototype, "userId", void 0);
+_ts_decorate([
+ OneToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], UserProfile.prototype, "user", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true,
+ comment: "The location of the User."
+ }),
+ _ts_metadata("design:type", Object)
+], UserProfile.prototype, "location", void 0);
+_ts_decorate([
+ Column("char", {
+ length: 10,
+ nullable: true,
+ comment: "The birthday (YYYY-MM-DD) of the User."
+ }),
+ _ts_metadata("design:type", Object)
+], UserProfile.prototype, "birthday", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 2048,
+ nullable: true,
+ comment: "The description (bio) of the User."
+ }),
+ _ts_metadata("design:type", Object)
+], UserProfile.prototype, "description", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: []
+ }),
+ _ts_metadata("design:type", Array)
+], UserProfile.prototype, "fields", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: []
+ }),
+ _ts_metadata("design:type", typeof IMentionedRemoteUsers === "undefined" ? Object : IMentionedRemoteUsers)
+], UserProfile.prototype, "mentions", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 32,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], UserProfile.prototype, "lang", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "Remote URL of the user."
+ }),
+ _ts_metadata("design:type", Object)
+], UserProfile.prototype, "url", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true,
+ comment: "The email address of the User."
+ }),
+ _ts_metadata("design:type", Object)
+], UserProfile.prototype, "email", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], UserProfile.prototype, "emailVerifyCode", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], UserProfile.prototype, "emailVerified", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: [
+ "follow",
+ "receiveFollowRequest",
+ "groupInvited"
+ ]
+ }),
+ _ts_metadata("design:type", Array)
+], UserProfile.prototype, "emailNotificationTypes", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], UserProfile.prototype, "publicReactions", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], UserProfile.prototype, "allowCalls", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], UserProfile.prototype, "symbolFileId", void 0);
+_ts_decorate([
+ Column("enum", {
+ enum: ffVisibility,
+ default: "public"
+ }),
+ _ts_metadata("design:type", Object)
+], UserProfile.prototype, "ffVisibility", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], UserProfile.prototype, "twoFactorTempSecret", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], UserProfile.prototype, "twoFactorSecret", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], UserProfile.prototype, "twoFactorEnabled", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], UserProfile.prototype, "securityKeysAvailable", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], UserProfile.prototype, "usePasswordLessLogin", void 0);
+_ts_decorate([
+ 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."
+ }),
+ _ts_metadata("design:type", Object)
+], UserProfile.prototype, "password", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 8192,
+ default: ""
+ }),
+ _ts_metadata("design:type", Object)
+], UserProfile.prototype, "moderationNote", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: {},
+ comment: "The client-specific data of the User."
+ }),
+ _ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
+], UserProfile.prototype, "clientData", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: {},
+ comment: "The room data of the User."
+ }),
+ _ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
+], UserProfile.prototype, "room", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], UserProfile.prototype, "autoAcceptFollowed", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false,
+ comment: "Whether reject index by crawler."
+ }),
+ _ts_metadata("design:type", Boolean)
+], UserProfile.prototype, "noCrawle", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: true
+ }),
+ _ts_metadata("design:type", Boolean)
+], UserProfile.prototype, "preventAiLearning", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], UserProfile.prototype, "alwaysMarkNsfw", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], UserProfile.prototype, "carefulBot", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: true
+ }),
+ _ts_metadata("design:type", Boolean)
+], UserProfile.prototype, "injectFeaturedNote", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: true
+ }),
+ _ts_metadata("design:type", Boolean)
+], UserProfile.prototype, "receiveAnnouncementEmail", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], UserProfile.prototype, "pinnedPageId", void 0);
+_ts_decorate([
+ OneToOne((type)=>Page, {
+ onDelete: "SET NULL"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], UserProfile.prototype, "pinnedPage", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: {}
+ }),
+ _ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
+], UserProfile.prototype, "integrations", void 0);
+_ts_decorate([
+ Index(),
+ Column("boolean", {
+ default: false,
+ select: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], UserProfile.prototype, "enableWordMute", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: []
+ }),
+ _ts_metadata("design:type", Array)
+], UserProfile.prototype, "mutedWords", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: [],
+ comment: "List of instances muted by the user."
+ }),
+ _ts_metadata("design:type", Array)
+], UserProfile.prototype, "mutedInstances", void 0);
+_ts_decorate([
+ Column("enum", {
+ enum: notificationTypes,
+ array: true,
+ default: []
+ }),
+ _ts_metadata("design:type", Array)
+], UserProfile.prototype, "mutingNotificationTypes", void 0);
+_ts_decorate([
+ Column("jsonb", {
+ default: {},
+ comment: "Language map of user pronouns"
+ }),
+ _ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
+], UserProfile.prototype, "pronouns", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "[Denormalized]"
+ }),
+ _ts_metadata("design:type", Object)
+], UserProfile.prototype, "userHost", void 0);
+UserProfile = _ts_decorate([
+ Entity(),
+ _ts_metadata("design:type", Function),
+ _ts_metadata("design:paramtypes", [
+ typeof Partial === "undefined" ? Object : Partial
+ ])
+], UserProfile);
diff --git a/packages/backend/built/models/entities/user-publickey.js b/packages/backend/built/models/entities/user-publickey.js
new file mode 100644
index 0000000..d61851a
--- /dev/null
+++ b/packages/backend/built/models/entities/user-publickey.js
@@ -0,0 +1,57 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let UserPublickey = class UserPublickey {
+ userId;
+ user;
+ keyId;
+ keyPem;
+ constructor(data){
+ if (data == null) return;
+ for (const [k, v] of Object.entries(data)){
+ this[k] = v;
+ }
+ }
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", Object)
+], UserPublickey.prototype, "userId", void 0);
+_ts_decorate([
+ OneToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], UserPublickey.prototype, "user", void 0);
+_ts_decorate([
+ Index({
+ unique: true
+ }),
+ Column("varchar", {
+ length: 512
+ }),
+ _ts_metadata("design:type", String)
+], UserPublickey.prototype, "keyId", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 4096
+ }),
+ _ts_metadata("design:type", String)
+], UserPublickey.prototype, "keyPem", void 0);
+UserPublickey = _ts_decorate([
+ Entity(),
+ _ts_metadata("design:type", Function),
+ _ts_metadata("design:paramtypes", [
+ typeof Partial === "undefined" ? Object : Partial
+ ])
+], UserPublickey);
diff --git a/packages/backend/built/models/entities/user-security-key.js b/packages/backend/built/models/entities/user-security-key.js
new file mode 100644
index 0000000..68a13d0
--- /dev/null
+++ b/packages/backend/built/models/entities/user-security-key.js
@@ -0,0 +1,71 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, JoinColumn, Column, ManyToOne, Index } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let UserSecurityKey = class UserSecurityKey {
+ id;
+ userId;
+ user;
+ publicKey;
+ lastUsed;
+ name;
+ constructor(data){
+ if (data == null) return;
+ for (const [k, v] of Object.entries(data)){
+ this[k] = v;
+ }
+ }
+};
+_ts_decorate([
+ PrimaryColumn("varchar", {
+ comment: "Variable-length id given to navigator.credentials.get()"
+ }),
+ _ts_metadata("design:type", String)
+], UserSecurityKey.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], UserSecurityKey.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], UserSecurityKey.prototype, "user", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ comment: "Variable-length public key used to verify attestations (hex-encoded)."
+ }),
+ _ts_metadata("design:type", String)
+], UserSecurityKey.prototype, "publicKey", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The date of the last time the UserSecurityKey was successfully validated."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], UserSecurityKey.prototype, "lastUsed", void 0);
+_ts_decorate([
+ Column("varchar", {
+ comment: "User-defined name for this key",
+ length: 30
+ }),
+ _ts_metadata("design:type", String)
+], UserSecurityKey.prototype, "name", void 0);
+UserSecurityKey = _ts_decorate([
+ Entity(),
+ _ts_metadata("design:type", Function),
+ _ts_metadata("design:paramtypes", [
+ typeof Partial === "undefined" ? Object : Partial
+ ])
+], UserSecurityKey);
diff --git a/packages/backend/built/models/entities/user.js b/packages/backend/built/models/entities/user.js
new file mode 100644
index 0000000..760b7ab
--- /dev/null
+++ b/packages/backend/built/models/entities/user.js
@@ -0,0 +1,425 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from "typeorm";
+import { id } from "../id.js";
+import { DriveFile } from "./drive-file.js";
+export let User = class User {
+ id;
+ createdAt;
+ updatedAt;
+ lastFetchedAt;
+ lastActiveDate;
+ hideOnlineStatus;
+ username;
+ usernameLower;
+ name;
+ followersCount;
+ followingCount;
+ movedToUri;
+ alsoKnownAs;
+ notesCount;
+ avatarId;
+ avatarUrl;
+ avatarBlurhash;
+ avatar;
+ bannerId;
+ bannerUrl;
+ bannerBlurhash;
+ banner;
+ tags;
+ isSuspended;
+ isSilenced;
+ isLocked;
+ isBot;
+ isCat;
+ speakAsCat;
+ isAdmin;
+ isModerator;
+ isVerified;
+ minorBadges;
+ isExplorable;
+ // アカウントが削除されたかどうかのフラグだが、完全に削除される際は物理削除なので実質削除されるまでの「削除が進行しているかどうか」のフラグ
+ isDeleted;
+ emojis;
+ host;
+ inbox;
+ sharedInbox;
+ featured;
+ uri;
+ followersUri;
+ token;
+ driveCapacityOverrideMb;
+ canBite;
+ constructor(data){
+ if (data == null) return;
+ for (const [k, v] of Object.entries(data)){
+ this[k] = v;
+ }
+ }
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], User.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the User."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], User.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ nullable: true,
+ comment: "The updated date of the User."
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "updatedAt", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "lastFetchedAt", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "lastActiveDate", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false
+ }),
+ _ts_metadata("design:type", Boolean)
+], User.prototype, "hideOnlineStatus", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ comment: "The username of the User."
+ }),
+ _ts_metadata("design:type", String)
+], User.prototype, "username", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 128,
+ select: false,
+ comment: "The username (lowercased) of the User."
+ }),
+ _ts_metadata("design:type", String)
+], User.prototype, "usernameLower", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true,
+ comment: "The name of the User."
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "name", void 0);
+_ts_decorate([
+ Column("integer", {
+ default: 0,
+ comment: "The count of followers."
+ }),
+ _ts_metadata("design:type", Number)
+], User.prototype, "followersCount", void 0);
+_ts_decorate([
+ Column("integer", {
+ default: 0,
+ comment: "The count of following."
+ }),
+ _ts_metadata("design:type", Number)
+], User.prototype, "followingCount", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "The URI of the new account of the User"
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "movedToUri", void 0);
+_ts_decorate([
+ Column("simple-array", {
+ nullable: true,
+ comment: "URIs the user is known as too"
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "alsoKnownAs", void 0);
+_ts_decorate([
+ Column("integer", {
+ default: 0,
+ comment: "The count of notes."
+ }),
+ _ts_metadata("design:type", Number)
+], User.prototype, "notesCount", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "The ID of avatar DriveFile."
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "avatarId", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "The URL of the avatar DriveFile"
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "avatarUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true,
+ comment: "The blurhash of the avatar DriveFile"
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "avatarBlurhash", void 0);
+_ts_decorate([
+ OneToOne((type)=>DriveFile, {
+ onDelete: "SET NULL"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], User.prototype, "avatar", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true,
+ comment: "The ID of banner DriveFile."
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "bannerId", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "The URL of the banner DriveFile"
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "bannerUrl", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ nullable: true,
+ comment: "The blurhash of the banner DriveFile"
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "bannerBlurhash", void 0);
+_ts_decorate([
+ OneToOne((type)=>DriveFile, {
+ onDelete: "SET NULL"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], User.prototype, "banner", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 128,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], User.prototype, "tags", void 0);
+_ts_decorate([
+ Index(),
+ Column("boolean", {
+ default: false,
+ comment: "Whether the User is suspended."
+ }),
+ _ts_metadata("design:type", Boolean)
+], User.prototype, "isSuspended", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false,
+ comment: "Whether the User is silenced."
+ }),
+ _ts_metadata("design:type", Boolean)
+], User.prototype, "isSilenced", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false,
+ comment: "Whether the User is locked."
+ }),
+ _ts_metadata("design:type", Boolean)
+], User.prototype, "isLocked", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false,
+ comment: "Whether the User is a bot."
+ }),
+ _ts_metadata("design:type", Boolean)
+], User.prototype, "isBot", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false,
+ comment: "Whether the User is a cat."
+ }),
+ _ts_metadata("design:type", Boolean)
+], User.prototype, "isCat", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: true,
+ comment: "Whether to speak as a cat if isCat."
+ }),
+ _ts_metadata("design:type", Boolean)
+], User.prototype, "speakAsCat", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false,
+ comment: "Whether the User is the admin."
+ }),
+ _ts_metadata("design:type", Boolean)
+], User.prototype, "isAdmin", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false,
+ comment: "Whether the User is a moderator."
+ }),
+ _ts_metadata("design:type", Boolean)
+], User.prototype, "isModerator", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false,
+ comment: "Whether the User has an administrator-issued verified badge."
+ }),
+ _ts_metadata("design:type", Boolean)
+], User.prototype, "isVerified", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 1,
+ array: true,
+ default: "{}",
+ comment: "Self-selected minor-safety badges."
+ }),
+ _ts_metadata("design:type", Array)
+], User.prototype, "minorBadges", void 0);
+_ts_decorate([
+ Index(),
+ Column("boolean", {
+ default: true,
+ comment: "Whether the User is explorable."
+ }),
+ _ts_metadata("design:type", Boolean)
+], User.prototype, "isExplorable", void 0);
+_ts_decorate([
+ Column("boolean", {
+ default: false,
+ comment: "Whether the User is deleted."
+ }),
+ _ts_metadata("design:type", Boolean)
+], User.prototype, "isDeleted", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], User.prototype, "emojis", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 512,
+ nullable: true,
+ comment: "The host of the User. It will be null if the origin of the user is local."
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "host", void 0);
+_ts_decorate([
+ 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."
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "inbox", void 0);
+_ts_decorate([
+ 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."
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "sharedInbox", void 0);
+_ts_decorate([
+ 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."
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "featured", void 0);
+_ts_decorate([
+ 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."
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "uri", void 0);
+_ts_decorate([
+ 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."
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "followersUri", void 0);
+_ts_decorate([
+ 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."
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "token", void 0);
+_ts_decorate([
+ Column("integer", {
+ nullable: true,
+ comment: "Overrides user drive capacity limit"
+ }),
+ _ts_metadata("design:type", Object)
+], User.prototype, "driveCapacityOverrideMb", void 0);
+_ts_decorate([
+ Column("enum", {
+ nullable: false,
+ default: "nobody",
+ enum: [
+ "anyone",
+ "followers",
+ "nobody"
+ ]
+ }),
+ _ts_metadata("design:type", String)
+], User.prototype, "canBite", void 0);
+User = _ts_decorate([
+ Entity(),
+ Index([
+ "usernameLower",
+ "host"
+ ], {
+ unique: true
+ }),
+ _ts_metadata("design:type", Function),
+ _ts_metadata("design:paramtypes", [
+ typeof Partial === "undefined" ? Object : Partial
+ ])
+], User);
diff --git a/packages/backend/built/models/entities/verified-badge-request.js b/packages/backend/built/models/entities/verified-badge-request.js
new file mode 100644
index 0000000..8cd3f7d
--- /dev/null
+++ b/packages/backend/built/models/entities/verified-badge-request.js
@@ -0,0 +1,84 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export let VerifiedBadgeRequest = class VerifiedBadgeRequest {
+ id;
+ createdAt;
+ resolvedAt;
+ userId;
+ user;
+ resolverId;
+ resolver;
+ status;
+ comment;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], VerifiedBadgeRequest.prototype, "id", void 0);
+_ts_decorate([
+ Index(),
+ Column("timestamp with time zone", {
+ comment: "The created date of the VerifiedBadgeRequest."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], VerifiedBadgeRequest.prototype, "createdAt", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], VerifiedBadgeRequest.prototype, "resolvedAt", void 0);
+_ts_decorate([
+ Index(),
+ Column(id()),
+ _ts_metadata("design:type", Object)
+], VerifiedBadgeRequest.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], VerifiedBadgeRequest.prototype, "user", void 0);
+_ts_decorate([
+ Column({
+ ...id(),
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], VerifiedBadgeRequest.prototype, "resolverId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "SET NULL"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], VerifiedBadgeRequest.prototype, "resolver", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 16,
+ default: "pending"
+ }),
+ _ts_metadata("design:type", String)
+], VerifiedBadgeRequest.prototype, "status", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 2048,
+ default: ""
+ }),
+ _ts_metadata("design:type", String)
+], VerifiedBadgeRequest.prototype, "comment", void 0);
+VerifiedBadgeRequest = _ts_decorate([
+ Entity()
+], VerifiedBadgeRequest);
diff --git a/packages/backend/built/models/entities/webhook.js b/packages/backend/built/models/entities/webhook.js
new file mode 100644
index 0000000..f45086e
--- /dev/null
+++ b/packages/backend/built/models/entities/webhook.js
@@ -0,0 +1,114 @@
+function _ts_decorate(decorators, target, key, desc) {
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
+}
+function _ts_metadata(k, v) {
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
+}
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from "typeorm";
+import { User } from "./user.js";
+import { id } from "../id.js";
+export const webhookEventTypes = [
+ "mention",
+ "unfollow",
+ "follow",
+ "followed",
+ "note",
+ "reply",
+ "renote",
+ "reaction"
+];
+export let Webhook = class Webhook {
+ id;
+ createdAt;
+ userId;
+ user;
+ name;
+ on;
+ url;
+ secret;
+ active;
+ /**
+ * 直近のリクエスト送信日時
+ */ latestSentAt;
+ /**
+ * 直近のリクエスト送信時のHTTPステータスコード
+ */ latestStatus;
+};
+_ts_decorate([
+ PrimaryColumn(id()),
+ _ts_metadata("design:type", String)
+], Webhook.prototype, "id", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ comment: "The created date of the Antenna."
+ }),
+ _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
+], Webhook.prototype, "createdAt", void 0);
+_ts_decorate([
+ Index(),
+ Column({
+ ...id(),
+ comment: "The owner ID."
+ }),
+ _ts_metadata("design:type", Object)
+], Webhook.prototype, "userId", void 0);
+_ts_decorate([
+ ManyToOne((type)=>User, {
+ onDelete: "CASCADE"
+ }),
+ JoinColumn(),
+ _ts_metadata("design:type", Object)
+], Webhook.prototype, "user", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 128,
+ comment: "The name of the Antenna."
+ }),
+ _ts_metadata("design:type", String)
+], Webhook.prototype, "name", void 0);
+_ts_decorate([
+ Index(),
+ Column("varchar", {
+ length: 128,
+ array: true,
+ default: "{}"
+ }),
+ _ts_metadata("design:type", Array)
+], Webhook.prototype, "on", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 1024
+ }),
+ _ts_metadata("design:type", String)
+], Webhook.prototype, "url", void 0);
+_ts_decorate([
+ Column("varchar", {
+ length: 1024
+ }),
+ _ts_metadata("design:type", String)
+], Webhook.prototype, "secret", void 0);
+_ts_decorate([
+ Index(),
+ Column("boolean", {
+ default: true
+ }),
+ _ts_metadata("design:type", Boolean)
+], Webhook.prototype, "active", void 0);
+_ts_decorate([
+ Column("timestamp with time zone", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Webhook.prototype, "latestSentAt", void 0);
+_ts_decorate([
+ Column("integer", {
+ nullable: true
+ }),
+ _ts_metadata("design:type", Object)
+], Webhook.prototype, "latestStatus", void 0);
+Webhook = _ts_decorate([
+ Entity()
+], Webhook);
diff --git a/packages/backend/built/models/id.js b/packages/backend/built/models/id.js
new file mode 100644
index 0000000..f0fde90
--- /dev/null
+++ b/packages/backend/built/models/id.js
@@ -0,0 +1,4 @@
+export const id = ()=>({
+ type: "varchar",
+ length: 32
+ });
diff --git a/packages/backend/built/models/index.js b/packages/backend/built/models/index.js
new file mode 100644
index 0000000..792bae0
--- /dev/null
+++ b/packages/backend/built/models/index.js
@@ -0,0 +1,164 @@
+import "typeorm";
+import { db } from "../db/postgre.js";
+import { Announcement } from "./entities/announcement.js";
+import { AnnouncementRead } from "./entities/announcement-read.js";
+import { Poll } from "./entities/poll.js";
+import { PollVote } from "./entities/poll-vote.js";
+import { Meta } from "./entities/meta.js";
+import { SwSubscription } from "./entities/sw-subscription.js";
+import { NoteWatching } from "./entities/note-watching.js";
+import { NoteThreadMuting } from "./entities/note-thread-muting.js";
+import { NoteUnread } from "./entities/note-unread.js";
+import { RegistrationTicket } from "./entities/registration-tickets.js";
+import { UserRepository } from "./repositories/user.js";
+import { NoteRepository } from "./repositories/note.js";
+import { DriveFileRepository } from "./repositories/drive-file.js";
+import { DriveFolderRepository } from "./repositories/drive-folder.js";
+import { AccessToken } from "./entities/access-token.js";
+import { UserNotePining } from "./entities/user-note-pining.js";
+import { SigninRepository } from "./repositories/signin.js";
+import { MessagingMessageRepository } from "./repositories/messaging-message.js";
+import { UserListRepository } from "./repositories/user-list.js";
+import { UserListJoining } from "./entities/user-list-joining.js";
+import { UserGroupRepository } from "./repositories/user-group.js";
+import { UserGroupJoining } from "./entities/user-group-joining.js";
+import { UserGroupInvitationRepository } from "./repositories/user-group-invitation.js";
+import { FollowRequestRepository } from "./repositories/follow-request.js";
+import { MutingRepository } from "./repositories/muting.js";
+import { RenoteMutingRepository } from "./repositories/renote-muting.js";
+import { BlockingRepository } from "./repositories/blocking.js";
+import { CallBlockingRepository } from "./repositories/call-blocking.js";
+import { NoteReactionRepository } from "./repositories/note-reaction.js";
+import { NotificationRepository } from "./repositories/notification.js";
+import { NoteFavoriteRepository } from "./repositories/note-favorite.js";
+import { UserPublickey } from "./entities/user-publickey.js";
+import { UserKeypair } from "./entities/user-keypair.js";
+import { AppRepository } from "./repositories/app.js";
+import { FollowingRepository } from "./repositories/following.js";
+import { AbuseUserReportRepository } from "./repositories/abuse-user-report.js";
+import { AuthSessionRepository } from "./repositories/auth-session.js";
+import { AttestationChallenge } from "./entities/attestation-challenge.js";
+import { UserSecurityKey } from "./entities/user-security-key.js";
+import { HashtagRepository } from "./repositories/hashtag.js";
+import { PageRepository } from "./repositories/page.js";
+import { PageLikeRepository } from "./repositories/page-like.js";
+import { GalleryPostRepository } from "./repositories/gallery-post.js";
+import { GalleryLikeRepository } from "./repositories/gallery-like.js";
+import { ModerationLogRepository } from "./repositories/moderation-logs.js";
+import { UsedUsername } from "./entities/used-username.js";
+import { ClipRepository } from "./repositories/clip.js";
+import { ClipNote } from "./entities/clip-note.js";
+import { AntennaRepository } from "./repositories/antenna.js";
+import { PromoNote } from "./entities/promo-note.js";
+import { PromoRead } from "./entities/promo-read.js";
+import { EmojiRepository } from "./repositories/emoji.js";
+import { RelayRepository } from "./repositories/relay.js";
+import { ChannelRepository } from "./repositories/channel.js";
+import { ChannelFollowing } from "./entities/channel-following.js";
+import { ChannelNotePining } from "./entities/channel-note-pining.js";
+import { RegistryItem } from "./entities/registry-item.js";
+import { PasswordResetRequest } from "./entities/password-reset-request.js";
+import { UserPending } from "./entities/user-pending.js";
+import { InstanceRepository } from "./repositories/instance.js";
+import { Webhook } from "./entities/webhook.js";
+import { UserIp } from "./entities/user-ip.js";
+import { UserEmoji } from "./entities/user-emoji.js";
+import { NoteEdit } from "./entities/note-edit.js";
+import { OAuthApp } from "./entities/oauth-app.js";
+import { OAuthToken } from "./entities/oauth-token.js";
+import { UserProfileRepository } from "./repositories/user-profile.js";
+import { HtmlNoteCacheEntry } from "./entities/html-note-cache-entry.js";
+import { HtmlUserCacheEntry } from "./entities/html-user-cache-entry.js";
+import { BiteRespository } from "./repositories/bite.js";
+import { InteractionStamp } from "./entities/interaction-stamp.js";
+import { ReversiGameRepository } from "./repositories/reversi-game.js";
+import { ReversiMatching } from "./entities/reversi-matching.js";
+import { ShogiGameRepository } from "./repositories/shogi-game.js";
+import { ScheduledNote } from "./entities/scheduled-note.js";
+import { Memoriet } from "./entities/memoriet.js";
+import { MemorietArchive } from "./entities/memoriet-archive.js";
+import { MemorietView } from "./entities/memoriet-view.js";
+import { VerifiedBadgeRequestRepository } from "./repositories/verified-badge-request.js";
+import { PlanRepository } from "./repositories/plan.js";
+import { UserPlan } from "./entities/user-plan.js";
+export const Announcements = db.getRepository(Announcement);
+export const AnnouncementReads = db.getRepository(AnnouncementRead);
+export const Apps = AppRepository;
+export const Notes = NoteRepository;
+export const NoteEdits = db.getRepository(NoteEdit);
+export const NoteFavorites = NoteFavoriteRepository;
+export const NoteWatchings = db.getRepository(NoteWatching);
+export const NoteThreadMutings = db.getRepository(NoteThreadMuting);
+export const NoteReactions = NoteReactionRepository;
+export const NoteUnreads = db.getRepository(NoteUnread);
+export const Polls = db.getRepository(Poll);
+export const PollVotes = db.getRepository(PollVote);
+export const Users = UserRepository;
+export const UserProfiles = UserProfileRepository;
+export const UserKeypairs = db.getRepository(UserKeypair);
+export const UserPendings = db.getRepository(UserPending);
+export const AttestationChallenges = db.getRepository(AttestationChallenge);
+export const UserSecurityKeys = db.getRepository(UserSecurityKey);
+export const UserPublickeys = db.getRepository(UserPublickey);
+export const UserLists = UserListRepository;
+export const UserListJoinings = db.getRepository(UserListJoining);
+export const UserGroups = UserGroupRepository;
+export const UserGroupJoinings = db.getRepository(UserGroupJoining);
+export const UserGroupInvitations = UserGroupInvitationRepository;
+export const UserNotePinings = db.getRepository(UserNotePining);
+export const UserIps = db.getRepository(UserIp);
+export const UserEmojis = db.getRepository(UserEmoji);
+export const UsedUsernames = db.getRepository(UsedUsername);
+export const Followings = FollowingRepository;
+export const FollowRequests = FollowRequestRepository;
+export const Instances = InstanceRepository;
+export const Emojis = EmojiRepository;
+export const DriveFiles = DriveFileRepository;
+export const DriveFolders = DriveFolderRepository;
+export const Notifications = NotificationRepository;
+export const Metas = db.getRepository(Meta);
+export const Mutings = MutingRepository;
+export const RenoteMutings = RenoteMutingRepository;
+export const Blockings = BlockingRepository;
+export const CallBlockings = CallBlockingRepository;
+export const SwSubscriptions = db.getRepository(SwSubscription);
+export const Hashtags = HashtagRepository;
+export const AbuseUserReports = AbuseUserReportRepository;
+export const RegistrationTickets = db.getRepository(RegistrationTicket);
+export const AuthSessions = AuthSessionRepository;
+export const AccessTokens = db.getRepository(AccessToken);
+export const Signins = SigninRepository;
+export const MessagingMessages = MessagingMessageRepository;
+export const Pages = PageRepository;
+export const PageLikes = PageLikeRepository;
+export const GalleryPosts = GalleryPostRepository;
+export const GalleryLikes = GalleryLikeRepository;
+export const ModerationLogs = ModerationLogRepository;
+export const Clips = ClipRepository;
+export const ClipNotes = db.getRepository(ClipNote);
+export const Antennas = AntennaRepository;
+export const PromoNotes = db.getRepository(PromoNote);
+export const PromoReads = db.getRepository(PromoRead);
+export const Relays = RelayRepository;
+export const Channels = ChannelRepository;
+export const ChannelFollowings = db.getRepository(ChannelFollowing);
+export const ChannelNotePinings = db.getRepository(ChannelNotePining);
+export const RegistryItems = db.getRepository(RegistryItem);
+export const Webhooks = db.getRepository(Webhook);
+export const PasswordResetRequests = db.getRepository(PasswordResetRequest);
+export const OAuthApps = db.getRepository(OAuthApp);
+export const OAuthTokens = db.getRepository(OAuthToken);
+export const HtmlUserCacheEntries = db.getRepository(HtmlUserCacheEntry);
+export const HtmlNoteCacheEntries = db.getRepository(HtmlNoteCacheEntry);
+export const Bites = BiteRespository;
+export const InteractionStamps = db.getRepository(InteractionStamp);
+export const ReversiGames = ReversiGameRepository;
+export const ReversiMatchings = db.getRepository(ReversiMatching);
+export const ShogiGames = ShogiGameRepository;
+export const ScheduledNotes = db.getRepository(ScheduledNote);
+export const Memoriets = db.getRepository(Memoriet);
+export const MemorietArchives = db.getRepository(MemorietArchive);
+export const MemorietViews = db.getRepository(MemorietView);
+export const VerifiedBadgeRequests = VerifiedBadgeRequestRepository;
+export const Plans = PlanRepository;
+export const UserPlans = db.getRepository(UserPlan);
diff --git a/packages/backend/built/models/repositories/abuse-user-report.js b/packages/backend/built/models/repositories/abuse-user-report.js
new file mode 100644
index 0000000..938cc87
--- /dev/null
+++ b/packages/backend/built/models/repositories/abuse-user-report.js
@@ -0,0 +1,33 @@
+import { db } from "../../db/postgre.js";
+import { Users } from "../index.js";
+import { AbuseUserReport } from "../entities/abuse-user-report.js";
+import { awaitAll } from "../../prelude/await-all.js";
+export const AbuseUserReportRepository = db.getRepository(AbuseUserReport).extend({
+ async pack (src) {
+ const report = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return await awaitAll({
+ id: report.id,
+ createdAt: report.createdAt.toISOString(),
+ comment: report.comment,
+ resolved: report.resolved,
+ reporterId: report.reporterId,
+ targetUserId: report.targetUserId,
+ assigneeId: report.assigneeId,
+ reporter: Users.pack(report.reporter || report.reporterId, null, {
+ detail: true
+ }),
+ targetUser: Users.pack(report.targetUser || report.targetUserId, null, {
+ detail: true
+ }),
+ assignee: report.assigneeId ? Users.pack(report.assignee || report.assigneeId, null, {
+ detail: true
+ }) : null,
+ forwarded: report.forwarded
+ });
+ },
+ packMany (reports) {
+ return Promise.all(reports.map((x)=>this.pack(x)));
+ }
+});
diff --git a/packages/backend/built/models/repositories/antenna.js b/packages/backend/built/models/repositories/antenna.js
new file mode 100644
index 0000000..bd66097
--- /dev/null
+++ b/packages/backend/built/models/repositories/antenna.js
@@ -0,0 +1,30 @@
+import { db } from "../../db/postgre.js";
+import { Antenna } from "../entities/antenna.js";
+import { UserGroupJoinings } from "../index.js";
+export const AntennaRepository = db.getRepository(Antenna).extend({
+ async pack (src) {
+ const antenna = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ 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: false
+ };
+ }
+});
diff --git a/packages/backend/built/models/repositories/app.js b/packages/backend/built/models/repositories/app.js
new file mode 100644
index 0000000..7fe3c9c
--- /dev/null
+++ b/packages/backend/built/models/repositories/app.js
@@ -0,0 +1,30 @@
+import { db } from "../../db/postgre.js";
+import { App } from "../entities/app.js";
+import { AccessTokens } from "../index.js";
+export const AppRepository = db.getRepository(App).extend({
+ async pack (src, me, options) {
+ const opts = Object.assign({
+ detail: false,
+ includeSecret: false,
+ includeProfileImageIds: false
+ }, options);
+ const app = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return {
+ id: app.id,
+ name: app.name,
+ callbackUrl: app.callbackUrl,
+ permission: app.permission,
+ ...opts.includeSecret ? {
+ secret: app.secret
+ } : {},
+ ...me ? {
+ isAuthorized: await AccessTokens.countBy({
+ appId: app.id,
+ userId: me.id
+ }).then((count)=>count > 0)
+ } : {}
+ };
+ }
+});
diff --git a/packages/backend/built/models/repositories/auth-session.js b/packages/backend/built/models/repositories/auth-session.js
new file mode 100644
index 0000000..d42800c
--- /dev/null
+++ b/packages/backend/built/models/repositories/auth-session.js
@@ -0,0 +1,16 @@
+import { db } from "../../db/postgre.js";
+import { Apps } from "../index.js";
+import { AuthSession } from "../entities/auth-session.js";
+import { awaitAll } from "../../prelude/await-all.js";
+export const AuthSessionRepository = db.getRepository(AuthSession).extend({
+ async pack (src, me) {
+ const session = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return await awaitAll({
+ id: session.id,
+ app: Apps.pack(session.appId, me),
+ token: session.token
+ });
+ }
+});
diff --git a/packages/backend/built/models/repositories/bite.js b/packages/backend/built/models/repositories/bite.js
new file mode 100644
index 0000000..522828f
--- /dev/null
+++ b/packages/backend/built/models/repositories/bite.js
@@ -0,0 +1,106 @@
+import { db } from "../../db/postgre.js";
+import { Bite } from "../entities/bite.js";
+import { Bites, Notes, Users } from "../index.js";
+import { awaitAll } from "../../prelude/await-all.js";
+import config from "../../config/index.js";
+export const BiteRespository = db.getRepository(Bite).extend({
+ targetType (bite) {
+ if (bite.targetUserId) return "user";
+ if (bite.targetBiteId) return "bite";
+ return "note";
+ },
+ async pack (src, me) {
+ const bite = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return await awaitAll({
+ id: bite.id,
+ user: Users.pack(bite.user ?? bite.userId, me, {
+ detail: false
+ }),
+ targetType: BiteRespository.targetType(bite),
+ target: this.packTarget(bite, me),
+ replied: bite.replied
+ });
+ },
+ async packTarget (bite, me) {
+ switch(BiteRespository.targetType(bite)){
+ case "user":
+ return await Users.pack(bite.targetUser ?? bite.targetUserId, me, {
+ detail: false
+ });
+ case "bite":
+ return await this.pack(bite.targetBite ?? bite.targetBiteId, me);
+ case "note":
+ return await Notes.pack(bite.targetNote ?? bite.targetNoteId, me, {
+ detail: false
+ });
+ }
+ },
+ async targetUri (bite) {
+ switch(BiteRespository.targetType(bite)){
+ case "user":
+ {
+ bite.targetUser = bite.targetUser ?? await Users.findOneOrFail({
+ where: {
+ id: bite.targetUserId
+ }
+ });
+ return bite.targetUser.uri ?? `${config.url}/users/${bite.targetUserId}`;
+ }
+ case "bite":
+ {
+ bite.targetBite = bite.targetBite ?? await Bites.findOneOrFail({
+ where: {
+ id: bite.targetBiteId
+ }
+ });
+ return bite.targetBite.uri ?? `${config.url}/bites/${bite.targetBiteId}`;
+ }
+ case "note":
+ {
+ bite.targetNote = bite.targetNote ?? await Notes.findOneOrFail({
+ where: {
+ id: bite.targetNoteId
+ }
+ });
+ return bite.targetNote.uri ?? `${config.url}/notes/${bite.targetBiteId}`;
+ }
+ }
+ },
+ async targetUserUri (bite) {
+ switch(BiteRespository.targetType(bite)){
+ case "user":
+ if (!bite.targetUser) bite.targetUser = await Users.findOneByOrFail({
+ id: bite.targetUserId
+ });
+ return bite.targetUser.uri ?? `${config.url}/users/${bite.targetUserId}`;
+ case "bite":
+ bite.targetBite = bite.targetBite ?? await Bites.findOneOrFail({
+ where: {
+ id: bite.targetBiteId
+ },
+ relations: [
+ "user"
+ ]
+ });
+ bite.targetBite.user = bite.targetBite.user ?? await Users.findOneByOrFail({
+ id: bite.targetBite.userId
+ });
+ return bite.targetBite.user.uri ?? `${config.url}/users/${bite.targetBite.userId}`;
+ case "note":
+ bite.targetNote = bite.targetNote ?? await Notes.findOneOrFail({
+ where: {
+ id: bite.targetNoteId
+ },
+ relations: [
+ "user"
+ ]
+ });
+ bite.targetNote.user = bite.targetNote.user ?? await Users.findOneByOrFail({
+ id: bite.targetNote.userId
+ });
+ return bite.targetNote.user.uri ?? `${config.url}/users/${bite.targetNote.userId}`;
+ }
+ }
+});
diff --git a/packages/backend/built/models/repositories/blocking.js b/packages/backend/built/models/repositories/blocking.js
new file mode 100644
index 0000000..92e4437
--- /dev/null
+++ b/packages/backend/built/models/repositories/blocking.js
@@ -0,0 +1,22 @@
+import { db } from "../../db/postgre.js";
+import { Users } from "../index.js";
+import { Blocking } from "../entities/blocking.js";
+import { awaitAll } from "../../prelude/await-all.js";
+export const BlockingRepository = db.getRepository(Blocking).extend({
+ async pack (src, me) {
+ const blocking = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return await awaitAll({
+ id: blocking.id,
+ createdAt: blocking.createdAt.toISOString(),
+ blockeeId: blocking.blockeeId,
+ blockee: Users.pack(blocking.blockeeId, me, {
+ detail: true
+ })
+ });
+ },
+ packMany (blockings, me) {
+ return Promise.all(blockings.map((x)=>this.pack(x, me)));
+ }
+});
diff --git a/packages/backend/built/models/repositories/call-blocking.js b/packages/backend/built/models/repositories/call-blocking.js
new file mode 100644
index 0000000..43a4ab0
--- /dev/null
+++ b/packages/backend/built/models/repositories/call-blocking.js
@@ -0,0 +1,22 @@
+import { db } from "../../db/postgre.js";
+import { Users } from "../index.js";
+import { CallBlocking } from "../entities/call-blocking.js";
+import { awaitAll } from "../../prelude/await-all.js";
+export const CallBlockingRepository = db.getRepository(CallBlocking).extend({
+ async pack (src, me) {
+ const blocking = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return await awaitAll({
+ id: blocking.id,
+ createdAt: blocking.createdAt.toISOString(),
+ blockeeId: blocking.blockeeId,
+ blockee: Users.pack(blocking.blockeeId, me, {
+ detail: true
+ })
+ });
+ },
+ packMany (blockings, me) {
+ return Promise.all(blockings.map((x)=>this.pack(x, me)));
+ }
+});
diff --git a/packages/backend/built/models/repositories/channel.js b/packages/backend/built/models/repositories/channel.js
new file mode 100644
index 0000000..a546fa6
--- /dev/null
+++ b/packages/backend/built/models/repositories/channel.js
@@ -0,0 +1,37 @@
+import { db } from "../../db/postgre.js";
+import { Channel } from "../entities/channel.js";
+import { DriveFiles, ChannelFollowings, NoteUnreads } from "../index.js";
+export const ChannelRepository = db.getRepository(Channel).extend({
+ async pack (src, me) {
+ const channel = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ const meId = me ? me.id : null;
+ const banner = channel.bannerId ? await DriveFiles.findOneBy({
+ id: channel.bannerId
+ }) : null;
+ const hasUnreadNote = meId ? await NoteUnreads.findOneBy({
+ noteChannelId: channel.id,
+ userId: meId
+ }) != null : undefined;
+ const following = meId ? await ChannelFollowings.findOneBy({
+ followerId: meId,
+ followeeId: channel.id
+ }) : null;
+ return {
+ id: channel.id,
+ createdAt: channel.createdAt.toISOString(),
+ lastNotedAt: channel.lastNotedAt ? channel.lastNotedAt.toISOString() : null,
+ name: channel.name,
+ description: channel.description,
+ userId: channel.userId,
+ bannerUrl: banner ? DriveFiles.getPublicUrl(banner, false) : null,
+ usersCount: channel.usersCount,
+ notesCount: channel.notesCount,
+ ...me ? {
+ isFollowing: following != null,
+ hasUnreadNote
+ } : {}
+ };
+ }
+});
diff --git a/packages/backend/built/models/repositories/clip.js b/packages/backend/built/models/repositories/clip.js
new file mode 100644
index 0000000..e195c99
--- /dev/null
+++ b/packages/backend/built/models/repositories/clip.js
@@ -0,0 +1,23 @@
+import { db } from "../../db/postgre.js";
+import { Clip } from "../entities/clip.js";
+import { Users } from "../index.js";
+import { awaitAll } from "../../prelude/await-all.js";
+export const ClipRepository = db.getRepository(Clip).extend({
+ async pack (src) {
+ const clip = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return await awaitAll({
+ id: clip.id,
+ createdAt: clip.createdAt.toISOString(),
+ userId: clip.userId,
+ user: Users.pack(clip.user || clip.userId),
+ name: clip.name,
+ description: clip.description,
+ isPublic: clip.isPublic
+ });
+ },
+ packMany (clips) {
+ return Promise.all(clips.map((x)=>this.pack(x)));
+ }
+});
diff --git a/packages/backend/built/models/repositories/drive-file.js b/packages/backend/built/models/repositories/drive-file.js
new file mode 100644
index 0000000..f487d6a
--- /dev/null
+++ b/packages/backend/built/models/repositories/drive-file.js
@@ -0,0 +1,181 @@
+import { db } from "../../db/postgre.js";
+import { DriveFile } from "../entities/drive-file.js";
+import { toPuny } from "../../misc/convert-host.js";
+import { awaitAll } from "../../prelude/await-all.js";
+import config from "../../config/index.js";
+import { appendQuery, query } from "../../prelude/url.js";
+import { DriveFolders, Users } from "../index.js";
+import { deepClone } from "../../misc/clone.js";
+import { fetchMetaSync } from "../../misc/fetch-meta.js";
+export const DriveFileRepository = db.getRepository(DriveFile).extend({
+ validateFileName (name) {
+ return name.trim().length > 0 && name.length <= 200 && name.indexOf("\\") === -1 && name.indexOf("/") === -1 && name.indexOf("..") === -1;
+ },
+ getPublicProperties (file) {
+ if (file.properties.orientation != null) {
+ const properties = deepClone(file.properties);
+ if (file.properties.orientation >= 5) {
+ [properties.width, properties.height] = [
+ properties.height,
+ properties.width
+ ];
+ }
+ properties.orientation = undefined;
+ return properties;
+ }
+ return file.properties;
+ },
+ isImage (file) {
+ return !!file.type && [
+ "image/png",
+ "image/apng",
+ "image/gif",
+ "image/jpeg",
+ "image/webp",
+ "image/svg+xml",
+ "image/avif"
+ ].includes(file.type);
+ },
+ isStreamableMedia (file) {
+ return file.type.startsWith("video/") || file.type.startsWith("audio/");
+ },
+ withStreamQuery (file, url) {
+ if (url == null || file.allowDownload || !this.isStreamableMedia(file)) return url;
+ return appendQuery(url, query({
+ stream: "1"
+ }));
+ },
+ getPublicUrl (file, thumbnail = false) {
+ // リモートかつメディアプロキシ
+ if (file.uri != null && file.userHost != null && config.mediaProxy != null) {
+ return appendQuery(config.mediaProxy, query({
+ url: file.uri,
+ thumbnail: thumbnail ? "1" : undefined
+ }));
+ }
+ if (file.isLink && config.proxyRemoteFiles) {
+ const url = this.getDatabasePrefetchUrl(file, thumbnail);
+ if (url != null) return `${config.url}/proxy/${encodeURIComponent(new URL(url).pathname)}?${query({
+ url: url
+ })}`;
+ }
+ const url = thumbnail ? file.thumbnailUrl || (this.isImage(file) ? file.webpublicUrl || file.url : null) : file.webpublicUrl || file.url;
+ return thumbnail ? url : this.withStreamQuery(file, url);
+ },
+ getDatabasePrefetchUrl (file, thumbnail = false) {
+ return thumbnail ? file.thumbnailUrl ?? file.webpublicUrl ?? file.url : file.webpublicUrl ?? file.url;
+ },
+ getFinalUrl (url) {
+ if (!config.proxyRemoteFiles) return url;
+ if (!url.startsWith('https://') && !url.startsWith('http://')) return url;
+ if (url.startsWith(`${config.url}/files`)) return url;
+ if (url.startsWith(`${config.url}/static-assets`)) return url;
+ if (url.startsWith(`${config.url}/identicon`)) return url;
+ if (url.startsWith(`${config.url}/avatar`)) return url;
+ const meta = fetchMetaSync();
+ const baseUrl = meta ? meta.objectStorageBaseUrl ?? `${meta.objectStorageUseSSL ? "https" : "http"}://${meta.objectStorageEndpoint}${meta.objectStoragePort ? `:${meta.objectStoragePort}` : ""}/${meta.objectStorageBucket}` : null;
+ if (baseUrl !== null && url.startsWith(baseUrl)) return url;
+ return `${config.url}/proxy/${encodeURIComponent(new URL(url).pathname)}?${query({
+ url: url
+ })}`;
+ },
+ getFinalUrlMaybe (url) {
+ if (url == null) return null;
+ return this.getFinalUrl(url);
+ },
+ async calcDriveUsageOf (user) {
+ const id = typeof user === "object" ? user.id : user;
+ const { sum } = await this.createQueryBuilder("file").where("file.userId = :id", {
+ id: id
+ }).andWhere("file.isLink = FALSE").andWhere("file.isDatabase = FALSE").select("SUM(file.size)", "sum").getRawOne();
+ return parseInt(sum, 10) || 0;
+ },
+ async calcDatabaseUsageOf (user) {
+ const id = typeof user === "object" ? user.id : user;
+ const { sum } = await this.createQueryBuilder("file").where("file.userId = :id", {
+ id: id
+ }).andWhere("file.isLink = FALSE").andWhere("file.isDatabase = TRUE").select("SUM(file.size)", "sum").getRawOne();
+ return parseInt(sum, 10) || 0;
+ },
+ async calcDriveUsageOfHost (host) {
+ const { sum } = await this.createQueryBuilder("file").where("file.userHost = :host", {
+ host: toPuny(host)
+ }).andWhere("file.isLink = FALSE").andWhere("file.isDatabase = FALSE").select("SUM(file.size)", "sum").getRawOne();
+ return parseInt(sum, 10) || 0;
+ },
+ async calcDriveUsageOfLocal () {
+ const { sum } = await this.createQueryBuilder("file").where("file.userHost IS NULL").andWhere("file.isLink = FALSE").andWhere("file.isDatabase = FALSE").select("SUM(file.size)", "sum").getRawOne();
+ return parseInt(sum, 10) || 0;
+ },
+ async calcDriveUsageOfRemote () {
+ const { sum } = await this.createQueryBuilder("file").where("file.userHost IS NOT NULL").andWhere("file.isLink = FALSE").andWhere("file.isDatabase = FALSE").select("SUM(file.size)", "sum").getRawOne();
+ return parseInt(sum, 10) || 0;
+ },
+ async pack (src, options) {
+ const opts = Object.assign({
+ detail: false,
+ self: false
+ }, options);
+ const file = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return await awaitAll({
+ id: file.id,
+ createdAt: file.createdAt.toISOString(),
+ name: file.name,
+ type: file.type,
+ md5: file.md5,
+ size: file.size,
+ isSensitive: file.isSensitive,
+ allowDownload: file.allowDownload,
+ isDatabase: file.isDatabase,
+ blurhash: file.blurhash,
+ properties: opts.self ? file.properties : this.getPublicProperties(file),
+ url: opts.self ? file.url : this.getPublicUrl(file, false),
+ thumbnailUrl: this.getPublicUrl(file, true),
+ comment: file.comment,
+ folderId: file.folderId,
+ folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, {
+ detail: true
+ }) : null,
+ userId: opts.withUser ? file.userId : null,
+ user: opts.withUser && file.userId ? Users.pack(file.userId) : null
+ });
+ },
+ async packNullable (src, options) {
+ const opts = Object.assign({
+ detail: false,
+ self: false
+ }, options);
+ const file = typeof src === "object" ? src : await this.findOneBy({
+ id: src
+ });
+ if (file == null) return null;
+ return await awaitAll({
+ id: file.id,
+ createdAt: file.createdAt.toISOString(),
+ name: file.name,
+ type: file.type,
+ md5: file.md5,
+ size: file.size,
+ isSensitive: file.isSensitive,
+ allowDownload: file.allowDownload,
+ isDatabase: file.isDatabase,
+ blurhash: file.blurhash,
+ properties: opts.self ? file.properties : this.getPublicProperties(file),
+ url: opts.self ? file.url : this.getPublicUrl(file, false),
+ thumbnailUrl: this.getPublicUrl(file, true),
+ comment: file.comment,
+ folderId: file.folderId,
+ folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, {
+ detail: true
+ }) : null,
+ userId: opts.withUser ? file.userId : null,
+ user: opts.withUser && file.userId ? Users.pack(file.userId) : null
+ });
+ },
+ async packMany (files, options) {
+ const items = await Promise.all(files.map((f)=>this.packNullable(f, options)));
+ return items.filter((x)=>x != null);
+ }
+});
diff --git a/packages/backend/built/models/repositories/drive-folder.js b/packages/backend/built/models/repositories/drive-folder.js
new file mode 100644
index 0000000..2bc6010
--- /dev/null
+++ b/packages/backend/built/models/repositories/drive-folder.js
@@ -0,0 +1,33 @@
+import { db } from "../../db/postgre.js";
+import { DriveFolders, DriveFiles } from "../index.js";
+import { DriveFolder } from "../entities/drive-folder.js";
+import { awaitAll } from "../../prelude/await-all.js";
+export const DriveFolderRepository = db.getRepository(DriveFolder).extend({
+ async pack (src, options) {
+ const opts = Object.assign({
+ detail: false
+ }, options);
+ const folder = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return await awaitAll({
+ id: folder.id,
+ createdAt: folder.createdAt.toISOString(),
+ name: folder.name,
+ parentId: folder.parentId,
+ ...opts.detail ? {
+ foldersCount: DriveFolders.countBy({
+ parentId: folder.id
+ }),
+ filesCount: DriveFiles.countBy({
+ folderId: folder.id
+ }),
+ ...folder.parentId ? {
+ parent: this.pack(folder.parentId, {
+ detail: true
+ })
+ } : {}
+ } : {}
+ });
+ }
+});
diff --git a/packages/backend/built/models/repositories/emoji.js b/packages/backend/built/models/repositories/emoji.js
new file mode 100644
index 0000000..c711502
--- /dev/null
+++ b/packages/backend/built/models/repositories/emoji.js
@@ -0,0 +1,26 @@
+import { db } from "../../db/postgre.js";
+import { Emoji } from "../entities/emoji.js";
+export const EmojiRepository = db.getRepository(Emoji).extend({
+ async pack (src) {
+ const emoji = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return {
+ id: emoji.id,
+ aliases: emoji.aliases,
+ name: emoji.name,
+ category: emoji.category,
+ host: emoji.host,
+ // || emoji.originalUrl してるのは後方互換性のため
+ url: emoji.publicUrl || emoji.originalUrl,
+ license: emoji.license,
+ glyph: emoji.glyph,
+ glyphUrl: emoji.glyph ? emoji.originalUrl : null,
+ width: emoji.width,
+ height: emoji.height
+ };
+ },
+ packMany (emojis) {
+ return Promise.all(emojis.map((x)=>this.pack(x)));
+ }
+});
diff --git a/packages/backend/built/models/repositories/follow-request.js b/packages/backend/built/models/repositories/follow-request.js
new file mode 100644
index 0000000..aa5e618
--- /dev/null
+++ b/packages/backend/built/models/repositories/follow-request.js
@@ -0,0 +1,15 @@
+import { db } from "../../db/postgre.js";
+import { FollowRequest } from "../entities/follow-request.js";
+import { Users } from "../index.js";
+export const FollowRequestRepository = db.getRepository(FollowRequest).extend({
+ async pack (src, me) {
+ const request = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return {
+ id: request.id,
+ follower: await Users.pack(request.followerId, me),
+ followee: await Users.pack(request.followeeId, me)
+ };
+ }
+});
diff --git a/packages/backend/built/models/repositories/following.js b/packages/backend/built/models/repositories/following.js
new file mode 100644
index 0000000..34727ff
--- /dev/null
+++ b/packages/backend/built/models/repositories/following.js
@@ -0,0 +1,39 @@
+import { db } from "../../db/postgre.js";
+import { Users } from "../index.js";
+import { Following } from "../entities/following.js";
+import { awaitAll } from "../../prelude/await-all.js";
+export const FollowingRepository = db.getRepository(Following).extend({
+ isLocalFollower (following) {
+ return following.followerHost == null;
+ },
+ isRemoteFollower (following) {
+ return following.followerHost != null;
+ },
+ isLocalFollowee (following) {
+ return following.followeeHost == null;
+ },
+ isRemoteFollowee (following) {
+ return following.followeeHost != null;
+ },
+ async pack (src, me, opts) {
+ const following = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ if (opts == null) opts = {};
+ return await awaitAll({
+ id: following.id,
+ createdAt: following.createdAt.toISOString(),
+ followeeId: following.followeeId,
+ followerId: following.followerId,
+ followee: opts.populateFollowee ? Users.pack(following.followee || following.followeeId, me, {
+ detail: true
+ }) : undefined,
+ follower: opts.populateFollower ? Users.pack(following.follower || following.followerId, me, {
+ detail: true
+ }) : undefined
+ });
+ },
+ packMany (followings, me, opts) {
+ return Promise.all(followings.map((x)=>this.pack(x, me, opts)));
+ }
+});
diff --git a/packages/backend/built/models/repositories/gallery-like.js b/packages/backend/built/models/repositories/gallery-like.js
new file mode 100644
index 0000000..f5bd91f
--- /dev/null
+++ b/packages/backend/built/models/repositories/gallery-like.js
@@ -0,0 +1,17 @@
+import { db } from "../../db/postgre.js";
+import { GalleryLike } from "../entities/gallery-like.js";
+import { GalleryPosts } from "../index.js";
+export const GalleryLikeRepository = db.getRepository(GalleryLike).extend({
+ async pack (src, me) {
+ const like = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return {
+ id: like.id,
+ post: await GalleryPosts.pack(like.post || like.postId, me)
+ };
+ },
+ packMany (likes, me) {
+ return Promise.all(likes.map((x)=>this.pack(x, me)));
+ }
+});
diff --git a/packages/backend/built/models/repositories/gallery-post.js b/packages/backend/built/models/repositories/gallery-post.js
new file mode 100644
index 0000000..d13c8c5
--- /dev/null
+++ b/packages/backend/built/models/repositories/gallery-post.js
@@ -0,0 +1,33 @@
+import { db } from "../../db/postgre.js";
+import { GalleryPost } from "../entities/gallery-post.js";
+import { Users, DriveFiles, GalleryLikes } from "../index.js";
+import { awaitAll } from "../../prelude/await-all.js";
+export const GalleryPostRepository = db.getRepository(GalleryPost).extend({
+ async pack (src, me) {
+ const meId = me ? me.id : null;
+ const post = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return await awaitAll({
+ id: post.id,
+ createdAt: post.createdAt.toISOString(),
+ updatedAt: post.updatedAt.toISOString(),
+ userId: post.userId,
+ user: Users.pack(post.user || post.userId, me),
+ title: post.title,
+ description: post.description,
+ fileIds: post.fileIds,
+ files: DriveFiles.packMany(post.fileIds),
+ tags: post.tags.length > 0 ? post.tags : undefined,
+ isSensitive: post.isSensitive,
+ likedCount: post.likedCount,
+ isLiked: meId ? await GalleryLikes.findOneBy({
+ postId: post.id,
+ userId: meId
+ }).then((x)=>x != null) : undefined
+ });
+ },
+ packMany (posts, me) {
+ return Promise.all(posts.map((x)=>this.pack(x, me)));
+ }
+});
diff --git a/packages/backend/built/models/repositories/hashtag.js b/packages/backend/built/models/repositories/hashtag.js
new file mode 100644
index 0000000..a24a889
--- /dev/null
+++ b/packages/backend/built/models/repositories/hashtag.js
@@ -0,0 +1,18 @@
+import { db } from "../../db/postgre.js";
+import { Hashtag } from "../entities/hashtag.js";
+export const HashtagRepository = db.getRepository(Hashtag).extend({
+ async pack (src) {
+ return {
+ tag: src.name,
+ mentionedUsersCount: src.mentionedUsersCount,
+ mentionedLocalUsersCount: src.mentionedLocalUsersCount,
+ mentionedRemoteUsersCount: src.mentionedRemoteUsersCount,
+ attachedUsersCount: src.attachedUsersCount,
+ attachedLocalUsersCount: src.attachedLocalUsersCount,
+ attachedRemoteUsersCount: src.attachedRemoteUsersCount
+ };
+ },
+ packMany (hashtags) {
+ return Promise.all(hashtags.map((x)=>this.pack(x)));
+ }
+});
diff --git a/packages/backend/built/models/repositories/instance.js b/packages/backend/built/models/repositories/instance.js
new file mode 100644
index 0000000..3c11e61
--- /dev/null
+++ b/packages/backend/built/models/repositories/instance.js
@@ -0,0 +1,36 @@
+import { db } from "../../db/postgre.js";
+import { Instance } from "../entities/instance.js";
+import { shouldBlockInstance, shouldSilenceInstance } from "../../misc/should-block-instance.js";
+export const InstanceRepository = db.getRepository(Instance).extend({
+ async pack (instance, privileged = true) {
+ return {
+ id: instance.id,
+ caughtAt: instance.caughtAt.toISOString(),
+ host: instance.host,
+ usersCount: instance.usersCount,
+ notesCount: instance.notesCount,
+ followingCount: instance.followingCount,
+ followersCount: instance.followersCount,
+ latestRequestSentAt: instance.latestRequestSentAt ? instance.latestRequestSentAt.toISOString() : null,
+ lastCommunicatedAt: instance.lastCommunicatedAt.toISOString(),
+ isNotResponding: instance.isNotResponding,
+ isSuspended: privileged ? instance.isSuspended : false,
+ isBlocked: privileged ? await shouldBlockInstance(instance.host) : false,
+ isSilenced: privileged ? await shouldSilenceInstance(instance.host) : false,
+ softwareName: instance.softwareName,
+ softwareVersion: instance.softwareVersion,
+ openRegistrations: instance.openRegistrations,
+ name: instance.name,
+ description: instance.description,
+ maintainerName: instance.maintainerName,
+ maintainerEmail: instance.maintainerEmail,
+ iconUrl: instance.iconUrl,
+ faviconUrl: instance.faviconUrl,
+ themeColor: instance.themeColor,
+ infoUpdatedAt: instance.infoUpdatedAt ? instance.infoUpdatedAt.toISOString() : null
+ };
+ },
+ packMany (instances, privileged = true) {
+ return Promise.all(instances.map((x)=>this.pack(x, privileged)));
+ }
+});
diff --git a/packages/backend/built/models/repositories/messaging-message.js b/packages/backend/built/models/repositories/messaging-message.js
new file mode 100644
index 0000000..87ef1c3
--- /dev/null
+++ b/packages/backend/built/models/repositories/messaging-message.js
@@ -0,0 +1,29 @@
+import { db } from "../../db/postgre.js";
+import { MessagingMessage } from "../entities/messaging-message.js";
+import { Users, DriveFiles, UserGroups } from "../index.js";
+export const MessagingMessageRepository = db.getRepository(MessagingMessage).extend({
+ async pack (src, me, options) {
+ const opts = options || {
+ populateRecipient: true,
+ populateGroup: true
+ };
+ const message = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return {
+ id: message.id,
+ createdAt: message.createdAt.toISOString(),
+ text: message.text,
+ userId: message.userId,
+ user: await Users.pack(message.user || message.userId, me),
+ recipientId: message.recipientId,
+ recipient: message.recipientId && opts.populateRecipient ? await Users.pack(message.recipient || message.recipientId, me) : undefined,
+ groupId: message.groupId,
+ group: message.groupId && opts.populateGroup ? await UserGroups.pack(message.group || message.groupId) : undefined,
+ fileId: message.fileId,
+ file: message.fileId ? await DriveFiles.pack(message.fileId) : null,
+ isRead: message.isRead,
+ reads: message.reads
+ };
+ }
+});
diff --git a/packages/backend/built/models/repositories/moderation-logs.js b/packages/backend/built/models/repositories/moderation-logs.js
new file mode 100644
index 0000000..d14826a
--- /dev/null
+++ b/packages/backend/built/models/repositories/moderation-logs.js
@@ -0,0 +1,24 @@
+import { db } from "../../db/postgre.js";
+import { Users } from "../index.js";
+import { ModerationLog } from "../entities/moderation-log.js";
+import { awaitAll } from "../../prelude/await-all.js";
+export const ModerationLogRepository = db.getRepository(ModerationLog).extend({
+ async pack (src) {
+ const log = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return await awaitAll({
+ id: log.id,
+ createdAt: log.createdAt.toISOString(),
+ type: log.type,
+ info: log.info,
+ userId: log.userId,
+ user: Users.pack(log.user || log.userId, null, {
+ detail: true
+ })
+ });
+ },
+ packMany (reports) {
+ return Promise.all(reports.map((x)=>this.pack(x)));
+ }
+});
diff --git a/packages/backend/built/models/repositories/muting.js b/packages/backend/built/models/repositories/muting.js
new file mode 100644
index 0000000..7ccb4b5
--- /dev/null
+++ b/packages/backend/built/models/repositories/muting.js
@@ -0,0 +1,23 @@
+import { db } from "../../db/postgre.js";
+import { Users } from "../index.js";
+import { Muting } from "../entities/muting.js";
+import { awaitAll } from "../../prelude/await-all.js";
+export const MutingRepository = db.getRepository(Muting).extend({
+ async pack (src, me) {
+ const muting = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return await awaitAll({
+ id: muting.id,
+ createdAt: muting.createdAt.toISOString(),
+ expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null,
+ muteeId: muting.muteeId,
+ mutee: Users.pack(muting.muteeId, me, {
+ detail: true
+ })
+ });
+ },
+ packMany (mutings, me) {
+ return Promise.all(mutings.map((x)=>this.pack(x, me)));
+ }
+});
diff --git a/packages/backend/built/models/repositories/note-favorite.js b/packages/backend/built/models/repositories/note-favorite.js
new file mode 100644
index 0000000..3aa91c8
--- /dev/null
+++ b/packages/backend/built/models/repositories/note-favorite.js
@@ -0,0 +1,22 @@
+import { db } from "../../db/postgre.js";
+import { NoteFavorite } from "../entities/note-favorite.js";
+import { Notes } from "../index.js";
+export const NoteFavoriteRepository = db.getRepository(NoteFavorite).extend({
+ async pack (src, me) {
+ const favorite = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return {
+ id: favorite.id,
+ createdAt: favorite.createdAt.toISOString(),
+ noteId: favorite.noteId,
+ // may throw error
+ note: await Notes.pack(favorite.note || favorite.noteId, me)
+ };
+ },
+ packMany (favorites, me) {
+ return Promise.allSettled(favorites.map((x)=>this.pack(x, me))).then((promises)=>promises.flatMap((result)=>result.status === "fulfilled" ? [
+ result.value
+ ] : []));
+ }
+});
diff --git a/packages/backend/built/models/repositories/note-reaction.js b/packages/backend/built/models/repositories/note-reaction.js
new file mode 100644
index 0000000..6ace51e
--- /dev/null
+++ b/packages/backend/built/models/repositories/note-reaction.js
@@ -0,0 +1,31 @@
+import { db } from "../../db/postgre.js";
+import { NoteReaction } from "../entities/note-reaction.js";
+import { Notes, Users } from "../index.js";
+import { convertLegacyReaction } from "../../misc/reaction-lib.js";
+export const NoteReactionRepository = db.getRepository(NoteReaction).extend({
+ async pack (src, me, options) {
+ const opts = Object.assign({
+ withNote: false
+ }, options);
+ const reaction = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return {
+ id: reaction.id,
+ createdAt: reaction.createdAt.toISOString(),
+ user: await Users.pack(reaction.user ?? reaction.userId, me),
+ type: convertLegacyReaction(reaction.reaction),
+ ...opts.withNote ? {
+ // may throw error
+ note: await Notes.pack(reaction.note ?? reaction.noteId, me)
+ } : {}
+ };
+ },
+ async packMany (src, me, options) {
+ const reactions = await Promise.allSettled(src.map((reaction)=>this.pack(reaction, me, options)));
+ // filter out rejected promises, only keep fulfilled values
+ return reactions.flatMap((result)=>result.status === "fulfilled" ? [
+ result.value
+ ] : []);
+ }
+});
diff --git a/packages/backend/built/models/repositories/note.js b/packages/backend/built/models/repositories/note.js
new file mode 100644
index 0000000..ca71739
--- /dev/null
+++ b/packages/backend/built/models/repositories/note.js
@@ -0,0 +1,312 @@
+import { In } from "typeorm";
+import * as mfm from "mfm-js";
+import { Note } from "../entities/note.js";
+import { Users, PollVotes, DriveFiles, NoteReactions, Followings, Polls, Channels, Notes, Blockings, UserGroups } from "../index.js";
+import { nyaize } from "../../misc/nyaize.js";
+import { awaitAll } from "../../prelude/await-all.js";
+import { convertLegacyReaction, convertLegacyReactions, decodeReaction } from "../../misc/reaction-lib.js";
+import { aggregateNoteEmojis, populateEmojis, prefetchEmojis } from "../../misc/populate-emojis.js";
+import { db } from "../../db/postgre.js";
+import { IdentifiableError } from "../../misc/identifiable-error.js";
+import { isFiltered } from "../../misc/is-filtered.js";
+export async function populatePoll(note, meId) {
+ const poll = await Polls.findOneByOrFail({
+ noteId: note.id
+ });
+ const choices = poll.choices.map((c)=>({
+ text: c,
+ votes: poll.votes[poll.choices.indexOf(c)],
+ isVoted: false
+ }));
+ if (meId) {
+ if (poll.multiple) {
+ const votes = await PollVotes.findBy({
+ userId: meId,
+ noteId: note.id
+ });
+ const myChoices = votes.map((v)=>v.choice);
+ for (const myChoice of myChoices){
+ choices[myChoice].isVoted = true;
+ }
+ } else {
+ const vote = await PollVotes.findOneBy({
+ userId: meId,
+ noteId: note.id
+ });
+ if (vote) {
+ choices[vote.choice].isVoted = true;
+ }
+ }
+ }
+ return {
+ multiple: poll.multiple,
+ expiresAt: poll.expiresAt,
+ choices
+ };
+}
+async function populateMyReaction(note, meId, _hint_) {
+ if (_hint_?.myReactions) {
+ const reaction = _hint_.myReactions.get(note.id);
+ if (reaction) {
+ return convertLegacyReaction(reaction.reaction);
+ } else if (reaction === null) {
+ return undefined;
+ }
+ // 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない
+ }
+ const reaction = await NoteReactions.findOneBy({
+ userId: meId,
+ noteId: note.id
+ });
+ if (reaction) {
+ return convertLegacyReaction(reaction.reaction);
+ }
+ return undefined;
+}
+async function populateIsRenoted(note, meId, _hint_) {
+ return _hint_?.myRenotes ? _hint_.myRenotes.get(note.id) ? true : undefined : Notes.exist({
+ where: {
+ renoteId: note.id,
+ userId: meId
+ }
+ }).then((res)=>res ? true : undefined);
+}
+export const NoteRepository = db.getRepository(Note).extend({
+ async isVisibleForMe (note, meId) {
+ if (meId != null && meId !== note.userId) {
+ const blocked = await Blockings.count({
+ where: [
+ {
+ blockeeId: meId,
+ blockerId: note.userId,
+ groupId: null
+ },
+ ...note.groupId ? [
+ {
+ blockeeId: meId,
+ groupId: note.groupId
+ }
+ ] : []
+ ],
+ take: 1
+ });
+ if (blocked !== 0) {
+ return false;
+ }
+ const minorBadgeBlocked = await Users.createQueryBuilder("author").where("author.id = :authorId", {
+ authorId: note.userId
+ }).andWhere("'E' = ANY(author.\"minorBadges\")").andWhere(`EXISTS (` + `SELECT 1 FROM "user" viewer ` + `WHERE viewer.id = :meId ` + `AND viewer."isAdmin" = FALSE ` + `AND viewer."isModerator" = FALSE ` + `AND ('K' = ANY(viewer."minorBadges") OR 'T' = ANY(viewer."minorBadges"))` + `)`, {
+ meId
+ }).getCount();
+ if (minorBadgeBlocked !== 0) {
+ return false;
+ }
+ }
+ // This code must always be synchronized with the checks in generateVisibilityQuery.
+ // visibility が specified かつ自分が指定されていなかったら非表示
+ if (note.visibility === "specified") {
+ if (meId == null) {
+ return false;
+ } else if (meId === note.userId) {
+ return true;
+ } else {
+ // 指定されているかどうか
+ return note.visibleUserIds.some((id)=>meId === id);
+ }
+ }
+ // visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
+ if (note.visibility === "followers") {
+ if (meId == null) {
+ return false;
+ } else if (meId === note.userId) {
+ return true;
+ } else if (note.reply && meId === note.reply.userId) {
+ // 自分の投稿に対するリプライ
+ return true;
+ } else if (note.mentions?.some((id)=>meId === id)) {
+ // 自分へのメンション
+ return true;
+ } else {
+ // フォロワーかどうか
+ const [following, user] = await Promise.all([
+ Followings.count({
+ where: {
+ followeeId: note.userId,
+ followerId: meId
+ },
+ take: 1
+ }),
+ Users.findOneByOrFail({
+ id: meId
+ })
+ ]);
+ /* If we know the following, everyhting is fine.
+
+ But if we do not know the following, it might be that both the
+ author of the note and the author of the like are remote users,
+ in which case we can never know the following. Instead we have
+ to assume that the users are following each other.
+ */ return following > 0 || note.userHost != null && user.host != null;
+ }
+ }
+ return true;
+ },
+ async pack (src, me, options, userCache = Users.getFreshPackedUserCache()) {
+ const opts = Object.assign({
+ detail: true
+ }, options);
+ const meId = me ? me.id : null;
+ const note = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ const host = note.userHost;
+ if (!opts.allowAdservice && !note._prId_ && note.tags.includes("adservice") && note.userId !== meId) {
+ throw new IdentifiableError("9725d0ce-ba28-4dde-95a7-2cbb2c15de24", "No such note.");
+ }
+ if (!await this.isVisibleForMe(note, meId)) {
+ throw new IdentifiableError("9725d0ce-ba28-4dde-95a7-2cbb2c15de24", "No such note.");
+ }
+ let text = note.text;
+ if (note.name && (note.url ?? note.uri)) {
+ text = `【${note.name}】\n${(note.text || "").trim()}\n\n${note.url ?? note.uri}`;
+ }
+ const channel = note.channelId ? note.channel ? note.channel : await Channels.findOneBy({
+ id: note.channelId
+ }) : null;
+ const reactionEmojiNames = Object.keys(note.reactions).filter((x)=>x?.startsWith(":")).map((x)=>decodeReaction(x).reaction).map((x)=>x.replace(/:/g, ""));
+ const noteEmoji = populateEmojis(note.emojis.concat(reactionEmojiNames), host);
+ const reactionEmoji = populateEmojis(reactionEmojiNames, host);
+ const packed = await awaitAll({
+ id: note.id,
+ createdAt: note.createdAt.toISOString(),
+ userId: note.userId,
+ user: Users.packCached(note.user ?? note.userId, userCache, me, {
+ detail: false
+ }),
+ groupId: note.groupId,
+ group: note.groupId ? UserGroups.pack(note.group ?? note.groupId) : null,
+ text: text,
+ cw: note.cw,
+ visibility: note.visibility,
+ localOnly: note.localOnly || undefined,
+ visibleUserIds: note.visibility === "specified" ? note.visibleUserIds : undefined,
+ renoteCount: note.renoteCount,
+ repliesCount: note.repliesCount,
+ viewCount: note.viewCount,
+ reactions: convertLegacyReactions(note.reactions),
+ reactionEmojis: reactionEmoji,
+ emojis: noteEmoji,
+ tags: note.tags.length > 0 ? note.tags : undefined,
+ fileIds: note.fileIds,
+ files: DriveFiles.packMany(note.fileIds),
+ replyId: note.replyId,
+ renoteId: note.renoteId,
+ channelId: note.channelId || undefined,
+ channel: channel ? {
+ id: channel.id,
+ name: channel.name
+ } : undefined,
+ mentions: note.mentions.length > 0 ? note.mentions : undefined,
+ uri: note.uri || undefined,
+ url: note.url || undefined,
+ updatedAt: note.updatedAt?.toISOString() || undefined,
+ poll: note.hasPoll ? populatePoll(note, meId) : undefined,
+ quoteAuthorization: note.quoteAuthorization || undefined,
+ canBite: false,
+ ...meId ? {
+ myReaction: populateMyReaction(note, meId, options?._hint_),
+ isRenoted: populateIsRenoted(note, meId, options?._hint_),
+ isFiltered: isFiltered(note, me)
+ } : {},
+ ...opts.detail ? {
+ reply: note.replyId ? this.tryPack(note.reply || note.replyId, me, {
+ detail: false,
+ _hint_: options?._hint_
+ }, userCache) : undefined,
+ renote: note.renoteId ? this.pack(note.renote || note.renoteId, me, {
+ detail: true,
+ _hint_: options?._hint_
+ }, userCache) : undefined
+ } : {}
+ });
+ if (packed.user.isCat && packed.user.speakAsCat && packed.text) {
+ const tokens = packed.text ? mfm.parse(packed.text) : [];
+ function nyaizeNode(node) {
+ if (node.type === "quote") return;
+ if (node.type === "text") node.props.text = nyaize(node.props.text);
+ if (node.children) {
+ for (const child of node.children){
+ nyaizeNode(child);
+ }
+ }
+ }
+ for (const node of tokens)nyaizeNode(node);
+ packed.text = mfm.toString(tokens);
+ }
+ if (me) {
+ if (packed.user.canBite === "anyone") {
+ packed.canBite = true;
+ } else if (packed.user.canBite === "followers") {
+ const isFollowing = await Followings.exist({
+ where: {
+ followerId: me.id,
+ followeeId: packed.userId
+ },
+ take: 1
+ });
+ packed.canBite = isFollowing;
+ } else {
+ packed.canBite = false;
+ }
+ }
+ if (note._prId_) {
+ packed._prId_ = note._prId_;
+ }
+ return packed;
+ },
+ async tryPack (src, me, options, userCache = Users.getFreshPackedUserCache()) {
+ try {
+ return await this.pack(src, me, options, userCache);
+ } catch {
+ return undefined;
+ }
+ },
+ async packMany (notes, me, options, userCache = Users.getFreshPackedUserCache()) {
+ if (notes.length === 0) return [];
+ const meId = me ? me.id : null;
+ const myReactionsMap = new Map();
+ const myRenotesMap = new Map();
+ if (meId) {
+ const renoteIds = notes.filter((n)=>n.renoteId != null).map((n)=>n.renoteId);
+ const targets = [
+ ...notes.map((n)=>n.id),
+ ...renoteIds
+ ];
+ const myReactions = await NoteReactions.findBy({
+ userId: meId,
+ noteId: In(targets)
+ });
+ const myRenotes = await Notes.createQueryBuilder('note').select('note.renoteId').where('note.userId = :meId', {
+ meId
+ }).andWhere('note.renoteId IN (:...targets)', {
+ targets
+ }).andWhere('note.text IS NULL').andWhere('note.hasPoll = FALSE').andWhere(`note.fileIds = '{}'`).getMany();
+ for (const target of targets){
+ myReactionsMap.set(target, myReactions.find((reaction)=>reaction.noteId === target) || null);
+ myRenotesMap.set(target, !!myRenotes.find((p)=>p.renoteId == target));
+ }
+ }
+ await prefetchEmojis(aggregateNoteEmojis(notes));
+ const promises = await Promise.allSettled(notes.map((n)=>this.pack(n, me, {
+ ...options,
+ _hint_: {
+ myReactions: myReactionsMap,
+ myRenotes: myRenotesMap
+ }
+ }, userCache)));
+ // filter out rejected promises, only keep fulfilled values
+ return promises.flatMap((result)=>result.status === "fulfilled" ? [
+ result.value
+ ] : []);
+ }
+});
diff --git a/packages/backend/built/models/repositories/notification.js b/packages/backend/built/models/repositories/notification.js
new file mode 100644
index 0000000..2260c15
--- /dev/null
+++ b/packages/backend/built/models/repositories/notification.js
@@ -0,0 +1,128 @@
+import { In } from "typeorm";
+import { Notification } from "../entities/notification.js";
+import { awaitAll } from "../../prelude/await-all.js";
+import { aggregateNoteEmojis, prefetchEmojis } from "../../misc/populate-emojis.js";
+import { db } from "../../db/postgre.js";
+import { Users, Notes, UserGroupInvitations, AccessTokens, NoteReactions, Bites } from "../index.js";
+export const NotificationRepository = db.getRepository(Notification).extend({
+ async pack (src, options) {
+ const notification = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ const token = notification.appAccessTokenId ? await AccessTokens.findOneByOrFail({
+ id: notification.appAccessTokenId
+ }) : null;
+ return await awaitAll({
+ id: notification.id,
+ createdAt: notification.createdAt.toISOString(),
+ type: notification.type,
+ isRead: notification.isRead,
+ userId: notification.notifierId,
+ user: notification.notifierId ? Users.pack(notification.notifier || notification.notifierId) : null,
+ ...notification.type === "mention" ? {
+ note: Notes.pack(notification.note || notification.noteId, {
+ id: notification.notifieeId
+ }, {
+ detail: true,
+ _hint_: options._hintForEachNotes_
+ })
+ } : {},
+ ...notification.type === "reply" ? {
+ note: Notes.pack(notification.note || notification.noteId, {
+ id: notification.notifieeId
+ }, {
+ detail: true,
+ _hint_: options._hintForEachNotes_
+ })
+ } : {},
+ ...notification.type === "renote" ? {
+ note: Notes.pack(notification.note || notification.noteId, {
+ id: notification.notifieeId
+ }, {
+ detail: true,
+ _hint_: options._hintForEachNotes_
+ })
+ } : {},
+ ...notification.type === "quote" ? {
+ note: Notes.pack(notification.note || notification.noteId, {
+ id: notification.notifieeId
+ }, {
+ detail: true,
+ _hint_: options._hintForEachNotes_
+ })
+ } : {},
+ ...notification.type === "reaction" ? {
+ note: Notes.pack(notification.note || notification.noteId, {
+ id: notification.notifieeId
+ }, {
+ detail: true,
+ _hint_: options._hintForEachNotes_
+ }),
+ reaction: notification.reaction
+ } : {},
+ ...notification.type === "pollVote" ? {
+ note: Notes.pack(notification.note || notification.noteId, {
+ id: notification.notifieeId
+ }, {
+ detail: true,
+ _hint_: options._hintForEachNotes_
+ }),
+ choice: notification.choice
+ } : {},
+ ...notification.type === "pollEnded" ? {
+ note: Notes.pack(notification.note || notification.noteId, {
+ id: notification.notifieeId
+ }, {
+ detail: true,
+ _hint_: options._hintForEachNotes_
+ })
+ } : {},
+ ...notification.type === "groupInvited" ? {
+ invitation: UserGroupInvitations.pack(notification.userGroupInvitationId)
+ } : {},
+ ...notification.type === "app" ? {
+ body: notification.customBody,
+ header: notification.customHeader || token?.name,
+ icon: notification.customIcon || token?.iconUrl
+ } : {},
+ ...notification.type === "bite" ? {
+ bite: Bites.pack(notification.bite || notification.biteId, {
+ id: notification.notifieeId
+ })
+ } : {}
+ });
+ },
+ async packMany (notifications, meId) {
+ if (notifications.length === 0) return [];
+ const notes = notifications.filter((x)=>x.note != null).map((x)=>x.note);
+ const noteIds = notes.map((n)=>n.id);
+ const myReactionsMap = new Map();
+ const myRenotesMap = new Map();
+ const renoteIds = notes.filter((n)=>n.renoteId != null).map((n)=>n.renoteId);
+ const targets = [
+ ...noteIds,
+ ...renoteIds
+ ];
+ const myReactions = await NoteReactions.findBy({
+ userId: meId,
+ noteId: In(targets)
+ });
+ const myRenotes = targets.length > 0 ? await Notes.createQueryBuilder('note').select('note.renoteId').where('note.userId = :meId', {
+ meId
+ }).andWhere('note.renoteId IN (:...targets)', {
+ targets
+ }).getMany() : [];
+ for (const target of targets){
+ myReactionsMap.set(target, myReactions.find((reaction)=>reaction.noteId === target) || null);
+ myRenotesMap.set(target, !!myRenotes.find((p)=>p.renoteId == target));
+ }
+ await prefetchEmojis(aggregateNoteEmojis(notes));
+ const results = await Promise.all(notifications.map((x)=>this.pack(x, {
+ _hintForEachNotes_: {
+ myReactions: myReactionsMap,
+ myRenotes: myRenotesMap
+ }
+ }).catch((e)=>null)));
+ return results.filter((x)=>x != null);
+ }
+});
diff --git a/packages/backend/built/models/repositories/page-like.js b/packages/backend/built/models/repositories/page-like.js
new file mode 100644
index 0000000..9e62f94
--- /dev/null
+++ b/packages/backend/built/models/repositories/page-like.js
@@ -0,0 +1,17 @@
+import { db } from "../../db/postgre.js";
+import { PageLike } from "../entities/page-like.js";
+import { Pages } from "../index.js";
+export const PageLikeRepository = db.getRepository(PageLike).extend({
+ async pack (src, me) {
+ const like = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return {
+ id: like.id,
+ page: await Pages.pack(like.page || like.pageId, me)
+ };
+ },
+ packMany (likes, me) {
+ return Promise.all(likes.map((x)=>this.pack(x, me)));
+ }
+});
diff --git a/packages/backend/built/models/repositories/page.js b/packages/backend/built/models/repositories/page.js
new file mode 100644
index 0000000..5c2613a
--- /dev/null
+++ b/packages/backend/built/models/repositories/page.js
@@ -0,0 +1,80 @@
+import { db } from "../../db/postgre.js";
+import { Page } from "../entities/page.js";
+import { awaitAll } from "../../prelude/await-all.js";
+import { Users, DriveFiles, PageLikes } from "../index.js";
+export const PageRepository = db.getRepository(Page).extend({
+ async pack (src, me) {
+ const meId = me ? me.id : null;
+ const page = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ const attachedFiles = [];
+ const collectFile = (xs)=>{
+ for (const x of xs){
+ if (x.type === "image") {
+ attachedFiles.push(DriveFiles.findOneBy({
+ id: x.fileId,
+ userId: page.userId
+ }));
+ }
+ if (x.children) {
+ collectFile(x.children);
+ }
+ }
+ };
+ collectFile(page.content);
+ // 後方互換性のため
+ let migrated = false;
+ const migrate = (xs)=>{
+ for (const x of xs){
+ if (x.type === "input") {
+ if (x.inputType === "text") {
+ x.type = "textInput";
+ }
+ if (x.inputType === "number") {
+ x.type = "numberInput";
+ if (x.default) x.default = parseInt(x.default, 10);
+ }
+ migrated = true;
+ }
+ if (x.children) {
+ migrate(x.children);
+ }
+ }
+ };
+ migrate(page.content);
+ if (migrated) {
+ this.update(page.id, {
+ content: page.content
+ });
+ }
+ return await awaitAll({
+ id: page.id,
+ createdAt: page.createdAt.toISOString(),
+ updatedAt: page.updatedAt.toISOString(),
+ userId: page.userId,
+ user: Users.pack(page.user || page.userId, me),
+ content: page.content,
+ variables: page.variables,
+ title: page.title,
+ isPublic: page.isPublic,
+ name: page.name,
+ summary: page.summary,
+ hideTitleWhenPinned: page.hideTitleWhenPinned,
+ alignCenter: page.alignCenter,
+ font: page.font,
+ script: page.script,
+ eyeCatchingImageId: page.eyeCatchingImageId,
+ eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null,
+ attachedFiles: DriveFiles.packMany((await Promise.all(attachedFiles)).filter((x)=>x != null)),
+ likedCount: page.likedCount,
+ isLiked: meId ? await PageLikes.findOneBy({
+ pageId: page.id,
+ userId: meId
+ }).then((x)=>x != null) : undefined
+ });
+ },
+ packMany (pages, me) {
+ return Promise.all(pages.map((x)=>this.pack(x, me)));
+ }
+});
diff --git a/packages/backend/built/models/repositories/plan.js b/packages/backend/built/models/repositories/plan.js
new file mode 100644
index 0000000..b3e90a2
--- /dev/null
+++ b/packages/backend/built/models/repositories/plan.js
@@ -0,0 +1,22 @@
+import { db } from "../../db/postgre.js";
+import { Plan } from "../entities/plan.js";
+export const PlanRepository = db.getRepository(Plan).extend({
+ pack (src) {
+ return Promise.resolve().then(async ()=>{
+ const plan = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return {
+ id: plan.id,
+ createdAt: plan.createdAt.toISOString(),
+ updatedAt: plan.updatedAt?.toISOString() ?? null,
+ name: plan.name,
+ icon: plan.icon,
+ description: plan.description
+ };
+ });
+ },
+ packMany (plans) {
+ return Promise.all(plans.map((x)=>this.pack(x)));
+ }
+});
diff --git a/packages/backend/built/models/repositories/relay.js b/packages/backend/built/models/repositories/relay.js
new file mode 100644
index 0000000..07ec98c
--- /dev/null
+++ b/packages/backend/built/models/repositories/relay.js
@@ -0,0 +1,3 @@
+import { db } from "../../db/postgre.js";
+import { Relay } from "../entities/relay.js";
+export const RelayRepository = db.getRepository(Relay).extend({});
diff --git a/packages/backend/built/models/repositories/renote-muting.js b/packages/backend/built/models/repositories/renote-muting.js
new file mode 100644
index 0000000..a60e5c0
--- /dev/null
+++ b/packages/backend/built/models/repositories/renote-muting.js
@@ -0,0 +1,22 @@
+import { db } from "../../db/postgre.js";
+import { RenoteMuting } from "../entities/renote-muting.js";
+import { awaitAll } from "../../prelude/await-all.js";
+import { Users } from "../index.js";
+export const RenoteMutingRepository = db.getRepository(RenoteMuting).extend({
+ async pack (src, me) {
+ const muting = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return await awaitAll({
+ id: muting.id,
+ createdAt: muting.createdAt.toISOString(),
+ muteeId: muting.muteeId,
+ mutee: Users.pack(muting.muteeId, me, {
+ detail: true
+ })
+ });
+ },
+ packMany (mutings, me) {
+ return Promise.all(mutings.map((x)=>this.pack(x, me)));
+ }
+});
diff --git a/packages/backend/built/models/repositories/reversi-game.js b/packages/backend/built/models/repositories/reversi-game.js
new file mode 100644
index 0000000..3a1e4b6
--- /dev/null
+++ b/packages/backend/built/models/repositories/reversi-game.js
@@ -0,0 +1,69 @@
+import { db } from "../../db/postgre.js";
+import { ReversiGame } from "../entities/reversi-game.js";
+import { Users } from "../index.js";
+import { genId } from "../../misc/gen-id.js";
+function createdAtFromId(id) {
+ const time = parseInt(id.slice(0, 8), 36);
+ if (Number.isNaN(time)) return new Date().toISOString();
+ return new Date(time + 946684800000).toISOString();
+}
+function assertBw(bw) {
+ return bw === "1" || bw === "2" ? bw : "random";
+}
+export const ReversiGameRepository = db.getRepository(ReversiGame).extend({
+ async packDetail (src) {
+ const game = typeof src === "object" ? src : await this.findOneOrFail({
+ where: {
+ id: src
+ },
+ relations: {
+ user1: true,
+ user2: true
+ }
+ });
+ const user1 = await Users.pack(game.user1 ?? game.user1Id, null, {
+ detail: false
+ });
+ const user2 = await Users.pack(game.user2 ?? game.user2Id, null, {
+ detail: false
+ });
+ return {
+ id: game.id,
+ createdAt: createdAtFromId(game.id),
+ startedAt: game.startedAt?.toISOString() ?? null,
+ endedAt: game.endedAt?.toISOString() ?? null,
+ isStarted: game.isStarted,
+ isEnded: game.isEnded,
+ form1: game.form1,
+ form2: game.form2,
+ user1Ready: game.user1Ready,
+ user2Ready: game.user2Ready,
+ user1Id: game.user1Id,
+ user2Id: game.user2Id,
+ user1,
+ user2,
+ winnerId: game.winnerId,
+ winner: game.winnerId ? [
+ user1,
+ user2
+ ].find((u)=>u.id === game.winnerId) ?? null : null,
+ surrenderedUserId: game.surrenderedUserId,
+ timeoutUserId: game.timeoutUserId,
+ black: game.black,
+ bw: assertBw(game.bw),
+ isLlotheo: game.isLlotheo,
+ canPutEverywhere: game.canPutEverywhere,
+ loopedBoard: game.loopedBoard,
+ timeLimitForEachTurn: game.timeLimitForEachTurn,
+ noIrregularRules: game.noIrregularRules,
+ logs: game.logs,
+ map: game.map
+ };
+ },
+ async packLite (src) {
+ const detail = await this.packDetail(src);
+ const { logs, map, form1, form2, user1Ready, user2Ready, ...lite } = detail;
+ return lite;
+ },
+ genId
+});
diff --git a/packages/backend/built/models/repositories/shogi-game.js b/packages/backend/built/models/repositories/shogi-game.js
new file mode 100644
index 0000000..ac0f7c8
--- /dev/null
+++ b/packages/backend/built/models/repositories/shogi-game.js
@@ -0,0 +1,57 @@
+import { db } from "../../db/postgre.js";
+import { genId } from "../../misc/gen-id.js";
+import { ShogiGame } from "../entities/shogi-game.js";
+import { Users } from "../index.js";
+function createdAtFromId(id) {
+ const time = parseInt(id.slice(0, 8), 36);
+ if (Number.isNaN(time)) return new Date().toISOString();
+ return new Date(time + 946684800000).toISOString();
+}
+export const ShogiGameRepository = db.getRepository(ShogiGame).extend({
+ async packDetail (src) {
+ const game = typeof src === "object" ? src : await this.findOneOrFail({
+ where: {
+ id: src
+ },
+ relations: {
+ user1: true,
+ user2: true
+ }
+ });
+ const user1 = await Users.pack(game.user1 ?? game.user1Id, null, {
+ detail: false
+ });
+ const user2 = await Users.pack(game.user2 ?? game.user2Id, null, {
+ detail: false
+ });
+ return {
+ id: game.id,
+ createdAt: createdAtFromId(game.id),
+ startedAt: game.startedAt?.toISOString() ?? null,
+ endedAt: game.endedAt?.toISOString() ?? null,
+ isStarted: game.isStarted,
+ isEnded: game.isEnded,
+ user1Ready: game.user1Ready,
+ user2Ready: game.user2Ready,
+ user1Id: game.user1Id,
+ user2Id: game.user2Id,
+ user1,
+ user2,
+ winnerId: game.winnerId,
+ winner: game.winnerId ? [
+ user1,
+ user2
+ ].find((u)=>u.id === game.winnerId) ?? null : null,
+ surrenderedUserId: game.surrenderedUserId,
+ sente: game.sente,
+ sfen: game.sfen,
+ logs: game.logs
+ };
+ },
+ async packLite (src) {
+ const detail = await this.packDetail(src);
+ const { logs, ...lite } = detail;
+ return lite;
+ },
+ genId
+});
diff --git a/packages/backend/built/models/repositories/signin.js b/packages/backend/built/models/repositories/signin.js
new file mode 100644
index 0000000..fc68797
--- /dev/null
+++ b/packages/backend/built/models/repositories/signin.js
@@ -0,0 +1,7 @@
+import { db } from "../../db/postgre.js";
+import { Signin } from "../entities/signin.js";
+export const SigninRepository = db.getRepository(Signin).extend({
+ async pack (src) {
+ return src;
+ }
+});
diff --git a/packages/backend/built/models/repositories/user-group-invitation.js b/packages/backend/built/models/repositories/user-group-invitation.js
new file mode 100644
index 0000000..1e24548
--- /dev/null
+++ b/packages/backend/built/models/repositories/user-group-invitation.js
@@ -0,0 +1,17 @@
+import { db } from "../../db/postgre.js";
+import { UserGroupInvitation } from "../entities/user-group-invitation.js";
+import { UserGroups } from "../index.js";
+export const UserGroupInvitationRepository = db.getRepository(UserGroupInvitation).extend({
+ async pack (src) {
+ const invitation = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return {
+ id: invitation.id,
+ group: await UserGroups.pack(invitation.userGroup || invitation.userGroupId)
+ };
+ },
+ packMany (invitations) {
+ return Promise.all(invitations.map((x)=>this.pack(x)));
+ }
+});
diff --git a/packages/backend/built/models/repositories/user-group.js b/packages/backend/built/models/repositories/user-group.js
new file mode 100644
index 0000000..04b25d5
--- /dev/null
+++ b/packages/backend/built/models/repositories/user-group.js
@@ -0,0 +1,23 @@
+import { db } from "../../db/postgre.js";
+import { UserGroup } from "../entities/user-group.js";
+import { DriveFiles, UserGroupJoinings } from "../index.js";
+export const UserGroupRepository = db.getRepository(UserGroup).extend({
+ async pack (src) {
+ const userGroup = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ const users = await UserGroupJoinings.findBy({
+ userGroupId: userGroup.id
+ });
+ return {
+ id: userGroup.id,
+ createdAt: userGroup.createdAt.toISOString(),
+ name: userGroup.name,
+ username: userGroup.username,
+ ownerId: userGroup.userId,
+ allowCalls: userGroup.allowCalls,
+ iconUrl: userGroup.iconFileId ? (await DriveFiles.pack(userGroup.iconFileId)).url : null,
+ userIds: users.map((x)=>x.userId)
+ };
+ }
+});
diff --git a/packages/backend/built/models/repositories/user-list.js b/packages/backend/built/models/repositories/user-list.js
new file mode 100644
index 0000000..5cda66f
--- /dev/null
+++ b/packages/backend/built/models/repositories/user-list.js
@@ -0,0 +1,20 @@
+import { db } from "../../db/postgre.js";
+import { UserList } from "../entities/user-list.js";
+import { UserListJoinings } from "../index.js";
+export const UserListRepository = db.getRepository(UserList).extend({
+ async pack (src) {
+ const userList = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ const users = await UserListJoinings.findBy({
+ userListId: userList.id
+ });
+ return {
+ id: userList.id,
+ createdAt: userList.createdAt.toISOString(),
+ name: userList.name,
+ hideFromHomeTl: userList.hideFromHomeTl,
+ userIds: users.map((x)=>x.userId)
+ };
+ }
+});
diff --git a/packages/backend/built/models/repositories/user-profile.js b/packages/backend/built/models/repositories/user-profile.js
new file mode 100644
index 0000000..2890650
--- /dev/null
+++ b/packages/backend/built/models/repositories/user-profile.js
@@ -0,0 +1,53 @@
+import { db } from "../../db/postgre.js";
+import { UserProfile } from "../entities/user-profile.js";
+import mfm from "mfm-js";
+import { extractMentions } from "../../misc/extract-mentions.js";
+import { resolveMentionToUserAndProfile } from "../../remote/resolve-user.js";
+import { unique } from "../../prelude/array.js";
+import config from "../../config/index.js";
+import { Mutex, Semaphore } from "async-mutex";
+const queue = new Semaphore(5);
+export const UserProfileRepository = db.getRepository(UserProfile).extend({
+ // We must never await this without promiseEarlyReturn, otherwise giant webring-style profile mention trees will cause the queue to stop working
+ async updateMentions (id, limiter = new RecursionLimiter()) {
+ const profile = await this.findOneBy({
+ userId: id
+ });
+ if (!profile) return;
+ const tokens = [];
+ if (profile.description) tokens.push(...mfm.parse(profile.description));
+ if (profile.fields.length > 0) tokens.push(...profile.fields.map((p)=>mfm.parse(p.value).concat(mfm.parse(p.name))).flat());
+ return queue.runExclusive(async ()=>{
+ const partial = {
+ mentions: await populateMentions(tokens, profile.userHost, limiter)
+ };
+ return UserProfileRepository.update(profile.userId, partial);
+ });
+ }
+});
+async function populateMentions(tokens, objectHost, limiter) {
+ const mentions = extractMentions(tokens);
+ const resolved = await Promise.all(mentions.map((m)=>resolveMentionToUserAndProfile(m.username, m.host, objectHost, limiter)));
+ const remote = resolved.filter((p)=>p && p.data.host !== config.domain && (p.data.host !== null || objectHost !== null)).map((p)=>p);
+ const res = remote.map((m)=>{
+ return {
+ uri: m.user.uri,
+ url: m.profile?.url ?? undefined,
+ username: m.data.username,
+ host: m.data.host
+ };
+ });
+ return unique(res);
+}
+export class RecursionLimiter {
+ counter;
+ mutex = new Mutex();
+ constructor(count = 10){
+ this.counter = count;
+ }
+ shouldContinue() {
+ return this.mutex.runExclusive(()=>{
+ return this.counter-- > 0;
+ });
+ }
+}
diff --git a/packages/backend/built/models/repositories/user.js b/packages/backend/built/models/repositories/user.js
new file mode 100644
index 0000000..b54609c
--- /dev/null
+++ b/packages/backend/built/models/repositories/user.js
@@ -0,0 +1,503 @@
+import { In, Not } from "typeorm";
+import Ajv from "ajv";
+import { User } from "../entities/user.js";
+import config from "../../config/index.js";
+import { awaitAll } from "../../prelude/await-all.js";
+import { populateEmojis } from "../../misc/populate-emojis.js";
+import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from "../../const.js";
+import { Cache } from "../../misc/cache.js";
+import { db } from "../../db/postgre.js";
+import { isActor, getApId } from "../../remote/activitypub/type.js";
+import DbResolver from "../../remote/activitypub/db-resolver.js";
+import Resolver from "../../remote/activitypub/resolver.js";
+import { createPerson } from "../../remote/activitypub/models/person.js";
+import { AnnouncementReads, Announcements, Blockings, ChannelFollowings, DriveFiles, Followings, FollowRequests, Instances, MessagingMessages, Mutings, RenoteMutings, Notes, NoteUnreads, Notifications, Pages, Plans, UserGroupJoinings, UserNotePinings, UserPlans, UserProfiles, UserSecurityKeys } from "../index.js";
+import AsyncLock from "async-lock";
+const userInstanceCache = new Cache("userInstance", 60 * 60 * 3);
+function isMissingRelationError(err) {
+ const error = err;
+ return error.code === "42P01" || error.driverError?.code === "42P01";
+}
+const ajv = new Ajv();
+const localUsernameSchema = {
+ type: "string",
+ pattern: /^\w{1,20}$/.toString().slice(1, -1)
+};
+const passwordSchema = {
+ type: "string",
+ minLength: 1
+};
+const nameSchema = {
+ type: "string",
+ minLength: 1,
+ maxLength: 50
+};
+const descriptionSchema = {
+ type: "string",
+ minLength: 1,
+ maxLength: 2048
+};
+const locationSchema = {
+ type: "string",
+ minLength: 1,
+ maxLength: 50
+};
+const birthdaySchema = {
+ type: "string",
+ pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1)
+};
+/**
+ * Returns true if the user is local.
+ *
+ * @param user The user to check.
+ * @returns True if the user is local.
+ */ function isLocalUser(user) {
+ return user.host == null;
+}
+/**
+ * Returns true if the user is remote.
+ *
+ * @param user The user to check.
+ * @returns True if the user is remote.
+ */ function isRemoteUser(user) {
+ return !isLocalUser(user);
+}
+export const UserRepository = db.getRepository(User).extend({
+ localUsernameSchema,
+ passwordSchema,
+ nameSchema,
+ descriptionSchema,
+ locationSchema,
+ birthdaySchema,
+ //#region Validators
+ validateLocalUsername: ajv.compile(localUsernameSchema),
+ validatePassword: ajv.compile(passwordSchema),
+ validateName: ajv.compile(nameSchema),
+ validateDescription: ajv.compile(descriptionSchema),
+ validateLocation: ajv.compile(locationSchema),
+ validateBirthday: ajv.compile(birthdaySchema),
+ //#endregion
+ async getRelation (me, target) {
+ return awaitAll({
+ id: target,
+ isFollowing: Followings.count({
+ where: {
+ followerId: me,
+ followeeId: target
+ },
+ take: 1
+ }).then((n)=>n > 0),
+ isFollowed: Followings.count({
+ where: {
+ followerId: target,
+ followeeId: me
+ },
+ take: 1
+ }).then((n)=>n > 0),
+ hasPendingFollowRequestFromYou: FollowRequests.count({
+ where: {
+ followerId: me,
+ followeeId: target
+ },
+ take: 1
+ }).then((n)=>n > 0),
+ hasPendingFollowRequestToYou: FollowRequests.count({
+ where: {
+ followerId: target,
+ followeeId: me
+ },
+ take: 1
+ }).then((n)=>n > 0),
+ isBlocking: Blockings.count({
+ where: {
+ blockerId: me,
+ blockeeId: target
+ },
+ take: 1
+ }).then((n)=>n > 0),
+ isBlocked: Blockings.count({
+ where: {
+ blockerId: target,
+ blockeeId: me
+ },
+ take: 1
+ }).then((n)=>n > 0),
+ isMuted: Mutings.count({
+ where: {
+ muterId: me,
+ muteeId: target
+ },
+ take: 1
+ }).then((n)=>n > 0),
+ isRenoteMuted: RenoteMutings.count({
+ where: {
+ muterId: me,
+ muteeId: target
+ },
+ take: 1
+ }).then((n)=>n > 0)
+ });
+ },
+ async getHasUnreadMessagingMessage (userId) {
+ const mute = await Mutings.findBy({
+ muterId: userId
+ });
+ const joinings = await UserGroupJoinings.findBy({
+ userId: userId
+ });
+ const groupQs = Promise.all(joinings.map((j)=>MessagingMessages.createQueryBuilder("message").where("message.groupId = :groupId", {
+ groupId: j.userGroupId
+ }).andWhere("message.userId != :userId", {
+ userId: userId
+ }).andWhere("NOT (:userId = ANY(message.reads))", {
+ userId: userId
+ }).andWhere("message.createdAt > :joinedAt", {
+ joinedAt: j.createdAt
+ }) // 自分が加入する前の会話については、未読扱いしない
+ .getOne().then((x)=>x != null)));
+ const [withUser, withGroups] = await Promise.all([
+ MessagingMessages.count({
+ where: {
+ recipientId: userId,
+ isRead: false,
+ ...mute.length > 0 ? {
+ userId: Not(In(mute.map((x)=>x.muteeId)))
+ } : {}
+ },
+ take: 1
+ }).then((count)=>count > 0),
+ groupQs
+ ]);
+ return withUser || withGroups.some((x)=>x);
+ },
+ async getHasUnreadAnnouncement (userId) {
+ const reads = await AnnouncementReads.findBy({
+ userId: userId
+ });
+ const count = await Announcements.countBy(reads.length > 0 ? {
+ id: Not(In(reads.map((read)=>read.announcementId)))
+ } : {});
+ return count > 0;
+ },
+ async userFromURI (uri) {
+ try {
+ const dbResolver = new DbResolver();
+ let local = await dbResolver.getUserFromApId(uri);
+ if (local) {
+ return local;
+ }
+ // fetching Object once from remote
+ const resolver = new Resolver();
+ const object = await resolver.resolve(uri);
+ // /@user If a URI other than the id is specified,
+ // the URI is determined here
+ if (uri !== object.id) {
+ local = await dbResolver.getUserFromApId(object.id);
+ if (local != null) return local;
+ }
+ return isActor(object) ? await createPerson(getApId(object)) : null;
+ } catch {
+ return null;
+ }
+ },
+ async getHasUnreadAntenna (userId) {
+ // try {
+ // 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;
+ // return unread != null;
+ // } catch (e) {
+ // return false;
+ // }
+ return false; // TODO
+ },
+ async getHasUnreadChannel (userId) {
+ const channels = await ChannelFollowings.findBy({
+ followerId: userId
+ });
+ const unread = channels.length > 0 ? await NoteUnreads.findOneBy({
+ userId: userId,
+ noteChannelId: In(channels.map((x)=>x.followeeId))
+ }) : null;
+ return unread != null;
+ },
+ async getHasUnreadNotification (userId) {
+ const mute = await Mutings.findBy({
+ muterId: userId
+ });
+ const mutedUserIds = mute.map((m)=>m.muteeId);
+ const count = await Notifications.count({
+ where: {
+ notifieeId: userId,
+ ...mutedUserIds.length > 0 ? {
+ notifierId: Not(In(mutedUserIds))
+ } : {},
+ isRead: false
+ },
+ take: 1
+ });
+ return count > 0;
+ },
+ async getHasPendingReceivedFollowRequest (userId) {
+ const count = await FollowRequests.countBy({
+ followeeId: userId
+ });
+ return count > 0;
+ },
+ getOnlineStatus (user) {
+ if (user.hideOnlineStatus) return "unknown";
+ if (user.lastActiveDate == null) return "unknown";
+ const elapsed = Date.now() - user.lastActiveDate.getTime();
+ return elapsed < USER_ONLINE_THRESHOLD ? "online" : elapsed < USER_ACTIVE_THRESHOLD ? "active" : "offline";
+ },
+ async getAvatarUrl (user) {
+ if (user.avatar) {
+ return DriveFiles.getPublicUrl(user.avatar, true) || this.getIdenticonUrl(user.id);
+ } else if (user.avatarId) {
+ if (user.avatarUrl) return DriveFiles.getFinalUrl(user.avatarUrl);
+ const avatar = await DriveFiles.findOneByOrFail({
+ id: user.avatarId
+ });
+ return DriveFiles.getPublicUrl(avatar, true) || this.getIdenticonUrl(user.id);
+ } else {
+ return this.getIdenticonUrl(user.id);
+ }
+ },
+ getAvatarUrlSync (user) {
+ if (user.avatarId && user.avatarUrl) {
+ return DriveFiles.getFinalUrl(user.avatarUrl);
+ } else if (user.avatar) {
+ return DriveFiles.getPublicUrl(user.avatar, true) || this.getIdenticonUrl(user.id);
+ } else {
+ return this.getIdenticonUrl(user.id);
+ }
+ },
+ getIdenticonUrl (userId) {
+ return `${config.url}/identicon/${userId}`;
+ },
+ getFreshPackedUserCache () {
+ return {
+ locks: new AsyncLock(),
+ results: []
+ };
+ },
+ async getRandomFollower (targetId) {
+ return await this.createQueryBuilder("u").select(`u.id`).leftJoinAndSelect("following", "f", `f."followerId" = u.id`).where(`f."followeeId" = :id`, {
+ id: targetId
+ }).getOne();
+ },
+ async packCached (src, cache, me, options) {
+ const id = typeof src === "object" ? src.id : src;
+ return cache.locks.acquire(id, async ()=>{
+ const result = cache.results.find((p)=>p.id === id);
+ if (result) return result;
+ return this.pack(src, me, options).then((result)=>{
+ cache.results.push(result);
+ return result;
+ });
+ });
+ },
+ async pack (src, me, options) {
+ const opts = Object.assign({
+ detail: false,
+ includeSecrets: false,
+ isPrivateMode: false
+ }, options);
+ let user;
+ if (typeof src === "object") {
+ user = src;
+ } else {
+ user = await this.findOneOrFail({
+ where: {
+ id: src
+ }
+ });
+ }
+ const meId = me ? me.id : null;
+ const isMe = meId === user.id;
+ const relation = meId && !isMe && opts.detail ? await this.getRelation(meId, user.id) : null;
+ const pins = opts.detail ? await UserNotePinings.createQueryBuilder("pin").where("pin.userId = :userId", {
+ userId: user.id
+ }).innerJoinAndSelect("pin.note", "note").orderBy("pin.id", "DESC").getMany() : [];
+ const profile = opts.detail ? await UserProfiles.findOneByOrFail({
+ userId: user.id
+ }) : null;
+ const followingCount = profile == null ? null : profile.ffVisibility === "public" || isMe ? user.followingCount : profile.ffVisibility === "followers" && relation && relation.isFollowing ? user.followingCount : null;
+ const followersCount = profile == null ? null : profile.ffVisibility === "public" || isMe ? user.followersCount : profile.ffVisibility === "followers" && relation && relation.isFollowing ? user.followersCount : null;
+ const falsy = opts.detail ? false : undefined;
+ if (opts.isPrivateMode) {
+ const packed = {
+ id: user.id,
+ username: user.username,
+ host: user.host,
+ ...opts.detail ? {
+ twoFactorEnabled: profile.twoFactorEnabled,
+ usePasswordLessLogin: profile.usePasswordLessLogin,
+ securityKeys: profile.twoFactorEnabled ? UserSecurityKeys.countBy({
+ userId: user.id
+ }).then((result)=>result >= 1) : false
+ } : {}
+ };
+ return await awaitAll(packed);
+ }
+ const packed = {
+ id: user.id,
+ name: user.name,
+ username: user.username,
+ host: user.host,
+ avatarUrl: this.getAvatarUrlSync(user),
+ avatarBlurhash: user.avatarId ? user.avatarBlurhash ?? user.avatar?.blurhash ?? null : null,
+ avatarColor: null,
+ isAdmin: user.isAdmin || falsy,
+ isModerator: user.isModerator || falsy,
+ isVerified: user.isVerified || falsy,
+ minorBadges: user.minorBadges ?? [],
+ plans: UserPlans.find({
+ where: {
+ userId: user.id
+ },
+ relations: [
+ "plan"
+ ],
+ order: {
+ createdAt: "ASC"
+ }
+ }).then((joins)=>Plans.packMany(joins.map((join)=>join.plan).filter((plan)=>plan != null))).catch((err)=>{
+ if (isMissingRelationError(err)) return [];
+ throw err;
+ }),
+ isBot: user.isBot || falsy,
+ isLocked: user.isLocked,
+ isCat: user.isCat || falsy,
+ speakAsCat: user.speakAsCat || falsy,
+ instance: user.host ? userInstanceCache.fetch(user.host, ()=>Instances.findOneBy({
+ host: user.host
+ }), (v)=>v != null).then((instance)=>instance ? {
+ name: instance.name,
+ softwareName: instance.softwareName,
+ softwareVersion: instance.softwareVersion,
+ iconUrl: instance.iconUrl,
+ faviconUrl: instance.faviconUrl,
+ themeColor: instance.themeColor
+ } : undefined) : undefined,
+ emojis: populateEmojis(user.emojis, user.host),
+ onlineStatus: this.getOnlineStatus(user),
+ driveCapacityOverrideMb: user.driveCapacityOverrideMb,
+ canBite: user.canBite,
+ ...opts.detail ? {
+ url: profile.url,
+ uri: user.uri,
+ movedToUri: user.movedToUri ? await this.userFromURI(user.movedToUri) : null,
+ alsoKnownAs: user.alsoKnownAs,
+ createdAt: user.createdAt.toISOString(),
+ updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null,
+ lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null,
+ bannerUrl: user.bannerId ? DriveFiles.getFinalUrlMaybe(user.bannerUrl) ?? (user.banner ? DriveFiles.getPublicUrl(user.banner, false) : null) : null,
+ bannerBlurhash: user.bannerId ? user.bannerBlurhash ?? user.banner?.blurhash ?? null : null,
+ bannerColor: null,
+ isSilenced: user.isSilenced || falsy,
+ isSuspended: user.isSuspended || falsy,
+ description: profile.description,
+ location: profile.location,
+ birthday: profile.birthday,
+ lang: profile.lang,
+ fields: profile.fields,
+ followersCount: followersCount || 0,
+ followingCount: followingCount || 0,
+ notesCount: user.notesCount,
+ pinnedNoteIds: pins.map((pin)=>pin.noteId),
+ pinnedNotes: Notes.packMany(pins.map((pin)=>pin.note), me, {
+ detail: true
+ }),
+ pinnedPageId: profile.pinnedPageId,
+ pinnedPage: profile.pinnedPageId ? Pages.pack(profile.pinnedPageId, me) : null,
+ publicReactions: profile.publicReactions,
+ allowCalls: profile.allowCalls,
+ symbolFileId: profile.symbolFileId,
+ ffVisibility: profile.ffVisibility,
+ twoFactorEnabled: profile.twoFactorEnabled,
+ usePasswordLessLogin: profile.usePasswordLessLogin,
+ securityKeys: profile.twoFactorEnabled ? UserSecurityKeys.countBy({
+ userId: user.id
+ }).then((result)=>result >= 1) : false,
+ pronouns: profile.pronouns
+ } : {},
+ ...opts.detail && isMe ? {
+ avatarId: user.avatarId,
+ bannerId: user.bannerId,
+ injectFeaturedNote: profile.injectFeaturedNote,
+ receiveAnnouncementEmail: profile.receiveAnnouncementEmail,
+ alwaysMarkNsfw: profile.alwaysMarkNsfw,
+ carefulBot: profile.carefulBot,
+ autoAcceptFollowed: profile.autoAcceptFollowed,
+ noCrawle: profile.noCrawle,
+ preventAiLearning: profile.preventAiLearning,
+ isExplorable: user.isExplorable,
+ isDeleted: user.isDeleted,
+ hideOnlineStatus: user.hideOnlineStatus,
+ hasUnreadSpecifiedNotes: NoteUnreads.count({
+ where: {
+ userId: user.id,
+ isSpecified: true
+ },
+ take: 1
+ }).then((count)=>count > 0),
+ hasUnreadMentions: NoteUnreads.count({
+ where: {
+ userId: user.id,
+ isMentioned: true
+ },
+ take: 1
+ }).then((count)=>count > 0),
+ hasUnreadAnnouncement: this.getHasUnreadAnnouncement(user.id),
+ hasUnreadAntenna: this.getHasUnreadAntenna(user.id),
+ hasUnreadChannel: this.getHasUnreadChannel(user.id),
+ hasUnreadMessagingMessage: this.getHasUnreadMessagingMessage(user.id),
+ hasUnreadNotification: this.getHasUnreadNotification(user.id),
+ hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
+ integrations: profile.integrations,
+ mutedWords: profile.mutedWords,
+ mutedInstances: profile.mutedInstances,
+ mutingNotificationTypes: profile.mutingNotificationTypes,
+ emailNotificationTypes: profile.emailNotificationTypes
+ } : {},
+ ...opts.includeSecrets ? {
+ email: profile.email,
+ emailVerified: profile.emailVerified,
+ securityKeysList: profile.twoFactorEnabled ? UserSecurityKeys.find({
+ where: {
+ userId: user.id
+ },
+ select: {
+ id: true,
+ name: true,
+ lastUsed: true
+ }
+ }) : []
+ } : {},
+ ...relation ? {
+ isFollowing: relation.isFollowing,
+ isFollowed: relation.isFollowed,
+ hasPendingFollowRequestFromYou: relation.hasPendingFollowRequestFromYou,
+ hasPendingFollowRequestToYou: relation.hasPendingFollowRequestToYou,
+ isBlocking: relation.isBlocking,
+ isBlocked: relation.isBlocked,
+ isMuted: relation.isMuted,
+ isRenoteMuted: relation.isRenoteMuted
+ } : {}
+ };
+ return await awaitAll(packed);
+ },
+ packMany (users, me, options, cache) {
+ return Promise.all(users.map((u)=>this.packCached(u, cache ?? this.getFreshPackedUserCache(), me, options)));
+ },
+ isLocalUser,
+ isRemoteUser
+});
diff --git a/packages/backend/built/models/repositories/verified-badge-request.js b/packages/backend/built/models/repositories/verified-badge-request.js
new file mode 100644
index 0000000..b3ff7e8
--- /dev/null
+++ b/packages/backend/built/models/repositories/verified-badge-request.js
@@ -0,0 +1,29 @@
+import { db } from "../../db/postgre.js";
+import { Users } from "../index.js";
+import { VerifiedBadgeRequest } from "../entities/verified-badge-request.js";
+import { awaitAll } from "../../prelude/await-all.js";
+export const VerifiedBadgeRequestRepository = db.getRepository(VerifiedBadgeRequest).extend({
+ async pack (src) {
+ const request = typeof src === "object" ? src : await this.findOneByOrFail({
+ id: src
+ });
+ return await awaitAll({
+ id: request.id,
+ createdAt: request.createdAt.toISOString(),
+ resolvedAt: request.resolvedAt?.toISOString() ?? null,
+ status: request.status,
+ comment: request.comment,
+ userId: request.userId,
+ resolverId: request.resolverId,
+ user: Users.pack(request.user || request.userId, null, {
+ detail: true
+ }),
+ resolver: request.resolverId ? Users.pack(request.resolver || request.resolverId, null, {
+ detail: true
+ }) : null
+ });
+ },
+ packMany (requests) {
+ return Promise.all(requests.map((x)=>this.pack(x)));
+ }
+});
diff --git a/packages/backend/built/models/schema/antenna.js b/packages/backend/built/models/schema/antenna.js
new file mode 100644
index 0000000..cee39b1
--- /dev/null
+++ b/packages/backend/built/models/schema/antenna.js
@@ -0,0 +1,125 @@
+export const packedAntennaSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ name: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ keywords: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ }
+ },
+ excludeKeywords: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ }
+ },
+ src: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ enum: [
+ "home",
+ "all",
+ "users",
+ "list",
+ "group",
+ "instances"
+ ]
+ },
+ userListId: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "id"
+ },
+ userGroupId: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "id"
+ },
+ users: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ },
+ instances: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ },
+ caseSensitive: {
+ type: "boolean",
+ optional: false,
+ nullable: false,
+ default: false
+ },
+ notify: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ withReplies: {
+ type: "boolean",
+ optional: false,
+ nullable: false,
+ default: false
+ },
+ withFile: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ hasUnreadNote: {
+ type: "boolean",
+ optional: false,
+ nullable: false,
+ default: false
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/app.js b/packages/backend/built/models/schema/app.js
new file mode 100644
index 0000000..127288b
--- /dev/null
+++ b/packages/backend/built/models/schema/app.js
@@ -0,0 +1,40 @@
+export const packedAppSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ name: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ callbackUrl: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ permission: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ },
+ secret: {
+ type: "string",
+ optional: true,
+ nullable: false
+ },
+ isAuthorized: {
+ type: "boolean",
+ optional: true,
+ nullable: false
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/bite.js b/packages/backend/built/models/schema/bite.js
new file mode 100644
index 0000000..59af68e
--- /dev/null
+++ b/packages/backend/built/models/schema/bite.js
@@ -0,0 +1,35 @@
+export const packedBiteSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ format: "id",
+ optional: false,
+ nullable: false
+ },
+ user: {
+ type: "object",
+ ref: "UserLite"
+ },
+ targetType: {
+ type: "string",
+ enum: [
+ "user",
+ "bite",
+ "note"
+ ]
+ },
+ target: {
+ oneOf: [
+ {
+ type: "object",
+ ref: "UserLite"
+ },
+ {
+ type: "object",
+ ref: "Bite"
+ }
+ ]
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/blocking.js b/packages/backend/built/models/schema/blocking.js
new file mode 100644
index 0000000..101c9c8
--- /dev/null
+++ b/packages/backend/built/models/schema/blocking.js
@@ -0,0 +1,30 @@
+export const packedBlockingSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ blockeeId: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ blockee: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserDetailed"
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/channel.js b/packages/backend/built/models/schema/channel.js
new file mode 100644
index 0000000..4bb2f28
--- /dev/null
+++ b/packages/backend/built/models/schema/channel.js
@@ -0,0 +1,61 @@
+export const packedChannelSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ lastNotedAt: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "date-time"
+ },
+ name: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ description: {
+ type: "string",
+ nullable: true,
+ optional: false
+ },
+ bannerUrl: {
+ type: "string",
+ format: "url",
+ nullable: true,
+ optional: false
+ },
+ notesCount: {
+ type: "number",
+ nullable: false,
+ optional: false
+ },
+ usersCount: {
+ type: "number",
+ nullable: false,
+ optional: false
+ },
+ isFollowing: {
+ type: "boolean",
+ optional: true,
+ nullable: false
+ },
+ userId: {
+ type: "string",
+ nullable: true,
+ optional: false,
+ format: "id"
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/clip.js b/packages/backend/built/models/schema/clip.js
new file mode 100644
index 0000000..edd42d9
--- /dev/null
+++ b/packages/backend/built/models/schema/clip.js
@@ -0,0 +1,45 @@
+export const packedClipSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ userId: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ user: {
+ type: "object",
+ ref: "UserLite",
+ optional: false,
+ nullable: false
+ },
+ name: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ description: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ isPublic: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/drive-file.js b/packages/backend/built/models/schema/drive-file.js
new file mode 100644
index 0000000..7ecf080
--- /dev/null
+++ b/packages/backend/built/models/schema/drive-file.js
@@ -0,0 +1,132 @@
+export const packedDriveFileSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ name: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ example: "lenna.jpg"
+ },
+ type: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ example: "image/jpeg"
+ },
+ md5: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "md5",
+ example: "15eca7fba0480996e2245f5185bf39f2"
+ },
+ size: {
+ type: "number",
+ optional: false,
+ nullable: false,
+ example: 51469
+ },
+ isSensitive: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ allowDownload: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ blurhash: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ properties: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ width: {
+ type: "number",
+ optional: true,
+ nullable: false,
+ example: 1280
+ },
+ height: {
+ type: "number",
+ optional: true,
+ nullable: false,
+ example: 720
+ },
+ orientation: {
+ type: "number",
+ optional: true,
+ nullable: false,
+ example: 8
+ },
+ avgColor: {
+ type: "string",
+ optional: true,
+ nullable: false,
+ example: "rgb(40,65,87)"
+ }
+ }
+ },
+ url: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "url"
+ },
+ thumbnailUrl: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "url"
+ },
+ comment: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ folderId: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ folder: {
+ type: "object",
+ optional: true,
+ nullable: true,
+ ref: "DriveFolder"
+ },
+ userId: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ user: {
+ type: "object",
+ optional: true,
+ nullable: true,
+ ref: "UserLite"
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/drive-folder.js b/packages/backend/built/models/schema/drive-folder.js
new file mode 100644
index 0000000..7e28e3f
--- /dev/null
+++ b/packages/backend/built/models/schema/drive-folder.js
@@ -0,0 +1,46 @@
+export const packedDriveFolderSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ name: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ foldersCount: {
+ type: "number",
+ optional: true,
+ nullable: false
+ },
+ filesCount: {
+ type: "number",
+ optional: true,
+ nullable: false
+ },
+ parentId: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ parent: {
+ type: "object",
+ optional: true,
+ nullable: true,
+ ref: "DriveFolder"
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/emoji.js b/packages/backend/built/models/schema/emoji.js
new file mode 100644
index 0000000..0f23a26
--- /dev/null
+++ b/packages/backend/built/models/schema/emoji.js
@@ -0,0 +1,69 @@
+export const packedEmojiSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ aliases: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ }
+ },
+ name: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ category: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ host: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ description: "The local host is represented with `null`."
+ },
+ url: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ license: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ glyph: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ glyphUrl: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ width: {
+ type: "number",
+ optional: false,
+ nullable: true
+ },
+ height: {
+ type: "number",
+ optional: false,
+ nullable: true
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/federation-instance.js b/packages/backend/built/models/schema/federation-instance.js
new file mode 100644
index 0000000..29a4881
--- /dev/null
+++ b/packages/backend/built/models/schema/federation-instance.js
@@ -0,0 +1,137 @@
+import config from "../../config/index.js";
+export const packedFederationInstanceSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ caughtAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ host: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ example: "iceshrimp.example.com"
+ },
+ usersCount: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ notesCount: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ followingCount: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ followersCount: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ latestRequestSentAt: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "date-time"
+ },
+ lastCommunicatedAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ isNotResponding: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ isSuspended: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ isBlocked: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ isSilenced: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ softwareName: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ example: "iceshrimp"
+ },
+ softwareVersion: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ example: config.version
+ },
+ openRegistrations: {
+ type: "boolean",
+ optional: false,
+ nullable: true,
+ example: true
+ },
+ name: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ description: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ maintainerName: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ maintainerEmail: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ iconUrl: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "url"
+ },
+ faviconUrl: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "url"
+ },
+ themeColor: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ infoUpdatedAt: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "date-time"
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/following.js b/packages/backend/built/models/schema/following.js
new file mode 100644
index 0000000..c957460
--- /dev/null
+++ b/packages/backend/built/models/schema/following.js
@@ -0,0 +1,42 @@
+export const packedFollowingSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ followeeId: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ followee: {
+ type: "object",
+ optional: true,
+ nullable: false,
+ ref: "UserDetailed"
+ },
+ followerId: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ follower: {
+ type: "object",
+ optional: true,
+ nullable: false,
+ ref: "UserDetailed"
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/gallery-post.js b/packages/backend/built/models/schema/gallery-post.js
new file mode 100644
index 0000000..a02da4c
--- /dev/null
+++ b/packages/backend/built/models/schema/gallery-post.js
@@ -0,0 +1,83 @@
+export const packedGalleryPostSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ updatedAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ title: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ description: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ userId: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ user: {
+ type: "object",
+ ref: "UserLite",
+ optional: false,
+ nullable: false
+ },
+ fileIds: {
+ type: "array",
+ optional: true,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ }
+ },
+ files: {
+ type: "array",
+ optional: true,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "DriveFile"
+ }
+ },
+ tags: {
+ type: "array",
+ optional: true,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ },
+ isSensitive: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/hashtag.js b/packages/backend/built/models/schema/hashtag.js
new file mode 100644
index 0000000..8aa530b
--- /dev/null
+++ b/packages/backend/built/models/schema/hashtag.js
@@ -0,0 +1,41 @@
+export const packedHashtagSchema = {
+ type: "object",
+ properties: {
+ tag: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ example: "iceshrimp"
+ },
+ mentionedUsersCount: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ mentionedLocalUsersCount: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ mentionedRemoteUsersCount: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ attachedUsersCount: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ attachedLocalUsersCount: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ attachedRemoteUsersCount: {
+ type: "number",
+ optional: false,
+ nullable: false
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/messaging-message.js b/packages/backend/built/models/schema/messaging-message.js
new file mode 100644
index 0000000..2f7fdfa
--- /dev/null
+++ b/packages/backend/built/models/schema/messaging-message.js
@@ -0,0 +1,87 @@
+export const packedMessagingMessageSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ userId: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ user: {
+ type: "object",
+ ref: "UserLite",
+ optional: true,
+ nullable: false
+ },
+ text: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ fileId: {
+ type: "string",
+ optional: true,
+ nullable: true,
+ format: "id"
+ },
+ file: {
+ type: "object",
+ optional: true,
+ nullable: true,
+ ref: "DriveFile"
+ },
+ recipientId: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "id"
+ },
+ recipient: {
+ type: "object",
+ optional: true,
+ nullable: true,
+ ref: "UserLite"
+ },
+ groupId: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "id"
+ },
+ group: {
+ type: "object",
+ optional: true,
+ nullable: true,
+ ref: "UserGroup"
+ },
+ isRead: {
+ type: "boolean",
+ optional: true,
+ nullable: false
+ },
+ reads: {
+ type: "array",
+ optional: true,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ }
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/muting.js b/packages/backend/built/models/schema/muting.js
new file mode 100644
index 0000000..d3a7088
--- /dev/null
+++ b/packages/backend/built/models/schema/muting.js
@@ -0,0 +1,36 @@
+export const packedMutingSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ expiresAt: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "date-time"
+ },
+ muteeId: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ mutee: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserDetailed"
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/note-edit.js b/packages/backend/built/models/schema/note-edit.js
new file mode 100644
index 0000000..2a099d3
--- /dev/null
+++ b/packages/backend/built/models/schema/note-edit.js
@@ -0,0 +1,49 @@
+export const packedNoteEdit = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ updatedAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ note: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ },
+ noteId: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ text: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ cw: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ fileIds: {
+ type: "array",
+ optional: true,
+ nullable: true,
+ items: {
+ type: "string",
+ format: "id"
+ }
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/note-favorite.js b/packages/backend/built/models/schema/note-favorite.js
new file mode 100644
index 0000000..dfec7d5
--- /dev/null
+++ b/packages/backend/built/models/schema/note-favorite.js
@@ -0,0 +1,30 @@
+export const packedNoteFavoriteSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ note: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ },
+ noteId: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/note-reaction.js b/packages/backend/built/models/schema/note-reaction.js
new file mode 100644
index 0000000..d186a48
--- /dev/null
+++ b/packages/backend/built/models/schema/note-reaction.js
@@ -0,0 +1,29 @@
+export const packedNoteReactionSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ user: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserLite"
+ },
+ type: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/note.js b/packages/backend/built/models/schema/note.js
new file mode 100644
index 0000000..e7ce13d
--- /dev/null
+++ b/packages/backend/built/models/schema/note.js
@@ -0,0 +1,236 @@
+export const packedNoteSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ text: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ cw: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ userId: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ user: {
+ type: "object",
+ ref: "UserLite",
+ optional: false,
+ nullable: false
+ },
+ groupId: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "id"
+ },
+ group: {
+ type: "object",
+ ref: "UserGroup",
+ optional: false,
+ nullable: true
+ },
+ replyId: {
+ type: "string",
+ optional: true,
+ nullable: true,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ renoteId: {
+ type: "string",
+ optional: true,
+ nullable: true,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ reply: {
+ type: "object",
+ optional: true,
+ nullable: true,
+ ref: "Note"
+ },
+ renote: {
+ type: "object",
+ optional: true,
+ nullable: true,
+ ref: "Note"
+ },
+ visibility: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ mentions: {
+ type: "array",
+ optional: true,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ }
+ },
+ visibleUserIds: {
+ type: "array",
+ optional: true,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ }
+ },
+ fileIds: {
+ type: "array",
+ optional: true,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ }
+ },
+ files: {
+ type: "array",
+ optional: true,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "DriveFile"
+ }
+ },
+ tags: {
+ type: "array",
+ optional: true,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ },
+ poll: {
+ type: "object",
+ optional: true,
+ nullable: true
+ },
+ channelId: {
+ type: "string",
+ optional: true,
+ nullable: true,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ channel: {
+ type: "object",
+ optional: true,
+ nullable: true,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ name: {
+ type: "string",
+ optional: false,
+ nullable: true
+ }
+ }
+ }
+ },
+ localOnly: {
+ type: "boolean",
+ optional: true,
+ nullable: false
+ },
+ emojis: {
+ type: "object",
+ optional: true,
+ nullable: true
+ },
+ reactions: {
+ type: "object",
+ optional: false,
+ nullable: false
+ },
+ renoteCount: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ repliesCount: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ viewCount: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ uri: {
+ type: "string",
+ optional: true,
+ nullable: false
+ },
+ url: {
+ type: "string",
+ optional: true,
+ nullable: false
+ },
+ myReaction: {
+ type: "object",
+ optional: true,
+ nullable: true
+ },
+ isRenoted: {
+ type: "boolean",
+ optional: true,
+ nullable: true
+ },
+ isFiltered: {
+ type: "boolean",
+ optional: true,
+ nullable: true
+ },
+ quoteAuthorization: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ canBite: {
+ type: "boolean",
+ optional: true,
+ nullable: true
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/notification.js b/packages/backend/built/models/schema/notification.js
new file mode 100644
index 0000000..072cbf6
--- /dev/null
+++ b/packages/backend/built/models/schema/notification.js
@@ -0,0 +1,86 @@
+import { notificationTypes } from "../../types.js";
+export const packedNotificationSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ isRead: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ type: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ enum: [
+ ...notificationTypes
+ ]
+ },
+ user: {
+ type: "object",
+ ref: "UserLite",
+ optional: true,
+ nullable: true
+ },
+ userId: {
+ type: "string",
+ optional: true,
+ nullable: true,
+ format: "id"
+ },
+ note: {
+ type: "object",
+ ref: "Note",
+ optional: true,
+ nullable: true
+ },
+ reaction: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ choice: {
+ type: "number",
+ optional: true,
+ nullable: true
+ },
+ invitation: {
+ type: "object",
+ optional: true,
+ nullable: true
+ },
+ body: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ header: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ icon: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ bite: {
+ type: "object",
+ ref: "Bite",
+ optional: true,
+ nullable: true
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/page.js b/packages/backend/built/models/schema/page.js
new file mode 100644
index 0000000..8f8a0f0
--- /dev/null
+++ b/packages/backend/built/models/schema/page.js
@@ -0,0 +1,66 @@
+export const packedPageSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ updatedAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ title: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ name: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ summary: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ content: {
+ type: "array",
+ optional: false,
+ nullable: false
+ },
+ variables: {
+ type: "array",
+ optional: false,
+ nullable: false
+ },
+ userId: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ user: {
+ type: "object",
+ ref: "UserLite",
+ optional: false,
+ nullable: false
+ },
+ isPublic: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/queue.js b/packages/backend/built/models/schema/queue.js
new file mode 100644
index 0000000..856b0d7
--- /dev/null
+++ b/packages/backend/built/models/schema/queue.js
@@ -0,0 +1,30 @@
+export const packedQueueCountSchema = {
+ type: "object",
+ properties: {
+ waiting: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ active: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ completed: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ failed: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ delayed: {
+ type: "number",
+ optional: false,
+ nullable: false
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/renote-muting.js b/packages/backend/built/models/schema/renote-muting.js
new file mode 100644
index 0000000..c027999
--- /dev/null
+++ b/packages/backend/built/models/schema/renote-muting.js
@@ -0,0 +1,30 @@
+export const packedRenoteMutingSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ muteeId: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ mutee: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserDetailed"
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/user-group.js b/packages/backend/built/models/schema/user-group.js
new file mode 100644
index 0000000..25a0ab9
--- /dev/null
+++ b/packages/backend/built/models/schema/user-group.js
@@ -0,0 +1,40 @@
+export const packedUserGroupSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ name: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ ownerId: {
+ type: "string",
+ nullable: false,
+ optional: false,
+ format: "id"
+ },
+ userIds: {
+ type: "array",
+ nullable: false,
+ optional: true,
+ items: {
+ type: "string",
+ nullable: false,
+ optional: false,
+ format: "id"
+ }
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/user-list.js b/packages/backend/built/models/schema/user-list.js
new file mode 100644
index 0000000..ff16827
--- /dev/null
+++ b/packages/backend/built/models/schema/user-list.js
@@ -0,0 +1,39 @@
+export const packedUserListSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ name: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ hideFromHomeTl: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ userIds: {
+ type: "array",
+ nullable: false,
+ optional: true,
+ items: {
+ type: "string",
+ nullable: false,
+ optional: false,
+ format: "id"
+ }
+ }
+ }
+};
diff --git a/packages/backend/built/models/schema/user.js b/packages/backend/built/models/schema/user.js
new file mode 100644
index 0000000..9451ad4
--- /dev/null
+++ b/packages/backend/built/models/schema/user.js
@@ -0,0 +1,657 @@
+export const packedUserLiteSchema = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ nullable: false,
+ optional: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ name: {
+ type: "string",
+ nullable: true,
+ optional: false,
+ example: "藍"
+ },
+ username: {
+ type: "string",
+ nullable: false,
+ optional: false,
+ example: "calc"
+ },
+ host: {
+ type: "string",
+ nullable: true,
+ optional: false,
+ example: "misskey.example.com",
+ description: "The local host is represented with `null`."
+ },
+ avatarUrl: {
+ type: "string",
+ format: "url",
+ nullable: true,
+ optional: false
+ },
+ avatarBlurhash: {
+ type: "any",
+ nullable: true,
+ optional: false
+ },
+ avatarColor: {
+ type: "any",
+ nullable: true,
+ optional: false,
+ default: null
+ },
+ isAdmin: {
+ type: "boolean",
+ nullable: false,
+ optional: true,
+ default: false
+ },
+ isModerator: {
+ type: "boolean",
+ nullable: false,
+ optional: true,
+ default: false
+ },
+ isVerified: {
+ type: "boolean",
+ nullable: false,
+ optional: true,
+ default: false
+ },
+ minorBadges: {
+ type: "array",
+ nullable: false,
+ optional: true,
+ maxItems: 1,
+ items: {
+ type: "string",
+ nullable: false,
+ optional: false,
+ enum: [
+ "K",
+ "T",
+ "E"
+ ]
+ }
+ },
+ plans: {
+ type: "array",
+ nullable: false,
+ optional: true,
+ items: {
+ type: "object",
+ nullable: false,
+ optional: false,
+ properties: {
+ id: {
+ type: "string",
+ nullable: false,
+ optional: false,
+ format: "id"
+ },
+ name: {
+ type: "string",
+ nullable: false,
+ optional: false
+ },
+ icon: {
+ type: "string",
+ nullable: false,
+ optional: false
+ },
+ description: {
+ type: "string",
+ nullable: false,
+ optional: false
+ }
+ }
+ }
+ },
+ isBot: {
+ type: "boolean",
+ nullable: false,
+ optional: true
+ },
+ isCat: {
+ type: "boolean",
+ nullable: false,
+ optional: true
+ },
+ speakAsCat: {
+ type: "boolean",
+ nullable: false,
+ optional: true
+ },
+ emojis: {
+ type: "array",
+ nullable: false,
+ optional: false,
+ items: {
+ type: "object",
+ nullable: false,
+ optional: false,
+ properties: {
+ name: {
+ type: "string",
+ nullable: false,
+ optional: false
+ },
+ url: {
+ type: "string",
+ nullable: false,
+ optional: false,
+ format: "url"
+ }
+ }
+ }
+ },
+ onlineStatus: {
+ type: "string",
+ format: "url",
+ nullable: true,
+ optional: false,
+ enum: [
+ "unknown",
+ "online",
+ "active",
+ "offline"
+ ]
+ },
+ canBite: {
+ type: "string",
+ enum: [
+ "anyone",
+ "followers",
+ "nobody"
+ ]
+ }
+ }
+};
+export const packedUserDetailedNotMeOnlySchema = {
+ type: "object",
+ properties: {
+ url: {
+ type: "string",
+ format: "url",
+ nullable: true,
+ optional: false
+ },
+ uri: {
+ type: "string",
+ format: "uri",
+ nullable: true,
+ optional: false
+ },
+ movedToUri: {
+ type: "string",
+ format: "uri",
+ nullable: true,
+ optional: false
+ },
+ alsoKnownAs: {
+ type: "array",
+ format: "uri",
+ nullable: true,
+ optional: false
+ },
+ createdAt: {
+ type: "string",
+ nullable: false,
+ optional: false,
+ format: "date-time"
+ },
+ updatedAt: {
+ type: "string",
+ nullable: true,
+ optional: false,
+ format: "date-time"
+ },
+ lastFetchedAt: {
+ type: "string",
+ nullable: true,
+ optional: false,
+ format: "date-time"
+ },
+ bannerUrl: {
+ type: "string",
+ format: "url",
+ nullable: true,
+ optional: false
+ },
+ bannerBlurhash: {
+ type: "any",
+ nullable: true,
+ optional: false
+ },
+ bannerColor: {
+ type: "any",
+ nullable: true,
+ optional: false,
+ default: null
+ },
+ isLocked: {
+ type: "boolean",
+ nullable: false,
+ optional: false
+ },
+ isSilenced: {
+ type: "boolean",
+ nullable: false,
+ optional: false
+ },
+ isSuspended: {
+ type: "boolean",
+ nullable: false,
+ optional: false,
+ example: false
+ },
+ description: {
+ type: "string",
+ nullable: true,
+ optional: false,
+ example: "Hi masters, I am Ai!"
+ },
+ location: {
+ type: "string",
+ nullable: true,
+ optional: false
+ },
+ birthday: {
+ type: "string",
+ nullable: true,
+ optional: false,
+ example: "2018-03-12"
+ },
+ lang: {
+ type: "string",
+ nullable: true,
+ optional: false,
+ example: "ja-JP"
+ },
+ fields: {
+ type: "array",
+ nullable: false,
+ optional: false,
+ items: {
+ type: "object",
+ nullable: false,
+ optional: false,
+ properties: {
+ name: {
+ type: "string",
+ nullable: false,
+ optional: false
+ },
+ value: {
+ type: "string",
+ nullable: false,
+ optional: false
+ }
+ },
+ maxLength: 4
+ }
+ },
+ followersCount: {
+ type: "number",
+ nullable: false,
+ optional: false
+ },
+ followingCount: {
+ type: "number",
+ nullable: false,
+ optional: false
+ },
+ notesCount: {
+ type: "number",
+ nullable: false,
+ optional: false
+ },
+ pinnedNoteIds: {
+ type: "array",
+ nullable: false,
+ optional: false,
+ items: {
+ type: "string",
+ nullable: false,
+ optional: false,
+ format: "id"
+ }
+ },
+ pinnedNotes: {
+ type: "array",
+ nullable: false,
+ optional: false,
+ items: {
+ type: "object",
+ nullable: false,
+ optional: false,
+ ref: "Note"
+ }
+ },
+ pinnedPageId: {
+ type: "string",
+ nullable: true,
+ optional: false
+ },
+ pinnedPage: {
+ type: "object",
+ nullable: true,
+ optional: false,
+ ref: "Page"
+ },
+ publicReactions: {
+ type: "boolean",
+ nullable: false,
+ optional: false
+ },
+ twoFactorEnabled: {
+ type: "boolean",
+ nullable: false,
+ optional: false,
+ default: false
+ },
+ usePasswordLessLogin: {
+ type: "boolean",
+ nullable: false,
+ optional: false,
+ default: false
+ },
+ securityKeys: {
+ type: "boolean",
+ nullable: false,
+ optional: false,
+ default: false
+ },
+ pronouns: {
+ type: "object"
+ },
+ //#region relations
+ isFollowing: {
+ type: "boolean",
+ nullable: false,
+ optional: true
+ },
+ isFollowed: {
+ type: "boolean",
+ nullable: false,
+ optional: true
+ },
+ hasPendingFollowRequestFromYou: {
+ type: "boolean",
+ nullable: false,
+ optional: true
+ },
+ hasPendingFollowRequestToYou: {
+ type: "boolean",
+ nullable: false,
+ optional: true
+ },
+ isBlocking: {
+ type: "boolean",
+ nullable: false,
+ optional: true
+ },
+ isBlocked: {
+ type: "boolean",
+ nullable: false,
+ optional: true
+ },
+ isMuted: {
+ type: "boolean",
+ nullable: false,
+ optional: true
+ },
+ isRenoteMuted: {
+ type: "boolean",
+ nullable: false,
+ optional: true
+ }
+ }
+};
+export const packedMeDetailedOnlySchema = {
+ type: "object",
+ properties: {
+ avatarId: {
+ type: "string",
+ nullable: true,
+ optional: false,
+ format: "id"
+ },
+ bannerId: {
+ type: "string",
+ nullable: true,
+ optional: false,
+ format: "id"
+ },
+ injectFeaturedNote: {
+ type: "boolean",
+ nullable: true,
+ optional: false
+ },
+ receiveAnnouncementEmail: {
+ type: "boolean",
+ nullable: true,
+ optional: false
+ },
+ alwaysMarkNsfw: {
+ type: "boolean",
+ nullable: true,
+ optional: false
+ },
+ carefulBot: {
+ type: "boolean",
+ nullable: true,
+ optional: false
+ },
+ autoAcceptFollowed: {
+ type: "boolean",
+ nullable: true,
+ optional: false
+ },
+ noCrawle: {
+ type: "boolean",
+ nullable: true,
+ optional: false
+ },
+ preventAiLearning: {
+ type: "boolean",
+ nullable: true,
+ optional: false
+ },
+ isExplorable: {
+ type: "boolean",
+ nullable: false,
+ optional: false
+ },
+ isDeleted: {
+ type: "boolean",
+ nullable: false,
+ optional: false
+ },
+ hideOnlineStatus: {
+ type: "boolean",
+ nullable: false,
+ optional: false
+ },
+ allowCalls: {
+ type: "boolean",
+ nullable: false,
+ optional: true
+ },
+ symbolFileId: {
+ type: "string",
+ nullable: true,
+ optional: true,
+ format: "id"
+ },
+ hasUnreadSpecifiedNotes: {
+ type: "boolean",
+ nullable: false,
+ optional: false
+ },
+ hasUnreadMentions: {
+ type: "boolean",
+ nullable: false,
+ optional: false
+ },
+ hasUnreadAnnouncement: {
+ type: "boolean",
+ nullable: false,
+ optional: false
+ },
+ hasUnreadAntenna: {
+ type: "boolean",
+ nullable: false,
+ optional: false
+ },
+ hasUnreadChannel: {
+ type: "boolean",
+ nullable: false,
+ optional: false
+ },
+ hasUnreadMessagingMessage: {
+ type: "boolean",
+ nullable: false,
+ optional: false
+ },
+ hasUnreadNotification: {
+ type: "boolean",
+ nullable: false,
+ optional: false
+ },
+ hasPendingReceivedFollowRequest: {
+ type: "boolean",
+ nullable: false,
+ optional: false
+ },
+ integrations: {
+ type: "object",
+ nullable: true,
+ optional: false
+ },
+ mutedWords: {
+ type: "array",
+ nullable: false,
+ optional: false,
+ items: {
+ type: "array",
+ nullable: false,
+ optional: false,
+ items: {
+ type: "string",
+ nullable: false,
+ optional: false
+ }
+ }
+ },
+ mutedInstances: {
+ type: "array",
+ nullable: true,
+ optional: false,
+ items: {
+ type: "string",
+ nullable: false,
+ optional: false
+ }
+ },
+ mutingNotificationTypes: {
+ type: "array",
+ nullable: true,
+ optional: false,
+ items: {
+ type: "string",
+ nullable: false,
+ optional: false
+ }
+ },
+ emailNotificationTypes: {
+ type: "array",
+ nullable: true,
+ optional: false,
+ items: {
+ type: "string",
+ nullable: false,
+ optional: false
+ }
+ },
+ //#region secrets
+ email: {
+ type: "string",
+ nullable: true,
+ optional: true
+ },
+ emailVerified: {
+ type: "boolean",
+ nullable: true,
+ optional: true
+ },
+ securityKeysList: {
+ type: "array",
+ nullable: false,
+ optional: true,
+ items: {
+ type: "object",
+ nullable: false,
+ optional: false
+ }
+ }
+ }
+};
+export const packedUserDetailedNotMeSchema = {
+ type: "object",
+ allOf: [
+ {
+ type: "object",
+ ref: "UserLite"
+ },
+ {
+ type: "object",
+ ref: "UserDetailedNotMeOnly"
+ }
+ ]
+};
+export const packedMeDetailedSchema = {
+ type: "object",
+ allOf: [
+ {
+ type: "object",
+ ref: "UserLite"
+ },
+ {
+ type: "object",
+ ref: "UserDetailedNotMeOnly"
+ },
+ {
+ type: "object",
+ ref: "MeDetailedOnly"
+ }
+ ]
+};
+export const packedUserDetailedSchema = {
+ oneOf: [
+ {
+ type: "object",
+ ref: "UserDetailedNotMe"
+ },
+ {
+ type: "object",
+ ref: "MeDetailed"
+ }
+ ]
+};
+export const packedUserSchema = {
+ oneOf: [
+ {
+ type: "object",
+ ref: "UserLite"
+ },
+ {
+ type: "object",
+ ref: "UserDetailed"
+ }
+ ]
+};
diff --git a/packages/backend/built/ormconfig.js b/packages/backend/built/ormconfig.js
new file mode 100644
index 0000000..2b1fdcf
--- /dev/null
+++ b/packages/backend/built/ormconfig.js
@@ -0,0 +1,16 @@
+import { DataSource } from "typeorm";
+import config from "./config/index.js";
+import { entities } from "./db/postgre.js";
+export default new DataSource({
+ type: "postgres",
+ host: config.db.host,
+ port: config.db.port,
+ username: config.db.user,
+ password: config.db.pass,
+ database: config.db.db,
+ extra: config.db.extra,
+ entities: entities,
+ migrations: [
+ "built/migration/*.js"
+ ]
+});
diff --git a/packages/backend/built/prelude/array.js b/packages/backend/built/prelude/array.js
new file mode 100644
index 0000000..f782f23
--- /dev/null
+++ b/packages/backend/built/prelude/array.js
@@ -0,0 +1,125 @@
+/**
+ * Count the number of elements that satisfy the predicate
+ */ export function countIf(f, xs) {
+ return xs.filter(f).length;
+}
+/**
+ * Count the number of elements that is equal to the element
+ */ export function count(a, xs) {
+ return countIf((x)=>x === a, xs);
+}
+/**
+ * Concatenate an array of arrays
+ */ export function concat(xss) {
+ return [].concat(...xss);
+}
+/**
+ * Intersperse the element between the elements of the array
+ * @param sep The element to be interspersed
+ */ export function intersperse(sep, xs) {
+ return concat(xs.map((x)=>[
+ sep,
+ x
+ ])).slice(1);
+}
+/**
+ * Returns the array of elements that is not equal to the element
+ */ export function erase(a, xs) {
+ return xs.filter((x)=>x !== a);
+}
+/**
+ * Finds the array of all elements in the first array not contained in the second array.
+ * The order of result values are determined by the first array.
+ */ export function difference(xs, ys) {
+ return xs.filter((x)=>!ys.includes(x));
+}
+/**
+ * Remove all but the first element from every group of equivalent elements
+ */ export function unique(xs) {
+ return [
+ ...new Set(xs)
+ ];
+}
+export function uniqBy(a, key) {
+ const seen = new Set();
+ return a.filter(function(item) {
+ const k = key(item);
+ return seen.has(k) ? false : seen.add(k);
+ });
+}
+export function sum(xs) {
+ return xs.reduce((a, b)=>a + b, 0);
+}
+export function maximum(xs) {
+ return Math.max(...xs);
+}
+/**
+ * Splits an array based on the equivalence relation.
+ * The concatenation of the result is equal to the argument.
+ */ export function groupBy(f, xs) {
+ const groups = [];
+ for (const x of xs){
+ if (groups.length !== 0 && f(groups[groups.length - 1][0], x)) {
+ groups[groups.length - 1].push(x);
+ } else {
+ groups.push([
+ x
+ ]);
+ }
+ }
+ return groups;
+}
+/**
+ * Splits an array based on the equivalence relation induced by the function.
+ * The concatenation of the result is equal to the argument.
+ */ export function groupOn(f, xs) {
+ return groupBy((a, b)=>f(a) === f(b), xs);
+}
+export function groupByX(collections, keySelector) {
+ return collections.reduce((obj, item)=>{
+ const key = keySelector(item);
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) {
+ obj[key] = [];
+ }
+ obj[key].push(item);
+ return obj;
+ }, {});
+}
+/**
+ * Compare two arrays by lexicographical order
+ */ export function lessThan(xs, ys) {
+ for(let i = 0; i < Math.min(xs.length, ys.length); i++){
+ if (xs[i] < ys[i]) return true;
+ if (xs[i] > ys[i]) return false;
+ }
+ return xs.length < ys.length;
+}
+/**
+ * Returns the longest prefix of elements that satisfy the predicate
+ */ export function takeWhile(f, xs) {
+ const ys = [];
+ for (const x of xs){
+ if (f(x)) {
+ ys.push(x);
+ } else {
+ break;
+ }
+ }
+ return ys;
+}
+export function cumulativeSum(xs) {
+ const ys = Array.from(xs); // deep copy
+ for(let i = 1; i < ys.length; i++)ys[i] += ys[i - 1];
+ return ys;
+}
+export function toArray(x) {
+ return Array.isArray(x) ? x : x != null ? [
+ x
+ ] : [];
+}
+export function toSingle(x) {
+ return Array.isArray(x) ? x[0] : x;
+}
+export function toSingleLast(x) {
+ return Array.isArray(x) ? x.at(-1) : x;
+}
diff --git a/packages/backend/built/prelude/await-all.js b/packages/backend/built/prelude/await-all.js
new file mode 100644
index 0000000..d7963ac
--- /dev/null
+++ b/packages/backend/built/prelude/await-all.js
@@ -0,0 +1,10 @@
+export async function awaitAll(obj) {
+ const target = {};
+ const keys = Object.keys(obj);
+ const values = Object.values(obj);
+ const resolvedValues = await Promise.all(values.map((value)=>!value?.constructor || value.constructor.name !== "Object" ? value : awaitAll(value)));
+ for(let i = 0; i < keys.length; i++){
+ target[keys[i]] = resolvedValues[i];
+ }
+ return target;
+}
diff --git a/packages/backend/built/prelude/math.js b/packages/backend/built/prelude/math.js
new file mode 100644
index 0000000..d98b38b
--- /dev/null
+++ b/packages/backend/built/prelude/math.js
@@ -0,0 +1,3 @@
+export function gcd(a, b) {
+ return b === 0 ? a : gcd(b, a % b);
+}
diff --git a/packages/backend/built/prelude/maybe.js b/packages/backend/built/prelude/maybe.js
new file mode 100644
index 0000000..7f9a52a
--- /dev/null
+++ b/packages/backend/built/prelude/maybe.js
@@ -0,0 +1,11 @@
+export function just(value) {
+ return {
+ isJust: ()=>true,
+ get: ()=>value
+ };
+}
+export function nothing() {
+ return {
+ isJust: ()=>false
+ };
+}
diff --git a/packages/backend/built/prelude/promise.js b/packages/backend/built/prelude/promise.js
new file mode 100644
index 0000000..89f83c8
--- /dev/null
+++ b/packages/backend/built/prelude/promise.js
@@ -0,0 +1,8 @@
+// Returns T if promise settles before timeout, otherwise returns void, finishing execution in the background.
+export async function promiseEarlyReturn(promise, after) {
+ const timer = new Promise((res)=>setTimeout(()=>res(undefined), after));
+ return Promise.race([
+ promise,
+ timer
+ ]);
+}
diff --git a/packages/backend/built/prelude/relation.js b/packages/backend/built/prelude/relation.js
new file mode 100644
index 0000000..2234b9c
--- /dev/null
+++ b/packages/backend/built/prelude/relation.js
@@ -0,0 +1 @@
+export { };
diff --git a/packages/backend/built/prelude/string.js b/packages/backend/built/prelude/string.js
new file mode 100644
index 0000000..c4abfc9
--- /dev/null
+++ b/packages/backend/built/prelude/string.js
@@ -0,0 +1,12 @@
+export function concat(xs) {
+ return xs.join("");
+}
+export function capitalize(s) {
+ return toUpperCase(s.charAt(0)) + toLowerCase(s.slice(1));
+}
+export function toUpperCase(s) {
+ return s.toUpperCase();
+}
+export function toLowerCase(s) {
+ return s.toLowerCase();
+}
diff --git a/packages/backend/built/prelude/symbol.js b/packages/backend/built/prelude/symbol.js
new file mode 100644
index 0000000..5b88467
--- /dev/null
+++ b/packages/backend/built/prelude/symbol.js
@@ -0,0 +1 @@
+export const fallback = Symbol("fallback");
diff --git a/packages/backend/built/prelude/time.js b/packages/backend/built/prelude/time.js
new file mode 100644
index 0000000..f5a0cf8
--- /dev/null
+++ b/packages/backend/built/prelude/time.js
@@ -0,0 +1,25 @@
+const dateTimeIntervals = {
+ day: 86400000,
+ hour: 3600000,
+ ms: 1
+};
+export function dateUTC(time) {
+ const d = time.length === 2 ? Date.UTC(time[0], time[1]) : time.length === 3 ? Date.UTC(time[0], time[1], time[2]) : time.length === 4 ? Date.UTC(time[0], time[1], time[2], time[3]) : time.length === 5 ? Date.UTC(time[0], time[1], time[2], time[3], time[4]) : time.length === 6 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5]) : time.length === 7 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6]) : null;
+ if (!d) throw new Error("wrong number of arguments");
+ return new Date(d);
+}
+export function isTimeSame(a, b) {
+ return a.getTime() === b.getTime();
+}
+export function isTimeBefore(a, b) {
+ return a.getTime() - b.getTime() < 0;
+}
+export function isTimeAfter(a, b) {
+ return a.getTime() - b.getTime() > 0;
+}
+export function addTime(x, value, span = "ms") {
+ return new Date(x.getTime() + value * dateTimeIntervals[span]);
+}
+export function subtractTime(x, value, span = "ms") {
+ return new Date(x.getTime() - value * dateTimeIntervals[span]);
+}
diff --git a/packages/backend/built/prelude/url.js b/packages/backend/built/prelude/url.js
new file mode 100644
index 0000000..c788fe3
--- /dev/null
+++ b/packages/backend/built/prelude/url.js
@@ -0,0 +1,7 @@
+export function query(obj) {
+ const params = Object.entries(obj).filter(([, v])=>Array.isArray(v) ? v.length : v !== undefined).reduce((a, [k, v])=>(a[k] = v, a), {});
+ return Object.entries(params).map((e)=>`${e[0]}=${encodeURIComponent(e[1])}`).join("&");
+}
+export function appendQuery(url, query) {
+ return `${url}${/\?/.test(url) ? url.endsWith("?") ? "" : "&" : "?"}${query}`;
+}
diff --git a/packages/backend/built/prelude/xml.js b/packages/backend/built/prelude/xml.js
new file mode 100644
index 0000000..9e1e6af
--- /dev/null
+++ b/packages/backend/built/prelude/xml.js
@@ -0,0 +1,35 @@
+const map = {
+ "&": "&",
+ "<": "<",
+ ">": ">",
+ '"': """,
+ "'": "'"
+};
+const beginingOfCDATA = "";
+export function escapeValue(x) {
+ let insideOfCDATA = false;
+ let builder = "";
+ for(let i = 0; i < x.length;){
+ if (insideOfCDATA) {
+ if (x.slice(i, i + beginingOfCDATA.length) === beginingOfCDATA) {
+ insideOfCDATA = true;
+ i += beginingOfCDATA.length;
+ } else {
+ builder += x[i++];
+ }
+ } else {
+ if (x.slice(i, i + endOfCDATA.length) === endOfCDATA) {
+ insideOfCDATA = false;
+ i += endOfCDATA.length;
+ } else {
+ const b = x[i++];
+ builder += map[b] || b;
+ }
+ }
+ }
+ return builder;
+}
+export function escapeAttribute(x) {
+ return Object.entries(map).reduce((a, [k, v])=>a.replace(k, v), x);
+}
diff --git a/packages/backend/built/queue/index.js b/packages/backend/built/queue/index.js
new file mode 100644
index 0000000..c90402e
--- /dev/null
+++ b/packages/backend/built/queue/index.js
@@ -0,0 +1,35 @@
+import { deliverQueue, inboxQueue } from "./queues.js";
+import { dbInit } from "./queues/db/index.js";
+import { deliverInit, deliverLogger } from "./queues/deliver.js";
+import { endedPollNotificationInit } from "./queues/ended-poll-notification.js";
+import { inboxInit, inboxLogger } from "./queues/inbox.js";
+import { objectStorageInit } from "./queues/object-storage/index.js";
+import { systemInit } from "./queues/system/index.js";
+import { webhookDeliverInit } from "./queues/webhook-deliver.js";
+export { createDeleteDriveFilesJob, createExportCustomEmojisJob, createExportNotesJob, createExportFollowingJob, createExportMuteJob, createExportBlockingJob, createExportUserListsJob, createImportFollowingJob, createImportMutingJob, createImportBlockingJob, createImportUserListsJob, createImportCustomEmojisJob, createDeleteAccountJob } from "./queues/db/index.js";
+export { createDeleteObjectStorageFileJob, createCleanRemoteFilesJob } from "./queues/object-storage/index.js";
+export { systemQueue } from "./queues/system/index.js";
+export { deliverJob as deliver, deliverQueue } from "./queues/deliver.js";
+export { endedPollNotificationQueue } from "./queues/ended-poll-notification.js";
+export { inboxJob as inbox, inboxQueue } from "./queues/inbox.js";
+export { webhookDeliverJob as webhookDeliver, webhookDeliverQueue } from "./queues/webhook-deliver.js";
+export default async function() {
+ // initialize queue workers
+ await dbInit();
+ await objectStorageInit();
+ await systemInit();
+ await deliverInit();
+ await endedPollNotificationInit();
+ await inboxInit();
+ await webhookDeliverInit();
+}
+export function destroy() {
+ deliverQueue.once("cleaned", (jobs, status)=>{
+ deliverLogger.succ(`Cleaned ${jobs.length} ${status} jobs`);
+ });
+ deliverQueue.clean(0, Infinity, "delayed");
+ inboxQueue.once("cleaned", (jobs, status)=>{
+ inboxLogger.succ(`Cleaned ${jobs.length} ${status} jobs`);
+ });
+ inboxQueue.clean(0, Infinity, "delayed");
+}
diff --git a/packages/backend/built/queue/logger.js b/packages/backend/built/queue/logger.js
new file mode 100644
index 0000000..de2ad3d
--- /dev/null
+++ b/packages/backend/built/queue/logger.js
@@ -0,0 +1,9 @@
+import Logger from "../services/logger.js";
+export const queueLogger = new Logger("queue", "orange");
+export function renderError(e) {
+ return {
+ stack: e.stack,
+ message: e.message,
+ name: e.name
+ };
+}
diff --git a/packages/backend/built/queue/queues.js b/packages/backend/built/queue/queues.js
new file mode 100644
index 0000000..9464144
--- /dev/null
+++ b/packages/backend/built/queue/queues.js
@@ -0,0 +1,23 @@
+import { dbQueue } from "./queues/db/index.js";
+import { deliverQueue } from "./queues/deliver.js";
+import { endedPollNotificationQueue } from "./queues/ended-poll-notification.js";
+import { inboxQueue } from "./queues/inbox.js";
+import { objectStorageQueue } from "./queues/object-storage/index.js";
+import { systemQueue } from "./queues/system/index.js";
+import { webhookDeliverQueue } from "./queues/webhook-deliver.js";
+export { dbQueue } from "./queues/db/index.js";
+export { deliverQueue } from "./queues/deliver.js";
+export { endedPollNotificationQueue } from "./queues/ended-poll-notification.js";
+export { inboxQueue } from "./queues/inbox.js";
+export { objectStorageQueue } from "./queues/object-storage/index.js";
+export { systemQueue } from "./queues/system/index.js";
+export { webhookDeliverQueue } from "./queues/webhook-deliver.js";
+export const queues = [
+ dbQueue,
+ deliverQueue,
+ endedPollNotificationQueue,
+ inboxQueue,
+ objectStorageQueue,
+ systemQueue,
+ webhookDeliverQueue
+];
diff --git a/packages/backend/built/queue/queues/db/delete-account.js b/packages/backend/built/queue/queues/db/delete-account.js
new file mode 100644
index 0000000..1fb5623
--- /dev/null
+++ b/packages/backend/built/queue/queues/db/delete-account.js
@@ -0,0 +1,84 @@
+import { queueLogger } from "../../logger.js";
+import { DriveFiles, Notes, UserProfiles, Users } from "../../../models/index.js";
+import { MoreThan } from "typeorm";
+import { deleteFileSync } from "../../../services/drive/delete-file.js";
+import { sendEmail } from "../../../services/send-email.js";
+import { publishInternalEvent } from "../../../services/stream.js";
+const logger = queueLogger.createSubLogger("delete-account");
+export async function deleteAccount(job) {
+ logger.info(`Deleting account of ${job.data.user.id} ...`);
+ const user = await Users.findOneBy({
+ id: job.data.user.id
+ });
+ if (!user) return "skip: User not found";
+ const isLocal = Users.isLocalUser(user);
+ {
+ // Delete notes
+ let cursor = null;
+ while(true){
+ const notes = await Notes.find({
+ where: {
+ userId: user.id,
+ ...cursor ? {
+ id: MoreThan(cursor)
+ } : {}
+ },
+ take: 10,
+ order: {
+ id: 1
+ }
+ });
+ if (notes.length === 0) {
+ break;
+ }
+ cursor = notes[notes.length - 1].id;
+ await Notes.delete(notes.map((note)=>note.id));
+ }
+ logger.succ("All of notes deleted");
+ }
+ {
+ // Delete files
+ let cursor = null;
+ while(true){
+ const files = await DriveFiles.find({
+ where: {
+ userId: user.id,
+ ...cursor ? {
+ id: MoreThan(cursor)
+ } : {}
+ },
+ take: 10,
+ order: {
+ id: 1
+ }
+ });
+ if (files.length === 0) {
+ break;
+ }
+ cursor = files[files.length - 1].id;
+ for (const file of files){
+ await deleteFileSync(file);
+ }
+ }
+ logger.succ("All of files deleted");
+ }
+ {
+ // Send email notification
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ if (profile.email && profile.emailVerified) {
+ sendEmail(profile.email, "Account deleted", "Your account has been deleted.", "Your account has been deleted.");
+ }
+ }
+ // soft指定されている場合は物理削除しない
+ if (job.data.soft) {
+ // nop
+ } else {
+ await Users.delete(job.data.user.id);
+ publishInternalEvent(isLocal ? "localUserDeleted" : "remoteUserDeleted", {
+ id: user.id
+ });
+ }
+ return "Account deleted";
+}
diff --git a/packages/backend/built/queue/queues/db/delete-drive-files.js b/packages/backend/built/queue/queues/db/delete-drive-files.js
new file mode 100644
index 0000000..493292e
--- /dev/null
+++ b/packages/backend/built/queue/queues/db/delete-drive-files.js
@@ -0,0 +1,45 @@
+import { queueLogger } from "../../logger.js";
+import { deleteFileSync } from "../../../services/drive/delete-file.js";
+import { Users, DriveFiles } from "../../../models/index.js";
+import { MoreThan } from "typeorm";
+const logger = queueLogger.createSubLogger("delete-drive-files");
+export async function deleteDriveFiles(job) {
+ logger.info(`Deleting drive files of ${job.data.user.id} ...`);
+ const user = await Users.findOneBy({
+ id: job.data.user.id
+ });
+ if (user == null) {
+ return "skip: User not found";
+ }
+ let deletedCount = 0;
+ let cursor = null;
+ while(true){
+ const files = await DriveFiles.find({
+ where: {
+ userId: user.id,
+ ...cursor ? {
+ id: MoreThan(cursor)
+ } : {}
+ },
+ take: 100,
+ order: {
+ id: 1
+ }
+ });
+ if (files.length === 0) {
+ job.updateProgress(100);
+ break;
+ }
+ cursor = files[files.length - 1].id;
+ for (const file of files){
+ await deleteFileSync(file);
+ deletedCount++;
+ }
+ const total = await DriveFiles.countBy({
+ userId: user.id
+ });
+ job.updateProgress(deletedCount / total);
+ }
+ logger.succ(`All drive files (${deletedCount}) of ${user.id} has been deleted.`);
+ return "Success";
+}
diff --git a/packages/backend/built/queue/queues/db/export-blocking.js b/packages/backend/built/queue/queues/db/export-blocking.js
new file mode 100644
index 0000000..5e2624a
--- /dev/null
+++ b/packages/backend/built/queue/queues/db/export-blocking.js
@@ -0,0 +1,85 @@
+import * as fs from "node:fs";
+import { queueLogger } from "../../logger.js";
+import { addFile } from "../../../services/drive/add-file.js";
+import { format as dateFormat } from "date-fns";
+import { getFullApAccount } from "../../../misc/convert-host.js";
+import { createTemp } from "../../../misc/create-temp.js";
+import { Users, Blockings } from "../../../models/index.js";
+import { MoreThan } from "typeorm";
+const logger = queueLogger.createSubLogger("export-blocking");
+export async function exportBlocking(job) {
+ logger.info(`Exporting blocking of ${job.data.user.id} ...`);
+ const user = await Users.findOneBy({
+ id: job.data.user.id
+ });
+ if (user == null) {
+ return "skip: User not found";
+ }
+ // Create temp file
+ const [path, cleanup] = await createTemp();
+ logger.info(`Temp file is ${path}`);
+ try {
+ const stream = fs.createWriteStream(path, {
+ flags: "a"
+ });
+ let exportedCount = 0;
+ let cursor = null;
+ while(true){
+ const blockings = await Blockings.find({
+ where: {
+ blockerId: user.id,
+ ...cursor ? {
+ id: MoreThan(cursor)
+ } : {}
+ },
+ take: 100,
+ order: {
+ id: 1
+ }
+ });
+ if (blockings.length === 0) {
+ job.updateProgress(100);
+ break;
+ }
+ cursor = blockings[blockings.length - 1].id;
+ for (const block of blockings){
+ const u = await Users.findOneBy({
+ id: block.blockeeId
+ });
+ if (u == null) {
+ exportedCount++;
+ continue;
+ }
+ const content = getFullApAccount(u.username, u.host);
+ await new Promise((res, rej)=>{
+ stream.write(content + "\n", (err)=>{
+ if (err) {
+ logger.error(err);
+ rej(err);
+ } else {
+ res();
+ }
+ });
+ });
+ exportedCount++;
+ }
+ const total = await Blockings.countBy({
+ blockerId: user.id
+ });
+ job.updateProgress(exportedCount / total);
+ }
+ stream.end();
+ logger.succ(`Exported to: ${path}`);
+ const fileName = `blocking-${dateFormat(new Date(), "yyyy-MM-dd-HH-mm-ss")}.csv`;
+ const driveFile = await addFile({
+ user,
+ path,
+ name: fileName,
+ force: true
+ });
+ logger.succ(`Exported to: ${driveFile.id}`);
+ } finally{
+ cleanup();
+ }
+ return "Success";
+}
diff --git a/packages/backend/built/queue/queues/db/export-custom-emojis.js b/packages/backend/built/queue/queues/db/export-custom-emojis.js
new file mode 100644
index 0000000..1f07a29
--- /dev/null
+++ b/packages/backend/built/queue/queues/db/export-custom-emojis.js
@@ -0,0 +1,109 @@
+import * as fs from "node:fs";
+import mime from "mime-types";
+import archiver from "archiver";
+import { queueLogger } from "../../logger.js";
+import { addFile } from "../../../services/drive/add-file.js";
+import { format as dateFormat } from "date-fns";
+import { Users, Emojis } from "../../../models/index.js";
+import { createTemp, createTempDir } from "../../../misc/create-temp.js";
+import { downloadUrl } from "../../../misc/download-url.js";
+import config from "../../../config/index.js";
+import { IsNull } from "typeorm";
+const logger = queueLogger.createSubLogger("export-custom-emojis");
+export async function exportCustomEmojis(job) {
+ logger.info("Exporting custom emojis ...");
+ const user = await Users.findOneBy({
+ id: job.data.user.id
+ });
+ if (user == null) {
+ return "skip: User not found";
+ }
+ const [path, cleanup] = await createTempDir();
+ logger.info(`Temp dir is ${path}`);
+ const metaPath = `${path}/meta.json`;
+ fs.writeFileSync(metaPath, "", "utf-8");
+ const metaStream = fs.createWriteStream(metaPath, {
+ flags: "a"
+ });
+ const writeMeta = (text)=>{
+ return new Promise((res, rej)=>{
+ metaStream.write(text, (err)=>{
+ if (err) {
+ logger.error(err);
+ rej(err);
+ } else {
+ res();
+ }
+ });
+ });
+ };
+ await writeMeta(`{"metaVersion":2,"host":"${config.host}","exportedAt":"${new Date().toString()}","emojis":[`);
+ const customEmojis = await Emojis.find({
+ where: {
+ host: IsNull()
+ },
+ order: {
+ id: "ASC"
+ }
+ });
+ for (const emoji of customEmojis){
+ const ext = mime.extension(emoji.type);
+ // there are some restrictions on file names, so to be safe the files are
+ // named after their database id instead of the actual emoji name
+ const fileName = emoji.id + (ext ? '.' + ext : '');
+ const emojiPath = `${path}/${fileName}`;
+ fs.writeFileSync(emojiPath, "", "binary");
+ let downloaded = false;
+ try {
+ await downloadUrl(emoji.originalUrl, emojiPath);
+ downloaded = true;
+ } catch (e) {
+ // TODO: 何度か再試行
+ logger.error(e instanceof Error ? e : new Error(e));
+ }
+ if (!downloaded) {
+ fs.unlinkSync(emojiPath);
+ }
+ const content = JSON.stringify({
+ fileName: fileName,
+ downloaded: downloaded,
+ emoji: emoji
+ });
+ const isFirst = customEmojis.indexOf(emoji) === 0;
+ await writeMeta(isFirst ? content : ",\n" + content);
+ }
+ await writeMeta("]}");
+ metaStream.end();
+ // Create archive
+ await new Promise(async (resolve, reject)=>{
+ try {
+ const [archivePath, archiveCleanup] = await createTemp();
+ const archiveStream = fs.createWriteStream(archivePath);
+ const archive = archiver("zip", {
+ zlib: {
+ level: 0
+ }
+ });
+ archiveStream.on("close", async ()=>{
+ logger.succ(`Exported to: ${archivePath}`);
+ const fileName = `custom-emojis-${dateFormat(new Date(), "yyyy-MM-dd-HH-mm-ss")}.zip`;
+ const driveFile = await addFile({
+ user,
+ path: archivePath,
+ name: fileName,
+ force: true
+ });
+ logger.succ(`Exported to: ${driveFile.id}`);
+ cleanup();
+ archiveCleanup();
+ resolve(undefined);
+ });
+ archive.pipe(archiveStream);
+ archive.directory(path, false);
+ archive.finalize();
+ } catch (e) {
+ reject(e);
+ }
+ });
+ return "Success";
+}
diff --git a/packages/backend/built/queue/queues/db/export-following.js b/packages/backend/built/queue/queues/db/export-following.js
new file mode 100644
index 0000000..175184f
--- /dev/null
+++ b/packages/backend/built/queue/queues/db/export-following.js
@@ -0,0 +1,86 @@
+import * as fs from "node:fs";
+import { queueLogger } from "../../logger.js";
+import { addFile } from "../../../services/drive/add-file.js";
+import { format as dateFormat } from "date-fns";
+import { getFullApAccount } from "../../../misc/convert-host.js";
+import { createTemp } from "../../../misc/create-temp.js";
+import { Users, Followings, Mutings } from "../../../models/index.js";
+import { In, MoreThan, Not } from "typeorm";
+const logger = queueLogger.createSubLogger("export-following");
+export async function exportFollowing(job) {
+ logger.info(`Exporting following of ${job.data.user.id} ...`);
+ const user = await Users.findOneBy({
+ id: job.data.user.id
+ });
+ if (user == null) {
+ return "skip: User not found";
+ }
+ // Create temp file
+ const [path, cleanup] = await createTemp();
+ logger.info(`Temp file is ${path}`);
+ try {
+ const stream = fs.createWriteStream(path, {
+ flags: "a"
+ });
+ let cursor = null;
+ const mutings = job.data.excludeMuting ? await Mutings.findBy({
+ muterId: user.id
+ }) : [];
+ while(true){
+ const followings = await Followings.find({
+ where: {
+ followerId: user.id,
+ ...mutings.length > 0 ? {
+ followeeId: Not(In(mutings.map((x)=>x.muteeId)))
+ } : {},
+ ...cursor ? {
+ id: MoreThan(cursor)
+ } : {}
+ },
+ take: 100,
+ order: {
+ id: 1
+ }
+ });
+ if (followings.length === 0) {
+ break;
+ }
+ cursor = followings[followings.length - 1].id;
+ for (const following of followings){
+ const u = await Users.findOneBy({
+ id: following.followeeId
+ });
+ if (u == null) {
+ continue;
+ }
+ if (job.data.excludeInactive && u.updatedAt && Date.now() - u.updatedAt.getTime() > 1000 * 60 * 60 * 24 * 90) {
+ continue;
+ }
+ const content = getFullApAccount(u.username, u.host);
+ await new Promise((res, rej)=>{
+ stream.write(content + "\n", (err)=>{
+ if (err) {
+ logger.error(err);
+ rej(err);
+ } else {
+ res();
+ }
+ });
+ });
+ }
+ }
+ stream.end();
+ logger.succ(`Exported to: ${path}`);
+ const fileName = `following-${dateFormat(new Date(), "yyyy-MM-dd-HH-mm-ss")}.csv`;
+ const driveFile = await addFile({
+ user,
+ path,
+ name: fileName,
+ force: true
+ });
+ logger.succ(`Exported to: ${driveFile.id}`);
+ } finally{
+ cleanup();
+ }
+ return "Success";
+}
diff --git a/packages/backend/built/queue/queues/db/export-mute.js b/packages/backend/built/queue/queues/db/export-mute.js
new file mode 100644
index 0000000..259df6d
--- /dev/null
+++ b/packages/backend/built/queue/queues/db/export-mute.js
@@ -0,0 +1,86 @@
+import * as fs from "node:fs";
+import { queueLogger } from "../../logger.js";
+import { addFile } from "../../../services/drive/add-file.js";
+import { format as dateFormat } from "date-fns";
+import { getFullApAccount } from "../../../misc/convert-host.js";
+import { createTemp } from "../../../misc/create-temp.js";
+import { Users, Mutings } from "../../../models/index.js";
+import { IsNull, MoreThan } from "typeorm";
+const logger = queueLogger.createSubLogger("export-mute");
+export async function exportMute(job) {
+ logger.info(`Exporting mute of ${job.data.user.id} ...`);
+ const user = await Users.findOneBy({
+ id: job.data.user.id
+ });
+ if (user == null) {
+ return "skip: User not found";
+ }
+ // Create temp file
+ const [path, cleanup] = await createTemp();
+ logger.info(`Temp file is ${path}`);
+ try {
+ const stream = fs.createWriteStream(path, {
+ flags: "a"
+ });
+ let exportedCount = 0;
+ let cursor = null;
+ while(true){
+ const mutes = await Mutings.find({
+ where: {
+ muterId: user.id,
+ expiresAt: IsNull(),
+ ...cursor ? {
+ id: MoreThan(cursor)
+ } : {}
+ },
+ take: 100,
+ order: {
+ id: 1
+ }
+ });
+ if (mutes.length === 0) {
+ job.updateProgress(100);
+ break;
+ }
+ cursor = mutes[mutes.length - 1].id;
+ for (const mute of mutes){
+ const u = await Users.findOneBy({
+ id: mute.muteeId
+ });
+ if (u == null) {
+ exportedCount++;
+ continue;
+ }
+ const content = getFullApAccount(u.username, u.host);
+ await new Promise((res, rej)=>{
+ stream.write(content + "\n", (err)=>{
+ if (err) {
+ logger.error(err);
+ rej(err);
+ } else {
+ res();
+ }
+ });
+ });
+ exportedCount++;
+ }
+ const total = await Mutings.countBy({
+ muterId: user.id
+ });
+ job.updateProgress(exportedCount / total);
+ }
+ stream.end();
+ logger.succ(`Exported to: ${path}`);
+ const fileName = `mute-${dateFormat(new Date(), "yyyy-MM-dd-HH-mm-ss")}.csv`;
+ const driveFile = await addFile({
+ user,
+ path,
+ name: fileName,
+ force: true
+ });
+ logger.succ(`Exported to: ${driveFile.id}`);
+ } finally{
+ cleanup();
+ }
+ return "Success";
+}
diff --git a/packages/backend/built/queue/queues/db/export-notes.js b/packages/backend/built/queue/queues/db/export-notes.js
new file mode 100644
index 0000000..0a2e427
--- /dev/null
+++ b/packages/backend/built/queue/queues/db/export-notes.js
@@ -0,0 +1,105 @@
+import * as fs from "node:fs";
+import { queueLogger } from "../../logger.js";
+import { addFile } from "../../../services/drive/add-file.js";
+import { format as dateFormat } from "date-fns";
+import { Users, Notes, Polls, DriveFiles } from "../../../models/index.js";
+import { MoreThan } from "typeorm";
+import { createTemp } from "../../../misc/create-temp.js";
+const logger = queueLogger.createSubLogger("export-notes");
+export async function exportNotes(job) {
+ logger.info(`Exporting notes of ${job.data.user.id} ...`);
+ const user = await Users.findOneBy({
+ id: job.data.user.id
+ });
+ if (user == null) {
+ return "skip: User not found";
+ }
+ // Create temp file
+ const [path, cleanup] = await createTemp();
+ logger.info(`Temp file is ${path}`);
+ try {
+ const stream = fs.createWriteStream(path, {
+ flags: "a"
+ });
+ const write = (text)=>{
+ return new Promise((res, rej)=>{
+ stream.write(text, (err)=>{
+ if (err) {
+ logger.error(err);
+ rej(err);
+ } else {
+ res();
+ }
+ });
+ });
+ };
+ await write("[");
+ let exportedNotesCount = 0;
+ let cursor = null;
+ while(true){
+ const notes = await Notes.find({
+ where: {
+ userId: user.id,
+ ...cursor ? {
+ id: MoreThan(cursor)
+ } : {}
+ },
+ take: 100,
+ order: {
+ id: 1
+ }
+ });
+ if (notes.length === 0) {
+ job.updateProgress(100);
+ break;
+ }
+ cursor = notes[notes.length - 1].id;
+ for (const note of notes){
+ let poll;
+ if (note.hasPoll) {
+ poll = await Polls.findOneByOrFail({
+ noteId: note.id
+ });
+ }
+ const content = JSON.stringify(await serialize(note, poll));
+ const isFirst = exportedNotesCount === 0;
+ await write(isFirst ? content : ",\n" + content);
+ exportedNotesCount++;
+ }
+ const total = await Notes.countBy({
+ userId: user.id
+ });
+ job.updateProgress(exportedNotesCount / total);
+ }
+ await write("]");
+ stream.end();
+ logger.succ(`Exported to: ${path}`);
+ const fileName = `notes-${dateFormat(new Date(), "yyyy-MM-dd-HH-mm-ss")}.json`;
+ const driveFile = await addFile({
+ user,
+ path,
+ name: fileName,
+ force: true
+ });
+ logger.succ(`Exported to: ${driveFile.id}`);
+ } finally{
+ cleanup();
+ }
+ return "Success";
+}
+async function serialize(note, poll = null) {
+ return {
+ id: note.id,
+ text: note.text,
+ createdAt: note.createdAt,
+ fileIds: note.fileIds,
+ files: await DriveFiles.packMany(note.fileIds),
+ replyId: note.replyId,
+ renoteId: note.renoteId,
+ poll: poll,
+ cw: note.cw,
+ visibility: note.visibility,
+ visibleUserIds: note.visibleUserIds,
+ localOnly: note.localOnly
+ };
+}
diff --git a/packages/backend/built/queue/queues/db/export-user-lists.js b/packages/backend/built/queue/queues/db/export-user-lists.js
new file mode 100644
index 0000000..1adfc29
--- /dev/null
+++ b/packages/backend/built/queue/queues/db/export-user-lists.js
@@ -0,0 +1,64 @@
+import * as fs from "node:fs";
+import { queueLogger } from "../../logger.js";
+import { addFile } from "../../../services/drive/add-file.js";
+import { format as dateFormat } from "date-fns";
+import { getFullApAccount } from "../../../misc/convert-host.js";
+import { createTemp } from "../../../misc/create-temp.js";
+import { Users, UserLists, UserListJoinings } from "../../../models/index.js";
+import { In } from "typeorm";
+const logger = queueLogger.createSubLogger("export-user-lists");
+export async function exportUserLists(job) {
+ logger.info(`Exporting user lists of ${job.data.user.id} ...`);
+ const user = await Users.findOneBy({
+ id: job.data.user.id
+ });
+ if (user == null) {
+ return "skip: User not found";
+ }
+ const lists = await UserLists.findBy({
+ userId: user.id
+ });
+ // Create temp file
+ const [path, cleanup] = await createTemp();
+ logger.info(`Temp file is ${path}`);
+ try {
+ const stream = fs.createWriteStream(path, {
+ flags: "a"
+ });
+ for (const list of lists){
+ const joinings = await UserListJoinings.findBy({
+ userListId: list.id
+ });
+ const users = await Users.findBy({
+ id: In(joinings.map((j)=>j.userId))
+ });
+ for (const u of users){
+ const acct = getFullApAccount(u.username, u.host);
+ const content = `${list.name},${acct}`;
+ await new Promise((res, rej)=>{
+ stream.write(content + "\n", (err)=>{
+ if (err) {
+ logger.error(err);
+ rej(err);
+ } else {
+ res();
+ }
+ });
+ });
+ }
+ }
+ stream.end();
+ logger.succ(`Exported to: ${path}`);
+ const fileName = `user-lists-${dateFormat(new Date(), "yyyy-MM-dd-HH-mm-ss")}.csv`;
+ const driveFile = await addFile({
+ user,
+ path,
+ name: fileName,
+ force: true
+ });
+ logger.succ(`Exported to: ${driveFile.id}`);
+ } finally{
+ cleanup();
+ }
+ return "Success";
+}
diff --git a/packages/backend/built/queue/queues/db/import-blocking.js b/packages/backend/built/queue/queues/db/import-blocking.js
new file mode 100644
index 0000000..322a269
--- /dev/null
+++ b/packages/backend/built/queue/queues/db/import-blocking.js
@@ -0,0 +1,55 @@
+import { queueLogger } from "../../logger.js";
+import * as Acct from "../../../misc/acct.js";
+import { resolveUser } from "../../../remote/resolve-user.js";
+import { downloadTextFile } from "../../../misc/download-text-file.js";
+import { isSelfHost, toPuny } from "../../../misc/convert-host.js";
+import { Users, DriveFiles } from "../../../models/index.js";
+import block from "../../../services/blocking/create.js";
+import { IsNull } from "typeorm";
+const logger = queueLogger.createSubLogger("import-blocking");
+export async function importBlocking(job) {
+ logger.info(`Importing blocking of ${job.data.user.id} ...`);
+ const user = await Users.findOneBy({
+ id: job.data.user.id
+ });
+ if (user == null) {
+ return "skip: User not found";
+ }
+ const file = await DriveFiles.findOneBy({
+ id: job.data.fileId
+ });
+ if (file == null) {
+ return "skip: File not found";
+ }
+ const csv = await downloadTextFile(file.url);
+ let linenum = 0;
+ for (const line of csv.trim().split("\n")){
+ linenum++;
+ try {
+ const acct = line.split(",")[0].trim();
+ const { username, host } = Acct.parse(acct);
+ let target = isSelfHost(host) ? await Users.findOneBy({
+ host: IsNull(),
+ usernameLower: username.toLowerCase()
+ }) : await Users.findOneBy({
+ host: toPuny(host),
+ usernameLower: username.toLowerCase()
+ });
+ if (host == null && target == null) continue;
+ if (target == null) {
+ target = await resolveUser(username, host);
+ }
+ if (target == null) {
+ throw new Error(`cannot resolve user: @${username}@${host}`);
+ }
+ // skip myself
+ if (target.id === job.data.user.id) continue;
+ logger.info(`Block[${linenum}] ${target.id} ...`);
+ await block(user, target);
+ } catch (e) {
+ logger.warn(`Error in line:${linenum} ${e}`);
+ }
+ }
+ logger.succ("Imported");
+ return "Success";
+}
diff --git a/packages/backend/built/queue/queues/db/import-custom-emojis.js b/packages/backend/built/queue/queues/db/import-custom-emojis.js
new file mode 100644
index 0000000..7e8c464
--- /dev/null
+++ b/packages/backend/built/queue/queues/db/import-custom-emojis.js
@@ -0,0 +1,129 @@
+import * as fs from "node:fs";
+import AdmZip from "adm-zip";
+import { queueLogger } from "../../logger.js";
+import { createTempDir } from "../../../misc/create-temp.js";
+import { downloadUrl } from "../../../misc/download-url.js";
+import { DriveFiles, Emojis } from "../../../models/index.js";
+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");
+// TODO: 名前衝突時の動作を選べるようにする
+export async function importCustomEmojis(job) {
+ logger.info("Importing custom emojis ...");
+ const file = await DriveFiles.findOneBy({
+ id: job.data.fileId
+ });
+ if (file == null) {
+ return "skip: File not found";
+ }
+ const [tempPath, cleanup] = await createTempDir();
+ logger.info(`Temp dir is ${tempPath}`);
+ const destPath = `${tempPath}/emojis.zip`;
+ try {
+ fs.writeFileSync(destPath, "", "binary");
+ await downloadUrl(file.url, destPath);
+ } catch (e) {
+ // TODO: 何度か再試行
+ if (e instanceof Error || typeof e === "string") {
+ logger.error(e);
+ }
+ throw e;
+ }
+ const outputPath = `${tempPath}/emojis`;
+ const unzipStream = fs.createReadStream(destPath);
+ const zip = new AdmZip(destPath);
+ logger.succ(`Unzipping to ${outputPath}`);
+ await new Promise((resolve, reject)=>{
+ zip.extractAllToAsync(outputPath, true, false, async (error)=>{
+ if (error) reject(error);
+ 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,
+ glyph: driveFile.type === "image/svg+xml",
+ 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 out accidental JSON files
+ containedEmojis = containedEmojis.filter((emoji)=>!emoji.match(/\.(json)$/i));
+ for (const emojiFilename of containedEmojis){
+ // strip extension and get filename to use as name
+ const name = path.basename(emojiFilename, path.extname(emojiFilename));
+ const emojiPath = `${outputPath}/${emojiFilename}`;
+ logger.info(`importing ${name}`);
+ await Emojis.delete({
+ name: name
+ });
+ const driveFile = await addFile({
+ user: null,
+ path: emojiPath,
+ name: path.basename(emojiFilename),
+ force: true
+ });
+ 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(),
+ name: name,
+ category: categoryName,
+ host: null,
+ aliases: [],
+ originalUrl: driveFile.url,
+ publicUrl: driveFile.webpublicUrl ?? driveFile.url,
+ type: driveFile.webpublicType ?? driveFile.type,
+ license: null,
+ glyph: driveFile.type === "image/svg+xml",
+ width: size.width || null,
+ height: size.height || null
+ }).then((x)=>Emojis.findOneByOrFail(x.identifiers[0]));
+ }
+ }
+ await db.queryResultCache.remove([
+ "meta_emojis"
+ ]);
+ cleanup();
+ logger.succ("Imported");
+ resolve(undefined);
+ });
+ });
+ return "Success";
+}
diff --git a/packages/backend/built/queue/queues/db/import-following.js b/packages/backend/built/queue/queues/db/import-following.js
new file mode 100644
index 0000000..bf0ee17
--- /dev/null
+++ b/packages/backend/built/queue/queues/db/import-following.js
@@ -0,0 +1,85 @@
+import { IsNull } from "typeorm";
+import follow from "../../../services/following/create.js";
+import * as Acct from "../../../misc/acct.js";
+import { resolveUser } from "../../../remote/resolve-user.js";
+import { downloadTextFile } from "../../../misc/download-text-file.js";
+import { isSelfHost, toPuny } from "../../../misc/convert-host.js";
+import { Users, DriveFiles } from "../../../models/index.js";
+import { queueLogger } from "../../logger.js";
+import { cache as heuristic } from "../../../server/api/common/generate-following-query.js";
+const logger = queueLogger.createSubLogger("import-following");
+export async function importFollowing(job) {
+ logger.info(`Importing following of ${job.data.user.id} ...`);
+ const user = await Users.findOneBy({
+ id: job.data.user.id
+ });
+ if (user == null) {
+ return "skip: User not found";
+ }
+ const file = await DriveFiles.findOneBy({
+ id: job.data.fileId
+ });
+ if (file == null) {
+ return "skip: File not found";
+ }
+ const csv = await downloadTextFile(file.url);
+ let linenum = 0;
+ if (file.type.endsWith("json")) {
+ for (const acct of JSON.parse(csv)){
+ try {
+ const { username, host } = Acct.parse(acct);
+ let target = isSelfHost(host) ? await Users.findOneBy({
+ host: IsNull(),
+ usernameLower: username.toLowerCase()
+ }) : await Users.findOneBy({
+ host: toPuny(host),
+ usernameLower: username.toLowerCase()
+ });
+ if (host == null && target == null) continue;
+ if (target == null) {
+ target = await resolveUser(username, host);
+ }
+ if (target == null) {
+ throw new Error(`cannot resolve user: @${username}@${host}`);
+ }
+ // skip myself
+ if (target.id === job.data.user.id) continue;
+ logger.info(`Follow[${linenum}] ${target.id} ...`);
+ follow(user, target);
+ } catch (e) {
+ logger.warn(`Error in line:${linenum} ${e}`);
+ }
+ }
+ } else {
+ for (const line of csv.trim().split("\n")){
+ linenum++;
+ try {
+ const acct = line.split(",")[0].trim();
+ const { username, host } = Acct.parse(acct);
+ let target = isSelfHost(host) ? await Users.findOneBy({
+ host: IsNull(),
+ usernameLower: username.toLowerCase()
+ }) : await Users.findOneBy({
+ host: toPuny(host),
+ usernameLower: username.toLowerCase()
+ });
+ if (host == null && target == null) continue;
+ if (target == null) {
+ target = await resolveUser(username, host);
+ }
+ if (target == null) {
+ throw new Error(`cannot resolve user: @${username}@${host}`);
+ }
+ // skip myself
+ if (target.id === job.data.user.id) continue;
+ logger.info(`Follow[${linenum}] ${target.id} ...`);
+ follow(user, target);
+ } catch (e) {
+ logger.warn(`Error in line:${linenum} ${e}`);
+ }
+ }
+ }
+ await heuristic.delete(user.id);
+ logger.succ("Imported");
+ return "Success";
+}
diff --git a/packages/backend/built/queue/queues/db/import-muting.js b/packages/backend/built/queue/queues/db/import-muting.js
new file mode 100644
index 0000000..3b8f83e
--- /dev/null
+++ b/packages/backend/built/queue/queues/db/import-muting.js
@@ -0,0 +1,63 @@
+import { queueLogger } from "../../logger.js";
+import * as Acct from "../../../misc/acct.js";
+import { resolveUser } from "../../../remote/resolve-user.js";
+import { downloadTextFile } from "../../../misc/download-text-file.js";
+import { isSelfHost, toPuny } from "../../../misc/convert-host.js";
+import { Users, DriveFiles, Mutings } from "../../../models/index.js";
+import { genId } from "../../../misc/gen-id.js";
+import { IsNull } from "typeorm";
+const logger = queueLogger.createSubLogger("import-muting");
+export async function importMuting(job) {
+ logger.info(`Importing muting of ${job.data.user.id} ...`);
+ const user = await Users.findOneBy({
+ id: job.data.user.id
+ });
+ if (user == null) {
+ return "skip: User not found";
+ }
+ const file = await DriveFiles.findOneBy({
+ id: job.data.fileId
+ });
+ if (file == null) {
+ return "skip: File not found";
+ }
+ const csv = await downloadTextFile(file.url);
+ let linenum = 0;
+ for (const line of csv.trim().split("\n")){
+ linenum++;
+ try {
+ const acct = line.split(",")[0].trim();
+ const { username, host } = Acct.parse(acct);
+ let target = isSelfHost(host) ? await Users.findOneBy({
+ host: IsNull(),
+ usernameLower: username.toLowerCase()
+ }) : await Users.findOneBy({
+ host: toPuny(host),
+ usernameLower: username.toLowerCase()
+ });
+ if (host == null && target == null) continue;
+ if (target == null) {
+ target = await resolveUser(username, host);
+ }
+ if (target == null) {
+ throw new Error(`cannot resolve user: @${username}@${host}`);
+ }
+ // skip myself
+ if (target.id === job.data.user.id) continue;
+ logger.info(`Mute[${linenum}] ${target.id} ...`);
+ await mute(user, target);
+ } catch (e) {
+ logger.warn(`Error in line:${linenum} ${e}`);
+ }
+ }
+ logger.succ("Imported");
+ return "Success";
+}
+async function mute(user, target) {
+ await Mutings.insert({
+ id: genId(),
+ createdAt: new Date(),
+ muterId: user.id,
+ muteeId: target.id
+ });
+}
diff --git a/packages/backend/built/queue/queues/db/import-user-lists.js b/packages/backend/built/queue/queues/db/import-user-lists.js
new file mode 100644
index 0000000..4ef413a
--- /dev/null
+++ b/packages/backend/built/queue/queues/db/import-user-lists.js
@@ -0,0 +1,78 @@
+import { queueLogger } from "../../logger.js";
+import * as Acct from "../../../misc/acct.js";
+import { resolveUser } from "../../../remote/resolve-user.js";
+import { pushUserToUserList } from "../../../services/user-list/push.js";
+import { downloadTextFile } from "../../../misc/download-text-file.js";
+import { isSelfHost, toPuny } from "../../../misc/convert-host.js";
+import { DriveFiles, Users, UserLists, UserListJoinings, Blockings, Followings } from "../../../models/index.js";
+import { genId } from "../../../misc/gen-id.js";
+import { IsNull } from "typeorm";
+const logger = queueLogger.createSubLogger("import-user-lists");
+export async function importUserLists(job) {
+ logger.info(`Importing user lists of ${job.data.user.id} ...`);
+ const user = await Users.findOneBy({
+ id: job.data.user.id
+ });
+ if (user == null) {
+ return "skip: User not found";
+ }
+ const file = await DriveFiles.findOneBy({
+ id: job.data.fileId
+ });
+ if (file == null) {
+ return "skip: File not found";
+ }
+ const csv = await downloadTextFile(file.url);
+ let linenum = 0;
+ for (const line of csv.trim().split("\n")){
+ linenum++;
+ try {
+ const listName = line.split(",")[0].trim();
+ const { username, host } = Acct.parse(line.split(",")[1].trim());
+ let list = await UserLists.findOneBy({
+ userId: user.id,
+ name: listName
+ });
+ if (list == null) {
+ list = await UserLists.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ name: listName
+ }).then((x)=>UserLists.findOneByOrFail(x.identifiers[0]));
+ }
+ let target = isSelfHost(host) ? await Users.findOneBy({
+ host: IsNull(),
+ usernameLower: username.toLowerCase()
+ }) : await Users.findOneBy({
+ host: toPuny(host),
+ usernameLower: username.toLowerCase()
+ });
+ if (target == null) {
+ target = await resolveUser(username, host);
+ }
+ const isBlocked = await Blockings.exist({
+ where: {
+ blockerId: target.id,
+ blockeeId: user.id
+ }
+ });
+ const isFollowed = await Followings.exist({
+ where: {
+ followerId: user.id,
+ followeeId: target.id
+ }
+ });
+ if (isBlocked || !isFollowed) continue;
+ if (await UserListJoinings.findOneBy({
+ userListId: list.id,
+ userId: target.id
+ }) != null) continue;
+ pushUserToUserList(target, list);
+ } catch (e) {
+ logger.warn(`Error in line:${linenum} ${e}`);
+ }
+ }
+ logger.succ("Imported");
+ return "Success";
+}
diff --git a/packages/backend/built/queue/queues/db/index.js b/packages/backend/built/queue/queues/db/index.js
new file mode 100644
index 0000000..e1b5248
--- /dev/null
+++ b/packages/backend/built/queue/queues/db/index.js
@@ -0,0 +1,129 @@
+import { deleteAccount } from "./delete-account.js";
+import { deleteDriveFiles } from "./delete-drive-files.js";
+import { exportBlocking } from "./export-blocking.js";
+import { exportCustomEmojis } from "./export-custom-emojis.js";
+import { exportFollowing } from "./export-following.js";
+import { exportMute } from "./export-mute.js";
+import { exportNotes } from "./export-notes.js";
+import { exportUserLists } from "./export-user-lists.js";
+import { importBlocking } from "./import-blocking.js";
+import { importCustomEmojis } from "./import-custom-emojis.js";
+import { importFollowing } from "./import-following.js";
+import { importMuting } from "./import-muting.js";
+import { importUserLists } from "./import-user-lists.js";
+import { createQueue, defaultJobOpts } from "../index.js";
+import config from "../../../config/index.js";
+const processors = {
+ deleteAccount,
+ deleteDriveFiles,
+ exportBlocking,
+ exportCustomEmojis,
+ exportFollowing,
+ exportMute,
+ exportNotes,
+ exportUserLists,
+ importBlocking,
+ importCustomEmojis,
+ importFollowing,
+ importMuting,
+ importUserLists
+};
+async function process(job) {
+ const processor = processors[job.name];
+ if (processor === undefined) return "skip: unknown job name";
+ return await processor(job);
+}
+export const [dbQueue, dbInit] = createQueue("db", process, {
+ limitPerSec: 256,
+ concurrency: 16
+});
+export function createDeleteDriveFilesJob(user) {
+ return dbQueue.add("deleteDriveFiles", {
+ user: user
+ }, defaultJobOpts);
+}
+export function createExportCustomEmojisJob(user) {
+ return dbQueue.add("exportCustomEmojis", {
+ user: user
+ }, defaultJobOpts);
+}
+export function createExportNotesJob(user) {
+ return dbQueue.add("exportNotes", {
+ user: user
+ }, defaultJobOpts);
+}
+export function createExportFollowingJob(user, excludeMuting = false, excludeInactive = false) {
+ return dbQueue.add("exportFollowing", {
+ user: user,
+ excludeMuting,
+ excludeInactive
+ }, defaultJobOpts);
+}
+export function createExportMuteJob(user) {
+ return dbQueue.add("exportMute", {
+ user: user
+ }, defaultJobOpts);
+}
+export function createExportBlockingJob(user) {
+ return dbQueue.add("exportBlocking", {
+ user: user
+ }, defaultJobOpts);
+}
+export function createExportUserListsJob(user) {
+ return dbQueue.add("exportUserLists", {
+ user: user
+ }, defaultJobOpts);
+}
+export function createImportFollowingJob(user, fileId) {
+ return dbQueue.add("importFollowing", {
+ user: user,
+ fileId: fileId
+ }, defaultJobOpts);
+}
+export function createImportMastoPostJob(user, post, signatureCheck) {
+ return dbQueue.add("importMastoPost", {
+ user: user,
+ post: post,
+ signatureCheck: signatureCheck
+ }, {
+ attempts: config.inboxJobMaxAttempts || 8,
+ ...defaultJobOpts
+ });
+}
+export function createImportCkPostJob(user, post, signatureCheck) {
+ return dbQueue.add("importCkPost", {
+ user: user,
+ post: post,
+ signatureCheck: signatureCheck
+ }, defaultJobOpts);
+}
+export function createImportMutingJob(user, fileId) {
+ return dbQueue.add("importMuting", {
+ user: user,
+ fileId: fileId
+ }, defaultJobOpts);
+}
+export function createImportBlockingJob(user, fileId) {
+ return dbQueue.add("importBlocking", {
+ user: user,
+ fileId: fileId
+ }, defaultJobOpts);
+}
+export function createImportUserListsJob(user, fileId) {
+ return dbQueue.add("importUserLists", {
+ user: user,
+ fileId: fileId
+ }, defaultJobOpts);
+}
+export function createImportCustomEmojisJob(user, fileId) {
+ return dbQueue.add("importCustomEmojis", {
+ user: user,
+ fileId: fileId
+ }, defaultJobOpts);
+}
+export function createDeleteAccountJob(user, opts = {}) {
+ return dbQueue.add("deleteAccount", {
+ user: user,
+ soft: opts.soft
+ }, defaultJobOpts);
+}
diff --git a/packages/backend/built/queue/queues/deliver.js b/packages/backend/built/queue/queues/deliver.js
new file mode 100644
index 0000000..c9764c5
--- /dev/null
+++ b/packages/backend/built/queue/queues/deliver.js
@@ -0,0 +1,92 @@
+import { URL } from "node:url";
+import request from "../../remote/activitypub/request.js";
+import { registerOrFetchInstanceDoc } from "../../services/register-or-fetch-instance-doc.js";
+import Logger from "../../services/logger.js";
+import { Instances } from "../../models/index.js";
+import { apRequestChart, federationChart, instanceChart } from "../../services/chart/index.js";
+import { fetchInstanceMetadata } from "../../services/fetch-instance-metadata.js";
+import { toPuny } from "../../misc/convert-host.js";
+import { StatusError } from "../../misc/fetch.js";
+import { shouldSkipInstance } from "../../misc/skipped-instances.js";
+import config from "../../config/index.js";
+import { createQueue, defaultJobOpts, processorTimeout } from "./index.js";
+import { tickOutbox } from "../../metrics.js";
+export const deliverLogger = new Logger("deliver");
+let latest = null;
+async function process(job) {
+ if (job.data == null || Object.keys(job.data).length === 0) {
+ job.opts.removeOnComplete = true;
+ return "Skip (data was null or empty)";
+ }
+ const { host } = new URL(job.data.to);
+ const puny = toPuny(host);
+ if (await shouldSkipInstance(puny)) return "skip";
+ try {
+ if (latest !== (latest = JSON.stringify(job.data.content, null, 2))) {
+ deliverLogger.debug(`delivering ${latest}`);
+ }
+ await request(job.data.user, job.data.to, job.data.content);
+ // Update stats
+ registerOrFetchInstanceDoc(host).then((i)=>{
+ Instances.update(i.id, {
+ latestRequestSentAt: new Date(),
+ latestStatus: 200,
+ lastCommunicatedAt: new Date(),
+ isNotResponding: false
+ });
+ fetchInstanceMetadata(i);
+ instanceChart.requestSent(i.host, true);
+ apRequestChart.deliverSucc();
+ federationChart.deliverd(i.host, true);
+ });
+ tickOutbox();
+ return "Success";
+ } catch (res) {
+ // Update stats
+ registerOrFetchInstanceDoc(host).then((i)=>{
+ Instances.update(i.id, {
+ latestRequestSentAt: new Date(),
+ latestStatus: res instanceof StatusError ? res.statusCode : null,
+ isNotResponding: true
+ });
+ instanceChart.requestSent(i.host, false);
+ apRequestChart.deliverFail();
+ federationChart.deliverd(i.host, false);
+ });
+ if (res instanceof StatusError) {
+ // 4xx
+ if (!res.isRetryable) {
+ // HTTPステータスコード4xxはクライアントエラーであり、それはつまり
+ // 何回再送しても成功することはないということなのでエラーにはしないでおく
+ return `${res.statusCode} ${res.statusMessage}`;
+ }
+ // 5xx etc.
+ throw new Error(`${res.statusCode} ${res.statusMessage}`);
+ } else {
+ // DNS error, socket error, timeout ...
+ throw res;
+ }
+ }
+}
+export const [deliverQueue, deliverInit] = createQueue("deliver", processorTimeout(process, 60), {
+ limitPerSec: config.deliverJobPerSec || 128,
+ concurrency: config.deliverJobConcurrency || 128
+});
+export function deliverJob(user, content, to) {
+ if (content == null) return null;
+ if (to == null) return null;
+ const data = {
+ user: {
+ id: user.id
+ },
+ content,
+ to
+ };
+ return deliverQueue.add("default", data, {
+ attempts: config.deliverJobMaxAttempts || 12,
+ backoff: {
+ type: "custom"
+ },
+ ...defaultJobOpts
+ });
+}
diff --git a/packages/backend/built/queue/queues/ended-poll-notification.js b/packages/backend/built/queue/queues/ended-poll-notification.js
new file mode 100644
index 0000000..7ffcbe4
--- /dev/null
+++ b/packages/backend/built/queue/queues/ended-poll-notification.js
@@ -0,0 +1,35 @@
+import { Notes, PollVotes } from "../../models/index.js";
+import { createNotification } from "../../services/create-notification.js";
+import { deliverQuestionUpdate } from "../../services/note/polls/update.js";
+import { createQueue } from "./index.js";
+async function process(job) {
+ if (job.data == null || Object.keys(job.data).length === 0) {
+ job.opts.removeOnComplete = true;
+ return "skip: corrupt job";
+ }
+ const note = await Notes.findOneBy({
+ id: job.data.noteId
+ });
+ if (note == null || !note.hasPoll) {
+ return "skip: note not found";
+ }
+ const votes = await PollVotes.createQueryBuilder("vote").select("vote.userId").where("vote.noteId = :noteId", {
+ noteId: note.id
+ }).innerJoinAndSelect("vote.user", "user").andWhere("user.host IS NULL").getMany();
+ const userIds = [
+ ...new Set([
+ note.userId,
+ ...votes.map((v)=>v.userId)
+ ])
+ ];
+ for (const userId of userIds){
+ createNotification(userId, "pollEnded", {
+ note: note,
+ noteId: note.id
+ });
+ }
+ // Broadcast the poll result once it ends
+ if (!note.localOnly) await deliverQuestionUpdate(note.id);
+ return "complete";
+}
+export const [endedPollNotificationQueue, endedPollNotificationInit] = createQueue("endedPollNotification", process);
diff --git a/packages/backend/built/queue/queues/inbox.js b/packages/backend/built/queue/queues/inbox.js
new file mode 100644
index 0000000..b5268b9
--- /dev/null
+++ b/packages/backend/built/queue/queues/inbox.js
@@ -0,0 +1,194 @@
+import { URL } from "node:url";
+import httpSignature from "@peertube/http-signature";
+import perform from "../../remote/activitypub/perform.js";
+import Logger from "../../services/logger.js";
+import { registerOrFetchInstanceDoc } from "../../services/register-or-fetch-instance-doc.js";
+import { Instances } from "../../models/index.js";
+import { apRequestChart, federationChart, instanceChart } from "../../services/chart/index.js";
+import { fetchMeta } from "../../misc/fetch-meta.js";
+import { toPuny, extractDbHost } from "../../misc/convert-host.js";
+import { getApId } from "../../remote/activitypub/type.js";
+import { fetchInstanceMetadata } from "../../services/fetch-instance-metadata.js";
+import DbResolver from "../../remote/activitypub/db-resolver.js";
+import { resolvePerson } from "../../remote/activitypub/models/person.js";
+import { LdSignature } from "../../remote/activitypub/misc/ld-signature.js";
+import { StatusError } from "../../misc/fetch.js";
+import { shouldBlockInstance } from "../../misc/should-block-instance.js";
+import { verifySignature } from "../../remote/activitypub/check-fetch.js";
+import { createQueue, defaultJobOpts, processorTimeout } from "./index.js";
+import config from "../../config/index.js";
+import { tickInbox } from "../../metrics.js";
+export const inboxLogger = new Logger("inbox");
+// Processing when an activity arrives in the user's inbox
+async function process(job) {
+ if (job.data == null || Object.keys(job.data).length === 0) {
+ job.opts.removeOnComplete = true;
+ return "Skip (data was null or empty)";
+ }
+ const signature = job.data.signature; // HTTP-signature
+ let activity = job.data.activity;
+ //#region Log
+ const info = Object.assign({}, activity);
+ info["@context"] = undefined;
+ inboxLogger.debug(JSON.stringify(info, null, 2));
+ if (!signature?.keyId) {
+ throw new Error(`Invalid signature: ${signature}`);
+ }
+ //#endregion
+ const host = toPuny(new URL(signature.keyId).hostname);
+ // interrupt if blocked
+ const meta = await fetchMeta();
+ if (await shouldBlockInstance(host, meta)) {
+ return `Blocked request: ${host}`;
+ }
+ // only allowlisted instances in private mode
+ if (meta.privateMode && !meta.allowedHosts.includes(host)) {
+ return `Blocked request: ${host}`;
+ }
+ const keyIdLower = signature.keyId.toLowerCase();
+ if (keyIdLower.startsWith("acct:")) {
+ return `Old keyId is no longer supported. ${keyIdLower}`;
+ }
+ const dbResolver = new DbResolver();
+ // HTTP-Signature keyId from DB
+ let authUser = await dbResolver.getAuthUserFromKeyId(signature.keyId);
+ // keyIdでわからなければ、activity.actorを元にDBから取得 || activity.actorを元にリモートから取得
+ if (authUser == null) {
+ try {
+ authUser = await dbResolver.getAuthUserFromApId(getApId(activity.actor));
+ } catch (e) {
+ // Skip if target is 4xx
+ if (e instanceof StatusError) {
+ if (!e.isRetryable) {
+ return `skip: Ignored deleted actors on both ends ${activity.actor} - ${e.statusCode}`;
+ }
+ throw new Error(`Error in actor ${activity.actor} - ${e.statusCode || e}`);
+ }
+ }
+ }
+ // それでもわからなければ終了
+ if (authUser == null) {
+ return "skip: failed to resolve user";
+ }
+ // publicKey がなくても終了
+ if (authUser.key == null) {
+ return "skip: failed to resolve user publicKey";
+ }
+ // HTTP-Signatureの検証
+ let httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem);
+ // If signature validation failed, try refetching the actor
+ if (!httpSignatureValidated) {
+ authUser.key = await dbResolver.refetchPublicKeyForApId(authUser.user);
+ if (authUser.key == null) {
+ return "skip: failed to re-resolve user publicKey";
+ }
+ httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem);
+ }
+ if (httpSignatureValidated) {
+ if (!verifySignature(signature, authUser.key)) return `skip: Invalid HTTP signature`;
+ }
+ // また、signatureのsignerは、activity.actorと一致する必要がある
+ if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {
+ // 一致しなくても、でもLD-Signatureがありそうならそっちも見る
+ if (activity.signature) {
+ if (activity.signature.type !== "RsaSignature2017") {
+ return `skip: unsupported LD-signature type ${activity.signature.type}`;
+ }
+ // activity.signature.creator: https://example.oom/users/user#main-key
+ // みたいになっててUserを引っ張れば公開キーも入ることを期待する
+ if (activity.signature.creator) {
+ const candicate = activity.signature.creator.replace(/#.*/, "");
+ await resolvePerson(candicate).catch(()=>null);
+ }
+ // keyIdからLD-Signatureのユーザーを取得
+ authUser = await dbResolver.getAuthUserFromKeyId(activity.signature.creator);
+ if (authUser == null) {
+ return "skip: LD-Signatureのユーザーが取得できませんでした";
+ }
+ if (authUser.key == null) {
+ return "skip: LD-SignatureのユーザーはpublicKeyを持っていませんでした";
+ }
+ // LD-Signature検証
+ const ldSignature = new LdSignature();
+ const signature = activity.signature;
+ delete activity["signature"];
+ activity = await ldSignature.compactToWellKnown(activity);
+ if (ldSignature.containsForbiddenDirectives(activity)) {
+ return "skip: activity contains forbidden directives";
+ }
+ const verified = await ldSignature.verifyRsaSignature2017(activity, signature, authUser.key.keyPem);
+ if (!verified) {
+ return "skip: LD-Signatureの検証に失敗しました";
+ }
+ // もう一度actorチェック
+ if (authUser.user.uri !== activity.actor) {
+ return `skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`;
+ }
+ // ブロックしてたら中断
+ const ldHost = extractDbHost(authUser.user.uri);
+ if (await shouldBlockInstance(ldHost, meta)) {
+ return `Blocked request: ${ldHost}`;
+ }
+ } else {
+ return `skip: http-signature verification failed and no LD-Signature. keyId=${signature.keyId}`;
+ }
+ }
+ // activity.idがあればホストが署名者のホストであることを確認する
+ if (typeof activity.id !== "string") {
+ return 'skip: activity.id is not a string';
+ }
+ const signerHost = extractDbHost(authUser.user.uri);
+ const activityIdHost = extractDbHost(activity.id);
+ if (signerHost !== activityIdHost) {
+ return `skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`;
+ }
+ // Update stats
+ registerOrFetchInstanceDoc(authUser.user.host).then((i)=>{
+ Instances.update(i.id, {
+ latestRequestReceivedAt: new Date(),
+ lastCommunicatedAt: new Date(),
+ isNotResponding: false
+ });
+ fetchInstanceMetadata(i);
+ instanceChart.requestReceived(i.host);
+ apRequestChart.inbox();
+ federationChart.inbox(i.host);
+ });
+ const inbox = authUser.user.sharedInbox ?? authUser.user.inbox;
+ if (inbox !== null) {
+ const { host: inboxHost } = new URL(inbox);
+ if (inboxHost !== authUser.user.host) {
+ registerOrFetchInstanceDoc(inboxHost).then((i)=>{
+ Instances.update(i.id, {
+ latestRequestReceivedAt: new Date(),
+ lastCommunicatedAt: new Date(),
+ isNotResponding: false
+ });
+ fetchInstanceMetadata(i);
+ instanceChart.requestReceived(i.host);
+ apRequestChart.inbox();
+ federationChart.inbox(i.host);
+ });
+ }
+ }
+ tickInbox();
+ // アクティビティを処理
+ return await perform(authUser.user, activity);
+}
+export const [inboxQueue, inboxInit] = createQueue("inbox", processorTimeout(process, 5 * 60), {
+ limitPerSec: config.inboxJobPerSec || 16,
+ concurrency: config.inboxJobConcurrency || 16
+});
+export function inboxJob(activity, signature) {
+ const data = {
+ activity: activity,
+ signature
+ };
+ return inboxQueue.add("default", data, {
+ attempts: config.inboxJobMaxAttempts || 8,
+ backoff: {
+ type: "custom"
+ },
+ ...defaultJobOpts
+ });
+}
diff --git a/packages/backend/built/queue/queues/index.js b/packages/backend/built/queue/queues/index.js
new file mode 100644
index 0000000..160eb5b
--- /dev/null
+++ b/packages/backend/built/queue/queues/index.js
@@ -0,0 +1,77 @@
+import { Worker, Queue } from "bullmq";
+import config from "../../config/index.js";
+import { queueLogger, renderError } from "../logger.js";
+const connectionDetails = {
+ port: config.redis.port,
+ host: config.redis.host,
+ family: config.redis.family == null ? 0 : config.redis.family,
+ username: config.redis.user ?? "default",
+ password: config.redis.pass,
+ db: config.redis.db || 0,
+ tls: config.redis.tls,
+ lazyConnect: true,
+ maxRetriesPerRequest: null
+};
+// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019
+function apBackoff(attemptsMade) {
+ const baseDelay = 60 * 1000; // 1min
+ const maxBackoff = 8 * 60 * 60 * 1000; // 8hours
+ let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay;
+ backoff = Math.min(backoff, maxBackoff);
+ backoff += Math.round(backoff * Math.random() * 0.2);
+ return backoff;
+}
+export function createQueue(name, processor, opts = {}) {
+ const backoffStrategy = opts.backoff || apBackoff;
+ const sublogger = queueLogger.createSubLogger(name);
+ const queue = new Queue(name, {
+ connection: connectionDetails,
+ prefix: config.redis.prefix
+ });
+ queue.on("waiting", (job)=>sublogger.debug(`waiting id=${job.id}`));
+ const initWorker = ()=>{
+ return new Worker(name, processor, {
+ connection: connectionDetails,
+ prefix: config.redis.prefix,
+ settings: {
+ backoffStrategy
+ },
+ limiter: opts.limitPerSec === undefined || opts.limitPerSec === -1 ? undefined : {
+ max: opts.limitPerSec,
+ duration: 1000
+ },
+ concurrency: opts.concurrency || 1
+ }).on("ready", ()=>{}).on("active", (job)=>sublogger.debug(`active id=${job.id}`)).on("completed", (job)=>sublogger.debug(`completed id=${job.id}`)).on("failed", (job, err)=>sublogger.warn(`failed(${err}) id=${job?.id}`, {
+ job,
+ e: renderError(err)
+ })).on("error", (err)=>sublogger.error(`error(${err})`, {
+ e: renderError(err)
+ })).on("stalled", (jobId)=>sublogger.warn(`stalled id=${jobId}`)).waitUntilReady().then(()=>{});
+ };
+ return [
+ queue,
+ initWorker
+ ];
+}
+export function createQueueProducer(name) {
+ return new Queue(name, {
+ connection: connectionDetails,
+ prefix: config.redis.prefix
+ });
+}
+export function processorTimeout(processor, timeout) {
+ return (job)=>{
+ return new Promise((resolve, reject)=>{
+ const timer = setTimeout(()=>reject(new Error("timeout reached")), timeout * 1000);
+ processor(job).then(resolve).catch(reject).finally(()=>clearTimeout(timer));
+ });
+ };
+}
+export const defaultJobOpts = {
+ removeOnComplete: process.env["NODE_ENV"] === "production" ? {
+ age: 600
+ } : true,
+ removeOnFail: process.env["NODE_ENV"] === "production" ? {
+ age: 3600
+ } : true
+};
diff --git a/packages/backend/built/queue/queues/object-storage/clean-remote-files.js b/packages/backend/built/queue/queues/object-storage/clean-remote-files.js
new file mode 100644
index 0000000..70dc6da
--- /dev/null
+++ b/packages/backend/built/queue/queues/object-storage/clean-remote-files.js
@@ -0,0 +1,45 @@
+import { queueLogger } from "../../logger.js";
+import { deleteFileSync } from "../../../services/drive/delete-file.js";
+import { DriveFiles } from "../../../models/index.js";
+import { User } from "../../../models/entities/user.js";
+import config from "../../../config/index.js";
+const logger = queueLogger.createSubLogger("clean-remote-files");
+export async function cleanRemoteFiles(job) {
+ let progress = 0;
+ const untilDate = new Date(Date.now() - new Date().getTimezoneOffset() * 60000);
+ untilDate.setDate(untilDate.getDate() - (config.mediaCleanup?.maxAgeDays ?? 0));
+ const avatars = !(config.mediaCleanup?.keepHeaders ?? true);
+ const headers = !(config.mediaCleanup?.keepHeaders ?? true);
+ const until = untilDate.toISOString().replace("T", " ").slice(0, -1);
+ let target = "files";
+ if (avatars) if (headers) target += ", avatars & headers";
+ else target += " & avatars";
+ else if (headers) target += " & headers";
+ logger.info(`Deleting cached remote ${target} created before ${until}...`);
+ let query = DriveFiles.createQueryBuilder("file").where(`file.isLink = FALSE`).andWhere(`file.userHost IS NOT NULL`).andWhere("file.createdAt < :until", {
+ until
+ });
+ if (!avatars || !headers) {
+ query = query.andWhere((qb)=>{
+ let sq = qb.subQuery().from(User, "user");
+ if (!avatars) sq = sq.where("file.id = user.avatarId");
+ if (!headers) sq = sq.orWhere("file.id = user.bannerId");
+ return `NOT EXISTS ${sq.getQuery()}`;
+ });
+ }
+ query = query.take(8);
+ const total = await query.getCount();
+ logger.info(`Deleting ${total} files, please wait...`);
+ while(true){
+ const files = await query.getMany();
+ if (files.length === 0) {
+ job.updateProgress(100);
+ break;
+ }
+ await Promise.all(files.map((file)=>deleteFileSync(file, true)));
+ progress += files.length;
+ job.updateProgress(progress / total * 100);
+ }
+ logger.succ(`Remote media cleanup job completed successfully.`);
+ return "Success";
+}
diff --git a/packages/backend/built/queue/queues/object-storage/delete-file.js b/packages/backend/built/queue/queues/object-storage/delete-file.js
new file mode 100644
index 0000000..1cf9b2d
--- /dev/null
+++ b/packages/backend/built/queue/queues/object-storage/delete-file.js
@@ -0,0 +1,6 @@
+import { deleteObjectStorageFile } from "../../../services/drive/delete-file.js";
+export async function deleteFile(job) {
+ const key = job.data.key;
+ await deleteObjectStorageFile(key);
+ return "Success";
+}
diff --git a/packages/backend/built/queue/queues/object-storage/index.js b/packages/backend/built/queue/queues/object-storage/index.js
new file mode 100644
index 0000000..55c507a
--- /dev/null
+++ b/packages/backend/built/queue/queues/object-storage/index.js
@@ -0,0 +1,36 @@
+import { createQueue, defaultJobOpts } from "../index.js";
+import { cleanRemoteFiles } from "./clean-remote-files.js";
+import { deleteFile } from "./delete-file.js";
+import config from "../../../config/index.js";
+const processors = {
+ cleanRemoteFiles,
+ deleteFile
+};
+async function process(job) {
+ const processor = processors[job.name];
+ if (processor === undefined) return "skip: unknown job name";
+ return await processor(job);
+}
+const [objectStorageQueue_, objectStorageInitQueue] = createQueue("objectStorage", process, {
+ concurrency: 16
+});
+export const objectStorageQueue = objectStorageQueue_;
+export async function objectStorageInit() {
+ await objectStorageInitQueue();
+ if (config.mediaCleanup?.cron) {
+ await objectStorageQueue.upsertJobScheduler("cleanRemoteFiles", {
+ pattern: "0 0 * * *"
+ }, {
+ name: "cleanRemoteFiles",
+ opts: defaultJobOpts
+ });
+ }
+}
+export function createDeleteObjectStorageFileJob(key) {
+ return objectStorageQueue.add("deleteFile", {
+ key: key
+ }, defaultJobOpts);
+}
+export function createCleanRemoteFilesJob() {
+ return objectStorageQueue.add("cleanRemoteFiles", {}, defaultJobOpts);
+}
diff --git a/packages/backend/built/queue/queues/system/check-expired-memoriets.js b/packages/backend/built/queue/queues/system/check-expired-memoriets.js
new file mode 100644
index 0000000..e79a91b
--- /dev/null
+++ b/packages/backend/built/queue/queues/system/check-expired-memoriets.js
@@ -0,0 +1,42 @@
+import { MemorietArchives, Memoriets, Notes, Users } from "../../../models/index.js";
+import deleteNote from "../../../services/note/delete.js";
+import { queueLogger } from "../../logger.js";
+import { genId } from "../../../misc/gen-id.js";
+const logger = queueLogger.createSubLogger("check-expired-memoriets");
+export async function checkExpiredMemoriets() {
+ logger.info("Checking expired Memoriets...");
+ const expired = await Memoriets.createQueryBuilder("memoriet").where("memoriet.expiresAt IS NOT NULL").andWhere("memoriet.expiresAt < :now", {
+ now: new Date()
+ }).getMany();
+ for (const memoriet of expired){
+ const note = await Notes.findOneBy({
+ id: memoriet.noteId
+ });
+ if (note != null) {
+ const user = await Users.findOneBy({
+ id: note.userId
+ });
+ if (user != null) {
+ await MemorietArchives.save({
+ id: genId(),
+ createdAt: memoriet.createdAt,
+ deletedAt: new Date(),
+ userId: memoriet.userId,
+ text: note.text?.replace(/\n?#Memoriet\b/gi, "").trim() || null,
+ cw: note.cw,
+ fileIds: note.fileIds,
+ textLayers: memoriet.textLayers,
+ visibility: note.visibility
+ }).catch((err)=>{
+ logger.warn(`Failed to archive expired Memoriet ${memoriet.id}: ${err}`);
+ });
+ await deleteNote(user, note, true).catch((err)=>{
+ logger.warn(`Failed to delete expired Memoriet note ${note.id}: ${err}`);
+ });
+ }
+ }
+ await Memoriets.delete(memoriet.id);
+ }
+ logger.succ("All expired Memoriets checked.");
+ return `done: ${expired.length}`;
+}
diff --git a/packages/backend/built/queue/queues/system/check-expired-mutings.js b/packages/backend/built/queue/queues/system/check-expired-mutings.js
new file mode 100644
index 0000000..5c4cd5b
--- /dev/null
+++ b/packages/backend/built/queue/queues/system/check-expired-mutings.js
@@ -0,0 +1,21 @@
+import { In } from "typeorm";
+import { Mutings } from "../../../models/index.js";
+import { queueLogger } from "../../logger.js";
+import { publishUserEvent } from "../../../services/stream.js";
+const logger = queueLogger.createSubLogger("check-expired-mutings");
+export async function checkExpiredMutings() {
+ logger.info("Checking expired mutings...");
+ const expired = await Mutings.createQueryBuilder("muting").where("muting.expiresAt IS NOT NULL").andWhere("muting.expiresAt < :now", {
+ now: new Date()
+ }).innerJoinAndSelect("muting.mutee", "mutee").getMany();
+ if (expired.length > 0) {
+ await Mutings.delete({
+ id: In(expired.map((m)=>m.id))
+ });
+ for (const m of expired){
+ publishUserEvent(m.muterId, "unmute", m.mutee);
+ }
+ }
+ logger.succ("All expired mutings checked.");
+ return "Success";
+}
diff --git a/packages/backend/built/queue/queues/system/clean-charts.js b/packages/backend/built/queue/queues/system/clean-charts.js
new file mode 100644
index 0000000..8dd4f4f
--- /dev/null
+++ b/packages/backend/built/queue/queues/system/clean-charts.js
@@ -0,0 +1,22 @@
+import { queueLogger } from "../../logger.js";
+import { activeUsersChart, driveChart, federationChart, hashtagChart, instanceChart, notesChart, perUserDriveChart, perUserFollowingChart, perUserNotesChart, perUserReactionsChart, usersChart, apRequestChart } from "../../../services/chart/index.js";
+const logger = queueLogger.createSubLogger("clean-charts");
+export async function cleanCharts() {
+ logger.info("Clean charts...");
+ await Promise.all([
+ federationChart.clean(),
+ notesChart.clean(),
+ usersChart.clean(),
+ activeUsersChart.clean(),
+ instanceChart.clean(),
+ perUserNotesChart.clean(),
+ driveChart.clean(),
+ perUserReactionsChart.clean(),
+ hashtagChart.clean(),
+ perUserFollowingChart.clean(),
+ perUserDriveChart.clean(),
+ apRequestChart.clean()
+ ]);
+ logger.succ("All charts successfully cleaned.");
+ return "Success";
+}
diff --git a/packages/backend/built/queue/queues/system/clean.js b/packages/backend/built/queue/queues/system/clean.js
new file mode 100644
index 0000000..a86b129
--- /dev/null
+++ b/packages/backend/built/queue/queues/system/clean.js
@@ -0,0 +1,12 @@
+import { LessThan } from "typeorm";
+import { UserIps } from "../../../models/index.js";
+import { queueLogger } from "../../logger.js";
+const logger = queueLogger.createSubLogger("clean");
+export async function clean() {
+ logger.info("Cleaning...");
+ await UserIps.delete({
+ createdAt: LessThan(new Date(Date.now() - 1000 * 60 * 60 * 24 * 90))
+ });
+ logger.succ("Cleaned.");
+ return "Success";
+}
diff --git a/packages/backend/built/queue/queues/system/index.js b/packages/backend/built/queue/queues/system/index.js
new file mode 100644
index 0000000..d4186df
--- /dev/null
+++ b/packages/backend/built/queue/queues/system/index.js
@@ -0,0 +1,80 @@
+import { ScheduledNotes } from "../../../models/index.js";
+import { createQueue, defaultJobOpts } from "../index.js";
+import { clean } from "./clean.js";
+import { cleanCharts } from "./clean-charts.js";
+import { checkExpiredMutings } from "./check-expired-mutings.js";
+import { checkExpiredMemoriets } from "./check-expired-memoriets.js";
+import { resyncCharts } from "./resync-charts.js";
+import { tickCharts } from "./tick-charts.js";
+import { verifyLinks } from "./verify-links.js";
+import { publishScheduledNoteJob } from "./publish-scheduled-note.js";
+import { enqueueScheduledNote } from "./scheduled-note.js";
+const processors = {
+ clean,
+ cleanCharts,
+ checkExpiredMutings,
+ checkExpiredMemoriets,
+ resyncCharts,
+ tickCharts,
+ verifyLinks,
+ publishScheduledNote: publishScheduledNoteJob
+};
+async function process(job) {
+ const processor = processors[job.name];
+ if (processor === undefined) return "skip: unknown job name";
+ return await processor(job);
+}
+const [systemQueue_, systemInitQueue] = createQueue("system", process, {
+ concurrency: Object.keys(processors).length
+});
+export const systemQueue = systemQueue_;
+async function enqueuePendingScheduledNotes() {
+ const scheduledNotes = await ScheduledNotes.find({
+ where: {
+ status: "scheduled"
+ }
+ });
+ for (const scheduledNote of scheduledNotes){
+ await enqueueScheduledNote(scheduledNote);
+ }
+}
+export async function systemInit() {
+ await systemInitQueue();
+ for (const { name, seconds } of [
+ {
+ name: "clean",
+ seconds: 60 * 60
+ },
+ {
+ name: "cleanCharts",
+ seconds: 60 * 60
+ },
+ {
+ name: "checkExpiredMutings",
+ seconds: 5 * 60
+ },
+ {
+ name: "checkExpiredMemoriets",
+ seconds: 60
+ },
+ {
+ name: "resyncCharts",
+ seconds: 60 * 60
+ },
+ {
+ name: "tickCharts",
+ seconds: 60
+ },
+ {
+ name: "verifyLinks",
+ seconds: 60 * 60 * 24
+ }
+ ]){
+ await systemQueue.upsertJobScheduler(name, {
+ every: seconds * 1000
+ }, {
+ opts: defaultJobOpts
+ });
+ }
+ await enqueuePendingScheduledNotes();
+}
diff --git a/packages/backend/built/queue/queues/system/publish-scheduled-note.js b/packages/backend/built/queue/queues/system/publish-scheduled-note.js
new file mode 100644
index 0000000..2b1cdbe
--- /dev/null
+++ b/packages/backend/built/queue/queues/system/publish-scheduled-note.js
@@ -0,0 +1,4 @@
+import { publishScheduledNote } from "../../../services/note/scheduled.js";
+export async function publishScheduledNoteJob(job) {
+ return await publishScheduledNote(job.data.scheduledNoteId);
+}
diff --git a/packages/backend/built/queue/queues/system/resync-charts.js b/packages/backend/built/queue/queues/system/resync-charts.js
new file mode 100644
index 0000000..796a100
--- /dev/null
+++ b/packages/backend/built/queue/queues/system/resync-charts.js
@@ -0,0 +1,15 @@
+import { queueLogger } from "../../logger.js";
+import { driveChart, notesChart, usersChart } from "../../../services/chart/index.js";
+const logger = queueLogger.createSubLogger("resync-charts");
+export async function resyncCharts() {
+ logger.info("Resync charts...");
+ // TODO: ユーザーごとのチャートも更新する
+ // TODO: インスタンスごとのチャートも更新する
+ await Promise.all([
+ driveChart.resync(),
+ notesChart.resync(),
+ usersChart.resync()
+ ]);
+ logger.succ("All charts successfully resynced.");
+ return "Success";
+}
diff --git a/packages/backend/built/queue/queues/system/scheduled-note.js b/packages/backend/built/queue/queues/system/scheduled-note.js
new file mode 100644
index 0000000..69ccd00
--- /dev/null
+++ b/packages/backend/built/queue/queues/system/scheduled-note.js
@@ -0,0 +1,11 @@
+import { createQueueProducer, defaultJobOpts } from "../index.js";
+const scheduledNoteSystemQueue = createQueueProducer("system");
+export async function enqueueScheduledNote(scheduledNote) {
+ await scheduledNoteSystemQueue.add("publishScheduledNote", {
+ scheduledNoteId: scheduledNote.id
+ }, {
+ ...defaultJobOpts,
+ delay: Math.max(0, scheduledNote.scheduledAt.getTime() - Date.now()),
+ jobId: `scheduledNote:${scheduledNote.id}`
+ });
+}
diff --git a/packages/backend/built/queue/queues/system/tick-charts.js b/packages/backend/built/queue/queues/system/tick-charts.js
new file mode 100644
index 0000000..69e72d7
--- /dev/null
+++ b/packages/backend/built/queue/queues/system/tick-charts.js
@@ -0,0 +1,22 @@
+import { queueLogger } from "../../logger.js";
+import { activeUsersChart, driveChart, federationChart, hashtagChart, instanceChart, notesChart, perUserDriveChart, perUserFollowingChart, perUserNotesChart, perUserReactionsChart, usersChart, apRequestChart } from "../../../services/chart/index.js";
+const logger = queueLogger.createSubLogger("tick-charts");
+export async function tickCharts() {
+ logger.info("Tick charts...");
+ await Promise.all([
+ federationChart.tick(false),
+ notesChart.tick(false),
+ usersChart.tick(false),
+ activeUsersChart.tick(false),
+ instanceChart.tick(false),
+ perUserNotesChart.tick(false),
+ driveChart.tick(false),
+ perUserReactionsChart.tick(false),
+ hashtagChart.tick(false),
+ perUserFollowingChart.tick(false),
+ perUserDriveChart.tick(false),
+ apRequestChart.tick(false)
+ ]);
+ logger.succ("All charts successfully ticked.");
+ return "Success";
+}
diff --git a/packages/backend/built/queue/queues/system/verify-links.js b/packages/backend/built/queue/queues/system/verify-links.js
new file mode 100644
index 0000000..4f5fb27
--- /dev/null
+++ b/packages/backend/built/queue/queues/system/verify-links.js
@@ -0,0 +1,34 @@
+import { UserProfiles } from "../../../models/index.js";
+import { Not } from "typeorm";
+import { queueLogger } from "../../logger.js";
+import { verifyLink } from "../../../services/fetch-rel-me.js";
+const logger = queueLogger.createSubLogger("verify-links");
+export async function verifyLinks() {
+ logger.info("Verifying links...");
+ const usersToVerify = await UserProfiles.findBy({
+ fields: Not(null),
+ userHost: ""
+ });
+ for (const user of usersToVerify){
+ for (const field of user.fields){
+ if (!field || field.name === "" || field.value === "") {
+ continue;
+ }
+ if (field.value.startsWith("http") && user.user?.username) {
+ field.verified = await verifyLink(field.value, user.user.username);
+ }
+ }
+ if (user.fields.length > 0) {
+ try {
+ await UserProfiles.update(user.userId, {
+ fields: user.fields
+ });
+ } catch (e) {
+ logger.error(`Failed to update user ${user.userId} ${e}`);
+ throw e;
+ }
+ }
+ }
+ logger.succ("All links successfully verified.");
+ return "Success";
+}
diff --git a/packages/backend/built/queue/queues/webhook-deliver.js b/packages/backend/built/queue/queues/webhook-deliver.js
new file mode 100644
index 0000000..bee9a76
--- /dev/null
+++ b/packages/backend/built/queue/queues/webhook-deliver.js
@@ -0,0 +1,83 @@
+import Logger from "../../services/logger.js";
+import { getResponse, StatusError } from "../../misc/fetch.js";
+import { Webhooks } from "../../models/index.js";
+import config from "../../config/index.js";
+import { v4 as uuid } from "uuid";
+import { createQueue, defaultJobOpts, processorTimeout } from "./index.js";
+const logger = new Logger("webhook");
+async function process(job) {
+ if (job.data == null || Object.keys(job.data).length === 0) {
+ job.opts.removeOnComplete = true;
+ return "Skip (data was null or empty)";
+ }
+ try {
+ logger.debug(`delivering ${job.data.webhookId}`);
+ const res = await getResponse({
+ url: job.data.to,
+ method: "POST",
+ headers: {
+ "User-Agent": "Iceshrimp-Hooks",
+ "X-Iceshrimp-Host": config.host,
+ "X-Iceshrimp-Hook-Id": job.data.webhookId,
+ "X-Iceshrimp-Hook-Secret": job.data.secret,
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({
+ hookId: job.data.webhookId,
+ userId: job.data.userId,
+ eventId: job.data.eventId,
+ createdAt: job.data.createdAt,
+ type: job.data.type,
+ body: job.data.content
+ })
+ });
+ Webhooks.update({
+ id: job.data.webhookId
+ }, {
+ latestSentAt: new Date(),
+ latestStatus: res.status
+ });
+ return "Success";
+ } catch (res) {
+ Webhooks.update({
+ id: job.data.webhookId
+ }, {
+ latestSentAt: new Date(),
+ latestStatus: res instanceof StatusError ? res.statusCode : 1
+ });
+ if (res instanceof StatusError) {
+ // 4xx
+ if (!res.isRetryable) {
+ return `${res.statusCode} ${res.statusMessage}`;
+ }
+ // 5xx etc.
+ throw new Error(`${res.statusCode} ${res.statusMessage}`);
+ } else {
+ // DNS error, socket error, timeout ...
+ throw res;
+ }
+ }
+}
+export const [webhookDeliverQueue, webhookDeliverInit] = createQueue("webhookDeliver", processorTimeout(process, 60), {
+ limitPerSec: 64,
+ concurrency: 64
+});
+export function webhookDeliverJob(webhook, type, content) {
+ const data = {
+ type,
+ content,
+ webhookId: webhook.id,
+ userId: webhook.userId,
+ to: webhook.url,
+ secret: webhook.secret,
+ createdAt: Date.now(),
+ eventId: uuid()
+ };
+ return webhookDeliverQueue.add("default", data, {
+ attempts: 4,
+ backoff: {
+ type: "custom"
+ },
+ ...defaultJobOpts
+ });
+}
diff --git a/packages/backend/built/queue/types.js b/packages/backend/built/queue/types.js
new file mode 100644
index 0000000..2234b9c
--- /dev/null
+++ b/packages/backend/built/queue/types.js
@@ -0,0 +1 @@
+export { };
diff --git a/packages/backend/built/remote/activitypub/ap-request.js b/packages/backend/built/remote/activitypub/ap-request.js
new file mode 100644
index 0000000..41ec640
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/ap-request.js
@@ -0,0 +1,86 @@
+import * as crypto from "node:crypto";
+import { URL } from "node:url";
+export function createSignedPost(args) {
+ const u = new URL(args.url);
+ const digestHeader = `SHA-256=${crypto.createHash("sha256").update(args.body).digest("base64")}`;
+ const request = {
+ url: u.href,
+ method: "POST",
+ headers: objectAssignWithLcKey({
+ Date: new Date().toUTCString(),
+ Host: u.hostname,
+ "Content-Type": "application/activity+json",
+ Digest: digestHeader
+ }, args.additionalHeaders)
+ };
+ const result = signToRequest(request, args.key, [
+ "(request-target)",
+ "date",
+ "host",
+ "digest"
+ ]);
+ return {
+ request,
+ signingString: result.signingString,
+ signature: result.signature,
+ signatureHeader: result.signatureHeader
+ };
+}
+export function createSignedGet(args) {
+ const u = new URL(args.url);
+ const request = {
+ url: u.href,
+ method: "GET",
+ headers: objectAssignWithLcKey({
+ Accept: "application/activity+json, application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
+ Date: new Date().toUTCString(),
+ Host: new URL(args.url).hostname
+ }, args.additionalHeaders)
+ };
+ const result = signToRequest(request, args.key, [
+ "(request-target)",
+ "date",
+ "host",
+ "accept"
+ ]);
+ return {
+ request,
+ signingString: result.signingString,
+ signature: result.signature,
+ signatureHeader: result.signatureHeader
+ };
+}
+function signToRequest(request, key, includeHeaders) {
+ const signingString = genSigningString(request, includeHeaders);
+ const signature = crypto.sign("sha256", Buffer.from(signingString), key.privateKeyPem).toString("base64");
+ const signatureHeader = `keyId="${key.keyId}",algorithm="rsa-sha256",headers="${includeHeaders.join(" ")}",signature="${signature}"`;
+ request.headers = objectAssignWithLcKey(request.headers, {
+ Signature: signatureHeader
+ });
+ return {
+ request,
+ signingString,
+ signature,
+ signatureHeader
+ };
+}
+function genSigningString(request, includeHeaders) {
+ request.headers = lcObjectKey(request.headers);
+ const results = [];
+ for (const key of includeHeaders.map((x)=>x.toLowerCase())){
+ if (key === "(request-target)") {
+ results.push(`(request-target): ${request.method.toLowerCase()} ${new URL(request.url).pathname}`);
+ } else {
+ results.push(`${key}: ${request.headers[key]}`);
+ }
+ }
+ return results.join("\n");
+}
+function lcObjectKey(src) {
+ const dst = {};
+ for (const key of Object.keys(src).filter((x)=>x !== "__proto__" && typeof src[x] === "string"))dst[key.toLowerCase()] = src[key];
+ return dst;
+}
+function objectAssignWithLcKey(a, b) {
+ return Object.assign(lcObjectKey(a), lcObjectKey(b));
+}
diff --git a/packages/backend/built/remote/activitypub/audience.js b/packages/backend/built/remote/activitypub/audience.js
new file mode 100644
index 0000000..339f6a8
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/audience.js
@@ -0,0 +1,71 @@
+import { getApIds } from "./type.js";
+import Resolver from "./resolver.js";
+import { resolvePerson } from "./models/person.js";
+import { unique, concat } from "../../prelude/array.js";
+import promiseLimit from "promise-limit";
+import { RecursionLimiter } from "../../models/repositories/user-profile.js";
+export async function parseAudience(actor, to, cc, resolver, limiter = new RecursionLimiter()) {
+ const toGroups = groupingAudience(getApIds(to), actor);
+ const ccGroups = groupingAudience(getApIds(cc), actor);
+ const others = unique(concat([
+ toGroups.other,
+ ccGroups.other
+ ]));
+ resolver ??= new Resolver();
+ const limit = promiseLimit(2);
+ const mentionedUsers = (await Promise.all(others.map((id)=>limit(()=>resolvePerson(id, resolver, limiter).catch(()=>null))))).filter((x)=>x != null);
+ if (toGroups.public.length > 0) {
+ return {
+ visibility: "public",
+ mentionedUsers,
+ visibleUsers: []
+ };
+ }
+ if (ccGroups.public.length > 0) {
+ return {
+ visibility: "home",
+ mentionedUsers,
+ visibleUsers: []
+ };
+ }
+ if (toGroups.followers.length > 0) {
+ return {
+ visibility: "followers",
+ mentionedUsers,
+ visibleUsers: []
+ };
+ }
+ return {
+ visibility: "specified",
+ mentionedUsers,
+ visibleUsers: mentionedUsers
+ };
+}
+function groupingAudience(ids, actor) {
+ const groups = {
+ public: [],
+ followers: [],
+ other: []
+ };
+ for (const id of ids){
+ if (isPublic(id)) {
+ groups.public.push(id);
+ } else if (isFollowers(id, actor)) {
+ groups.followers.push(id);
+ } else {
+ groups.other.push(id);
+ }
+ }
+ groups.other = unique(groups.other);
+ return groups;
+}
+function isPublic(id) {
+ return [
+ "https://www.w3.org/ns/activitystreams#Public",
+ "as:Public",
+ "Public"
+ ].includes(id);
+}
+function isFollowers(id, actor) {
+ return id === (actor.followersUri || `${actor.uri}/followers`);
+}
diff --git a/packages/backend/built/remote/activitypub/check-fetch.js b/packages/backend/built/remote/activitypub/check-fetch.js
new file mode 100644
index 0000000..2152ba6
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/check-fetch.js
@@ -0,0 +1,135 @@
+import { URL } from "url";
+import httpSignature from "@peertube/http-signature";
+import config from "../../config/index.js";
+import { fetchMeta } from "../../misc/fetch-meta.js";
+import { toPuny } from "../../misc/convert-host.js";
+import DbResolver from "./db-resolver.js";
+import { getApId } from "./type.js";
+import { shouldBlockInstance } from "../../misc/should-block-instance.js";
+import { verify } from "node:crypto";
+import { toSingle } from "../../prelude/array.js";
+import { createHash } from "node:crypto";
+import { tickFetch } from "../../metrics.js";
+export async function hasSignature(req) {
+ const meta = await fetchMeta();
+ const required = meta.secureMode || meta.privateMode;
+ try {
+ httpSignature.parseRequest(req, {
+ headers: []
+ });
+ } catch (e) {
+ if (e instanceof Error && e.name === "MissingHeaderError") {
+ return required ? "missing" : "optional";
+ }
+ return "invalid";
+ }
+ return required ? "supplied" : "unneeded";
+}
+export async function checkFetch(req) {
+ const meta = await fetchMeta();
+ if (meta.secureMode || meta.privateMode) {
+ if (req.headers.host !== config.host) return 400;
+ let signature;
+ try {
+ signature = httpSignature.parseRequest(req, {
+ headers: [
+ "(request-target)",
+ "host",
+ "date"
+ ],
+ authorizationHeaderName: 'signature'
+ });
+ } catch (e) {
+ return 401;
+ }
+ const keyId = new URL(signature.keyId);
+ const host = toPuny(keyId.hostname);
+ if (await shouldBlockInstance(host, meta)) {
+ return 403;
+ }
+ if (meta.privateMode && host !== config.host && host !== config.domain && !meta.allowedHosts.includes(host)) {
+ return 403;
+ }
+ const keyIdLower = signature.keyId.toLowerCase();
+ if (keyIdLower.startsWith("acct:")) {
+ // Old keyId is no longer supported.
+ return 401;
+ }
+ const dbResolver = new DbResolver();
+ // HTTP-Signature keyIdを元にDBから取得
+ let authUser = await dbResolver.getAuthUserFromKeyId(signature.keyId);
+ // keyIdでわからなければ、resolveしてみる
+ if (authUser == null) {
+ try {
+ keyId.hash = "";
+ authUser = await dbResolver.getAuthUserFromApId(getApId(keyId.toString()));
+ } catch (e) {
+ // できなければ駄目
+ return 403;
+ }
+ }
+ // publicKey がなくても終了
+ if (authUser?.key == null) {
+ return 403;
+ }
+ // Cannot authenticate against local user
+ if (authUser.user.uri === null || authUser.user.host === null) {
+ return 400;
+ }
+ // Check if keyId hostname matches actor hostname
+ if (toPuny(new URL(authUser.user.uri).hostname) !== host) {
+ return 403;
+ }
+ // HTTP-Signatureの検証
+ let httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem);
+ // If signature validation failed, try refetching the actor
+ if (!httpSignatureValidated) {
+ authUser.key = await dbResolver.refetchPublicKeyForApId(authUser.user);
+ if (authUser.key == null) {
+ return 403;
+ }
+ httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem);
+ }
+ if (!httpSignatureValidated) {
+ return 403;
+ }
+ if (!verifySignature(signature, authUser.key)) {
+ return 401;
+ }
+ tickFetch();
+ return 200;
+ }
+ return 200;
+}
+export async function getSignatureUser(req) {
+ const signature = httpSignature.parseRequest(req, {
+ headers: []
+ });
+ const keyId = new URL(signature.keyId);
+ const dbResolver = new DbResolver();
+ // Retrieve from DB by HTTP-Signature keyId
+ const authUser = await dbResolver.getAuthUserFromKeyId(signature.keyId);
+ if (authUser) {
+ return authUser;
+ }
+ // Resolve if failed to retrieve by keyId
+ keyId.hash = "";
+ return await dbResolver.getAuthUserFromApId(getApId(keyId.toString()));
+}
+export function verifySignature(sig, key) {
+ if (![
+ 'hs2019',
+ 'rsa-sha256'
+ ].includes(sig.algorithm.toLowerCase())) return false;
+ try {
+ return verify('rsa-sha256', Buffer.from(sig.signingString, 'utf8'), key.keyPem, Buffer.from(sig.params.signature, 'base64'));
+ } catch {
+ // Algo not supported
+ return false;
+ }
+}
+export function verifyDigest(body, digest) {
+ digest = toSingle(digest);
+ if (body == null || digest == null || !digest.toLowerCase().startsWith('sha-256=')) return false;
+ return createHash('sha256').update(body).digest('base64') === digest.substring(8);
+}
diff --git a/packages/backend/built/remote/activitypub/db-resolver.js b/packages/backend/built/remote/activitypub/db-resolver.js
new file mode 100644
index 0000000..df05d44
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/db-resolver.js
@@ -0,0 +1,148 @@
+import config from "../../config/index.js";
+import { Notes, Users, UserPublickeys, MessagingMessages } from "../../models/index.js";
+import { Cache } from "../../misc/cache.js";
+import { uriPersonCache, userByIdCache } from "../../services/user-cache.js";
+import { getApId } from "./type.js";
+import { resolvePerson, updatePerson } from "./models/person.js";
+import { subscriber } from "../../db/redis.js";
+import { toPuny } from "../../misc/convert-host.js";
+const publicKeyCache = new Cache("publicKey", 60 * 30);
+const publicKeyByUserIdCache = new Cache("publicKeyByUserId", 60 * 30);
+export function parseUri(value) {
+ const uri = getApId(value);
+ const parsed = new URL(uri);
+ if (toPuny(parsed.host) === toPuny(config.host)) {
+ const localRegex = new RegExp(`^.*?/(\\w+)/(\\w+)(?:/(.+))?`);
+ const matchLocal = uri.match(localRegex);
+ if (matchLocal == null) {
+ throw new Error(`Failed to parse local URI: ${uri}`);
+ }
+ return {
+ local: true,
+ type: matchLocal[1],
+ id: matchLocal[2],
+ rest: matchLocal[3]
+ };
+ } else {
+ return {
+ local: false,
+ uri
+ };
+ }
+}
+export default class DbResolver {
+ /**
+ * AP Note => Misskey Note in DB
+ */ async getNoteFromApId(value) {
+ const parsed = parseUri(value);
+ if (parsed.local) {
+ if (parsed.type !== "notes") return null;
+ return await Notes.findOneBy({
+ id: parsed.id
+ });
+ } else {
+ return await Notes.findOne({
+ where: [
+ {
+ uri: parsed.uri
+ },
+ {
+ url: parsed.uri
+ }
+ ]
+ });
+ }
+ }
+ async getMessageFromApId(value) {
+ const parsed = parseUri(value);
+ if (parsed.local) {
+ if (parsed.type !== "notes") return null;
+ return await MessagingMessages.findOneBy({
+ id: parsed.id
+ });
+ } else {
+ return await MessagingMessages.findOneBy({
+ uri: parsed.uri
+ });
+ }
+ }
+ /**
+ * AP Person => Misskey User in DB
+ */ async getUserFromApId(value) {
+ const parsed = parseUri(value);
+ if (parsed.local) {
+ if (parsed.type !== "users") return null;
+ return await userByIdCache.fetchMaybe(parsed.id, ()=>Users.findOneBy({
+ id: parsed.id
+ }).then((x)=>x ?? undefined), true) ?? null;
+ } else {
+ return await uriPersonCache.fetch(parsed.uri, ()=>Users.findOneBy({
+ uri: parsed.uri
+ }), true);
+ }
+ }
+ /**
+ * AP KeyId => Misskey User and Key
+ */ async getAuthUserFromKeyId(keyId) {
+ const key = await publicKeyCache.fetch(keyId, async ()=>{
+ const key = await UserPublickeys.findOneBy({
+ keyId
+ });
+ if (key == null) return null;
+ return key;
+ }, true, (key)=>key != null);
+ if (key == null) return null;
+ return {
+ user: await userByIdCache.fetch(key.userId, ()=>Users.findOneByOrFail({
+ id: key.userId
+ }), true),
+ key
+ };
+ }
+ /**
+ * AP Actor id => Misskey User and Key
+ */ async getAuthUserFromApId(uri) {
+ const user = await resolvePerson(uri);
+ if (user == null) return null;
+ const key = await publicKeyByUserIdCache.fetch(user.id, ()=>UserPublickeys.findOneBy({
+ userId: user.id
+ }), true, (v)=>v != null);
+ return {
+ user,
+ key
+ };
+ }
+ async refetchPublicKeyForApId(user) {
+ try {
+ await updatePerson(user.uri, undefined, undefined, user);
+ let key = await UserPublickeys.findOneBy({
+ userId: user.id
+ });
+ if (key != null) {
+ await publicKeyByUserIdCache.set(user.id, key);
+ }
+ return key;
+ } catch {
+ return null;
+ }
+ }
+}
+subscriber.on("message", async (_, data)=>{
+ const obj = JSON.parse(data);
+ if (obj.channel === "internal") {
+ const { type, body } = obj.message;
+ switch(type){
+ case "remoteUserDeleted":
+ case "localUserDeleted":
+ {
+ const toDelete = Array.from(await publicKeyByUserIdCache.getAll()).filter((v)=>v[1]?.userId === body.id).map((v)=>v[0]);
+ const toDeleteKey = Array.from(await publicKeyCache.getAll()).filter((v)=>v[1]?.userId === body.id).map((v)=>v[0]);
+ await publicKeyByUserIdCache.delete(...toDelete);
+ await publicKeyCache.delete(...toDeleteKey);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+});
diff --git a/packages/backend/built/remote/activitypub/deliver-manager.js b/packages/backend/built/remote/activitypub/deliver-manager.js
new file mode 100644
index 0000000..7c1827a
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/deliver-manager.js
@@ -0,0 +1,120 @@
+import { IsNull, Not } from "typeorm";
+import { Followings, Users } from "../../models/index.js";
+import { deliver } from "../../queue/index.js";
+import { skippedInstances } from "../../misc/skipped-instances.js";
+import { apLogger } from "./logger.js";
+const isFollowers = (recipe)=>recipe.type === "Followers";
+const isDirect = (recipe)=>recipe.type === "Direct";
+//#endregion
+export default class DeliverManager {
+ actor;
+ activity;
+ recipes = [];
+ /**
+ * Constructor
+ * @param actor Actor
+ * @param activity Activity to deliver
+ */ constructor(actor, activity){
+ this.actor = actor;
+ this.activity = activity;
+ }
+ /**
+ * Add recipe for followers deliver
+ */ addFollowersRecipe() {
+ const deliver = {
+ type: "Followers"
+ };
+ this.addRecipe(deliver);
+ }
+ /**
+ * Add recipe for direct deliver
+ * @param to To
+ */ addDirectRecipe(to) {
+ const recipe = {
+ type: "Direct",
+ to
+ };
+ this.addRecipe(recipe);
+ }
+ /**
+ * Add recipe
+ * @param recipe Recipe
+ */ addRecipe(recipe) {
+ this.recipes.push(recipe);
+ }
+ /**
+ * Execute delivers
+ */ async execute() {
+ if (!Users.isLocalUser(this.actor)) return;
+ const inboxes = new Set();
+ /*
+ build inbox list
+
+ Process follower recipes first to avoid duplication when processing
+ direct recipes later.
+ */ if (this.recipes.some((r)=>isFollowers(r))) {
+ // followers deliver
+ // TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう
+ // ただ、sharedInboxがnullなリモートユーザーも稀におり、その対応ができなさそう?
+ const followers = await Followings.find({
+ where: {
+ followeeId: this.actor.id,
+ followerHost: Not(IsNull())
+ },
+ select: {
+ followerSharedInbox: true,
+ followerInbox: true
+ }
+ });
+ for (const following of followers){
+ const inbox = following.followerSharedInbox || following.followerInbox;
+ inboxes.add(inbox);
+ }
+ }
+ this.recipes.filter((recipe)=>// followers recipes have already been processed
+ isDirect(recipe) && // check that shared inbox has not been added yet
+ !(recipe.to.sharedInbox && inboxes.has(recipe.to.sharedInbox)) && // check that they actually have an inbox
+ recipe.to.inbox != null).forEach((recipe)=>inboxes.add(recipe.to.inbox));
+ const instancesToSkip = await skippedInstances(// get (unique) list of hosts
+ Array.from(new Set(Array.from(inboxes).map((inbox)=>{
+ try {
+ return new URL(inbox).host;
+ } catch (e) {
+ apLogger.error(`Invalid inbox URL: ${inbox}`);
+ return null;
+ }
+ }).filter((host)=>host != null))));
+ // deliver
+ for (const inbox of inboxes){
+ // skip instances as indicated
+ try {
+ const host = new URL(inbox).host;
+ if (instancesToSkip.includes(host)) continue;
+ } catch (e) {
+ // skip invalid URLs
+ apLogger.error(`Invalid inbox URL: ${inbox}`);
+ continue;
+ }
+ deliver(this.actor, this.activity, inbox);
+ }
+ }
+}
+//#region Utilities
+/**
+ * Deliver activity to followers
+ * @param activity Activity
+ * @param from Followee
+ */ export async function deliverToFollowers(actor, activity) {
+ const manager = new DeliverManager(actor, activity);
+ manager.addFollowersRecipe();
+ await manager.execute();
+}
+/**
+ * Deliver activity to user
+ * @param activity Activity
+ * @param to Target user
+ */ export async function deliverToUser(actor, activity, to) {
+ const manager = new DeliverManager(actor, activity);
+ manager.addDirectRecipe(to);
+ await manager.execute();
+} //#endregion
diff --git a/packages/backend/built/remote/activitypub/kernel/accept/follow.js b/packages/backend/built/remote/activitypub/kernel/accept/follow.js
new file mode 100644
index 0000000..d082d4f
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/accept/follow.js
@@ -0,0 +1,21 @@
+import accept from "../../../../services/following/requests/accept.js";
+import DbResolver from "../../db-resolver.js";
+import { relayAccepted } from "../../../../services/relay.js";
+export default (async (actor, activity)=>{
+ // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
+ const dbResolver = new DbResolver();
+ const follower = await dbResolver.getUserFromApId(activity.actor);
+ if (follower == null) {
+ return "skip: follower not found";
+ }
+ if (follower.host != null) {
+ return "skip: follower is not a local user";
+ }
+ // relay
+ const match = activity.id?.match(/follow-relay\/(\w+)/);
+ if (match) {
+ return await relayAccepted(match[1]);
+ }
+ await accept(actor, follower);
+ return "ok";
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/accept/index.js b/packages/backend/built/remote/activitypub/kernel/accept/index.js
new file mode 100644
index 0000000..848cd78
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/accept/index.js
@@ -0,0 +1,18 @@
+import Resolver from "../../resolver.js";
+import acceptFollow from "./follow.js";
+import { isFollow, getApType, isQuoteRequest } from "../../type.js";
+import { apLogger } from "../../logger.js";
+import { acceptQuoteRequest } from "./quote-request.js";
+const logger = apLogger;
+export default (async (actor, activity)=>{
+ const uri = activity.id || activity;
+ logger.info(`Accept: ${uri}`);
+ const resolver = new Resolver();
+ const object = await resolver.resolve(activity.object).catch((e)=>{
+ logger.error(`Resolution failed: ${e}`);
+ throw e;
+ });
+ if (isFollow(object)) return await acceptFollow(actor, object);
+ else if (isQuoteRequest(object)) return await acceptQuoteRequest(actor, object, activity.result);
+ return `skip: Unknown Accept type: ${getApType(object)}`;
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/accept/quote-request.js b/packages/backend/built/remote/activitypub/kernel/accept/quote-request.js
new file mode 100644
index 0000000..d2419ec
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/accept/quote-request.js
@@ -0,0 +1,44 @@
+import { resolveNote } from "../../models/note.js";
+import { Notes } from "../../../../models/index.js";
+import { parseUri } from "../../db-resolver.js";
+import edit from "../../../../services/note/edit.js";
+import { toPuny } from "../../../../misc/convert-host.js";
+export async function acceptQuoteRequest(actor, activity, result) {
+ if (!result) return "skip: missing result";
+ const localParsed = parseUri(activity.instrument);
+ if (!localParsed.local) return "skip: note not local";
+ const resultUrl = new URL(result);
+ if (toPuny(resultUrl.host) !== actor.host) {
+ return "skip: result not on same host as actor";
+ }
+ const [note, targetNote] = await Promise.all([
+ Notes.findOne({
+ where: {
+ id: localParsed.id
+ },
+ relations: [
+ "user"
+ ]
+ }),
+ resolveNote(activity.object)
+ ]);
+ if (note === null) return "skip: note not found";
+ if (targetNote === null) return "skip: target note not found";
+ if (targetNote.userId !== actor.id) return "skip: tried to authorize note without ownership";
+ if (note.renoteId === null) return "skip: note not renote";
+ if (note.renoteId !== targetNote.id) return "skip: note not renoting target";
+ if (note.quoteAuthorization !== null) return "skip: quote already authorizated";
+ if (note.text == null && note.cw == null && !note.hasPoll && note.fileIds.length === 0) return "skip: note is plain renote";
+ note.quoteAuthorization = result;
+ await Notes.update({
+ id: note.id
+ }, {
+ quoteAuthorization: note.quoteAuthorization
+ });
+ await edit(note.user, note, {
+ text: note.text,
+ cw: note.cw,
+ quoteAuthorization: result
+ }, true);
+ return "ok";
+}
diff --git a/packages/backend/built/remote/activitypub/kernel/add/index.js b/packages/backend/built/remote/activitypub/kernel/add/index.js
new file mode 100644
index 0000000..71d0e50
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/add/index.js
@@ -0,0 +1,22 @@
+import { resolveNote } from "../../models/note.js";
+import { addPinned } from "../../../../services/i/pin.js";
+import Resolver from "../../resolver.js";
+import { Users } from "../../../../models/index.js";
+export default (async (actor, activity)=>{
+ if ("actor" in activity && actor.uri !== activity.actor) {
+ throw new Error("invalid actor");
+ }
+ if (activity.target == null) {
+ throw new Error("target is null");
+ }
+ if (activity.target === actor.featured) {
+ const resolver = new Resolver();
+ const follower = await Users.getRandomFollower(actor.id);
+ if (follower) resolver.setUser(follower);
+ const note = await resolveNote(activity.object, resolver);
+ if (note == null) throw new Error("note not found");
+ await addPinned(actor, note.id);
+ return "ok";
+ }
+ throw new Error(`unknown target: ${activity.target}`);
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/announce/index.js b/packages/backend/built/remote/activitypub/kernel/announce/index.js
new file mode 100644
index 0000000..a8e02f5
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/announce/index.js
@@ -0,0 +1,12 @@
+import Resolver from "../../resolver.js";
+import announceNote from "./note.js";
+import { getApId } from "../../type.js";
+import { apLogger } from "../../logger.js";
+const logger = apLogger;
+export default (async (actor, activity)=>{
+ const uri = getApId(activity);
+ logger.info(`Announce: ${uri}`);
+ const resolver = new Resolver();
+ const targetUri = getApId(activity.object);
+ return announceNote(resolver, actor, activity, targetUri);
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/announce/note.js b/packages/backend/built/remote/activitypub/kernel/announce/note.js
new file mode 100644
index 0000000..b13a697
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/announce/note.js
@@ -0,0 +1,57 @@
+import post from "../../../../services/note/create.js";
+import { getApId } from "../../type.js";
+import { fetchNote, resolveNote } from "../../models/note.js";
+import { apLogger } from "../../logger.js";
+import { extractDbHost } from "../../../../misc/convert-host.js";
+import { getApLock } from "../../../../misc/app-lock.js";
+import { parseAudience } from "../../audience.js";
+import { StatusError } from "../../../../misc/fetch.js";
+import { Notes } from "../../../../models/index.js";
+import { shouldBlockInstance } from "../../../../misc/should-block-instance.js";
+const logger = apLogger;
+/**
+ * Handle announcement activities
+ */ export default async function(resolver, actor, activity, targetUri) {
+ const uri = getApId(activity);
+ if (actor.isSuspended) {
+ return "skip: actor is suspended";
+ }
+ // Interrupt if you block the announcement destination
+ if (await shouldBlockInstance(extractDbHost(uri))) return "skip: instance is blocked";
+ const unlock = await getApLock(uri);
+ try {
+ // Check if something with the same URI is already registered
+ const exist = await fetchNote(uri);
+ if (exist) {
+ return "skip: note exists";
+ }
+ // Resolve Announce target
+ let renote;
+ try {
+ renote = await resolveNote(targetUri);
+ } catch (e) {
+ // Skip if target is 4xx
+ if (e instanceof StatusError) {
+ if (!e.isRetryable) {
+ logger.warn(`Ignored announce target ${targetUri} - ${e.statusCode}`);
+ return "skip: failed fetching note";
+ }
+ logger.warn(`Error in announce target ${targetUri} - ${e.statusCode || e}`);
+ }
+ throw e;
+ }
+ if (!await Notes.isVisibleForMe(renote, actor.id)) return "skip: invalid actor for this activity";
+ logger.info(`Creating the (Re)Note: ${uri}`);
+ const activityAudience = await parseAudience(actor, activity.to, activity.cc);
+ await post(actor, {
+ createdAt: activity.published ? new Date(activity.published) : null,
+ renote,
+ visibility: activityAudience.visibility,
+ visibleUsers: activityAudience.visibleUsers,
+ uri
+ });
+ return "ok";
+ } finally{
+ unlock();
+ }
+}
diff --git a/packages/backend/built/remote/activitypub/kernel/bite.js b/packages/backend/built/remote/activitypub/kernel/bite.js
new file mode 100644
index 0000000..169742b
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/bite.js
@@ -0,0 +1,61 @@
+import Resolver from "../resolver.js";
+import { fetchPerson } from "../models/person.js";
+import { createBite } from "../../../services/create-bite.js";
+import { tickBiteIncoming } from "../../../metrics.js";
+import { getNote } from "../../../server/api/common/getters.js";
+import { parseUri } from "../db-resolver.js";
+export default (async (actor, bite)=>{
+ if (actor.uri !== bite.actor) {
+ return "skip: actor uri mismatch";
+ }
+ if (bite.id === null) {
+ return "skip: bite id not specified";
+ }
+ const resolver = new Resolver();
+ const biteActor = await fetchPerson(bite.actor, resolver);
+ if (biteActor === null) {
+ return "skip: biteActor is null";
+ }
+ const targetParsed = parseUri(bite.target);
+ if (!targetParsed.local) {
+ return "skip: target is not local";
+ }
+ const targetDbId = targetParsed.id;
+ const targetPathType = targetParsed.type;
+ let targetType;
+ let targetId;
+ let fallback = false;
+ if (targetPathType === "users") {
+ targetType = "user";
+ targetId = targetDbId;
+ } else if (targetPathType === "bites") {
+ targetType = "bite";
+ targetId = targetDbId;
+ } else if (targetPathType === "notes") {
+ targetType = "note";
+ targetId = targetDbId;
+ try {
+ await getNote(targetDbId, actor);
+ } catch (err) {
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") {
+ // note either doesn't exist or the remote user shouldn't be able to access it
+ fallback = true;
+ }
+ }
+ } else {
+ fallback = true;
+ }
+ if (fallback) {
+ // fallback for unknown object types
+ targetType = "user";
+ if (bite.to !== undefined) {
+ const to = Array.isArray(bite.to) ? bite.to[0] : bite.to;
+ targetId = to.split("/").pop();
+ } else {
+ return "skip: unknown type missing to field";
+ }
+ }
+ await createBite(biteActor, targetType, targetId, bite.id, bite.published ? new Date(bite.published) : null);
+ tickBiteIncoming();
+ return "ok";
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/block/index.js b/packages/backend/built/remote/activitypub/kernel/block/index.js
new file mode 100644
index 0000000..9d40104
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/block/index.js
@@ -0,0 +1,20 @@
+import block from "../../../../services/blocking/create.js";
+import DbResolver from "../../db-resolver.js";
+import { Users } from "../../../../models/index.js";
+export default (async (actor, activity)=>{
+ // ※ There is a block target in activity.object, which should be a local user that exists.
+ const dbResolver = new DbResolver();
+ const blockee = await dbResolver.getUserFromApId(activity.object);
+ if (blockee == null) {
+ return "skip: blockee not found";
+ }
+ if (blockee.host != null) {
+ return "skip: The user you are trying to block is not a local user";
+ }
+ await block(await Users.findOneByOrFail({
+ id: actor.id
+ }), await Users.findOneByOrFail({
+ id: blockee.id
+ }));
+ return "ok";
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/create/index.js b/packages/backend/built/remote/activitypub/kernel/create/index.js
new file mode 100644
index 0000000..493be0a
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/create/index.js
@@ -0,0 +1,40 @@
+import Resolver from "../../resolver.js";
+import createNote from "./note.js";
+import { getApId, isPost, getApType } from "../../type.js";
+import { apLogger } from "../../logger.js";
+import { toArray, concat, unique } from "../../../../prelude/array.js";
+const logger = apLogger;
+export default (async (actor, activity)=>{
+ const uri = getApId(activity);
+ logger.info(`Create: ${uri}`);
+ // copy audiences between activity <=> object.
+ if (typeof activity.object === "object") {
+ const to = unique(concat([
+ toArray(activity.to),
+ toArray(activity.object.to)
+ ]));
+ const cc = unique(concat([
+ toArray(activity.cc),
+ toArray(activity.object.cc)
+ ]));
+ activity.to = to;
+ activity.cc = cc;
+ activity.object.to = to;
+ activity.object.cc = cc;
+ }
+ // If there is no attributedTo, use Activity actor.
+ if (typeof activity.object === "object" && !activity.object.attributedTo) {
+ activity.object.attributedTo = activity.actor;
+ }
+ const resolver = new Resolver();
+ const object = await resolver.resolve(activity.object).catch((e)=>{
+ logger.error(`Resolution failed: ${e}`);
+ throw e;
+ });
+ if (isPost(object)) {
+ return createNote(resolver, actor, object, false, activity);
+ } else {
+ logger.warn(`Unknown type: ${getApType(object)}`);
+ return "skip: unknown create type";
+ }
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/create/note.js b/packages/backend/built/remote/activitypub/kernel/create/note.js
new file mode 100644
index 0000000..158d2e0
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/create/note.js
@@ -0,0 +1,37 @@
+import { createNote, fetchNote } from "../../models/note.js";
+import { getApId } from "../../type.js";
+import { getApLock } from "../../../../misc/app-lock.js";
+import { extractDbHost } from "../../../../misc/convert-host.js";
+import { StatusError } from "../../../../misc/fetch.js";
+/**
+ * Handle post creation activity
+ */ export default async function(resolver, actor, note, silent = false, activity) {
+ const uri = getApId(note);
+ if (typeof note === "object") {
+ if (actor.uri !== note.attributedTo) {
+ return "skip: actor.uri !== note.attributedTo";
+ }
+ if (typeof note.id === "string") {
+ if (extractDbHost(actor.uri) !== extractDbHost(note.id)) {
+ return "skip: host in actor.uri !== note.id";
+ }
+ } else {
+ return "skip: note.id is not a string";
+ }
+ }
+ const unlock = await getApLock(uri);
+ try {
+ const exist = await fetchNote(note);
+ if (exist) return "skip: note exists";
+ await createNote(note, resolver, silent);
+ return "ok";
+ } catch (e) {
+ if (e instanceof StatusError && !e.isRetryable) {
+ return `skip ${e.statusCode}`;
+ } else {
+ throw e;
+ }
+ } finally{
+ unlock();
+ }
+}
diff --git a/packages/backend/built/remote/activitypub/kernel/delete/actor.js b/packages/backend/built/remote/activitypub/kernel/delete/actor.js
new file mode 100644
index 0000000..1990b23
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/delete/actor.js
@@ -0,0 +1,23 @@
+import { apLogger } from "../../logger.js";
+import { createDeleteAccountJob } from "../../../../queue/index.js";
+import { Users } from "../../../../models/index.js";
+const logger = apLogger;
+export async function deleteActor(actor, uri) {
+ logger.info(`Deleting the Actor: ${uri}`);
+ if (actor.uri !== uri) {
+ return `skip: delete actor ${actor.uri} !== ${uri}`;
+ }
+ const user = await Users.findOneBy({
+ id: actor.id
+ });
+ if (!user) {
+ return `skip: actor ${actor.id} not found in the local database`;
+ } else if (user.isDeleted) {
+ return `skip: user ${user.id} already deleted`;
+ }
+ const job = await createDeleteAccountJob(actor);
+ await Users.update(actor.id, {
+ isDeleted: true
+ });
+ return `ok: queued ${job.name} ${job.id}`;
+}
diff --git a/packages/backend/built/remote/activitypub/kernel/delete/index.js b/packages/backend/built/remote/activitypub/kernel/delete/index.js
new file mode 100644
index 0000000..8b92685
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/delete/index.js
@@ -0,0 +1,42 @@
+import { toSingle } from "../../../../prelude/array.js";
+import { getApId, isTombstone, validPost, validActor } from "../../type.js";
+import deleteNote from "./note.js";
+import { deleteActor } from "./actor.js";
+/**
+ * Handle delete activity
+ */ export default (async (actor, activity)=>{
+ if ("actor" in activity && actor.uri !== activity.actor) {
+ throw new Error("invalid actor");
+ }
+ // Type of object to be deleted
+ let formerType;
+ if (typeof activity.object === "string") {
+ // The type is unknown, but it has disappeared
+ // anyway, so it does not remote resolve
+ formerType = undefined;
+ } else {
+ const object = activity.object;
+ if (isTombstone(object)) {
+ formerType = toSingle(object.formerType);
+ } else {
+ formerType = toSingle(object.type);
+ }
+ }
+ const uri = getApId(activity.object);
+ // Even if type is unknown, if actor and object are the same,
+ // it must be `Person`.
+ if (!formerType && actor.uri === uri) {
+ formerType = "Person";
+ }
+ // If not, fallback to `Note`.
+ if (!formerType) {
+ formerType = "Note";
+ }
+ if (validPost.includes(formerType)) {
+ return await deleteNote(actor, uri);
+ } else if (validActor.includes(formerType)) {
+ return await deleteActor(actor, uri);
+ } else {
+ return `Unknown type ${formerType}`;
+ }
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/delete/note.js b/packages/backend/built/remote/activitypub/kernel/delete/note.js
new file mode 100644
index 0000000..e7ddcee
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/delete/note.js
@@ -0,0 +1,30 @@
+import deleteNode from "../../../../services/note/delete.js";
+import { apLogger } from "../../logger.js";
+import DbResolver from "../../db-resolver.js";
+import { getApLock } from "../../../../misc/app-lock.js";
+import { deleteMessage } from "../../../../services/messages/delete.js";
+const logger = apLogger;
+export default async function(actor, uri) {
+ logger.info(`Deleting the Note: ${uri}`);
+ const unlock = await getApLock(uri);
+ try {
+ const dbResolver = new DbResolver();
+ const note = await dbResolver.getNoteFromApId(uri);
+ if (note == null) {
+ const message = await dbResolver.getMessageFromApId(uri);
+ if (message == null) return "message not found";
+ if (message.userId !== actor.id) {
+ return "The user trying to delete the post is not the post author";
+ }
+ await deleteMessage(message);
+ return "ok: message deleted";
+ }
+ if (note.userId !== actor.id) {
+ return "The user trying to delete the post is not the post author";
+ }
+ await deleteNode(actor, note);
+ return "ok: note deleted";
+ } finally{
+ unlock();
+ }
+}
diff --git a/packages/backend/built/remote/activitypub/kernel/flag/index.js b/packages/backend/built/remote/activitypub/kernel/flag/index.js
new file mode 100644
index 0000000..e4625f9
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/flag/index.js
@@ -0,0 +1,26 @@
+import config from "../../../../config/index.js";
+import { getApIds } from "../../type.js";
+import { AbuseUserReports, Users } from "../../../../models/index.js";
+import { In } from "typeorm";
+import { genId } from "../../../../misc/gen-id.js";
+export default (async (actor, activity)=>{
+ // The object is `(User | Note) | (User | Note) []`, but it cannot be
+ // matched with all patterns of the DB schema, so the target user is the first
+ // user and it is stored as a comment.
+ const uris = getApIds(activity.object);
+ const userIds = uris.filter((uri)=>uri.startsWith(`${config.url}/users/`)).map((uri)=>uri.split("/").pop());
+ const users = await Users.findBy({
+ id: In(userIds)
+ });
+ if (users.length < 1) return "skip";
+ await AbuseUserReports.insert({
+ id: genId(),
+ createdAt: new Date(),
+ targetUserId: users[0].id,
+ targetUserHost: users[0].host,
+ reporterId: actor.id,
+ reporterHost: actor.host,
+ comment: `${activity.content}\n${JSON.stringify(uris, null, 2)}`
+ });
+ return "ok";
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/follow.js b/packages/backend/built/remote/activitypub/kernel/follow.js
new file mode 100644
index 0000000..e973f2a
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/follow.js
@@ -0,0 +1,14 @@
+import follow from "../../../services/following/create.js";
+import DbResolver from "../db-resolver.js";
+export default (async (actor, activity)=>{
+ const dbResolver = new DbResolver();
+ const followee = await dbResolver.getUserFromApId(activity.object);
+ if (followee == null) {
+ return "skip: followee not found";
+ }
+ if (followee.host != null) {
+ return "skip: user you are trying to follow is not a local user";
+ }
+ await follow(actor, followee, activity.id);
+ return "ok";
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/index.js b/packages/backend/built/remote/activitypub/kernel/index.js
new file mode 100644
index 0000000..04c8ead
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/index.js
@@ -0,0 +1,80 @@
+import { isCreate, isDelete, isUpdate, isRead, isFollow, isAccept, isReject, isAdd, isRemove, isAnnounce, isLike, isUndo, isBlock, isCollectionOrOrderedCollection, isFlag, isMove, getApId, isBite, isQuoteRequest } from "../type.js";
+import { apLogger } from "../logger.js";
+import create from "./create/index.js";
+import performDeleteActivity from "./delete/index.js";
+import performUpdateActivity from "./update/index.js";
+import { performReadActivity } from "./read.js";
+import follow from "./follow.js";
+import undo from "./undo/index.js";
+import like from "./like.js";
+import announce from "./announce/index.js";
+import accept from "./accept/index.js";
+import reject from "./reject/index.js";
+import add from "./add/index.js";
+import remove from "./remove/index.js";
+import block from "./block/index.js";
+import flag from "./flag/index.js";
+import move from "./move/index.js";
+import bite from "./bite.js";
+import quoteRequest from "./quote-request.js";
+import { extractDbHost } from "../../../misc/convert-host.js";
+import { shouldBlockInstance } from "../../../misc/should-block-instance.js";
+export async function performActivity(actor, activity) {
+ if (isCollectionOrOrderedCollection(activity)) {
+ apLogger.debug('Refusing to ingest collection as activity');
+ return "skip: activity is collection";
+ } else {
+ return await performOneActivity(actor, activity);
+ }
+}
+async function performOneActivity(actor, activity) {
+ if (actor.isSuspended) return "skip: actor suspended";
+ if (typeof activity.id !== "undefined") {
+ const host = extractDbHost(getApId(activity));
+ if (await shouldBlockInstance(host)) return "skip: instance blocked";
+ }
+ if (isCreate(activity)) {
+ return await create(actor, activity);
+ } else if (isDelete(activity)) {
+ return await performDeleteActivity(actor, activity);
+ } else if (isUpdate(activity)) {
+ return await performUpdateActivity(actor, activity);
+ } else if (isRead(activity)) {
+ return await performReadActivity(actor, activity);
+ } else if (isFollow(activity)) {
+ return await follow(actor, activity);
+ } else if (isAccept(activity)) {
+ return await accept(actor, activity);
+ } else if (isReject(activity)) {
+ return await reject(actor, activity);
+ } else if (isAdd(activity)) {
+ return await add(actor, activity).catch((err)=>{
+ apLogger.error(err);
+ return `skip: ${err}`;
+ });
+ } else if (isRemove(activity)) {
+ return await remove(actor, activity).catch((err)=>{
+ apLogger.error(err);
+ return `skip: ${err}`;
+ });
+ } else if (isAnnounce(activity)) {
+ return await announce(actor, activity);
+ } else if (isLike(activity)) {
+ return await like(actor, activity);
+ } else if (isUndo(activity)) {
+ return await undo(actor, activity);
+ } else if (isBlock(activity)) {
+ return await block(actor, activity);
+ } else if (isFlag(activity)) {
+ return await flag(actor, activity);
+ } else if (isMove(activity)) {
+ return await move(actor, activity);
+ } else if (isBite(activity)) {
+ return await bite(actor, activity);
+ } else if (isQuoteRequest(activity)) {
+ return await quoteRequest(actor, activity);
+ } else {
+ apLogger.warn(`unrecognized activity type: ${activity.type}`);
+ return `skip: unrecognized activity type: ${activity.type}`;
+ }
+}
diff --git a/packages/backend/built/remote/activitypub/kernel/like.js b/packages/backend/built/remote/activitypub/kernel/like.js
new file mode 100644
index 0000000..b96ceb9
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/like.js
@@ -0,0 +1,16 @@
+import { getApId } from "../type.js";
+import create from "../../../services/note/reaction/create.js";
+import { fetchNote, extractEmojis } from "../models/note.js";
+export default (async (actor, activity)=>{
+ const targetUri = getApId(activity.object);
+ const note = await fetchNote(targetUri);
+ if (!note) return `skip: target note not found ${targetUri}`;
+ await extractEmojis(activity.tag || [], actor.host).catch(()=>null);
+ return await create(actor, note, activity._misskey_reaction || activity.content || activity.name).catch((e)=>{
+ if (e.id === "51c42bb4-931a-456b-bff7-e5a8a70dd298") {
+ return "skip: already reacted";
+ } else {
+ throw e;
+ }
+ }).then(()=>"ok");
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/move/index.js b/packages/backend/built/remote/activitypub/kernel/move/index.js
new file mode 100644
index 0000000..123090b
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/move/index.js
@@ -0,0 +1,55 @@
+import { Followings, Users } from "../../../../models/index.js";
+import { resolvePerson, updatePerson } from "../../models/person.js";
+import create from "../../../../services/following/create.js";
+import deleteFollowing from "../../../../services/following/delete.js";
+import { getApHrefNullable } from "../../type.js";
+export default (async (actor, activity)=>{
+ // ※ There is a block target in activity.object, which should be a local user that exists.
+ // fetch the new and old accounts
+ const targetUri = getApHrefNullable(activity.target);
+ if (!targetUri) return "move: target uri is null";
+ let new_acc = await resolvePerson(targetUri);
+ if (!actor.uri) return "move: actor uri is null";
+ let old_acc = await resolvePerson(actor.uri);
+ // update them if they're remote
+ if (new_acc.uri) await updatePerson(new_acc.uri);
+ if (old_acc.uri) await updatePerson(old_acc.uri);
+ // retrieve updated users
+ new_acc = await resolvePerson(targetUri);
+ old_acc = await resolvePerson(actor.uri);
+ // check if alsoKnownAs of the new account is valid
+ let isValidMove = true;
+ if (old_acc.uri) {
+ if (!new_acc.alsoKnownAs?.includes(old_acc.uri)) {
+ isValidMove = false;
+ }
+ } else if (!new_acc.alsoKnownAs?.includes(old_acc.id)) {
+ isValidMove = false;
+ }
+ if (!isValidMove) {
+ return "skip: accounts invalid";
+ }
+ // add target uri to movedToUri in order to indicate that the user has moved
+ await Users.update(old_acc.id, {
+ movedToUri: targetUri
+ });
+ // follow the new account and unfollow the old one
+ const followings = await Followings.findBy({
+ followeeId: old_acc.id
+ });
+ followings.forEach(async (following)=>{
+ // If follower is local
+ if (!following.followerHost) {
+ try {
+ const follower = await Users.findOneBy({
+ id: following.followerId
+ });
+ if (!follower) return;
+ await create(follower, new_acc);
+ await deleteFollowing(follower, old_acc);
+ } catch {
+ /* empty */ }
+ }
+ });
+ return "ok";
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/quote-request.js b/packages/backend/built/remote/activitypub/kernel/quote-request.js
new file mode 100644
index 0000000..d25e35f
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/quote-request.js
@@ -0,0 +1,41 @@
+import { resolveNote } from "../models/note.js";
+import renderAcceptQuoteRequest from "../renderer/accept-quote-request.js";
+import { deliverToUser } from "../deliver-manager.js";
+import { renderActivity } from "../renderer/index.js";
+import { parseUri } from "../db-resolver.js";
+import { InteractionStamps, Notes } from "../../../models/index.js";
+import { genId } from "../../../misc/gen-id.js";
+export default (async (actor, activity)=>{
+ const localParsed = parseUri(activity.object);
+ if (!localParsed.local) return "skip: local note not local";
+ const [note, targetNote] = await Promise.all([
+ resolveNote(activity.instrument),
+ Notes.findOneBy({
+ id: localParsed.id
+ })
+ ]);
+ if (note === null) return "skip: note not found";
+ if (note.userId !== actor.id) return "skip: actor is requesting authorization for a quote they didn't make";
+ if (targetNote === null) return "skip: target note not found";
+ if (!await Notes.isVisibleForMe(targetNote, actor.id)) return "skip: target note is not visible for remote user";
+ let stamp = await InteractionStamps.findOneBy({
+ noteId: note.id,
+ targetNoteId: targetNote.id
+ });
+ if (stamp === null) {
+ stamp = {
+ id: genId(),
+ type: "quote",
+ noteId: note.id,
+ targetNoteId: targetNote.id
+ };
+ await InteractionStamps.insert(stamp);
+ }
+ stamp.note = note;
+ stamp.targetNote = targetNote;
+ await deliverToUser({
+ id: targetNote.userId,
+ host: null
+ }, renderActivity(await renderAcceptQuoteRequest(activity, stamp)), actor);
+ return "ok";
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/read.js b/packages/backend/built/remote/activitypub/kernel/read.js
new file mode 100644
index 0000000..5ffc9c8
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/read.js
@@ -0,0 +1,24 @@
+import { getApId } from "../type.js";
+import { isSelfHost, extractDbHost } from "../../../misc/convert-host.js";
+import { MessagingMessages } from "../../../models/index.js";
+import { readUserMessagingMessage } from "../../../server/api/common/read-messaging-message.js";
+export const performReadActivity = async (actor, activity)=>{
+ const id = await getApId(activity.object);
+ if (!isSelfHost(extractDbHost(id))) {
+ return `skip: Read to foreign host (${id})`;
+ }
+ const messageId = id.split("/").pop();
+ const message = await MessagingMessages.findOneBy({
+ id: messageId
+ });
+ if (message == null) {
+ return "skip: message not found";
+ }
+ if (actor.id !== message.recipientId) {
+ return "skip: actor is not a message recipient";
+ }
+ await readUserMessagingMessage(message.recipientId, message.userId, [
+ message.id
+ ]);
+ return `ok: mark as read (${message.userId} => ${message.recipientId} ${message.id})`;
+};
diff --git a/packages/backend/built/remote/activitypub/kernel/reject/follow.js b/packages/backend/built/remote/activitypub/kernel/reject/follow.js
new file mode 100644
index 0000000..841e511
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/reject/follow.js
@@ -0,0 +1,22 @@
+import { remoteReject } from "../../../../services/following/reject.js";
+import DbResolver from "../../db-resolver.js";
+import { relayRejected } from "../../../../services/relay.js";
+import { Users } from "../../../../models/index.js";
+export default (async (actor, activity)=>{
+ // ※ `activity.actor` must be an existing local user, since `activity` is a follow request thrown from us.
+ const dbResolver = new DbResolver();
+ const follower = await dbResolver.getUserFromApId(activity.actor);
+ if (follower == null) {
+ return "skip: follower not found";
+ }
+ if (!Users.isLocalUser(follower)) {
+ return "skip: follower is not a local user";
+ }
+ // relay
+ const match = activity.id?.match(/follow-relay\/(\w+)/);
+ if (match) {
+ return await relayRejected(match[1]);
+ }
+ await remoteReject(actor, follower);
+ return "ok";
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/reject/index.js b/packages/backend/built/remote/activitypub/kernel/reject/index.js
new file mode 100644
index 0000000..83895fb
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/reject/index.js
@@ -0,0 +1,16 @@
+import Resolver from "../../resolver.js";
+import rejectFollow from "./follow.js";
+import { isFollow, getApType } from "../../type.js";
+import { apLogger } from "../../logger.js";
+const logger = apLogger;
+export default (async (actor, activity)=>{
+ const uri = activity.id || activity;
+ logger.info(`Reject: ${uri}`);
+ const resolver = new Resolver();
+ const object = await resolver.resolve(activity.object).catch((e)=>{
+ logger.error(`Resolution failed: ${e}`);
+ throw e;
+ });
+ if (isFollow(object)) return await rejectFollow(actor, object);
+ return `skip: Unknown Reject type: ${getApType(object)}`;
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/remove/index.js b/packages/backend/built/remote/activitypub/kernel/remove/index.js
new file mode 100644
index 0000000..d5e81dd
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/remove/index.js
@@ -0,0 +1,17 @@
+import { fetchNote } from "../../models/note.js";
+import { removePinned } from "../../../../services/i/pin.js";
+export default (async (actor, activity)=>{
+ if ("actor" in activity && actor.uri !== activity.actor) {
+ throw new Error("invalid actor");
+ }
+ if (activity.target == null) {
+ throw new Error("target is null");
+ }
+ if (activity.target === actor.featured) {
+ const note = await fetchNote(activity.object);
+ if (note == null) return "skip: note not found"; // not pinned either way
+ await removePinned(actor, note.id);
+ return "ok";
+ }
+ throw new Error(`unknown target: ${activity.target}`);
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/undo/accept.js b/packages/backend/built/remote/activitypub/kernel/undo/accept.js
new file mode 100644
index 0000000..039150f
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/undo/accept.js
@@ -0,0 +1,19 @@
+import unfollow from "../../../../services/following/delete.js";
+import { Followings } from "../../../../models/index.js";
+import DbResolver from "../../db-resolver.js";
+export default (async (actor, activity)=>{
+ const dbResolver = new DbResolver();
+ const follower = await dbResolver.getUserFromApId(activity.object);
+ if (follower == null) {
+ return "skip: follower not found";
+ }
+ const following = await Followings.findOneBy({
+ followerId: follower.id,
+ followeeId: actor.id
+ });
+ if (following) {
+ await unfollow(follower, actor);
+ return "ok: unfollowed";
+ }
+ return "skip: skip: not followed";
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/undo/announce.js b/packages/backend/built/remote/activitypub/kernel/undo/announce.js
new file mode 100644
index 0000000..5e79e9b
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/undo/announce.js
@@ -0,0 +1,13 @@
+import { Notes } from "../../../../models/index.js";
+import { getApId } from "../../type.js";
+import deleteNote from "../../../../services/note/delete.js";
+export const undoAnnounce = async (actor, activity)=>{
+ const uri = getApId(activity);
+ const note = await Notes.findOneBy({
+ uri,
+ userId: actor.id
+ });
+ if (!note) return "skip: no such Announce";
+ await deleteNote(actor, note);
+ return "ok: deleted";
+};
diff --git a/packages/backend/built/remote/activitypub/kernel/undo/block.js b/packages/backend/built/remote/activitypub/kernel/undo/block.js
new file mode 100644
index 0000000..1e4ea9c
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/undo/block.js
@@ -0,0 +1,17 @@
+import unblock from "../../../../services/blocking/delete.js";
+import DbResolver from "../../db-resolver.js";
+import { Users } from "../../../../models/index.js";
+export default (async (actor, activity)=>{
+ const dbResolver = new DbResolver();
+ const blockee = await dbResolver.getUserFromApId(activity.object);
+ if (blockee == null) {
+ return "skip: blockee not found";
+ }
+ if (blockee.host != null) {
+ return "skip: The user you are trying to unblock is not a local user";
+ }
+ await unblock(await Users.findOneByOrFail({
+ id: actor.id
+ }), blockee);
+ return "ok";
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/undo/follow.js b/packages/backend/built/remote/activitypub/kernel/undo/follow.js
new file mode 100644
index 0000000..705c275
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/undo/follow.js
@@ -0,0 +1,31 @@
+import unfollow from "../../../../services/following/delete.js";
+import cancelRequest from "../../../../services/following/requests/cancel.js";
+import { FollowRequests, Followings } from "../../../../models/index.js";
+import DbResolver from "../../db-resolver.js";
+export default (async (actor, activity)=>{
+ const dbResolver = new DbResolver();
+ const followee = await dbResolver.getUserFromApId(activity.object);
+ if (followee == null) {
+ return "skip: followee not found";
+ }
+ if (followee.host != null) {
+ return "skip: The user you are trying to unfollow is not a local user";
+ }
+ const req = await FollowRequests.findOneBy({
+ followerId: actor.id,
+ followeeId: followee.id
+ });
+ const following = await Followings.findOneBy({
+ followerId: actor.id,
+ followeeId: followee.id
+ });
+ if (req) {
+ await cancelRequest(followee, actor);
+ return "ok: follow request canceled";
+ }
+ if (following) {
+ await unfollow(actor, followee);
+ return "ok: unfollowed";
+ }
+ return "skip: Not requested or followed";
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/undo/index.js b/packages/backend/built/remote/activitypub/kernel/undo/index.js
new file mode 100644
index 0000000..e744989
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/undo/index.js
@@ -0,0 +1,27 @@
+import { isFollow, isBlock, isLike, isAnnounce, getApType, isAccept } from "../../type.js";
+import unfollow from "./follow.js";
+import unblock from "./block.js";
+import undoLike from "./like.js";
+import undoAccept from "./accept.js";
+import { undoAnnounce } from "./announce.js";
+import Resolver from "../../resolver.js";
+import { apLogger } from "../../logger.js";
+const logger = apLogger;
+export default (async (actor, activity)=>{
+ if ("actor" in activity && actor.uri !== activity.actor) {
+ throw new Error("invalid actor");
+ }
+ const uri = activity.id || activity;
+ logger.info(`Undo: ${uri}`);
+ const resolver = new Resolver();
+ const object = await resolver.resolve(activity.object).catch((e)=>{
+ logger.error(`Resolution failed: ${e}`);
+ throw e;
+ });
+ if (isFollow(object)) return await unfollow(actor, object);
+ if (isBlock(object)) return await unblock(actor, object);
+ if (isLike(object)) return await undoLike(actor, object);
+ if (isAnnounce(object)) return await undoAnnounce(actor, object);
+ if (isAccept(object)) return await undoAccept(actor, object);
+ return `skip: unknown object type ${getApType(object)}`;
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/undo/like.js b/packages/backend/built/remote/activitypub/kernel/undo/like.js
new file mode 100644
index 0000000..0ede1d3
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/undo/like.js
@@ -0,0 +1,15 @@
+import { getApId } from "../../type.js";
+import deleteReaction from "../../../../services/note/reaction/delete.js";
+import { fetchNote } from "../../models/note.js";
+/**
+ * Process Undo.Like activity
+ */ export default (async (actor, activity)=>{
+ const targetUri = getApId(activity.object);
+ const note = await fetchNote(targetUri);
+ if (!note) return `skip: target note not found ${targetUri}`;
+ await deleteReaction(actor, note).catch((e)=>{
+ if (e.id === "60527ec9-b4cb-4a88-a6bd-32d3ad26817d") return;
+ throw e;
+ });
+ return "ok";
+});
diff --git a/packages/backend/built/remote/activitypub/kernel/update/index.js b/packages/backend/built/remote/activitypub/kernel/update/index.js
new file mode 100644
index 0000000..343f0b7
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/kernel/update/index.js
@@ -0,0 +1,41 @@
+import { getApId } from "../../type.js";
+import { getApType, isActor } from "../../type.js";
+import { apLogger } from "../../logger.js";
+import { updateNote } from "../../models/note.js";
+import Resolver from "../../resolver.js";
+import { updatePerson } from "../../models/person.js";
+/**
+ * Handler for the Update activity
+ */ export default (async (actor, activity)=>{
+ if (actor.uri == null || actor.uri !== getApId(activity.actor)) {
+ return "skip: invalid actor";
+ }
+ apLogger.debug("Update");
+ const resolver = new Resolver();
+ const object = await resolver.resolve(activity.object).catch((e)=>{
+ apLogger.error(`Resolution failed: ${e}`);
+ throw e;
+ });
+ if (isActor(object)) {
+ if (actor.uri !== object.id) {
+ return "skip: actor id mismatch";
+ }
+ await updatePerson(actor.uri, resolver, object);
+ return "ok: Person updated";
+ }
+ const objectType = getApType(object);
+ switch(objectType){
+ case "Question":
+ case "Note":
+ case "Article":
+ case "Document":
+ case "Page":
+ let failed = false;
+ await updateNote(object, actor, resolver).catch((e)=>{
+ failed = true;
+ });
+ return failed ? "skip: Note update failed" : "ok: Note updated";
+ default:
+ return `skip: Unknown type: ${objectType}`;
+ }
+});
diff --git a/packages/backend/built/remote/activitypub/logger.js b/packages/backend/built/remote/activitypub/logger.js
new file mode 100644
index 0000000..4130285
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/logger.js
@@ -0,0 +1,2 @@
+import { remoteLogger } from "../logger.js";
+export const apLogger = remoteLogger.createSubLogger("ap", "magenta");
diff --git a/packages/backend/built/remote/activitypub/misc/contexts.js b/packages/backend/built/remote/activitypub/misc/contexts.js
new file mode 100644
index 0000000..1311767
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/misc/contexts.js
@@ -0,0 +1,693 @@
+const id_v1 = {
+ "@context": {
+ id: "@id",
+ type: "@type",
+ cred: "https://w3id.org/credentials#",
+ dc: "http://purl.org/dc/terms/",
+ identity: "https://w3id.org/identity#",
+ perm: "https://w3id.org/permissions#",
+ ps: "https://w3id.org/payswarm#",
+ rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
+ rdfs: "http://www.w3.org/2000/01/rdf-schema#",
+ sec: "https://w3id.org/security#",
+ schema: "http://schema.org/",
+ xsd: "http://www.w3.org/2001/XMLSchema#",
+ Group: "https://www.w3.org/ns/activitystreams#Group",
+ claim: {
+ "@id": "cred:claim",
+ "@type": "@id"
+ },
+ credential: {
+ "@id": "cred:credential",
+ "@type": "@id"
+ },
+ issued: {
+ "@id": "cred:issued",
+ "@type": "xsd:dateTime"
+ },
+ issuer: {
+ "@id": "cred:issuer",
+ "@type": "@id"
+ },
+ recipient: {
+ "@id": "cred:recipient",
+ "@type": "@id"
+ },
+ Credential: "cred:Credential",
+ CryptographicKeyCredential: "cred:CryptographicKeyCredential",
+ about: {
+ "@id": "schema:about",
+ "@type": "@id"
+ },
+ address: {
+ "@id": "schema:address",
+ "@type": "@id"
+ },
+ addressCountry: "schema:addressCountry",
+ addressLocality: "schema:addressLocality",
+ addressRegion: "schema:addressRegion",
+ comment: "rdfs:comment",
+ created: {
+ "@id": "dc:created",
+ "@type": "xsd:dateTime"
+ },
+ creator: {
+ "@id": "dc:creator",
+ "@type": "@id"
+ },
+ description: "schema:description",
+ email: "schema:email",
+ familyName: "schema:familyName",
+ givenName: "schema:givenName",
+ image: {
+ "@id": "schema:image",
+ "@type": "@id"
+ },
+ label: "rdfs:label",
+ name: "schema:name",
+ postalCode: "schema:postalCode",
+ streetAddress: "schema:streetAddress",
+ title: "dc:title",
+ url: {
+ "@id": "schema:url",
+ "@type": "@id"
+ },
+ Person: "schema:Person",
+ PostalAddress: "schema:PostalAddress",
+ Organization: "schema:Organization",
+ identityService: {
+ "@id": "identity:identityService",
+ "@type": "@id"
+ },
+ idp: {
+ "@id": "identity:idp",
+ "@type": "@id"
+ },
+ Identity: "identity:Identity",
+ paymentProcessor: "ps:processor",
+ preferences: {
+ "@id": "ps:preferences",
+ "@type": "@vocab"
+ },
+ cipherAlgorithm: "sec:cipherAlgorithm",
+ cipherData: "sec:cipherData",
+ cipherKey: "sec:cipherKey",
+ digestAlgorithm: "sec:digestAlgorithm",
+ digestValue: "sec:digestValue",
+ domain: "sec:domain",
+ expires: {
+ "@id": "sec:expiration",
+ "@type": "xsd:dateTime"
+ },
+ initializationVector: "sec:initializationVector",
+ member: {
+ "@id": "schema:member",
+ "@type": "@id"
+ },
+ memberOf: {
+ "@id": "schema:memberOf",
+ "@type": "@id"
+ },
+ nonce: "sec:nonce",
+ normalizationAlgorithm: "sec:normalizationAlgorithm",
+ owner: {
+ "@id": "sec:owner",
+ "@type": "@id"
+ },
+ password: "sec:password",
+ privateKey: {
+ "@id": "sec:privateKey",
+ "@type": "@id"
+ },
+ privateKeyPem: "sec:privateKeyPem",
+ publicKey: {
+ "@id": "sec:publicKey",
+ "@type": "@id"
+ },
+ publicKeyPem: "sec:publicKeyPem",
+ publicKeyService: {
+ "@id": "sec:publicKeyService",
+ "@type": "@id"
+ },
+ revoked: {
+ "@id": "sec:revoked",
+ "@type": "xsd:dateTime"
+ },
+ signature: "sec:signature",
+ signatureAlgorithm: "sec:signatureAlgorithm",
+ signatureValue: "sec:signatureValue",
+ CryptographicKey: "sec:Key",
+ EncryptedMessage: "sec:EncryptedMessage",
+ GraphSignature2012: "sec:GraphSignature2012",
+ LinkedDataSignature2015: "sec:LinkedDataSignature2015",
+ accessControl: {
+ "@id": "perm:accessControl",
+ "@type": "@id"
+ },
+ writePermission: {
+ "@id": "perm:writePermission",
+ "@type": "@id"
+ }
+ }
+};
+const security_v1 = {
+ "@context": {
+ id: "@id",
+ type: "@type",
+ dc: "http://purl.org/dc/terms/",
+ sec: "https://w3id.org/security#",
+ xsd: "http://www.w3.org/2001/XMLSchema#",
+ EcdsaKoblitzSignature2016: "sec:EcdsaKoblitzSignature2016",
+ Ed25519Signature2018: "sec:Ed25519Signature2018",
+ EncryptedMessage: "sec:EncryptedMessage",
+ GraphSignature2012: "sec:GraphSignature2012",
+ LinkedDataSignature2015: "sec:LinkedDataSignature2015",
+ LinkedDataSignature2016: "sec:LinkedDataSignature2016",
+ CryptographicKey: "sec:Key",
+ authenticationTag: "sec:authenticationTag",
+ canonicalizationAlgorithm: "sec:canonicalizationAlgorithm",
+ cipherAlgorithm: "sec:cipherAlgorithm",
+ cipherData: "sec:cipherData",
+ cipherKey: "sec:cipherKey",
+ created: {
+ "@id": "dc:created",
+ "@type": "xsd:dateTime"
+ },
+ creator: {
+ "@id": "dc:creator",
+ "@type": "@id"
+ },
+ digestAlgorithm: "sec:digestAlgorithm",
+ digestValue: "sec:digestValue",
+ domain: "sec:domain",
+ encryptionKey: "sec:encryptionKey",
+ expiration: {
+ "@id": "sec:expiration",
+ "@type": "xsd:dateTime"
+ },
+ expires: {
+ "@id": "sec:expiration",
+ "@type": "xsd:dateTime"
+ },
+ initializationVector: "sec:initializationVector",
+ iterationCount: "sec:iterationCount",
+ nonce: "sec:nonce",
+ normalizationAlgorithm: "sec:normalizationAlgorithm",
+ owner: {
+ "@id": "sec:owner",
+ "@type": "@id"
+ },
+ password: "sec:password",
+ privateKey: {
+ "@id": "sec:privateKey",
+ "@type": "@id"
+ },
+ privateKeyPem: "sec:privateKeyPem",
+ publicKey: {
+ "@id": "sec:publicKey",
+ "@type": "@id"
+ },
+ publicKeyBase58: "sec:publicKeyBase58",
+ publicKeyPem: "sec:publicKeyPem",
+ publicKeyWif: "sec:publicKeyWif",
+ publicKeyService: {
+ "@id": "sec:publicKeyService",
+ "@type": "@id"
+ },
+ revoked: {
+ "@id": "sec:revoked",
+ "@type": "xsd:dateTime"
+ },
+ salt: "sec:salt",
+ signature: "sec:signature",
+ signatureAlgorithm: "sec:signingAlgorithm",
+ signatureValue: "sec:signatureValue"
+ }
+};
+const activitystreams = {
+ "@context": {
+ "@vocab": "_:",
+ xsd: "http://www.w3.org/2001/XMLSchema#",
+ as: "https://www.w3.org/ns/activitystreams#",
+ ldp: "http://www.w3.org/ns/ldp#",
+ vcard: "http://www.w3.org/2006/vcard/ns#",
+ id: "@id",
+ type: "@type",
+ Accept: "as:Accept",
+ Activity: "as:Activity",
+ IntransitiveActivity: "as:IntransitiveActivity",
+ Add: "as:Add",
+ Announce: "as:Announce",
+ Application: "as:Application",
+ Arrive: "as:Arrive",
+ Article: "as:Article",
+ Audio: "as:Audio",
+ Block: "as:Block",
+ Collection: "as:Collection",
+ CollectionPage: "as:CollectionPage",
+ Relationship: "as:Relationship",
+ Create: "as:Create",
+ Delete: "as:Delete",
+ Dislike: "as:Dislike",
+ Document: "as:Document",
+ Event: "as:Event",
+ Follow: "as:Follow",
+ Flag: "as:Flag",
+ Group: "as:Group",
+ Ignore: "as:Ignore",
+ Image: "as:Image",
+ Invite: "as:Invite",
+ Join: "as:Join",
+ Leave: "as:Leave",
+ Like: "as:Like",
+ Link: "as:Link",
+ Mention: "as:Mention",
+ Note: "as:Note",
+ Object: "as:Object",
+ Offer: "as:Offer",
+ OrderedCollection: "as:OrderedCollection",
+ OrderedCollectionPage: "as:OrderedCollectionPage",
+ Organization: "as:Organization",
+ Page: "as:Page",
+ Person: "as:Person",
+ Place: "as:Place",
+ Profile: "as:Profile",
+ Question: "as:Question",
+ Reject: "as:Reject",
+ Remove: "as:Remove",
+ Service: "as:Service",
+ TentativeAccept: "as:TentativeAccept",
+ TentativeReject: "as:TentativeReject",
+ Tombstone: "as:Tombstone",
+ Undo: "as:Undo",
+ Update: "as:Update",
+ Video: "as:Video",
+ View: "as:View",
+ Listen: "as:Listen",
+ Read: "as:Read",
+ Move: "as:Move",
+ Travel: "as:Travel",
+ IsFollowing: "as:IsFollowing",
+ IsFollowedBy: "as:IsFollowedBy",
+ IsContact: "as:IsContact",
+ IsMember: "as:IsMember",
+ subject: {
+ "@id": "as:subject",
+ "@type": "@id"
+ },
+ relationship: {
+ "@id": "as:relationship",
+ "@type": "@id"
+ },
+ actor: {
+ "@id": "as:actor",
+ "@type": "@id"
+ },
+ attributedTo: {
+ "@id": "as:attributedTo",
+ "@type": "@id"
+ },
+ attachment: {
+ "@id": "as:attachment",
+ "@type": "@id"
+ },
+ bcc: {
+ "@id": "as:bcc",
+ "@type": "@id"
+ },
+ bto: {
+ "@id": "as:bto",
+ "@type": "@id"
+ },
+ cc: {
+ "@id": "as:cc",
+ "@type": "@id"
+ },
+ context: {
+ "@id": "as:context",
+ "@type": "@id"
+ },
+ current: {
+ "@id": "as:current",
+ "@type": "@id"
+ },
+ first: {
+ "@id": "as:first",
+ "@type": "@id"
+ },
+ generator: {
+ "@id": "as:generator",
+ "@type": "@id"
+ },
+ icon: {
+ "@id": "as:icon",
+ "@type": "@id"
+ },
+ image: {
+ "@id": "as:image",
+ "@type": "@id"
+ },
+ inReplyTo: {
+ "@id": "as:inReplyTo",
+ "@type": "@id"
+ },
+ items: {
+ "@id": "as:items",
+ "@type": "@id"
+ },
+ instrument: {
+ "@id": "as:instrument",
+ "@type": "@id"
+ },
+ orderedItems: {
+ "@id": "as:items",
+ "@type": "@id",
+ "@container": "@list"
+ },
+ last: {
+ "@id": "as:last",
+ "@type": "@id"
+ },
+ location: {
+ "@id": "as:location",
+ "@type": "@id"
+ },
+ next: {
+ "@id": "as:next",
+ "@type": "@id"
+ },
+ object: {
+ "@id": "as:object",
+ "@type": "@id"
+ },
+ oneOf: {
+ "@id": "as:oneOf",
+ "@type": "@id"
+ },
+ anyOf: {
+ "@id": "as:anyOf",
+ "@type": "@id"
+ },
+ closed: {
+ "@id": "as:closed",
+ "@type": "xsd:dateTime"
+ },
+ origin: {
+ "@id": "as:origin",
+ "@type": "@id"
+ },
+ accuracy: {
+ "@id": "as:accuracy",
+ "@type": "xsd:float"
+ },
+ prev: {
+ "@id": "as:prev",
+ "@type": "@id"
+ },
+ preview: {
+ "@id": "as:preview",
+ "@type": "@id"
+ },
+ replies: {
+ "@id": "as:replies",
+ "@type": "@id"
+ },
+ result: {
+ "@id": "as:result",
+ "@type": "@id"
+ },
+ audience: {
+ "@id": "as:audience",
+ "@type": "@id"
+ },
+ partOf: {
+ "@id": "as:partOf",
+ "@type": "@id"
+ },
+ tag: {
+ "@id": "as:tag",
+ "@type": "@id"
+ },
+ target: {
+ "@id": "as:target",
+ "@type": "@id"
+ },
+ to: {
+ "@id": "as:to",
+ "@type": "@id"
+ },
+ url: {
+ "@id": "as:url",
+ "@type": "@id"
+ },
+ altitude: {
+ "@id": "as:altitude",
+ "@type": "xsd:float"
+ },
+ content: "as:content",
+ contentMap: {
+ "@id": "as:content",
+ "@container": "@language"
+ },
+ name: "as:name",
+ nameMap: {
+ "@id": "as:name",
+ "@container": "@language"
+ },
+ duration: {
+ "@id": "as:duration",
+ "@type": "xsd:duration"
+ },
+ endTime: {
+ "@id": "as:endTime",
+ "@type": "xsd:dateTime"
+ },
+ height: {
+ "@id": "as:height",
+ "@type": "xsd:nonNegativeInteger"
+ },
+ href: {
+ "@id": "as:href",
+ "@type": "@id"
+ },
+ hreflang: "as:hreflang",
+ latitude: {
+ "@id": "as:latitude",
+ "@type": "xsd:float"
+ },
+ longitude: {
+ "@id": "as:longitude",
+ "@type": "xsd:float"
+ },
+ mediaType: "as:mediaType",
+ published: {
+ "@id": "as:published",
+ "@type": "xsd:dateTime"
+ },
+ radius: {
+ "@id": "as:radius",
+ "@type": "xsd:float"
+ },
+ rel: "as:rel",
+ startIndex: {
+ "@id": "as:startIndex",
+ "@type": "xsd:nonNegativeInteger"
+ },
+ startTime: {
+ "@id": "as:startTime",
+ "@type": "xsd:dateTime"
+ },
+ summary: "as:summary",
+ summaryMap: {
+ "@id": "as:summary",
+ "@container": "@language"
+ },
+ totalItems: {
+ "@id": "as:totalItems",
+ "@type": "xsd:nonNegativeInteger"
+ },
+ units: "as:units",
+ updated: {
+ "@id": "as:updated",
+ "@type": "xsd:dateTime"
+ },
+ width: {
+ "@id": "as:width",
+ "@type": "xsd:nonNegativeInteger"
+ },
+ describes: {
+ "@id": "as:describes",
+ "@type": "@id"
+ },
+ formerType: {
+ "@id": "as:formerType",
+ "@type": "@id"
+ },
+ deleted: {
+ "@id": "as:deleted",
+ "@type": "xsd:dateTime"
+ },
+ inbox: {
+ "@id": "ldp:inbox",
+ "@type": "@id"
+ },
+ outbox: {
+ "@id": "as:outbox",
+ "@type": "@id"
+ },
+ following: {
+ "@id": "as:following",
+ "@type": "@id"
+ },
+ followers: {
+ "@id": "as:followers",
+ "@type": "@id"
+ },
+ streams: {
+ "@id": "as:streams",
+ "@type": "@id"
+ },
+ preferredUsername: "as:preferredUsername",
+ endpoints: {
+ "@id": "as:endpoints",
+ "@type": "@id"
+ },
+ uploadMedia: {
+ "@id": "as:uploadMedia",
+ "@type": "@id"
+ },
+ proxyUrl: {
+ "@id": "as:proxyUrl",
+ "@type": "@id"
+ },
+ liked: {
+ "@id": "as:liked",
+ "@type": "@id"
+ },
+ oauthAuthorizationEndpoint: {
+ "@id": "as:oauthAuthorizationEndpoint",
+ "@type": "@id"
+ },
+ oauthTokenEndpoint: {
+ "@id": "as:oauthTokenEndpoint",
+ "@type": "@id"
+ },
+ provideClientKey: {
+ "@id": "as:provideClientKey",
+ "@type": "@id"
+ },
+ signClientKey: {
+ "@id": "as:signClientKey",
+ "@type": "@id"
+ },
+ sharedInbox: {
+ "@id": "as:sharedInbox",
+ "@type": "@id"
+ },
+ Public: {
+ "@id": "as:Public",
+ "@type": "@id"
+ },
+ source: "as:source",
+ likes: {
+ "@id": "as:likes",
+ "@type": "@id"
+ },
+ shares: {
+ "@id": "as:shares",
+ "@type": "@id"
+ },
+ alsoKnownAs: {
+ "@id": "as:alsoKnownAs",
+ "@type": "@id"
+ }
+ }
+};
+export const WellKnownContext = {
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ // as non-standards
+ manuallyApprovesFollowers: "as:manuallyApprovesFollowers",
+ movedTo: {
+ "@id": "https://www.w3.org/ns/activitystreams#movedTo",
+ "@type": "@id"
+ },
+ movedToUri: "as:movedTo",
+ sensitive: "as:sensitive",
+ Hashtag: "as:Hashtag",
+ quoteUri: "fedibird:quoteUri",
+ quoteUrl: "as:quoteUrl",
+ // Mastodon
+ toot: "http://joinmastodon.org/ns#",
+ Emoji: "toot:Emoji",
+ featured: "toot:featured",
+ discoverable: "toot:discoverable",
+ // schema
+ schema: "http://schema.org#",
+ PropertyValue: "schema:PropertyValue",
+ value: "schema:value",
+ // Misskey
+ misskey: "https://misskey-hub.net/ns#",
+ _misskey_content: "misskey:_misskey_content",
+ _misskey_quote: "misskey:_misskey_quote",
+ _misskey_reaction: "misskey:_misskey_reaction",
+ _misskey_votes: "misskey:_misskey_votes",
+ _misskey_talk: "misskey:_misskey_talk",
+ _misskey_summary: "misskey:_misskey_summary",
+ isCat: "misskey:isCat",
+ // Fedibird
+ fedibird: "http://fedibird.com/ns#",
+ // vcard
+ vcard: "http://www.w3.org/2006/vcard/ns#",
+ // litepub
+ litepub: "http://litepub.social/ns#",
+ EmojiReact: "litepub:EmojiReact",
+ EmojiReaction: "litepub:EmojiReaction",
+ // mia
+ Bite: "https://ns.mia.jetzt/as#Bite",
+ canBite: {
+ "@id": "https://ns.mia.jetzt/as#canBite",
+ "@type": "@id"
+ },
+ // pancakes
+ pronouns: {
+ "@id": "https://ns.pancakes.gay/as#pronouns",
+ "@container": "@language"
+ },
+ // mastodon-style quotes
+ QuoteAuthorization: "https://w3id.org/fep/044f#QuoteAuthorization",
+ quote: {
+ "@id": "https://w3id.org/fep/044f#quote",
+ "@type": "@id"
+ },
+ gts: "https://gotosocial.org/ns#",
+ interactionPolicy: {
+ "@id": "gts:interactionPolicy",
+ "@type": "@id"
+ },
+ canQuote: {
+ "@id": "gts:canQuote",
+ "@type": "@id"
+ },
+ automaticApproval: {
+ "@id": "gts:automaticApproval",
+ "@type": "@id"
+ },
+ interactingObject: {
+ "@id": "gts:interactingObject",
+ "@type": "@id"
+ },
+ interactionTarget: {
+ "@id": "gts:interactionTarget",
+ "@type": "@id"
+ }
+ }
+ ]
+};
+export const CONTEXTS = {
+ "https://w3id.org/identity/v1": id_v1,
+ "https://w3id.org/security/v1": security_v1,
+ "https://www.w3.org/ns/activitystreams": activitystreams
+};
diff --git a/packages/backend/built/remote/activitypub/misc/get-note-html.js b/packages/backend/built/remote/activitypub/misc/get-note-html.js
new file mode 100644
index 0000000..6e319eb
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/misc/get-note-html.js
@@ -0,0 +1,6 @@
+import * as mfm from "mfm-js";
+import { toHtml } from "../../../mfm/to-html.js";
+export default async function(note) {
+ if (!note.text) return "";
+ return toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers), note.userHost);
+}
diff --git a/packages/backend/built/remote/activitypub/misc/html-to-mfm.js b/packages/backend/built/remote/activitypub/misc/html-to-mfm.js
new file mode 100644
index 0000000..23e4d7f
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/misc/html-to-mfm.js
@@ -0,0 +1,6 @@
+import { extractApHashtagObjects } from "../models/tag.js";
+import { fromHtml } from "../../../mfm/from-html.js";
+export async function htmlToMfm(html, tag) {
+ const hashtagNames = extractApHashtagObjects(tag).map((x)=>x.name).filter((x)=>x != null);
+ return await fromHtml(html, hashtagNames);
+}
diff --git a/packages/backend/built/remote/activitypub/misc/ld-signature.js b/packages/backend/built/remote/activitypub/misc/ld-signature.js
new file mode 100644
index 0000000..9e2fb6f
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/misc/ld-signature.js
@@ -0,0 +1,145 @@
+import * as crypto from "node:crypto";
+import jsonld from "jsonld";
+import { CONTEXTS, WellKnownContext } from "./contexts.js";
+import fetch from "node-fetch";
+import { httpAgent, httpsAgent } from "../../../misc/fetch.js";
+// RsaSignature2017 based from https://github.com/transmute-industries/RsaSignature2017
+export class LdSignature {
+ debug = false;
+ preLoad = true;
+ loaderTimeout = 10 * 1000;
+ async signRsaSignature2017(data, privateKey, creator, domain, created) {
+ const options = {
+ type: "RsaSignature2017",
+ creator,
+ domain,
+ nonce: crypto.randomBytes(16).toString("hex"),
+ created: (created || new Date()).toISOString()
+ };
+ if (!domain) {
+ options.domain = undefined;
+ }
+ const toBeSigned = await this.createVerifyData(data, options);
+ const signer = crypto.createSign("sha256");
+ signer.update(toBeSigned);
+ signer.end();
+ const signature = signer.sign(privateKey);
+ return {
+ ...data,
+ signature: {
+ ...options,
+ signatureValue: signature.toString("base64")
+ }
+ };
+ }
+ async verifyRsaSignature2017(data, signature, publicKey) {
+ const toBeSigned = await this.createVerifyData(data, signature);
+ const verifier = crypto.createVerify("sha256");
+ verifier.update(toBeSigned);
+ return verifier.verify(publicKey, signature.signatureValue, "base64");
+ }
+ async createVerifyData(data, options) {
+ const transformedOptions = {
+ ...options,
+ "@context": "https://w3id.org/identity/v1"
+ };
+ delete transformedOptions["type"];
+ delete transformedOptions["id"];
+ delete transformedOptions["signatureValue"];
+ const canonizedOptions = await this.normalize(transformedOptions);
+ const optionsHash = this.sha256(canonizedOptions);
+ const transformedData = {
+ ...data
+ };
+ const cannonidedData = await this.normalize(transformedData);
+ if (this.debug) console.debug(`cannonidedData: ${cannonidedData}`);
+ const documentHash = this.sha256(cannonidedData);
+ const verifyData = `${optionsHash}${documentHash}`;
+ return verifyData;
+ }
+ async normalize(data) {
+ const customLoader = this.getLoader();
+ return await jsonld.normalize(data, {
+ documentLoader: customLoader
+ });
+ }
+ async compactToWellKnown(data) {
+ const options = {
+ documentLoader: this.getLoader()
+ };
+ const context = WellKnownContext;
+ return await jsonld.compact(data, context, options);
+ }
+ getLoader() {
+ return async (url)=>{
+ if (!url.match("^https?://")) throw new Error(`Invalid URL ${url}`);
+ if (this.preLoad) {
+ if (url in CONTEXTS) {
+ if (this.debug) console.debug(`HIT: ${url}`);
+ return {
+ contextUrl: null,
+ document: CONTEXTS[url],
+ documentUrl: url
+ };
+ }
+ }
+ if (this.debug) console.debug(`MISS: ${url}`);
+ const document = await this.fetchDocument(url);
+ return {
+ contextUrl: null,
+ document: document,
+ documentUrl: url
+ };
+ };
+ }
+ async fetchDocument(url) {
+ const json = await fetch(url, {
+ headers: {
+ Accept: "application/ld+json, application/json"
+ },
+ size: 1024 * 1024,
+ // TODO
+ //timeout: this.loaderTimeout,
+ agent: (u)=>u.protocol === "http:" ? httpAgent : httpsAgent
+ }).then((res)=>{
+ if (!res.ok) {
+ throw new Error(`${res.status} ${res.statusText}`);
+ } else {
+ return res.json();
+ }
+ });
+ return json;
+ }
+ sha256(data) {
+ const hash = crypto.createHash("sha256");
+ hash.update(data);
+ return hash.digest("hex");
+ }
+ containsForbiddenDirectives(doc) {
+ if (typeof doc === "object" && doc !== null) {
+ if (Array.isArray(doc)) {
+ for (const item of doc){
+ if (this.containsForbiddenDirectives(item)) {
+ return true;
+ }
+ }
+ } else {
+ for (const [key, value] of Object.entries(doc)){
+ if ([
+ "@included",
+ "@graph",
+ "@reverse"
+ ].includes(key)) {
+ return true;
+ }
+ if (typeof value === "object" && value !== null) {
+ if (this.containsForbiddenDirectives(value)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/packages/backend/built/remote/activitypub/models/icon.js b/packages/backend/built/remote/activitypub/models/icon.js
new file mode 100644
index 0000000..2234b9c
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/models/icon.js
@@ -0,0 +1 @@
+export { };
diff --git a/packages/backend/built/remote/activitypub/models/identifier.js b/packages/backend/built/remote/activitypub/models/identifier.js
new file mode 100644
index 0000000..2234b9c
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/models/identifier.js
@@ -0,0 +1 @@
+export { };
diff --git a/packages/backend/built/remote/activitypub/models/image.js b/packages/backend/built/remote/activitypub/models/image.js
new file mode 100644
index 0000000..96d26e3
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/models/image.js
@@ -0,0 +1,59 @@
+import { uploadFromUrl } from "../../../services/drive/upload-from-url.js";
+import Resolver from "../resolver.js";
+import { fetchMeta } from "../../../misc/fetch-meta.js";
+import { apLogger } from "../logger.js";
+import { DriveFiles } from "../../../models/index.js";
+import { truncate } from "../../../misc/truncate.js";
+import { DB_MAX_IMAGE_COMMENT_LENGTH } from "../../../misc/hard-limits.js";
+const logger = apLogger;
+/**
+ * create an Image.
+ */ export async function createImage(actor, value) {
+ // Skip if author is frozen.
+ if (actor.isSuspended) {
+ throw new Error("actor has been suspended");
+ }
+ const image = await new Resolver().resolve(value);
+ if (image.url == null) {
+ throw new Error("Invalid image, URL not provided");
+ }
+ if (!image.url.startsWith("https://") && !image.url.startsWith("http://")) {
+ throw new Error(`Invalid image, unexpected schema: ${image.url}`);
+ }
+ logger.info(`Creating the Image: ${image.url}`);
+ const instance = await fetchMeta();
+ let file = await uploadFromUrl({
+ url: image.url,
+ user: actor,
+ uri: image.url,
+ sensitive: image.sensitive,
+ isLink: !instance.cacheRemoteFiles,
+ comment: truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH)
+ });
+ if (file.isLink) {
+ // If the URL is different, it means that the same image was previously
+ // registered with a different URL, so update the URL
+ if (file.url !== image.url) {
+ await DriveFiles.update({
+ id: file.id
+ }, {
+ url: image.url,
+ uri: image.url
+ });
+ file = await DriveFiles.findOneByOrFail({
+ id: file.id
+ });
+ }
+ }
+ return file;
+}
+/**
+ * Resolve Image.
+ *
+ * If the target Image is registered in Iceshrimp, return it, otherwise
+ * Fetch from remote server, register with Iceshrimp and return it.
+ */ export async function resolveImage(actor, value) {
+ // TODO
+ // Fetch from remote server and register
+ return await createImage(actor, value);
+}
diff --git a/packages/backend/built/remote/activitypub/models/mention.js b/packages/backend/built/remote/activitypub/models/mention.js
new file mode 100644
index 0000000..91dfa93
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/models/mention.js
@@ -0,0 +1,17 @@
+import promiseLimit from "promise-limit";
+import { toArray, unique } from "../../../prelude/array.js";
+import { isMention } from "../type.js";
+import Resolver from "../resolver.js";
+import { resolvePerson } from "./person.js";
+import { RecursionLimiter } from "../../../models/repositories/user-profile.js";
+export async function extractApMentions(tags, limiter = new RecursionLimiter()) {
+ const hrefs = unique(extractApMentionObjects(tags).map((x)=>x.href));
+ const resolver = new Resolver();
+ const limit = promiseLimit(2);
+ const mentionedUsers = (await Promise.all(hrefs.map((x)=>limit(()=>resolvePerson(x, resolver, limiter).catch(()=>null))))).filter((x)=>x != null);
+ return mentionedUsers;
+}
+export function extractApMentionObjects(tags) {
+ if (tags == null) return [];
+ return toArray(tags).filter(isMention);
+}
diff --git a/packages/backend/built/remote/activitypub/models/note.js b/packages/backend/built/remote/activitypub/models/note.js
new file mode 100644
index 0000000..dd657f1
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/models/note.js
@@ -0,0 +1,563 @@
+import promiseLimit from "promise-limit";
+import * as mfm from "mfm-js";
+import config from "../../../config/index.js";
+import Resolver from "../resolver.js";
+import post from "../../../services/note/create.js";
+import { extractMentionedUsers } from "../../../services/note/create.js";
+import { resolvePerson } from "./person.js";
+import { resolveImage } from "./image.js";
+import { htmlToMfm } from "../misc/html-to-mfm.js";
+import { extractApHashtags } from "./tag.js";
+import { unique, toArray, toSingle } from "../../../prelude/array.js";
+import { extractPollFromQuestion } from "./question.js";
+import vote from "../../../services/note/polls/vote.js";
+import { apLogger } from "../logger.js";
+import { extractDbHost, toPuny } from "../../../misc/convert-host.js";
+import { Emojis, Polls, MessagingMessages, Notes, NoteEdits, DriveFiles, PollVotes } from "../../../models/index.js";
+import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from "../type.js";
+import { genId } from "../../../misc/gen-id.js";
+import { getApLock } from "../../../misc/app-lock.js";
+import { createMessage } from "../../../services/messages/create.js";
+import { parseAudience } from "../audience.js";
+import { extractApMentions } from "./mention.js";
+import DbResolver from "../db-resolver.js";
+import { StatusError } from "../../../misc/fetch.js";
+import { shouldBlockInstance } from "../../../misc/should-block-instance.js";
+import { publishNoteStream, publishNoteUpdatesStream } from "../../../services/stream.js";
+import { extractHashtags } from "../../../misc/extract-hashtags.js";
+import { UserProfiles } from "../../../models/index.js";
+import { In } from "typeorm";
+import { DB_MAX_IMAGE_COMMENT_LENGTH } from "../../../misc/hard-limits.js";
+import { truncate } from "../../../misc/truncate.js";
+import { getEmojiSize } from "../../../misc/emoji-meta.js";
+import { RecursionLimiter } from "../../../models/repositories/user-profile.js";
+const logger = apLogger;
+export function validateNote(object, uri) {
+ const expectHost = extractDbHost(uri);
+ if (object == null) {
+ return new Error("invalid Note: object is null");
+ }
+ if (!validPost.includes(getApType(object))) {
+ return new Error(`invalid Note: invalid object type ${getApType(object)}`);
+ }
+ if (object.id && extractDbHost(object.id) !== expectHost) {
+ return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${extractDbHost(object.id)}`);
+ }
+ if (object.attributedTo && extractDbHost(getOneApId(object.attributedTo)) !== expectHost) {
+ return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${extractDbHost(object.attributedTo)}`);
+ }
+ return null;
+}
+/**
+ * Fetch Notes.
+ *
+ * If the target Note is registered in Iceshrimp, it will be returned.
+ */ export async function fetchNote(object) {
+ const dbResolver = new DbResolver();
+ return await dbResolver.getNoteFromApId(object);
+}
+/**
+ * Create a Note.
+ */ export async function createNote(value, resolver, silent = false, limiter = new RecursionLimiter()) {
+ if (resolver == null) resolver = new Resolver();
+ const object = await resolver.resolve(value);
+ const entryUri = getApId(value);
+ const err = validateNote(object, entryUri);
+ if (err) {
+ logger.error(`${err.message}`, {
+ resolver: {
+ history: resolver.getHistory()
+ },
+ value: value,
+ object: object
+ });
+ throw new Error("invalid note");
+ }
+ const note = object;
+ if (note.id == null) {
+ throw new Error('Note must have an id');
+ }
+ const idUrl = new URL(note.id);
+ if (idUrl.protocol != 'https:') {
+ throw new Error(`unexpected schema of note.id: ${note.id}`);
+ }
+ let url = getOneApHrefNullable(note.url);
+ const urlUrl = url != null ? new URL(url) : null;
+ if (urlUrl != null && urlUrl.protocol != 'https:') {
+ throw new Error(`unexpected schema of note url: ${url}`);
+ }
+ logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`);
+ logger.info(`Creating the Note: ${note.id}`);
+ // Skip if note is made before 2007 (1yr before Fedi was created)
+ // OR skip if note is made 3 days in advance
+ if (note.published) {
+ const DateChecker = new Date(note.published);
+ const FutureCheck = new Date();
+ FutureCheck.setDate(FutureCheck.getDate() + 3); // Allow some wiggle room for misconfigured hosts
+ if (DateChecker.getFullYear() < 2007) {
+ logger.warn("Note somehow made before Activitypub was created; discarding");
+ return null;
+ }
+ if (DateChecker > FutureCheck) {
+ logger.warn("Note somehow made after today; discarding");
+ return null;
+ }
+ }
+ // Fetch author
+ const actor = await resolvePerson(getOneApId(note.attributedTo), resolver, limiter);
+ if (actor.uri == null) {
+ logger.warn('Note actor uri is null, discarding');
+ return null;
+ }
+ const actorUri = new URL(actor.uri);
+ if (idUrl.host != actorUri.host) {
+ logger.warn("Note id host doesn't match actor host, discarding");
+ return null;
+ }
+ if (urlUrl != null && urlUrl.host != actorUri.host) {
+ logger.debug("Note url host doesn't match actor host, clearing variable");
+ url = undefined;
+ }
+ // Skip if author is suspended.
+ if (actor.isSuspended) {
+ logger.debug(`User ${actor.usernameLower}@${actor.host} suspended; discarding.`);
+ return null;
+ }
+ const noteAudience = await parseAudience(actor, note.to, note.cc, undefined, limiter);
+ let visibility = noteAudience.visibility;
+ const visibleUsers = noteAudience.visibleUsers;
+ // If Audience (to, cc) was not specified
+ if (visibility === "specified" && visibleUsers.length === 0) {
+ if (typeof value === "string") {
+ // If the input is a string, GET occurs in resolver
+ // Public if you can GET anonymously from here
+ visibility = "public";
+ }
+ }
+ let isTalk = note._misskey_talk && visibility === "specified";
+ const apMentions = await extractApMentions(note.tag, limiter);
+ const apHashtags = extractApHashtags(note.tag);
+ // Attachments
+ // TODO: attachmentは必ずしもImageではない
+ // TODO: attachmentは必ずしも配列ではない
+ // Noteがsensitiveなら添付もsensitiveにする
+ const limit = promiseLimit(2);
+ note.attachment = Array.isArray(note.attachment) ? note.attachment : note.attachment ? [
+ note.attachment
+ ] : [];
+ note.attachment = note.attachment.filter((attach)=>[
+ "Document",
+ "Image",
+ "Audio",
+ "Video"
+ ].includes(attach.type));
+ const files = note.attachment.map((attach)=>attach.sensitive = note.sensitive) ? (await Promise.all(note.attachment.map((x)=>limit(()=>resolveImage(actor, x))))).filter((image)=>image != null) : [];
+ // Reply
+ const reply = note.inReplyTo ? await resolveNote(note.inReplyTo, resolver, limiter).then((x)=>{
+ if (x == null) {
+ logger.warn("Specified inReplyTo, but nout found");
+ throw new Error("inReplyTo not found");
+ } else {
+ return x;
+ }
+ }).catch(async (e)=>{
+ // トークだったらinReplyToのエラーは無視
+ const uri = getApId(note.inReplyTo);
+ if (uri.startsWith(`${config.url}/`)) {
+ const id = uri.split("/").pop();
+ const talk = await MessagingMessages.findOneBy({
+ id
+ });
+ if (talk) {
+ isTalk = true;
+ return null;
+ }
+ }
+ logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${e.statusCode || e}`);
+ throw e;
+ }) : null;
+ // Quote
+ let quote;
+ if (note._misskey_quote || note.quoteUrl || note.quoteUri || note.quote) {
+ const tryResolveNote = async (uri)=>{
+ if (typeof uri !== "string" || !uri.match(/^https?:/)) return {
+ status: "permerror"
+ };
+ try {
+ const res = await resolveNote(uri, undefined, limiter);
+ if (res) {
+ return {
+ status: "ok",
+ res
+ };
+ } else {
+ return {
+ status: "permerror"
+ };
+ }
+ } catch (e) {
+ return {
+ status: e instanceof StatusError && !e.isRetryable ? "permerror" : "temperror"
+ };
+ }
+ };
+ const uris = unique([
+ note._misskey_quote,
+ note.quoteUrl,
+ note.quoteUri,
+ note.quote
+ ].filter((x)=>typeof x === "string"));
+ const results = await Promise.all(uris.map((uri)=>tryResolveNote(uri)));
+ quote = results.filter((x)=>x.status === "ok").map((x)=>x.res).find((x)=>x);
+ if (!quote) {
+ if (results.some((x)=>x.status === "temperror")) {
+ throw new Error("quote resolve failed");
+ }
+ }
+ }
+ const cw = note.summary === "" ? null : note.summary;
+ // Text parsing
+ let text = null;
+ if (note.source?.mediaType === "text/x.misskeymarkdown" && typeof note.source?.content === "string") {
+ text = note.source.content;
+ } else if (typeof note._misskey_content !== "undefined") {
+ text = note._misskey_content;
+ } else if (typeof note.content === "string") {
+ text = await htmlToMfm(note.content, note.tag);
+ }
+ // vote
+ if (reply?.hasPoll) {
+ const poll = await Polls.findOneByOrFail({
+ noteId: reply.id
+ });
+ const tryCreateVote = async (name, index)=>{
+ if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) {
+ logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`);
+ } else if (index >= 0) {
+ logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`);
+ await vote(actor, reply, index);
+ }
+ return null;
+ };
+ if (note.name) {
+ return await tryCreateVote(note.name, poll.choices.findIndex((x)=>x === note.name));
+ }
+ }
+ const emojis = await extractEmojis(note.tag || [], actor.host).catch((e)=>{
+ logger.info(`extractEmojis: ${e}`);
+ return [];
+ });
+ const apEmojis = emojis.map((emoji)=>emoji.name);
+ const poll = await extractPollFromQuestion(note, resolver).catch(()=>undefined);
+ if (isTalk) {
+ for (const recipient of visibleUsers){
+ await createMessage(actor, recipient, undefined, text || undefined, files && files.length > 0 ? files[0] : null, object.id);
+ return null;
+ }
+ }
+ return await post(actor, {
+ createdAt: note.published ? new Date(note.published) : null,
+ files,
+ reply,
+ renote: quote,
+ name: note.name,
+ cw,
+ text,
+ localOnly: false,
+ visibility,
+ visibleUsers,
+ apMentions,
+ apHashtags,
+ apEmojis,
+ poll,
+ uri: note.id,
+ url: url,
+ canQuote: !!note.interactionPolicy?.canQuote
+ }, silent, limiter);
+}
+/**
+ * Resolve Note.
+ *
+ * If the target Note is registered in Iceshrimp, return it, otherwise
+ * Fetch from remote server, register with Iceshrimp and return it.
+ */ export async function resolveNote(value, resolver, limiter = new RecursionLimiter()) {
+ const uri = typeof value === "string" ? value : value.id;
+ if (uri == null) throw new Error("missing uri");
+ // Abort if origin host is blocked
+ if (await shouldBlockInstance(extractDbHost(uri))) throw new StatusError("host blocked", 451, `host ${extractDbHost(uri)} is blocked`);
+ const unlock = await getApLock(uri);
+ try {
+ //#region Returns if already registered with this server
+ const exist = await fetchNote(uri);
+ if (exist) {
+ return exist;
+ }
+ //#endregion
+ if (extractDbHost(uri) === toPuny(config.host)) {
+ throw new StatusError("cannot resolve local note", 400, "cannot resolve local note");
+ }
+ // Fetch from remote server and register
+ // If the attached `Note` Object is specified here instead of the uri, the note will be generated without going through the server fetch.
+ // Since the attached Note Object may be disguised, always specify the uri and fetch it from the server.
+ return await createNote(uri, resolver, true, limiter);
+ } finally{
+ unlock();
+ }
+}
+export async function extractEmojis(tags, host) {
+ host = toPuny(host);
+ if (!tags) return [];
+ const eomjiTags = toArray(tags).filter(isEmoji);
+ return await Promise.all(eomjiTags.map(async (tag)=>{
+ const name = tag.name.replace(/^:/, "").replace(/:$/, "");
+ tag.icon = toSingle(tag.icon);
+ const exists = await Emojis.findOneBy({
+ host,
+ name
+ });
+ if (exists) {
+ if (tag.updated != null && exists.updatedAt == null || tag.id != null && exists.uri == null || tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt || tag.icon.url !== exists.originalUrl || !(exists.width && exists.height)) {
+ let size = {
+ width: 0,
+ height: 0
+ };
+ try {
+ size = await getEmojiSize(tag.icon.url);
+ } catch {
+ /* skip if any error happens */ }
+ await Emojis.update({
+ host,
+ name
+ }, {
+ uri: tag.id,
+ originalUrl: tag.icon.url,
+ publicUrl: tag.icon.url,
+ updatedAt: new Date(),
+ width: size.width || null,
+ height: size.height || null
+ });
+ return await Emojis.findOneBy({
+ host,
+ name
+ });
+ }
+ return exists;
+ }
+ logger.info(`register emoji host=${host}, name=${name}`);
+ let size = {
+ width: 0,
+ height: 0
+ };
+ try {
+ size = await getEmojiSize(tag.icon.url);
+ } catch {
+ /* skip if any error happens */ }
+ return await Emojis.insert({
+ id: genId(),
+ host,
+ name,
+ uri: tag.id,
+ originalUrl: tag.icon.url,
+ publicUrl: tag.icon.url,
+ updatedAt: new Date(),
+ aliases: [],
+ glyph: tag.icon?.type === "image/svg+xml",
+ width: size.width || null,
+ height: size.height || null
+ }).then((x)=>Emojis.findOneByOrFail(x.identifiers[0]));
+ }));
+}
+function notEmpty(partial) {
+ return Object.keys(partial).length > 0;
+}
+export async function updateNote(value, actor, resolver) {
+ const uri = typeof value === "string" ? value : value.id;
+ if (!uri) throw new Error("Missing note uri");
+ // Skip if URI points to this server
+ if (extractDbHost(uri) === toPuny(config.host)) throw new Error("uri points local");
+ // A new resolver is created if not specified
+ if (resolver == null) resolver = new Resolver();
+ // Resolve the updated Note object
+ const post1 = await resolver.resolve(value);
+ if (getOneApId(post1.attributedTo) !== actor.uri || actor.uri == null) {
+ throw new Error('Refusing to ingest update for note with mismatching actor');
+ }
+ // Already registered with this server?
+ const note = await Notes.findOneBy({
+ uri
+ });
+ if (note == null) {
+ return await createNote(post1, resolver);
+ }
+ if (note.userId !== actor.id) {
+ throw new Error('Refusing to ingest update for note of different user');
+ }
+ // Whether to tell clients the note has been updated and requires refresh.
+ let updating = false;
+ // Text parsing
+ let text = null;
+ if (post1.source?.mediaType === "text/x.misskeymarkdown" && typeof post1.source?.content === "string") {
+ text = post1.source.content;
+ } else if (typeof post1._misskey_content !== "undefined") {
+ text = post1._misskey_content;
+ } else if (typeof post1.content === "string") {
+ text = await htmlToMfm(post1.content, post1.tag);
+ }
+ const cw = post1.summary === "" ? null : post1.summary;
+ // File parsing
+ const fileList = post1.attachment ? Array.isArray(post1.attachment) ? post1.attachment : [
+ post1.attachment
+ ] : [];
+ // Fetch files
+ const limit = promiseLimit(2);
+ const driveFiles = (await Promise.all(fileList.map((x)=>limit(async ()=>{
+ const file = await resolveImage(actor, x);
+ const update = {};
+ const altText = truncate(x.name, DB_MAX_IMAGE_COMMENT_LENGTH) ?? null;
+ if (file.comment !== altText) {
+ update.comment = altText;
+ }
+ // Don't unmark previously marked sensitive files,
+ // but if edited post contains sensitive marker, update it.
+ if (post1.sensitive && !file.isSensitive) {
+ update.isSensitive = post1.sensitive;
+ }
+ if (notEmpty(update)) {
+ await DriveFiles.update(file.id, update);
+ updating = true;
+ }
+ return file;
+ })))).filter((file)=>file != null);
+ const fileIds = driveFiles.map((file)=>file.id);
+ const fileTypes = driveFiles.map((file)=>file.type);
+ const apEmojis = (await extractEmojis(post1.tag || [], actor.host).catch((e)=>[])).map((emoji)=>emoji.name);
+ const apMentions = await extractApMentions(post1.tag);
+ const apHashtags = await extractApHashtags(post1.tag);
+ const poll = await extractPollFromQuestion(post1, resolver).catch(()=>undefined);
+ const choices = poll?.choices.flatMap((choice)=>mfm.parse(choice)) ?? [];
+ const tokens = mfm.parse(text || "").concat(mfm.parse(cw || "")).concat(choices);
+ const hashTags = apHashtags || extractHashtags(tokens);
+ const mentionUsers = apMentions || await extractMentionedUsers(actor, tokens);
+ const mentionUserIds = mentionUsers.map((user)=>user.id);
+ const remoteUsers = mentionUsers.filter((user)=>user.host != null);
+ const remoteUserIds = remoteUsers.map((user)=>user.id);
+ const remoteProfiles = await UserProfiles.findBy({
+ userId: In(remoteUserIds)
+ });
+ const mentionedRemoteUsers = remoteUsers.map((user)=>{
+ const profile = remoteProfiles.find((profile)=>profile.userId === user.id);
+ return {
+ username: user.username,
+ host: user.host ?? null,
+ uri: user.uri,
+ url: profile ? profile.url : undefined
+ };
+ });
+ const update = {};
+ if (text && text !== note.text) {
+ update.text = text;
+ }
+ if (cw !== note.cw) {
+ update.cw = cw ? cw : null;
+ }
+ if (fileIds.sort().join(",") !== note.fileIds.sort().join(",")) {
+ update.fileIds = fileIds;
+ update.attachedFileTypes = fileTypes;
+ }
+ if (hashTags.sort().join(",") !== note.tags.sort().join(",")) {
+ update.tags = hashTags;
+ }
+ if (mentionUserIds.sort().join(",") !== note.mentions.sort().join(",")) {
+ update.mentions = mentionUserIds;
+ update.mentionedRemoteUsers = JSON.stringify(mentionedRemoteUsers);
+ }
+ if (apEmojis.sort().join(",") !== note.emojis.sort().join(",")) {
+ update.emojis = apEmojis;
+ }
+ if (note.hasPoll !== !!poll) {
+ update.hasPoll = !!poll;
+ }
+ if (poll) {
+ const dbPoll = await Polls.findOneBy({
+ noteId: note.id
+ });
+ if (poll?.votes != null && poll.votes.find((p)=>!Number.isInteger(p) || p < 0) !== undefined) {
+ throw new Error('Refusing to ingest poll with non-integer or negative vote count');
+ }
+ if (dbPoll == null) {
+ await Polls.insert({
+ noteId: note.id,
+ choices: poll?.choices,
+ multiple: poll?.multiple,
+ votes: poll?.votes,
+ expiresAt: poll?.expiresAt,
+ noteVisibility: note.visibility === "hidden" ? "home" : note.visibility,
+ userId: actor.id,
+ userHost: actor.host
+ });
+ updating = true;
+ } else {
+ const choicesChanged = JSON.stringify(dbPoll.choices) !== JSON.stringify(poll.choices);
+ if (dbPoll.multiple !== poll.multiple || dbPoll.expiresAt !== poll.expiresAt || dbPoll.noteVisibility !== note.visibility || choicesChanged) {
+ await Polls.update({
+ noteId: note.id
+ }, {
+ choices: poll?.choices,
+ multiple: poll?.multiple,
+ votes: poll?.votes,
+ expiresAt: poll?.expiresAt,
+ noteVisibility: note.visibility === "hidden" ? "home" : note.visibility
+ });
+ // Reset votes
+ if (choicesChanged) {
+ await PollVotes.delete({
+ noteId: dbPoll.noteId
+ });
+ }
+ updating = true;
+ } else {
+ for(let i = 0; i < poll.choices.length; i++){
+ if (dbPoll.votes[i] !== poll.votes?.[i]) {
+ await Polls.update({
+ noteId: note.id
+ }, {
+ votes: poll?.votes
+ });
+ updating = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ // Update Note
+ if (notEmpty(update)) {
+ update.updatedAt = new Date();
+ // Save updated note to the database
+ await Notes.update({
+ uri
+ }, update);
+ // Save an edit history for the previous note
+ await NoteEdits.insert({
+ id: genId(),
+ noteId: note.id,
+ text: note.text,
+ cw: note.cw,
+ fileIds: note.fileIds,
+ updatedAt: update.updatedAt
+ });
+ updating = true;
+ }
+ if (updating) {
+ // Publish update event for the updated note details
+ publishNoteStream(note.id, "updated", {
+ updatedAt: update.updatedAt
+ });
+ const updatedNote = {
+ ...note,
+ ...update
+ };
+ publishNoteUpdatesStream("updated", updatedNote);
+ }
+ return null;
+}
diff --git a/packages/backend/built/remote/activitypub/models/person.js b/packages/backend/built/remote/activitypub/models/person.js
new file mode 100644
index 0000000..63e03aa
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/models/person.js
@@ -0,0 +1,638 @@
+import { URL } from "node:url";
+import promiseLimit from "promise-limit";
+import config from "../../../config/index.js";
+import { registerOrFetchInstanceDoc } from "../../../services/register-or-fetch-instance-doc.js";
+import { updateUsertags } from "../../../services/update-hashtag.js";
+import { Users, Instances, DriveFiles, Followings, UserProfiles, UserPublickeys } from "../../../models/index.js";
+import { User } from "../../../models/entities/user.js";
+import { UserNotePining } from "../../../models/entities/user-note-pining.js";
+import { genId } from "../../../misc/gen-id.js";
+import { instanceChart, usersChart } from "../../../services/chart/index.js";
+import { UserPublickey } from "../../../models/entities/user-publickey.js";
+import { isDuplicateKeyValueError } from "../../../misc/is-duplicate-key-value-error.js";
+import { extractDbHost, toPuny } from "../../../misc/convert-host.js";
+import { UserProfile } from "../../../models/entities/user-profile.js";
+import { toArray } from "../../../prelude/array.js";
+import { fetchInstanceMetadata } from "../../../services/fetch-instance-metadata.js";
+import { normalizeForSearch } from "../../../misc/normalize-for-search.js";
+import { truncate } from "../../../misc/truncate.js";
+import { StatusError } from "../../../misc/fetch.js";
+import { uriPersonCache } from "../../../services/user-cache.js";
+import { publishInternalEvent } from "../../../services/stream.js";
+import { db } from "../../../db/postgre.js";
+import { apLogger } from "../logger.js";
+import { htmlToMfm } from "../misc/html-to-mfm.js";
+import { fromHtml } from "../../../mfm/from-html.js";
+import { isCollectionOrOrderedCollection, isCollection, getApId, getOneApHrefNullable, isPropertyValue, getApType, isActor } from "../type.js";
+import Resolver from "../resolver.js";
+import { extractApHashtags } from "./tag.js";
+import { resolveNote, extractEmojis } from "./note.js";
+import { resolveImage } from "./image.js";
+import { getSubjectHostFromUri, getSubjectHostFromRemoteUser, getSubjectHostFromAcctParts } from "../../resolve-user.js";
+import { RecursionLimiter } from "../../../models/repositories/user-profile.js";
+import { UserConverter } from "../../../server/api/mastodon/converters/user.js";
+import fetch from "node-fetch";
+const logger = apLogger;
+const nameLength = 128;
+const summaryLength = 2048;
+/**
+ * Validate and convert to actor object
+ * @param x Fetched object
+ * @param uri Fetch target URI
+ */ function validateActor(x, uri) {
+ const expectHost = extractDbHost(uri);
+ if (x == null) {
+ throw new Error("invalid Actor: object is null");
+ }
+ if (!isActor(x)) {
+ throw new Error(`invalid Actor type '${x.type}'`);
+ }
+ if (!(typeof x.id === "string" && x.id.length > 0)) {
+ throw new Error("invalid Actor: wrong id");
+ }
+ if (!(typeof x.inbox === "string" && x.inbox.length > 0 && extractDbHost(x.inbox) === expectHost)) {
+ throw new Error("invalid Actor: wrong inbox");
+ }
+ if (!(typeof x.outbox === "string" && x.outbox.length > 0 && extractDbHost(getApId(x.outbox)) === expectHost)) {
+ throw new Error("invalid Actor: wrong outbox");
+ }
+ const sharedInboxObject = x.sharedInbox ?? (x.endpoints ? x.endpoints.sharedInbox : undefined);
+ if (sharedInboxObject != null) {
+ const sharedInbox = getApId(sharedInboxObject);
+ if (!(typeof sharedInbox === "string" && sharedInbox.length > 0 && extractDbHost(sharedInbox) === expectHost)) {
+ throw new Error("invalid Actor: wrong shared inbox");
+ }
+ }
+ if (x.followers != null) {
+ x.followers = getApId(x.followers);
+ if (!(typeof x.followers === "string" && x.followers.length > 0 && extractDbHost(x.followers) === expectHost)) {
+ throw new Error("invalid Actor: wrong followers");
+ }
+ }
+ if (x.following != null) {
+ x.following = getApId(x.following);
+ if (!(typeof x.following === "string" && x.following.length > 0 && extractDbHost(x.following) === expectHost)) {
+ throw new Error("invalid Actor: wrong following");
+ }
+ }
+ if (!(typeof x.preferredUsername === "string" && x.preferredUsername.length > 0 && x.preferredUsername.length <= 128 && /^\w([\w-.]*\w)?$/.test(x.preferredUsername))) {
+ throw new Error("invalid Actor: wrong username");
+ }
+ // These fields are only informational, and some AP software allows these
+ // fields to be very long. If they are too long, we cut them off. This way
+ // we can at least see these users and their activities.
+ if (x.name) {
+ if (!(typeof x.name === "string" && x.name.length > 0)) {
+ throw new Error("invalid Actor: wrong name");
+ }
+ x.name = truncate(x.name, nameLength);
+ }
+ if (x.summary) {
+ if (!(typeof x.summary === "string" && x.summary.length > 0)) {
+ throw new Error("invalid Actor: wrong summary");
+ }
+ x.summary = truncate(x.summary, summaryLength);
+ }
+ const idHost = toPuny(new URL(x.id).host);
+ if (idHost !== expectHost) {
+ throw new Error("invalid Actor: id has different host");
+ }
+ if (x.publicKey) {
+ if (typeof x.publicKey.id !== "string") {
+ throw new Error("invalid Actor: publicKey.id is not a string");
+ }
+ const publicKeyIdHost = toPuny(new URL(x.publicKey.id).host);
+ if (publicKeyIdHost !== expectHost) {
+ throw new Error("invalid Actor: publicKey.id has different host");
+ }
+ }
+ if (x.pronouns) {
+ if (typeof x.pronouns !== "object") {
+ throw new Error("invalid Actor: pronouns is not an object");
+ }
+ for (const key of Object.keys(x.pronouns)){
+ if (typeof x.pronouns[key] !== "string") {
+ throw new Error(`invalid Actor: pronouns.${key} is not a string`);
+ }
+ }
+ }
+ if (x.canBite && typeof x.canBite !== "string") {
+ throw new Error("invalid Actor: canBite is not a string");
+ }
+ return x;
+}
+/**
+ * Fetch a Person.
+ *
+ * If the target Person is registered in Iceshrimp, it will be returned.
+ */ export async function fetchPerson(uri, resolver) {
+ if (typeof uri !== "string") throw new Error("uri is not string");
+ const cached = await uriPersonCache.get(uri, true);
+ if (cached) return cached;
+ // Fetch from the database if the URI points to this server
+ if (extractDbHost(uri) === toPuny(config.host)) {
+ const id = uri.split("/").pop();
+ const u = await Users.findOneBy({
+ id
+ });
+ if (u) await uriPersonCache.set(uri, u);
+ return u;
+ }
+ //#region Returns if already registered with this server
+ const user = await Users.findOneBy({
+ uri
+ });
+ if (user != null) {
+ await uriPersonCache.set(uri, user);
+ return user;
+ }
+ //#endregion
+ return null;
+}
+/**
+ * Create Person.
+ */ export async function createPerson(uri, resolver, subjectHost, limiter = new RecursionLimiter()) {
+ if (typeof uri !== "string") throw new Error("uri is not string");
+ if (extractDbHost(uri) === toPuny(config.host)) {
+ throw new StatusError("cannot resolve local user", 400, "cannot resolve local user");
+ }
+ if (resolver == null) resolver = new Resolver();
+ let object = await resolver.resolve(uri);
+ let person;
+ try {
+ person = validateActor(object, uri);
+ } catch (e) {
+ // Work around GoToSocial issue #1186 (ref: https://github.com/superseriousbusiness/gotosocial/issues/1186)
+ if (typeof object.publicKey?.owner !== 'string' || object.inbox != null) throw e;
+ logger.info(`Received stub actor, re-resolving with key owner uri: ${object.publicKey.owner}`);
+ object = await resolver.resolve(object.publicKey.owner);
+ person = validateActor(object, uri);
+ }
+ logger.info(`Creating the Person: ${person.id}`);
+ const usernameLower = person.preferredUsername?.toLowerCase();
+ const urlHostname = toPuny(new URL(object.id).hostname);
+ const host = subjectHost ?? await getSubjectHostFromUri(object.id) ?? await getSubjectHostFromAcctParts(usernameLower, urlHostname) ?? urlHostname;
+ if (usernameLower !== null) {
+ let checkUser = await Users.findOneBy({
+ usernameLower: usernameLower,
+ host: toPuny(new URL(object.id).hostname)
+ });
+ if (checkUser != null) {
+ logger.info('Person already exists');
+ if (host != checkUser.host) {
+ logger.info(`Updating existing person with canonical account domain (${usernameLower}@${checkUser.host} -> ${usernameLower}@${host})`);
+ await Users.update({
+ usernameLower: usernameLower,
+ host: checkUser.host
+ }, {
+ host: host
+ });
+ checkUser.host = host;
+ }
+ logger.info('Returning existing person');
+ return checkUser;
+ }
+ if (host != toPuny(new URL(object.id).hostname)) {
+ checkUser = await Users.findOneBy({
+ usernameLower: usernameLower,
+ host: host
+ });
+ if (checkUser != null) {
+ logger.info('Person already exists');
+ logger.info('Returning existing person');
+ return checkUser;
+ }
+ }
+ }
+ const { fields } = await analyzeAttachments(person.attachment || []);
+ const tags = extractApHashtags(person.tag).map((tag)=>normalizeForSearch(tag)).splice(0, 32);
+ const isBot = getApType(object) === "Service";
+ const bday = person["vcard:bday"]?.match(/^\d{4}-\d{2}-\d{2}/);
+ let url = getOneApHrefNullable(person.url);
+ const urlUrl = url != null ? new URL(url) : null;
+ const uriUrl = new URL(uri);
+ if (urlUrl != null && urlUrl.protocol != 'https:') {
+ throw new Error(`unexpected schema of person url: ${url}`);
+ }
+ if (urlUrl != null && urlUrl.host != uriUrl.host) {
+ logger.debug("Person url host doesn't match person uri host, clearing variable");
+ url = undefined;
+ }
+ let followersCount;
+ if (typeof person.followers === "string") {
+ try {
+ let data = await fetch(person.followers, {
+ headers: {
+ Accept: "application/json"
+ },
+ size: 1024 * 1024
+ });
+ let json_data = JSON.parse(await data.text());
+ followersCount = json_data.totalItems;
+ } catch {
+ followersCount = undefined;
+ }
+ }
+ let followingCount;
+ if (typeof person.following === "string") {
+ try {
+ let data = await fetch(person.following, {
+ headers: {
+ Accept: "application/json"
+ },
+ size: 1024 * 1024
+ });
+ let json_data = JSON.parse(await data.text());
+ followingCount = json_data.totalItems;
+ } catch (e) {
+ followingCount = undefined;
+ }
+ }
+ let canBite = "nobody";
+ if (person.canBite) {
+ if (person.canBite === "https://www.w3.org/ns/activitystreams#Public") {
+ canBite = "anyone";
+ } else if (person.followers && person.canBite === getApId(person.followers)) {
+ canBite = "followers";
+ }
+ }
+ // Prepare objects
+ let user = new User({
+ id: genId(),
+ avatarId: null,
+ bannerId: null,
+ createdAt: new Date(),
+ lastFetchedAt: new Date(),
+ name: truncate(person.name, nameLength),
+ isLocked: !!person.manuallyApprovesFollowers,
+ movedToUri: person.movedTo,
+ alsoKnownAs: person.alsoKnownAs,
+ isExplorable: !!person.discoverable,
+ username: person.preferredUsername,
+ usernameLower: person.preferredUsername.toLowerCase(),
+ host,
+ inbox: person.inbox,
+ sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
+ followersUri: person.followers ? getApId(person.followers) : undefined,
+ followersCount: followersCount !== undefined ? followersCount : person.followers && typeof person.followers !== "string" && isCollectionOrOrderedCollection(person.followers) ? person.followers.totalItems : undefined,
+ followingCount: followingCount !== undefined ? followingCount : person.following && typeof person.following !== "string" && isCollectionOrOrderedCollection(person.following) ? person.following.totalItems : undefined,
+ featured: person.featured ? getApId(person.featured) : undefined,
+ uri: person.id,
+ tags,
+ isBot,
+ isCat: person.isCat === true,
+ canBite
+ });
+ const profile = new UserProfile({
+ userId: user.id,
+ description: person.summary ? await htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null,
+ url: url,
+ fields,
+ birthday: bday ? bday[0] : null,
+ location: person["vcard:Address"] || null,
+ userHost: host,
+ pronouns: person.pronouns || {}
+ });
+ const publicKey = person.publicKey ? new UserPublickey({
+ userId: user.id,
+ keyId: person.publicKey.id,
+ keyPem: person.publicKey.publicKeyPem
+ }) : null;
+ try {
+ // Save the objects atomically using a db transaction, note that we should never run any code in a transaction block directly
+ await db.transaction(async (transactionalEntityManager)=>{
+ await transactionalEntityManager.save(user);
+ await transactionalEntityManager.save(profile);
+ if (publicKey) await transactionalEntityManager.save(publicKey);
+ });
+ } catch (e) {
+ // duplicate key error
+ if (isDuplicateKeyValueError(e)) {
+ // /users/@a => /users/:id Corresponds to an error that may occur when the input is an alias like
+ const u = await Users.findOneBy({
+ uri: person.id
+ });
+ if (u) {
+ user = u;
+ } else {
+ throw new Error("already registered");
+ }
+ } else {
+ logger.error(e instanceof Error ? e : new Error(e));
+ throw e;
+ }
+ }
+ // Register host
+ registerOrFetchInstanceDoc(host).then((i)=>{
+ Instances.increment({
+ id: i.id
+ }, "usersCount", 1);
+ instanceChart.newUser(i.host);
+ fetchInstanceMetadata(i);
+ });
+ usersChart.update(user, true);
+ // Hashtag update
+ updateUsertags(user, tags);
+ // Mentions update, then prewarm html cache
+ if (await limiter.shouldContinue()) UserProfiles.updateMentions(user.id, limiter).then((_)=>UserConverter.prewarmCacheById(user.id));
+ //#region Fetch avatar and header image
+ const [avatar, banner] = await Promise.all([
+ person.icon,
+ person.image
+ ].map((img)=>img == null ? Promise.resolve(null) : resolveImage(user, img).catch(()=>null)));
+ const avatarId = avatar?.id ?? null;
+ const avatarBlurhash = avatar?.blurhash ?? null;
+ const avatarUrl = avatar ? DriveFiles.getDatabasePrefetchUrl(avatar, true) : null;
+ const bannerId = banner?.id ?? null;
+ const bannerBlurhash = banner?.blurhash ?? null;
+ const bannerUrl = banner ? DriveFiles.getDatabasePrefetchUrl(banner, false) : null;
+ await Users.update(user.id, {
+ avatarId,
+ avatarBlurhash,
+ avatarUrl,
+ bannerId,
+ bannerBlurhash,
+ bannerUrl
+ });
+ user.avatarId = avatarId;
+ user.avatarBlurhash = avatarBlurhash;
+ user.avatarUrl = avatarUrl;
+ user.bannerId = bannerId;
+ user.bannerBlurhash = bannerBlurhash;
+ user.bannerUrl = bannerUrl;
+ //#endregion
+ //#region Get custom emoji
+ const emojis = await extractEmojis(person.tag || [], host).catch((e)=>{
+ logger.info(`extractEmojis: ${e}`);
+ return [];
+ });
+ const emojiNames = emojis.map((emoji)=>emoji.name);
+ await Users.update(user.id, {
+ emojis: emojiNames
+ });
+ //#endregion
+ await updateFeatured(user.id, resolver, limiter).catch((err)=>logger.error(err));
+ return user;
+}
+/**
+ * Update Person data from remote.
+ * If the target Person is not registered in Iceshrimp, it is ignored.
+ * @param uri URI of Person
+ * @param resolver Resolver
+ * @param hint Hint of Person object (If this value is a valid Person, it is used for updating without Remote resolve)
+ * @param userHint Hint of IRemoteUser object, used for updating user information for remotes that only support webfinger with acct: query
+ */ export async function updatePerson(uri, resolver, hint, userHint) {
+ if (typeof uri !== "string") throw new Error("uri is not string");
+ // Skip if the URI points to this server
+ if (extractDbHost(uri) === toPuny(config.host)) {
+ return;
+ }
+ //#region Already registered on this server?
+ const user = await Users.findOneBy({
+ uri
+ });
+ if (user == null) {
+ return;
+ }
+ //#endregion
+ if (resolver == null) resolver = new Resolver();
+ const object = hint || await resolver.resolve(uri);
+ const person = validateActor(object, uri);
+ logger.info(`Updating the Person: ${person.id}`);
+ const host = await getSubjectHostFromUri(uri) ?? await getSubjectHostFromRemoteUser(userHint);
+ // Fetch avatar and header image
+ const [avatar, banner] = await Promise.all([
+ person.icon,
+ person.image
+ ].map((img)=>img == null ? Promise.resolve(null) : resolveImage(user, img).catch(()=>null)));
+ // Custom pictogram acquisition
+ const emojis = await extractEmojis(person.tag || [], user.host).catch((e)=>{
+ logger.info(`extractEmojis: ${e}`);
+ return [];
+ });
+ const emojiNames = emojis.map((emoji)=>emoji.name);
+ const { fields } = await analyzeAttachments(person.attachment || []);
+ const tags = extractApHashtags(person.tag).map((tag)=>normalizeForSearch(tag)).splice(0, 32);
+ const bday = person["vcard:bday"]?.match(/^\d{4}-\d{2}-\d{2}/);
+ const url = getOneApHrefNullable(person.url);
+ if (url && !url.startsWith("https://")) {
+ throw new Error(`unexpected schema of person url: ${url}`);
+ }
+ let followersCount;
+ if (typeof person.followers === "string") {
+ try {
+ let data = await fetch(person.followers, {
+ headers: {
+ Accept: "application/json"
+ },
+ size: 1024 * 1024
+ });
+ let json_data = JSON.parse(await data.text());
+ followersCount = json_data.totalItems;
+ } catch {
+ followersCount = undefined;
+ }
+ }
+ let followingCount;
+ if (typeof person.following === "string") {
+ try {
+ let data = await fetch(person.following, {
+ headers: {
+ Accept: "application/json"
+ },
+ size: 1024 * 1024
+ });
+ let json_data = JSON.parse(await data.text());
+ followingCount = json_data.totalItems;
+ } catch {
+ followingCount = undefined;
+ }
+ }
+ let canBite = "nobody";
+ if (person.canBite) {
+ if (person.canBite === "https://www.w3.org/ns/activitystreams#Public") {
+ canBite = "anyone";
+ } else if (person.followers && person.canBite === getApId(person.followers)) {
+ canBite = "followers";
+ }
+ }
+ const updates = {
+ lastFetchedAt: new Date(),
+ inbox: person.inbox,
+ sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
+ followersUri: person.followers ? getApId(person.followers) : undefined,
+ followersCount: followersCount !== undefined ? followersCount : person.followers && typeof person.followers !== "string" && isCollectionOrOrderedCollection(person.followers) ? person.followers.totalItems : undefined,
+ followingCount: followingCount !== undefined ? followingCount : person.following && typeof person.following !== "string" && isCollectionOrOrderedCollection(person.following) ? person.following.totalItems : undefined,
+ featured: person.featured,
+ emojis: emojiNames,
+ name: truncate(person.name, nameLength),
+ tags,
+ isBot: getApType(object) === "Service",
+ isCat: person.isCat === true,
+ isLocked: !!person.manuallyApprovesFollowers,
+ movedToUri: person.movedTo || null,
+ alsoKnownAs: person.alsoKnownAs || null,
+ isExplorable: !!person.discoverable,
+ canBite
+ };
+ if (avatar) {
+ updates.avatarId = avatar.id;
+ updates.avatarUrl = DriveFiles.getDatabasePrefetchUrl(avatar, true);
+ updates.avatarBlurhash = avatar.blurhash;
+ }
+ if (banner) {
+ updates.bannerId = banner.id;
+ updates.bannerUrl = DriveFiles.getDatabasePrefetchUrl(banner, false);
+ updates.bannerBlurhash = banner.blurhash;
+ }
+ if (host) {
+ updates.host = host;
+ }
+ // Update user
+ await Users.update(user.id, updates);
+ if (person.publicKey) {
+ await UserPublickeys.update({
+ userId: user.id
+ }, {
+ keyId: person.publicKey.id,
+ keyPem: person.publicKey.publicKeyPem
+ });
+ }
+ // Get old profile to see if we need to update any matching html cache entries
+ const oldProfile = await UserProfiles.findOneBy({
+ userId: user.id
+ });
+ const newProfile = {
+ url: url,
+ fields,
+ description: person._misskey_summary ? truncate(person._misskey_summary, summaryLength) : person.summary ? await htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null,
+ birthday: bday ? bday[0] : null,
+ location: person["vcard:Address"] || null,
+ pronouns: person.pronouns || {}
+ };
+ await UserProfiles.update({
+ userId: user.id
+ }, newProfile);
+ publishInternalEvent("remoteUserUpdated", {
+ id: user.id
+ });
+ // Hashtag Update
+ updateUsertags(user, tags);
+ // Mentions update, then prewarm html cache
+ UserProfiles.updateMentions(user.id).then((_)=>UserConverter.prewarmCacheById(user.id, oldProfile));
+ // If the user in question is a follower, followers will also be updated.
+ await Followings.update({
+ followerId: user.id
+ }, {
+ followerSharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : null)
+ });
+ await updateFeatured(user.id, resolver).catch((err)=>logger.error(err));
+}
+/**
+ * Resolve Person.
+ *
+ * If the target person is registered in Iceshrimp, it returns it;
+ * otherwise, it fetches it from the remote server, registers it in Iceshrimp, and returns it.
+ */ export async function resolvePerson(uri, resolver, limiter = new RecursionLimiter()) {
+ if (typeof uri !== "string") throw new Error("uri is not string");
+ //#region If already registered on this server, return it.
+ const user = await fetchPerson(uri);
+ if (user != null) {
+ return user;
+ }
+ //#endregion
+ // Fetched from remote server and registered
+ if (resolver == null) resolver = new Resolver();
+ return await createPerson(uri, resolver, undefined, limiter);
+}
+const services = {
+ "misskey:authentication:github": (id, login)=>({
+ id,
+ login
+ }),
+ "misskey:authentication:discord": (id, name)=>$discord(id, name)
+};
+const $discord = (id, name)=>{
+ if (typeof name !== "string") {
+ name = "unknown#0000";
+ }
+ const [username, discriminator] = name.split("#");
+ return {
+ id,
+ username,
+ discriminator
+ };
+};
+function addService(target, source) {
+ const service = services[source.name];
+ if (typeof source.value !== "string") {
+ source.value = "unknown";
+ }
+ const [id, username] = source.value.split("@");
+ if (service) {
+ target[source.name.split(":")[2]] = service(id, username);
+ }
+}
+export async function analyzeAttachments(attachments) {
+ const fields = [];
+ const services = {};
+ if (Array.isArray(attachments)) {
+ for (const attachment of attachments.filter(isPropertyValue)){
+ if (isPropertyValue(attachment.identifier)) {
+ addService(services, attachment.identifier);
+ } else {
+ fields.push({
+ name: attachment.name,
+ value: await fromHtml(attachment.value)
+ });
+ }
+ }
+ }
+ return {
+ fields,
+ services
+ };
+}
+export async function updateFeatured(userId, resolver, limiter = new RecursionLimiter()) {
+ const user = await Users.findOneByOrFail({
+ id: userId
+ });
+ if (!Users.isRemoteUser(user)) return;
+ if (!user.featured) return;
+ logger.info(`Updating the featured: ${user.uri}`);
+ if (resolver == null) resolver = new Resolver();
+ // Attempt to get a local user that follows the remote user
+ const follower = await Users.getRandomFollower(userId);
+ if (follower) resolver.setUser(follower);
+ // Resolve to (Ordered)Collection Object
+ const collection = await resolver.resolveCollection(user.featured);
+ if (!isCollectionOrOrderedCollection(collection)) throw new Error("Object is not Collection or OrderedCollection");
+ // Resolve to Object(may be Note) arrays
+ const unresolvedItems = isCollection(collection) ? collection.items : collection.orderedItems;
+ const items = await Promise.all(toArray(unresolvedItems).map((x)=>resolver.resolve(x)));
+ // Resolve and register Notes
+ resolver.reset();
+ const limit = promiseLimit(2);
+ const featuredNotes = await Promise.all(items.filter((item)=>getApType(item) === "Note") // TODO: Maybe it doesn't have to be a Note.
+ .slice(0, 5).map((item)=>limit(()=>resolveNote(item, resolver, limiter))));
+ // Prepare the objects
+ // For now, generate the id at a different time and maintain the order.
+ const data = [];
+ let td = 0;
+ for (const note of featuredNotes.filter((note)=>note != null)){
+ td -= 1000;
+ data.push({
+ id: genId(new Date(Date.now() + td)),
+ createdAt: new Date(),
+ userId: user.id,
+ noteId: note.id
+ });
+ }
+ // Save the objects atomically using a db transaction, note that we should never run any code in a transaction block directly
+ await db.transaction(async (transactionalEntityManager)=>{
+ await transactionalEntityManager.delete(UserNotePining, {
+ userId: user.id
+ });
+ await transactionalEntityManager.insert(UserNotePining, data);
+ });
+}
diff --git a/packages/backend/built/remote/activitypub/models/question.js b/packages/backend/built/remote/activitypub/models/question.js
new file mode 100644
index 0000000..d961dcc
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/models/question.js
@@ -0,0 +1,64 @@
+import config from "../../../config/index.js";
+import { getApId, isQuestion } from "../type.js";
+import { apLogger } from "../logger.js";
+import { Notes, Polls } from "../../../models/index.js";
+import { extractDbHost, toPuny } from "../../../misc/convert-host.js";
+export async function extractPollFromQuestion(source, resolver) {
+ const question = await resolver.resolve(source);
+ if (!isQuestion(question)) {
+ throw new Error("invalid type");
+ }
+ const multiple = !question.oneOf;
+ const expiresAt = question.endTime ? new Date(question.endTime) : question.closed ? new Date(question.closed) : null;
+ if (multiple && !question.anyOf) {
+ throw new Error("invalid question");
+ }
+ const choices = question[multiple ? "anyOf" : "oneOf"].map((x, i)=>x.name);
+ const votes = question[multiple ? "anyOf" : "oneOf"].map((x, i)=>x.replies?.totalItems || x._misskey_votes || 0);
+ return {
+ choices,
+ votes,
+ multiple,
+ expiresAt
+ };
+}
+/**
+ * Update votes of Question
+ * @param value URI of AP Question object or object itself
+ * @returns true if updated
+ */ export async function updateQuestion(value, resolver) {
+ const uri = typeof value === "string" ? value : getApId(value);
+ // Skip if URI points to this server
+ if (extractDbHost(uri) === toPuny(config.host)) throw new Error("uri points local");
+ //#region Already registered with this server?
+ const note = await Notes.findOneBy({
+ uri
+ });
+ if (note == null) throw new Error("Question is not registed");
+ const poll = await Polls.findOneBy({
+ noteId: note.id
+ });
+ if (poll == null) throw new Error("Question is not registed");
+ //#endregion
+ // resolve new Question object
+ const question = await resolver.resolve(value);
+ apLogger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`);
+ if (question.type !== "Question") throw new Error("object is not a Question");
+ const apChoices = question.oneOf || question.anyOf;
+ if (!apChoices) return false;
+ let changed = false;
+ for (const choice of poll.choices){
+ const oldCount = poll.votes[poll.choices.indexOf(choice)];
+ const newCount = apChoices.filter((ap)=>ap.name === choice)[0]?.replies?.totalItems;
+ if (newCount !== undefined && oldCount !== newCount) {
+ changed = true;
+ poll.votes[poll.choices.indexOf(choice)] = newCount;
+ }
+ }
+ await Polls.update({
+ noteId: note.id
+ }, {
+ votes: poll.votes
+ });
+ return changed;
+}
diff --git a/packages/backend/built/remote/activitypub/models/tag.js b/packages/backend/built/remote/activitypub/models/tag.js
new file mode 100644
index 0000000..651e8f2
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/models/tag.js
@@ -0,0 +1,14 @@
+import { toArray } from "../../../prelude/array.js";
+import { isHashtag } from "../type.js";
+export function extractApHashtags(tags) {
+ if (tags == null) return [];
+ const hashtags = extractApHashtagObjects(tags);
+ return hashtags.map((tag)=>{
+ const m = tag.name.match(/^#(.+)/);
+ return m ? m[1] : null;
+ }).filter((x)=>x != null);
+}
+export function extractApHashtagObjects(tags) {
+ if (tags == null) return [];
+ return toArray(tags).filter(isHashtag);
+}
diff --git a/packages/backend/built/remote/activitypub/perform.js b/packages/backend/built/remote/activitypub/perform.js
new file mode 100644
index 0000000..7793ed6
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/perform.js
@@ -0,0 +1,14 @@
+import { performActivity } from "./kernel/index.js";
+import { updatePerson } from "./models/person.js";
+export default (async (actor, activity)=>{
+ const ret = await performActivity(actor, activity);
+ // Update the remote user information if it is out of date
+ if (actor.uri) {
+ if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) {
+ setImmediate(()=>{
+ updatePerson(actor.uri);
+ });
+ }
+ }
+ return ret;
+});
diff --git a/packages/backend/built/remote/activitypub/renderer/accept-follow.js b/packages/backend/built/remote/activitypub/renderer/accept-follow.js
new file mode 100644
index 0000000..a5ffb3c
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/accept-follow.js
@@ -0,0 +1,9 @@
+import config from "../../../config/index.js";
+import renderFollow from "./follow.js";
+export default ((follower, followee, requestId)=>{
+ return {
+ type: "Accept",
+ actor: `${config.url}/users/${followee.id}`,
+ object: renderFollow(follower, followee, requestId)
+ };
+});
diff --git a/packages/backend/built/remote/activitypub/renderer/accept-quote-request.js b/packages/backend/built/remote/activitypub/renderer/accept-quote-request.js
new file mode 100644
index 0000000..8a29d95
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/accept-quote-request.js
@@ -0,0 +1,15 @@
+import config from "../../../config/index.js";
+// assumes stamp.targetNote is populated
+export default (async (request, stamp)=>({
+ type: "Accept",
+ to: request.actor,
+ actor: `${config.url}/users/${stamp.targetNote.userId}`,
+ object: {
+ type: "QuoteRequest",
+ id: request.id,
+ actor: request.actor,
+ object: request.object,
+ instrument: request.instrument
+ },
+ result: `${config.url}/stamp/${stamp.id}`
+ }));
diff --git a/packages/backend/built/remote/activitypub/renderer/add.js b/packages/backend/built/remote/activitypub/renderer/add.js
new file mode 100644
index 0000000..fba59fb
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/add.js
@@ -0,0 +1,7 @@
+import config from "../../../config/index.js";
+export default ((user, target, object)=>({
+ type: "Add",
+ actor: `${config.url}/users/${user.id}`,
+ target,
+ object
+ }));
diff --git a/packages/backend/built/remote/activitypub/renderer/announce.js b/packages/backend/built/remote/activitypub/renderer/announce.js
new file mode 100644
index 0000000..40ba7b9
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/announce.js
@@ -0,0 +1,39 @@
+import config from "../../../config/index.js";
+export default ((object, note)=>{
+ const attributedTo = `${config.url}/users/${note.userId}`;
+ const mentions = JSON.parse(note.mentionedRemoteUsers).map((x)=>x.uri);
+ let to = [];
+ let cc = [];
+ if (note.visibility === "public") {
+ to = [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ];
+ cc = [
+ `${attributedTo}/followers`
+ ];
+ } else if (note.visibility === "home") {
+ to = [
+ `${attributedTo}/followers`
+ ];
+ cc = [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ];
+ } else if (note.visibility === "followers") {
+ to = [
+ `${attributedTo}/followers`
+ ];
+ } else if (note.visibility === "specified") {
+ to = mentions;
+ } else {
+ return null;
+ }
+ return {
+ id: `${config.url}/notes/${note.id}/activity`,
+ actor: `${config.url}/users/${note.userId}`,
+ type: "Announce",
+ published: note.createdAt.toISOString(),
+ to,
+ cc,
+ object
+ };
+});
diff --git a/packages/backend/built/remote/activitypub/renderer/bite.js b/packages/backend/built/remote/activitypub/renderer/bite.js
new file mode 100644
index 0000000..482bbe3
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/bite.js
@@ -0,0 +1,10 @@
+import config from "../../../config/index.js";
+import { Bites } from "../../../models/index.js";
+export default (async (bite)=>({
+ id: `${config.url}/bites/${bite.id}`,
+ type: "Bite",
+ actor: `${config.url}/users/${bite.userId}`,
+ target: await Bites.targetUri(bite),
+ published: bite.createdAt.toISOString(),
+ to: await Bites.targetUserUri(bite)
+ }));
diff --git a/packages/backend/built/remote/activitypub/renderer/block.js b/packages/backend/built/remote/activitypub/renderer/block.js
new file mode 100644
index 0000000..abbc37b
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/block.js
@@ -0,0 +1,16 @@
+import config from "../../../config/index.js";
+/**
+ * Renders a block into its ActivityPub representation.
+ *
+ * @param block The block to be rendered. The blockee relation must be loaded.
+ */ export function renderBlock(block) {
+ if (block.blockee?.uri == null) {
+ throw new Error("renderBlock: missing blockee uri");
+ }
+ return {
+ type: "Block",
+ id: `${config.url}/blocks/${block.id}`,
+ actor: `${config.url}/users/${block.blockerId}`,
+ object: block.blockee.uri
+ };
+}
diff --git a/packages/backend/built/remote/activitypub/renderer/create.js b/packages/backend/built/remote/activitypub/renderer/create.js
new file mode 100644
index 0000000..1aabf34
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/create.js
@@ -0,0 +1,13 @@
+import config from "../../../config/index.js";
+export default ((object, note)=>{
+ const activity = {
+ id: `${config.url}/notes/${note.id}/activity`,
+ actor: `${config.url}/users/${note.userId}`,
+ type: "Create",
+ published: note.createdAt.toISOString(),
+ object
+ };
+ if (object.to) activity.to = object.to;
+ if (object.cc) activity.cc = object.cc;
+ return activity;
+});
diff --git a/packages/backend/built/remote/activitypub/renderer/delete.js b/packages/backend/built/remote/activitypub/renderer/delete.js
new file mode 100644
index 0000000..f4153cf
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/delete.js
@@ -0,0 +1,7 @@
+import config from "../../../config/index.js";
+export default ((object, user)=>({
+ type: "Delete",
+ actor: `${config.url}/users/${user.id}`,
+ object,
+ published: new Date().toISOString()
+ }));
diff --git a/packages/backend/built/remote/activitypub/renderer/document.js b/packages/backend/built/remote/activitypub/renderer/document.js
new file mode 100644
index 0000000..266e8ba
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/document.js
@@ -0,0 +1,7 @@
+import { DriveFiles } from "../../../models/index.js";
+export default ((file)=>({
+ type: "Document",
+ mediaType: file.type,
+ url: DriveFiles.getPublicUrl(file),
+ name: file.comment
+ }));
diff --git a/packages/backend/built/remote/activitypub/renderer/emoji.js b/packages/backend/built/remote/activitypub/renderer/emoji.js
new file mode 100644
index 0000000..a185821
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/emoji.js
@@ -0,0 +1,12 @@
+import config from "../../../config/index.js";
+export default ((emoji)=>({
+ id: `${config.url}/emojis/${emoji.name}`,
+ type: "Emoji",
+ name: `:${emoji.name}:`,
+ updated: emoji.updatedAt != null ? emoji.updatedAt.toISOString() : new Date().toISOString,
+ icon: {
+ type: "Image",
+ mediaType: emoji.glyph ? "image/svg+xml" : emoji.type || "image/png",
+ url: emoji.glyph ? emoji.originalUrl : emoji.publicUrl || emoji.originalUrl
+ }
+ }));
diff --git a/packages/backend/built/remote/activitypub/renderer/flag.js b/packages/backend/built/remote/activitypub/renderer/flag.js
new file mode 100644
index 0000000..395feb9
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/flag.js
@@ -0,0 +1,11 @@
+import config from "../../../config/index.js";
+// to anonymise reporters, the reporting actor must be a system user
+// object has to be a uri or array of uris
+export const renderFlag = (user, object, content)=>{
+ return {
+ type: "Flag",
+ actor: `${config.url}/users/${user.id}`,
+ content,
+ object
+ };
+};
diff --git a/packages/backend/built/remote/activitypub/renderer/follow-relay.js b/packages/backend/built/remote/activitypub/renderer/follow-relay.js
new file mode 100644
index 0000000..c6593bb
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/follow-relay.js
@@ -0,0 +1,10 @@
+import config from "../../../config/index.js";
+export function renderFollowRelay(relay, relayActor) {
+ const follow = {
+ id: `${config.url}/activities/follow-relay/${relay.id}`,
+ type: "Follow",
+ actor: `${config.url}/users/${relayActor.id}`,
+ object: "https://www.w3.org/ns/activitystreams#Public"
+ };
+ return follow;
+}
diff --git a/packages/backend/built/remote/activitypub/renderer/follow-user.js b/packages/backend/built/remote/activitypub/renderer/follow-user.js
new file mode 100644
index 0000000..f99908e
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/follow-user.js
@@ -0,0 +1,11 @@
+import config from "../../../config/index.js";
+import { Users } from "../../../models/index.js";
+/**
+ * Convert (local|remote)(Follower|Followee)ID to URL
+ * @param id Follower|Followee ID
+ */ export default async function renderFollowUser(id) {
+ const user = await Users.findOneByOrFail({
+ id: id
+ });
+ return Users.isLocalUser(user) ? `${config.url}/users/${user.id}` : user.uri;
+}
diff --git a/packages/backend/built/remote/activitypub/renderer/follow.js b/packages/backend/built/remote/activitypub/renderer/follow.js
new file mode 100644
index 0000000..3285058
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/follow.js
@@ -0,0 +1,11 @@
+import config from "../../../config/index.js";
+import { Users } from "../../../models/index.js";
+export default ((follower, followee, requestId)=>{
+ const follow = {
+ id: requestId ?? `${config.url}/follows/${follower.id}/${followee.id}`,
+ type: "Follow",
+ actor: Users.isLocalUser(follower) ? `${config.url}/users/${follower.id}` : follower.uri,
+ object: Users.isLocalUser(followee) ? `${config.url}/users/${followee.id}` : followee.uri
+ };
+ return follow;
+});
diff --git a/packages/backend/built/remote/activitypub/renderer/hashtag.js b/packages/backend/built/remote/activitypub/renderer/hashtag.js
new file mode 100644
index 0000000..5562040
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/hashtag.js
@@ -0,0 +1,6 @@
+import config from "../../../config/index.js";
+export default ((tag)=>({
+ type: "Hashtag",
+ href: `${config.url}/tags/${encodeURIComponent(tag)}`,
+ name: `#${tag}`
+ }));
diff --git a/packages/backend/built/remote/activitypub/renderer/image.js b/packages/backend/built/remote/activitypub/renderer/image.js
new file mode 100644
index 0000000..9fe4796
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/image.js
@@ -0,0 +1,7 @@
+import { DriveFiles } from "../../../models/index.js";
+export default ((file)=>({
+ type: "Image",
+ url: DriveFiles.getPublicUrl(file),
+ sensitive: file.isSensitive,
+ name: file.comment
+ }));
diff --git a/packages/backend/built/remote/activitypub/renderer/index.js b/packages/backend/built/remote/activitypub/renderer/index.js
new file mode 100644
index 0000000..75d8950
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/index.js
@@ -0,0 +1,20 @@
+import { v4 as uuid } from "uuid";
+import config from "../../../config/index.js";
+import { getUserKeypair } from "../../../misc/keypair-store.js";
+import { LdSignature } from "../misc/ld-signature.js";
+import { WellKnownContext } from "../misc/contexts.js";
+export const renderActivity = (x)=>{
+ if (x == null) return null;
+ if (typeof x === "object" && x.id == null) {
+ x.id = `${config.url}/${uuid()}`;
+ }
+ return Object.assign({}, WellKnownContext, x);
+};
+export const attachLdSignature = async (activity, user)=>{
+ if (activity == null) return null;
+ const keypair = await getUserKeypair(user.id);
+ const ldSignature = new LdSignature();
+ ldSignature.debug = false;
+ activity = await ldSignature.signRsaSignature2017(activity, keypair.privateKey, `${config.url}/users/${user.id}#main-key`);
+ return activity;
+};
diff --git a/packages/backend/built/remote/activitypub/renderer/key.js b/packages/backend/built/remote/activitypub/renderer/key.js
new file mode 100644
index 0000000..60b049e
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/key.js
@@ -0,0 +1,11 @@
+import config from "../../../config/index.js";
+import { createPublicKey } from "node:crypto";
+export default ((user, key, postfix)=>({
+ id: `${config.url}/users/${user.id}${postfix || "/publickey"}`,
+ type: "Key",
+ owner: `${config.url}/users/${user.id}`,
+ publicKeyPem: createPublicKey(key.publicKey).export({
+ type: "spki",
+ format: "pem"
+ })
+ }));
diff --git a/packages/backend/built/remote/activitypub/renderer/like.js b/packages/backend/built/remote/activitypub/renderer/like.js
new file mode 100644
index 0000000..a127a5f
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/like.js
@@ -0,0 +1,30 @@
+import { IsNull } from "typeorm";
+import config from "../../../config/index.js";
+import { Emojis } from "../../../models/index.js";
+import renderEmoji from "./emoji.js";
+import { fetchMeta } from "../../../misc/fetch-meta.js";
+export const renderLike = async (noteReaction, note)=>{
+ const reaction = noteReaction.reaction;
+ const meta = await fetchMeta();
+ const object = {
+ type: "Like",
+ id: `${config.url}/likes/${noteReaction.id}`,
+ actor: `${config.url}/users/${noteReaction.userId}`,
+ object: note.uri ? note.uri : `${config.url}/notes/${noteReaction.noteId}`,
+ ...!meta.defaultReaction.includes(reaction) ? {
+ content: reaction,
+ _misskey_reaction: reaction
+ } : {}
+ };
+ if (reaction.startsWith(":")) {
+ const name = reaction.replace(/:/g, "");
+ const emoji = await Emojis.findOneBy({
+ name,
+ host: IsNull()
+ });
+ if (emoji) object.tag = [
+ renderEmoji(emoji)
+ ];
+ }
+ return object;
+};
diff --git a/packages/backend/built/remote/activitypub/renderer/mention.js b/packages/backend/built/remote/activitypub/renderer/mention.js
new file mode 100644
index 0000000..8d21229
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/mention.js
@@ -0,0 +1,7 @@
+import config from "../../../config/index.js";
+import { Users } from "../../../models/index.js";
+export default ((mention)=>({
+ type: "Mention",
+ href: Users.isRemoteUser(mention) ? mention.uri : `${config.url}/users/${mention.id}`,
+ name: Users.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${mention.username}`
+ }));
diff --git a/packages/backend/built/remote/activitypub/renderer/note.js b/packages/backend/built/remote/activitypub/renderer/note.js
new file mode 100644
index 0000000..2f4e99b
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/note.js
@@ -0,0 +1,174 @@
+import { In, IsNull } from "typeorm";
+import config from "../../../config/index.js";
+import { DriveFiles, Notes, Users, Emojis, Polls } from "../../../models/index.js";
+import toHtml from "../misc/get-note-html.js";
+import renderEmoji from "./emoji.js";
+import renderMention from "./mention.js";
+import renderHashtag from "./hashtag.js";
+import renderDocument from "./document.js";
+import sanitizeHtml from "sanitize-html";
+export default async function renderNote(note, dive = true, isTalk = false) {
+ const getPromisedFiles = async (ids)=>{
+ if (!ids || ids.length === 0) return [];
+ const items = await DriveFiles.findBy({
+ id: In(ids)
+ });
+ return ids.map((id)=>items.find((item)=>item.id === id)).filter((item)=>item != null);
+ };
+ let inReplyTo;
+ let inReplyToNote;
+ if (note.replyId) {
+ inReplyToNote = await Notes.findOneBy({
+ id: note.replyId
+ });
+ if (inReplyToNote != null) {
+ const inReplyToUser = await Users.findOneBy({
+ id: inReplyToNote.userId
+ });
+ if (inReplyToUser != null) {
+ if (inReplyToNote.uri) {
+ inReplyTo = inReplyToNote.uri;
+ } else {
+ if (dive) {
+ inReplyTo = await renderNote(inReplyToNote, false);
+ } else {
+ inReplyTo = `${config.url}/notes/${inReplyToNote.id}`;
+ }
+ }
+ }
+ }
+ } else {
+ inReplyTo = null;
+ }
+ let quoteId;
+ let quoteUrl;
+ if (note.renoteId) {
+ const renote = await Notes.findOneBy({
+ id: note.renoteId
+ });
+ if (renote) {
+ if (renote.userHost) {
+ quoteId = renote.uri;
+ quoteUrl = renote.url ?? renote.uri;
+ } else {
+ quoteId = quoteUrl = `${config.url}/notes/${renote.id}`;
+ }
+ }
+ }
+ const attributedTo = `${config.url}/users/${note.userId}`;
+ const mentions = JSON.parse(note.mentionedRemoteUsers).map((x)=>x.uri);
+ let to = [];
+ let cc = [];
+ if (note.visibility === "public") {
+ to = [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ];
+ cc = [
+ `${attributedTo}/followers`
+ ].concat(mentions);
+ } else if (note.visibility === "home") {
+ to = [
+ `${attributedTo}/followers`
+ ];
+ cc = [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ].concat(mentions);
+ } else if (note.visibility === "followers") {
+ to = [
+ `${attributedTo}/followers`
+ ];
+ cc = mentions;
+ } else {
+ to = mentions;
+ }
+ const mentionedUsers = note.mentions.length > 0 ? await Users.findBy({
+ id: In(note.mentions)
+ }) : [];
+ const hashtagTags = (note.tags || []).map((tag)=>renderHashtag(tag));
+ const mentionTags = mentionedUsers.map((u)=>renderMention(u));
+ const files = await getPromisedFiles(note.fileIds);
+ const text = note.text ?? "";
+ let poll = null;
+ if (note.hasPoll) {
+ poll = await Polls.findOneBy({
+ noteId: note.id
+ });
+ }
+ const summary = note.cw === "" ? String.fromCharCode(0x200b) : note.cw;
+ let content = await toHtml(Object.assign({}, note, {
+ text
+ }));
+ if (quoteId) {
+ // wrapping in p.quote-inline lets mastodon automatically strip the link
+ const quoteHREFSan = (quoteUrl || quoteId).replaceAll("&", "&").replaceAll('"', """);
+ const quoteTextSan = sanitizeHtml(quoteUrl || quoteId);
+ content += `RE: ${quoteTextSan}
`;
+ }
+ const emojis = await getEmojis(note.emojis);
+ const apemojis = emojis.map((emoji)=>renderEmoji(emoji));
+ const tag = [
+ ...hashtagTags,
+ ...mentionTags,
+ ...apemojis
+ ];
+ const asPoll = poll ? {
+ type: "Question",
+ content: await toHtml(Object.assign({}, note, {
+ text: text
+ })),
+ [poll.expiresAt && poll.expiresAt < new Date() ? "closed" : "endTime"]: poll.expiresAt,
+ [poll.multiple ? "anyOf" : "oneOf"]: poll.choices.map((text, i)=>({
+ type: "Note",
+ name: text,
+ replies: {
+ type: "Collection",
+ totalItems: poll.votes[i]
+ }
+ }))
+ } : {};
+ const asTalk = isTalk ? {
+ _misskey_talk: true
+ } : {};
+ return {
+ id: `${config.url}/notes/${note.id}`,
+ type: "Note",
+ attributedTo,
+ summary,
+ content,
+ _misskey_content: text,
+ source: {
+ content: text,
+ mediaType: "text/x.misskeymarkdown"
+ },
+ _misskey_quote: quoteId,
+ quoteUri: quoteId,
+ quoteUrl: quoteId,
+ quote: note.canQuote ? quoteId : undefined,
+ published: note.createdAt.toISOString(),
+ to,
+ cc,
+ inReplyTo,
+ attachment: files.map(renderDocument),
+ sensitive: note.cw != null || files.some((file)=>file.isSensitive),
+ tag,
+ interactionPolicy: note.visibility === "public" || note.visibility === "home" ? {
+ canQuote: {
+ automaticApproval: "https://www.w3.org/ns/activitystreams#Public"
+ }
+ } : undefined,
+ quoteAuthorization: note.quoteAuthorization || undefined,
+ ...asPoll,
+ ...asTalk
+ };
+}
+export async function getEmojis(names) {
+ if (names == null || names.length === 0) return [];
+ const emojis = await Promise.all(names.map((name)=>{
+ const parts = name.split("@");
+ return Emojis.findOneBy({
+ name: parts[0],
+ host: parts.length === 2 ? parts[1] : IsNull()
+ });
+ }));
+ return emojis.filter((emoji)=>emoji != null);
+}
diff --git a/packages/backend/built/remote/activitypub/renderer/ordered-collection-page.js b/packages/backend/built/remote/activitypub/renderer/ordered-collection-page.js
new file mode 100644
index 0000000..87cb689
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/ordered-collection-page.js
@@ -0,0 +1,20 @@
+/**
+ * Render OrderedCollectionPage
+ * @param id URL of self
+ * @param totalItems Number of total items
+ * @param orderedItems Items
+ * @param partOf URL of base
+ * @param prev URL of prev page (optional)
+ * @param next URL of next page (optional)
+ */ export default function(id, totalItems, orderedItems, partOf, prev, next) {
+ const page = {
+ id,
+ partOf,
+ type: "OrderedCollectionPage",
+ totalItems,
+ orderedItems
+ };
+ if (prev) page.prev = prev;
+ if (next) page.next = next;
+ return page;
+}
diff --git a/packages/backend/built/remote/activitypub/renderer/ordered-collection.js b/packages/backend/built/remote/activitypub/renderer/ordered-collection.js
new file mode 100644
index 0000000..90c9a2f
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/ordered-collection.js
@@ -0,0 +1,18 @@
+/**
+ * Render OrderedCollection
+ * @param id URL of self
+ * @param totalItems Total number of items
+ * @param first URL of first page (optional)
+ * @param last URL of last page (optional)
+ * @param orderedItems attached objects (optional)
+ */ export default function(id, totalItems, first, last, orderedItems) {
+ const page = {
+ id,
+ type: "OrderedCollection",
+ totalItems
+ };
+ if (first) page.first = first;
+ if (last) page.last = last;
+ if (orderedItems) page.orderedItems = orderedItems;
+ return page;
+}
diff --git a/packages/backend/built/remote/activitypub/renderer/person.js b/packages/backend/built/remote/activitypub/renderer/person.js
new file mode 100644
index 0000000..7f96168
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/person.js
@@ -0,0 +1,91 @@
+import * as mfm from "mfm-js";
+import config from "../../../config/index.js";
+import { DriveFiles, UserProfiles } from "../../../models/index.js";
+import { getUserKeypair } from "../../../misc/keypair-store.js";
+import { toHtml } from "../../../mfm/to-html.js";
+import renderImage from "./image.js";
+import renderKey from "./key.js";
+import { getEmojis } from "./note.js";
+import renderEmoji from "./emoji.js";
+import renderHashtag from "./hashtag.js";
+export async function renderPerson(user) {
+ const id = `${config.url}/users/${user.id}`;
+ const isSystem = !!user.username.match(/\./);
+ const [avatar, banner, profile] = await Promise.all([
+ user.avatarId ? DriveFiles.findOneBy({
+ id: user.avatarId
+ }) : Promise.resolve(undefined),
+ user.bannerId ? DriveFiles.findOneBy({
+ id: user.bannerId
+ }) : Promise.resolve(undefined),
+ UserProfiles.findOneByOrFail({
+ userId: user.id
+ })
+ ]);
+ const attachment = [];
+ if (profile.fields) {
+ for (const field of profile.fields){
+ const value = await toHtml(mfm.parse(field.value), profile.mentions, profile.userHost);
+ attachment.push({
+ type: "PropertyValue",
+ name: field.name,
+ value: value ?? field.value
+ });
+ }
+ }
+ const emojis = await getEmojis(user.emojis);
+ const apemojis = emojis.map((emoji)=>renderEmoji(emoji));
+ const hashtagTags = (user.tags || []).map((tag)=>renderHashtag(tag));
+ const tag = [
+ ...apemojis,
+ ...hashtagTags
+ ];
+ const keypair = await getUserKeypair(user.id);
+ let canBite;
+ if (user.canBite === "anyone") {
+ canBite = "https://www.w3.org/ns/activitystreams#Public";
+ } else if (user.canBite === "followers") {
+ canBite = user.followersUri ?? `${config.url}/users/${user.id}/followers`;
+ }
+ const person = {
+ type: isSystem ? "Application" : user.isBot ? "Service" : "Person",
+ id,
+ inbox: `${id}/inbox`,
+ outbox: `${id}/outbox`,
+ followers: `${id}/followers`,
+ following: `${id}/following`,
+ featured: `${id}/collections/featured`,
+ sharedInbox: `${config.url}/inbox`,
+ endpoints: {
+ sharedInbox: `${config.url}/inbox`
+ },
+ url: `${config.url}/@${user.username}`,
+ preferredUsername: user.username,
+ name: user.name,
+ summary: profile.description ? await toHtml(mfm.parse(profile.description), profile.mentions, profile.userHost) : null,
+ _misskey_summary: profile.description,
+ icon: avatar ? renderImage(avatar) : null,
+ image: banner ? renderImage(banner) : null,
+ tag,
+ manuallyApprovesFollowers: user.isLocked,
+ discoverable: !!user.isExplorable,
+ publicKey: renderKey(user, keypair, "#main-key"),
+ isCat: user.isCat,
+ attachment: attachment.length ? attachment : undefined,
+ pronouns: profile.pronouns,
+ canBite
+ };
+ if (user.movedToUri) {
+ person.movedTo = user.movedToUri;
+ }
+ if (user.alsoKnownAs) {
+ person.alsoKnownAs = user.alsoKnownAs;
+ }
+ if (profile.birthday) {
+ person["vcard:bday"] = profile.birthday;
+ }
+ if (profile.location) {
+ person["vcard:Address"] = profile.location;
+ }
+ return person;
+}
diff --git a/packages/backend/built/remote/activitypub/renderer/question.js b/packages/backend/built/remote/activitypub/renderer/question.js
new file mode 100644
index 0000000..874030b
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/question.js
@@ -0,0 +1,18 @@
+import config from "../../../config/index.js";
+export default async function renderQuestion(user, note, poll) {
+ const question = {
+ type: "Question",
+ id: `${config.url}/questions/${note.id}`,
+ actor: `${config.url}/users/${user.id}`,
+ content: note.text || "",
+ [poll.multiple ? "anyOf" : "oneOf"]: poll.choices.map((text, i)=>({
+ name: text,
+ _misskey_votes: poll.votes[i],
+ replies: {
+ type: "Collection",
+ totalItems: poll.votes[i]
+ }
+ }))
+ };
+ return question;
+}
diff --git a/packages/backend/built/remote/activitypub/renderer/quote-authorization.js b/packages/backend/built/remote/activitypub/renderer/quote-authorization.js
new file mode 100644
index 0000000..909f379
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/quote-authorization.js
@@ -0,0 +1,9 @@
+import config from "../../../config/index.js";
+// assumes stamp.note and stamp.targetNote are populated
+export default ((stamp)=>({
+ id: `${config.url}/stamp/${stamp.id}`,
+ type: "QuoteAuthorization",
+ attributedTo: `${config.url}/users/${stamp.targetNote.userId}`,
+ interactingObject: stamp.note.userHost === null ? `${config.url}/notes/${stamp.note.id}` : stamp.note.uri,
+ interactionTarget: `${config.url}/notes/${stamp.targetNoteId}`
+ }));
diff --git a/packages/backend/built/remote/activitypub/renderer/quote-request.js b/packages/backend/built/remote/activitypub/renderer/quote-request.js
new file mode 100644
index 0000000..28e9c42
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/quote-request.js
@@ -0,0 +1,9 @@
+import config from "../../../config/index.js";
+export default function renderQuoteRequest(note, targetNote) {
+ return {
+ type: "QuoteRequest",
+ actor: `${config.url}/users/${note.userId}`,
+ object: targetNote.uri,
+ instrument: `${config.url}/notes/${note.id}`
+ };
+}
diff --git a/packages/backend/built/remote/activitypub/renderer/read.js b/packages/backend/built/remote/activitypub/renderer/read.js
new file mode 100644
index 0000000..d409873
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/read.js
@@ -0,0 +1,6 @@
+import config from "../../../config/index.js";
+export const renderReadActivity = (user, message)=>({
+ type: "Read",
+ actor: `${config.url}/users/${user.id}`,
+ object: message.uri
+ });
diff --git a/packages/backend/built/remote/activitypub/renderer/reject.js b/packages/backend/built/remote/activitypub/renderer/reject.js
new file mode 100644
index 0000000..f7f968b
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/reject.js
@@ -0,0 +1,6 @@
+import config from "../../../config/index.js";
+export default ((object, user)=>({
+ type: "Reject",
+ actor: `${config.url}/users/${user.id}`,
+ object
+ }));
diff --git a/packages/backend/built/remote/activitypub/renderer/remove.js b/packages/backend/built/remote/activitypub/renderer/remove.js
new file mode 100644
index 0000000..41d98b6
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/remove.js
@@ -0,0 +1,7 @@
+import config from "../../../config/index.js";
+export default ((user, target, object)=>({
+ type: "Remove",
+ actor: `${config.url}/users/${user.id}`,
+ target,
+ object
+ }));
diff --git a/packages/backend/built/remote/activitypub/renderer/tombstone.js b/packages/backend/built/remote/activitypub/renderer/tombstone.js
new file mode 100644
index 0000000..d9daf15
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/tombstone.js
@@ -0,0 +1,4 @@
+export default ((id)=>({
+ id,
+ type: "Tombstone"
+ }));
diff --git a/packages/backend/built/remote/activitypub/renderer/undo.js b/packages/backend/built/remote/activitypub/renderer/undo.js
new file mode 100644
index 0000000..df09250
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/undo.js
@@ -0,0 +1,14 @@
+import config from "../../../config/index.js";
+export default ((object, user)=>{
+ if (object == null) return null;
+ const id = typeof object.id === "string" && object.id.startsWith(config.url) ? `${object.id}/undo` : undefined;
+ return {
+ type: "Undo",
+ ...id ? {
+ id
+ } : {},
+ actor: `${config.url}/users/${user.id}`,
+ object,
+ published: new Date().toISOString()
+ };
+});
diff --git a/packages/backend/built/remote/activitypub/renderer/update.js b/packages/backend/built/remote/activitypub/renderer/update.js
new file mode 100644
index 0000000..5063570
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/update.js
@@ -0,0 +1,14 @@
+import config from "../../../config/index.js";
+export default ((object, user)=>{
+ const activity = {
+ id: `${config.url}/users/${user.id}#updates/${new Date().getTime()}`,
+ actor: `${config.url}/users/${user.id}`,
+ type: "Update",
+ to: [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ object,
+ published: new Date().toISOString()
+ };
+ return activity;
+});
diff --git a/packages/backend/built/remote/activitypub/renderer/vote.js b/packages/backend/built/remote/activitypub/renderer/vote.js
new file mode 100644
index 0000000..a3337cf
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/renderer/vote.js
@@ -0,0 +1,22 @@
+import config from "../../../config/index.js";
+export default async function renderVote(user, vote, note, poll, pollOwner) {
+ return {
+ id: `${config.url}/users/${user.id}#votes/${vote.id}/activity`,
+ actor: `${config.url}/users/${user.id}`,
+ type: "Create",
+ to: [
+ pollOwner.uri
+ ],
+ published: new Date().toISOString(),
+ object: {
+ id: `${config.url}/users/${user.id}#votes/${vote.id}`,
+ type: "Note",
+ attributedTo: `${config.url}/users/${user.id}`,
+ to: [
+ pollOwner.uri
+ ],
+ inReplyTo: note.uri,
+ name: poll.choices[vote.choice]
+ }
+ };
+}
diff --git a/packages/backend/built/remote/activitypub/request.js b/packages/backend/built/remote/activitypub/request.js
new file mode 100644
index 0000000..3c79d6c
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/request.js
@@ -0,0 +1,70 @@
+import config from "../../config/index.js";
+import { getUserKeypair } from "../../misc/keypair-store.js";
+import { getResponse } from "../../misc/fetch.js";
+import { createSignedPost, createSignedGet } from "./ap-request.js";
+import { apLogger } from "./logger.js";
+export default (async (user, url, object)=>{
+ const body = JSON.stringify(object);
+ const keypair = await getUserKeypair(user.id);
+ const req = createSignedPost({
+ key: {
+ privateKeyPem: keypair.privateKey,
+ keyId: `${config.url}/users/${user.id}#main-key`
+ },
+ url,
+ body,
+ additionalHeaders: {
+ "User-Agent": config.userAgent
+ }
+ });
+ await getResponse({
+ url,
+ method: req.request.method,
+ headers: req.request.headers,
+ body
+ });
+});
+/**
+ * Get AP object with http-signature
+ * @param user http-signature user
+ * @param url URL to fetch
+ * @param redirects whether or not to accept redirects
+ */ export async function signedGet(url, user, redirects = true) {
+ apLogger.debug(`Running signedGet on url: ${url}`);
+ const keypair = await getUserKeypair(user.id);
+ const req = createSignedGet({
+ key: {
+ privateKeyPem: keypair.privateKey,
+ keyId: `${config.url}/users/${user.id}#main-key`
+ },
+ url,
+ additionalHeaders: {
+ "User-Agent": config.userAgent
+ }
+ });
+ const res = await getResponse({
+ url,
+ method: req.request.method,
+ headers: req.request.headers,
+ redirect: redirects ? "manual" : "error"
+ });
+ if (redirects && [
+ 301,
+ 302,
+ 307,
+ 308
+ ].includes(res.status)) {
+ const newUrl = res.headers.get('location');
+ if (!newUrl) throw new Error('signedGet got redirect but no target location');
+ apLogger.debug(`signedGet is redirecting to ${newUrl}`);
+ return signedGet(newUrl, user, false);
+ }
+ const contentType = res.headers.get('content-type');
+ if (contentType == null || contentType !== 'application/activity+json' && !contentType.startsWith('application/activity+json;') && (!contentType.startsWith('application/ld+json;') || !contentType.includes('profile="https://www.w3.org/ns/activitystreams"'))) {
+ throw new Error(`signedGet response had unexpected content-type: ${contentType}`);
+ }
+ return {
+ finalUrl: res.url,
+ content: await res.json()
+ };
+}
diff --git a/packages/backend/built/remote/activitypub/resolver.js b/packages/backend/built/remote/activitypub/resolver.js
new file mode 100644
index 0000000..4e26a17
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/resolver.js
@@ -0,0 +1,233 @@
+import config from "../../config/index.js";
+import { getJsonActivity } from "../../misc/fetch.js";
+import { getInstanceActor } from "../../services/instance-actor.js";
+import { fetchMeta } from "../../misc/fetch-meta.js";
+import { extractDbHost, isSelfHost } from "../../misc/convert-host.js";
+import { signedGet } from "./request.js";
+import { isCollectionOrOrderedCollection, getApId } from "./type.js";
+import { FollowRequests, Notes, NoteReactions, Polls, Users, Bites, InteractionStamps } from "../../models/index.js";
+import { parseUri } from "./db-resolver.js";
+import renderNote from "./renderer/note.js";
+import { renderLike } from "./renderer/like.js";
+import { renderPerson } from "./renderer/person.js";
+import renderQuestion from "./renderer/question.js";
+import renderCreate from "./renderer/create.js";
+import { renderActivity } from "./renderer/index.js";
+import renderFollow from "./renderer/follow.js";
+import { shouldBlockInstance } from "../../misc/should-block-instance.js";
+import { apLogger } from "./logger.js";
+import { IsNull, Not } from "typeorm";
+import { tickResolve } from "../../metrics.js";
+import renderBite from "./renderer/bite.js";
+import renderQuoteAuthorization from "./renderer/quote-authorization.js";
+export default class Resolver {
+ history;
+ user;
+ recursionLimit;
+ constructor(recursionLimit = 100){
+ this.history = new Set();
+ this.recursionLimit = recursionLimit;
+ }
+ setUser(user) {
+ this.user = user;
+ }
+ reset() {
+ this.history = new Set();
+ return this;
+ }
+ getHistory() {
+ return Array.from(this.history);
+ }
+ async resolveCollection(value) {
+ const collection = await this.resolve(value);
+ if (isCollectionOrOrderedCollection(collection)) {
+ return collection;
+ } else {
+ throw new Error(`unrecognized collection type: ${collection.type}`);
+ }
+ }
+ async resolve(value) {
+ if (value == null) {
+ throw new Error("resolvee is null (or undefined)");
+ }
+ if (typeof value !== "string") {
+ apLogger.debug("Object to resolve is not a string");
+ if (typeof value.id !== "undefined") {
+ const host = extractDbHost(getApId(value));
+ if (await shouldBlockInstance(host)) {
+ throw new Error("instance is blocked");
+ }
+ }
+ apLogger.debug("Returning existing object:");
+ apLogger.debug(JSON.stringify(value, null, 2));
+ return value;
+ }
+ apLogger.debug(`Resolving: ${value}`);
+ if (value.includes("#")) {
+ // URLs with fragment parts cannot be resolved correctly because
+ // the fragment part does not get transmitted over HTTP(S).
+ // Avoid strange behaviour by not trying to resolve these at all.
+ throw new Error(`cannot resolve URL with fragment: ${value}`);
+ }
+ if (this.history.has(value)) {
+ throw new Error("cannot resolve already resolved one");
+ }
+ if (this.recursionLimit && this.history.size > this.recursionLimit) {
+ throw new Error("hit recursion limit");
+ }
+ this.history.add(value);
+ const host = extractDbHost(value);
+ if (isSelfHost(host)) {
+ return await this.resolveLocal(value);
+ }
+ const meta = await fetchMeta();
+ if (await shouldBlockInstance(host, meta)) {
+ throw new Error("Instance is blocked");
+ }
+ if (meta.privateMode && config.host !== host && config.domain !== host && !meta.allowedHosts.includes(host)) {
+ throw new Error("Instance is not allowed");
+ }
+ if (!this.user) {
+ this.user = await getInstanceActor();
+ }
+ apLogger.debug("Getting object from remote, authenticated as user:");
+ apLogger.debug(JSON.stringify(this.user, null, 2));
+ const { res, object } = await this.doFetch(value);
+ if (object.id == null) throw new Error("Object has no ID");
+ const objectId = new URL(object.id);
+ const resFinalUrl = new URL(res.finalUrl);
+ if (resFinalUrl.toString() === objectId.toString()) {
+ tickResolve();
+ return object;
+ }
+ if (resFinalUrl.host !== objectId.host) throw new Error("Object ID host doesn't match final url host");
+ const { res: finalRes, object: finalObject } = await this.doFetch(object.id);
+ if (finalObject.id == null) throw new Error("Final object has no ID");
+ const finalObjectId = new URL(finalObject.id);
+ const finalResFinalUrl = new URL(finalRes.finalUrl);
+ if (finalResFinalUrl.toString() !== finalObjectId.toString()) throw new Error("Object ID still doesn't match final URL after second fetch attempt");
+ tickResolve();
+ return finalObject;
+ }
+ async doFetch(uri) {
+ let res = this.user ? await signedGet(uri, this.user) : await getJsonActivity(uri);
+ let object = res.content;
+ if (object == null || (Array.isArray(object["@context"]) ? !object["@context"].includes("https://www.w3.org/ns/activitystreams") : object["@context"] !== "https://www.w3.org/ns/activitystreams")) {
+ throw new Error("invalid response");
+ }
+ return {
+ res,
+ object
+ };
+ }
+ async resolveLocal(url) {
+ const parsed = parseUri(url);
+ if (!parsed.local) throw new Error("resolveLocal: not local");
+ switch(parsed.type){
+ case "notes":
+ {
+ const note = await Notes.findOneByOrFail({
+ id: parsed.id
+ });
+ if (parsed.rest === "activity") {
+ // this refers to the create activity and not the note itself
+ return renderActivity(renderCreate(await renderNote(note), note));
+ } else {
+ return renderActivity(await renderNote(note));
+ }
+ }
+ case "users":
+ {
+ const user = await Users.findOneByOrFail({
+ id: parsed.id
+ });
+ return await renderPerson(user);
+ }
+ case "questions":
+ {
+ // Polls are indexed by the note they are attached to.
+ const [note, poll] = await Promise.all([
+ Notes.findOneByOrFail({
+ id: parsed.id
+ }),
+ Polls.findOneByOrFail({
+ noteId: parsed.id
+ })
+ ]);
+ return renderActivity(await renderQuestion({
+ id: note.userId
+ }, note, poll));
+ }
+ case "likes":
+ {
+ const reaction = await NoteReactions.findOneByOrFail({
+ id: parsed.id
+ });
+ return renderActivity(await renderLike(reaction, {
+ uri: null
+ }));
+ }
+ case "follows":
+ {
+ // if rest is a
+ if (parsed.rest != null && /^\w+$/.test(parsed.rest)) {
+ const follower = await Users.findOneByOrFail({
+ id: parsed.id
+ });
+ const followee = await Users.findOneByOrFail({
+ id: parsed.rest
+ });
+ return renderActivity(renderFollow(follower, followee, url));
+ }
+ // Another situation is there is only requestId, then obtained object from database.
+ const followRequest = await FollowRequests.findOneBy({
+ id: parsed.id
+ });
+ if (followRequest == null) {
+ throw new Error("resolveLocal: invalid follow URI");
+ }
+ const follower = await Users.findOneBy({
+ id: followRequest.followerId,
+ host: IsNull()
+ });
+ const followee = await Users.findOneBy({
+ id: followRequest.followeeId,
+ host: Not(IsNull())
+ });
+ if (follower == null || followee == null) {
+ throw new Error("resolveLocal: invalid follow URI");
+ }
+ return renderActivity(renderFollow(follower, followee, url));
+ }
+ case "bites":
+ {
+ const bite = await Bites.findOneOrFail({
+ where: {
+ id: parsed.id
+ },
+ relations: [
+ "targetUser",
+ "targetBite",
+ "targetNote"
+ ]
+ });
+ return renderActivity(await renderBite(bite));
+ }
+ case "stamp":
+ {
+ const stamp = await InteractionStamps.findOneOrFail({
+ where: {
+ id: parsed.id
+ },
+ relations: [
+ "note",
+ "targetNote"
+ ]
+ });
+ return renderActivity(renderQuoteAuthorization(stamp));
+ }
+ default:
+ throw new Error(`resolveLocal: type ${parsed.type} unhandled`);
+ }
+ }
+}
diff --git a/packages/backend/built/remote/activitypub/type.js b/packages/backend/built/remote/activitypub/type.js
new file mode 100644
index 0000000..1bc42a5
--- /dev/null
+++ b/packages/backend/built/remote/activitypub/type.js
@@ -0,0 +1,84 @@
+/**
+ * Get array of ActivityStreams Objects id
+ */ export function getApIds(value) {
+ if (value == null) return [];
+ const array = Array.isArray(value) ? value : [
+ value
+ ];
+ return array.map((x)=>getApId(x));
+}
+/**
+ * Get first ActivityStreams Object id
+ */ export function getOneApId(value) {
+ const firstOne = Array.isArray(value) ? value[0] : value;
+ return getApId(firstOne);
+}
+/**
+ * Get ActivityStreams Object id
+ */ export function getApId(value) {
+ if (typeof value === "string") return value;
+ if (typeof value.id === "string") return value.id;
+ throw new Error("cannot detemine id");
+}
+/**
+ * Get ActivityStreams Object type
+ */ export function getApType(value) {
+ if (typeof value.type === "string") return value.type;
+ if (Array.isArray(value.type) && typeof value.type[0] === "string") return value.type[0];
+ throw new Error("cannot detect type");
+}
+export function getOneApHrefNullable(value) {
+ const firstOne = Array.isArray(value) ? value[0] : value;
+ return getApHrefNullable(firstOne);
+}
+export function getApHrefNullable(value) {
+ if (typeof value === "string") return value;
+ if (typeof value?.href === "string") return value.href;
+ return undefined;
+}
+export const validPost = [
+ "Note",
+ "Question",
+ "Article",
+ "Audio",
+ "Document",
+ "Image",
+ "Page",
+ "Video",
+ "Event"
+];
+export const isPost = (object)=>validPost.includes(getApType(object));
+export const isQuestion = (object)=>getApType(object) === "Note" || getApType(object) === "Question";
+export const isTombstone = (object)=>getApType(object) === "Tombstone";
+export const validActor = [
+ "Person",
+ "Service",
+ "Group",
+ "Organization",
+ "Application"
+];
+export const isActor = (object)=>validActor.includes(getApType(object));
+export const isCollection = (object)=>getApType(object) === "Collection";
+export const isOrderedCollection = (object)=>getApType(object) === "OrderedCollection";
+export const isCollectionOrOrderedCollection = (object)=>isCollection(object) || isOrderedCollection(object);
+export const isPropertyValue = (object)=>object && getApType(object) === "PropertyValue" && typeof object.name === "string" && typeof object.value === "string";
+export const isMention = (object)=>getApType(object) === "Mention" && typeof object.href === "string";
+export const isHashtag = (object)=>getApType(object) === "Hashtag" && typeof object.name === "string";
+export const isEmoji = (object)=>getApType(object) === "Emoji" && !Array.isArray(object.icon) && object.icon.url != null;
+export const isCreate = (object)=>getApType(object) === "Create";
+export const isDelete = (object)=>getApType(object) === "Delete";
+export const isUpdate = (object)=>getApType(object) === "Update";
+export const isRead = (object)=>getApType(object) === "Read";
+export const isUndo = (object)=>getApType(object) === "Undo";
+export const isFollow = (object)=>getApType(object) === "Follow";
+export const isAccept = (object)=>getApType(object) === "Accept";
+export const isReject = (object)=>getApType(object) === "Reject";
+export const isAdd = (object)=>getApType(object) === "Add";
+export const isRemove = (object)=>getApType(object) === "Remove";
+export const isLike = (object)=>getApType(object) === "Like" || getApType(object) === "EmojiReaction" || getApType(object) === "EmojiReact";
+export const isAnnounce = (object)=>getApType(object) === "Announce";
+export const isBlock = (object)=>getApType(object) === "Block";
+export const isFlag = (object)=>getApType(object) === "Flag";
+export const isMove = (object)=>getApType(object) === "Move";
+export const isBite = (object)=>getApType(object) === "Bite";
+export const isQuoteRequest = (object)=>getApType(object) === "QuoteRequest";
diff --git a/packages/backend/built/remote/logger.js b/packages/backend/built/remote/logger.js
new file mode 100644
index 0000000..b44f3ec
--- /dev/null
+++ b/packages/backend/built/remote/logger.js
@@ -0,0 +1,2 @@
+import Logger from "../services/logger.js";
+export const remoteLogger = new Logger("remote", "cyan");
diff --git a/packages/backend/built/remote/resolve-user.js b/packages/backend/built/remote/resolve-user.js
new file mode 100644
index 0000000..1a9f288
--- /dev/null
+++ b/packages/backend/built/remote/resolve-user.js
@@ -0,0 +1,301 @@
+import { URL } from "node:url";
+import chalk from "chalk";
+import { IsNull } from "typeorm";
+import config from "../config/index.js";
+import { UserProfiles, Users } from "../models/index.js";
+import { toPuny } from "../misc/convert-host.js";
+import webFinger from "./webfinger.js";
+import { createPerson, updatePerson } from "./activitypub/models/person.js";
+import { remoteLogger } from "./logger.js";
+import { Cache } from "../misc/cache.js";
+import { RecursionLimiter } from "../models/repositories/user-profile.js";
+import { promiseEarlyReturn } from "../prelude/promise.js";
+const logger = remoteLogger.createSubLogger("resolve-user");
+const uriHostCache = new Cache("resolveUserUriHost", 60 * 60 * 24);
+const localUsernameCache = new Cache("localUserNameCapitalization", 60 * 60 * 24);
+const profileMentionCache = new Cache("resolveProfileMentions", 60 * 60);
+export async function resolveUser(username, host, refresh = 'refresh', limiter = new RecursionLimiter()) {
+ const usernameLower = username.toLowerCase();
+ // Return local user if host part is empty
+ if (host == null) {
+ logger.info(`return local user: ${usernameLower}`);
+ return await Users.findOneBy({
+ usernameLower,
+ host: IsNull()
+ }).then((u)=>{
+ if (u == null) {
+ throw new Error("user not found");
+ } else {
+ return u;
+ }
+ });
+ }
+ host = toPuny(host);
+ // Also return local user if host part is specified but referencing the local instance
+ if (config.host === host || config.domain === host) {
+ logger.info(`return local user: ${usernameLower}`);
+ return await Users.findOneBy({
+ usernameLower,
+ host: IsNull()
+ }).then((u)=>{
+ if (u == null) {
+ throw new Error("user not found");
+ } else {
+ return u;
+ }
+ });
+ }
+ // Check if remote user is already in the database
+ let user = await Users.findOneBy({
+ usernameLower,
+ host
+ });
+ const acctLower = `${usernameLower}@${host}`;
+ // If not, look up the user on the remote server
+ if (user == null) {
+ // Run WebFinger
+ const fingerRes = await resolveUserWebFinger(acctLower);
+ const finalAcct = subjectToAcct(fingerRes.subject);
+ const finalAcctLower = finalAcct.toLowerCase();
+ const m = finalAcct.match(/^([^@]+)@(.*)/);
+ const subjectHost = m ? m[2] : undefined;
+ // If subject is different, we're dealing with a split domain setup (that's already been validated by resolveUserWebFinger)
+ if (acctLower != finalAcctLower) {
+ logger.info('re-resolving split domain redirect user...');
+ const m = finalAcct.match(/^([^@]+)@(.*)/);
+ if (m) {
+ // Re-check if we already have the user in the database post-redirect
+ user = await Users.findOneBy({
+ usernameLower: usernameLower,
+ host: subjectHost
+ });
+ // If yes, return existing user
+ if (user != null) {
+ logger.succ(`return existing remote user: ${chalk.magenta(finalAcctLower)}`);
+ return user;
+ } else {
+ logger.succ(`return new remote user: ${chalk.magenta(finalAcctLower)}`);
+ return await createPerson(fingerRes.self.href, undefined, subjectHost, limiter);
+ }
+ }
+ }
+ // Not a split domain setup, so we can simply create and return the new user
+ logger.succ(`return new remote user: ${chalk.magenta(finalAcctLower)}`);
+ return await createPerson(fingerRes.self.href, undefined, subjectHost, limiter);
+ }
+ // If user information is out of date, return it by starting over from WebFinger
+ if ((refresh === 'refresh' || refresh === 'refresh-timeout-1500ms') && (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24)) {
+ // Prevent multiple attempts to connect to unconnected instances, update before each attempt to prevent subsequent similar attempts
+ await Users.update(user.id, {
+ lastFetchedAt: new Date()
+ });
+ logger.info(`try resync: ${acctLower}`);
+ const fingerRes = await resolveUserWebFinger(acctLower);
+ if (user.uri !== fingerRes.self.href) {
+ // if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping.
+ logger.info(`uri missmatch: ${acctLower}`);
+ logger.info(`recovery mismatch uri for (username=${username}, host=${host}) from ${user.uri} to ${fingerRes.self.href}`);
+ // validate uri
+ const uri = new URL(fingerRes.self.href);
+ if (uri.hostname !== host) {
+ throw new Error("Invalid uri");
+ }
+ await Users.update({
+ usernameLower,
+ host: host
+ }, {
+ uri: fingerRes.self.href
+ });
+ } else {
+ logger.info(`uri is fine: ${acctLower}`);
+ }
+ const finalAcct = subjectToAcct(fingerRes.subject);
+ const finalAcctLower = finalAcct.toLowerCase();
+ const m = finalAcct.match(/^([^@]+)@(.*)/);
+ const finalHost = m ? m[2] : null;
+ // Update user.host if we're dealing with an account that's part of a split domain setup that hasn't been fixed yet
+ if (m && user.host != finalHost) {
+ logger.info(`updating user host to subject acct host: ${user.host} -> ${finalHost}`);
+ await Users.update({
+ usernameLower,
+ host: user.host
+ }, {
+ host: finalHost
+ });
+ }
+ if (refresh === 'refresh') {
+ await updatePerson(fingerRes.self.href);
+ logger.info(`return resynced remote user: ${finalAcctLower}`);
+ } else if (refresh === 'refresh-timeout-1500ms') {
+ const res = await promiseEarlyReturn(updatePerson(fingerRes.self.href), 1500);
+ logger.info(`return possibly resynced remote user: ${finalAcctLower}`);
+ }
+ return await Users.findOneBy({
+ uri: fingerRes.self.href
+ }).then((u)=>{
+ if (u == null) {
+ throw new Error("user not found");
+ } else {
+ return u;
+ }
+ });
+ } else if (refresh === 'refresh-in-background' && (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24)) {
+ // Run the refresh in the background
+ // noinspection ES6MissingAwait
+ resolveUser(username, host, 'refresh', limiter);
+ }
+ logger.info(`return existing remote user: ${acctLower}`);
+ return user;
+}
+export async function resolveMentionToUserAndProfile(username, host, objectHost, limiter) {
+ return profileMentionCache.fetch(`${username}@${host ?? objectHost}`, async ()=>{
+ try {
+ const user = await resolveUser(username, host ?? objectHost, 'no-refresh', limiter);
+ const profile = await UserProfiles.findOneBy({
+ userId: user.id
+ });
+ const data = {
+ username,
+ host: host ?? objectHost
+ };
+ return {
+ user,
+ profile,
+ data
+ };
+ } catch {
+ return null;
+ }
+ });
+}
+export function getMentionFallbackUri(username, host, objectHost) {
+ let fallback = `${config.url}/@${username}`;
+ if (host !== null && host !== config.domain) fallback += `@${host}`;
+ else if (objectHost !== null && objectHost !== config.domain && host !== config.domain) fallback += `@${objectHost}`;
+ return fallback;
+}
+async function getLocalUsernameCached(username) {
+ return localUsernameCache.fetch(username.toLowerCase(), ()=>Users.findOneBy({
+ usernameLower: username.toLowerCase(),
+ host: IsNull()
+ }).then((p)=>p ? p.username : null));
+}
+export async function resolveMentionFromCache(username, host, objectHost, cache) {
+ const isLocal = host === null && objectHost === null || host === config.domain;
+ if (isLocal) {
+ const finalUsername = await getLocalUsernameCached(username);
+ if (finalUsername === null) return null;
+ username = finalUsername;
+ }
+ const fallback = getMentionFallbackUri(username, host, objectHost);
+ const cached = cache.find((r)=>r.username.toLowerCase() === username.toLowerCase() && r.host === (host ?? objectHost));
+ const href = cached?.url ?? cached?.uri;
+ if (cached && href != null) return {
+ username: cached.username,
+ href: href
+ };
+ if (isLocal) return {
+ username: username,
+ href: fallback
+ };
+ return null;
+}
+export async function getSubjectHostFromUri(uri) {
+ try {
+ const acct = subjectToAcct((await webFinger(uri)).subject);
+ const res = await resolveUserWebFinger(acct.toLowerCase());
+ const finalAcct = subjectToAcct(res.subject);
+ const m = finalAcct.match(/^([^@]+)@(.*)/);
+ if (!m) {
+ return null;
+ }
+ return m[2];
+ } catch {
+ return null;
+ }
+}
+export async function getSubjectHostFromUriAndUsernameCached(uri, username) {
+ const url = new URL(uri);
+ const hostname = url.hostname;
+ username = username.substring(1); // remove leading @ from username
+ // This resolves invalid mentions with the URL format https://host.tld/@user@otherhost.tld
+ const match = url.pathname.match(/^\/@(?[a-zA-Z0-9_]+|$)@(?[a-zA-Z0-9-.]+\.[a-zA-Z0-9-]+)$/);
+ if (match && match.groups?.host) {
+ return match.groups.host;
+ }
+ if (hostname === config.hostname) {
+ // user is local, return local account domain
+ return config.domain;
+ }
+ const user = await Users.findOneBy({
+ usernameLower: username.toLowerCase(),
+ host: hostname
+ });
+ return user ? user.host : await uriHostCache.fetch(uri, async ()=>await getSubjectHostFromUri(uri) ?? await getSubjectHostFromAcctParts(username, hostname) ?? hostname);
+}
+export async function getSubjectHostFromAcct(acct) {
+ try {
+ const res = await resolveUserWebFinger(acct.toLowerCase());
+ const finalAcct = subjectToAcct(res.subject);
+ const m = finalAcct.match(/^([^@]+)@(.*)/);
+ if (!m) {
+ return null;
+ }
+ return m[2];
+ } catch {
+ return null;
+ }
+}
+export async function getSubjectHostFromRemoteUser(user) {
+ return user ? getSubjectHostFromAcct(`${user.username}@${user.host}`) : null;
+}
+export async function getSubjectHostFromAcctParts(username, host) {
+ return username !== null && host !== null ? getSubjectHostFromAcct(`${username}@${host}`) : null;
+}
+async function resolveUserWebFinger(acctLower, recurse = true) {
+ logger.info(`WebFinger for ${chalk.yellow(acctLower)}`);
+ const fingerRes = await webFinger(acctLower).catch((e)=>{
+ logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${e.statusCode || e.message}`);
+ throw new Error(`Failed to WebFinger for ${acctLower}: ${e.statusCode || e.message}`);
+ });
+ const self = fingerRes.links.find((link)=>link.rel != null && link.rel.toLowerCase() === "self");
+ if (!self) {
+ logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`);
+ throw new Error("self link not found");
+ }
+ if (`${acctToSubject(acctLower)}` !== normalizeSubject(fingerRes.subject)) {
+ logger.info(`acct subject mismatch (${acctToSubject(acctLower)} !== ${normalizeSubject(fingerRes.subject)}), possible split domain deployment detected, repeating webfinger`);
+ if (!recurse) {
+ logger.error('split domain verification failed (recurse limit reached), aborting');
+ throw new Error('split domain verification failed (recurse limit reached), aborting');
+ }
+ const initialAcct = subjectToAcct(fingerRes.subject);
+ const initialAcctLower = initialAcct.toLowerCase();
+ const splitFingerRes = await resolveUserWebFinger(initialAcctLower, false);
+ const finalAcct = subjectToAcct(splitFingerRes.subject);
+ const finalAcctLower = finalAcct.toLowerCase();
+ if (initialAcct !== finalAcct) {
+ logger.error('split domain verification failed (subject mismatch), aborting');
+ throw new Error('split domain verification failed (subject mismatch), aborting');
+ }
+ logger.info(`split domain configuration detected: ${acctLower} -> ${finalAcctLower}`);
+ return splitFingerRes;
+ }
+ return {
+ subject: fingerRes.subject,
+ self: self
+ };
+}
+function subjectToAcct(subject) {
+ if (!subject.startsWith('acct:')) {
+ logger.error("Subject isnt a valid acct");
+ throw "Subject isnt a valid acct";
+ }
+ return subject.substring(5);
+}
+function acctToSubject(acct) {
+ return normalizeSubject(`acct:${acct}`);
+}
+function normalizeSubject(subject) {
+ return subject.toLowerCase();
+}
diff --git a/packages/backend/built/remote/webfinger.js b/packages/backend/built/remote/webfinger.js
new file mode 100644
index 0000000..96adbdf
--- /dev/null
+++ b/packages/backend/built/remote/webfinger.js
@@ -0,0 +1,68 @@
+import { URL } from "node:url";
+import { getJson, getResponse } from "../misc/fetch.js";
+import config from "../config/index.js";
+import { XMLParser } from "fast-xml-parser";
+export default async function(query) {
+ const hostMetaUrl = queryToHostMetaUrl(query);
+ const webFingerTemplate = await hostMetaToWebFingerTemplate(hostMetaUrl) ?? queryToWebFingerTemplate(query);
+ const url = genWebFingerUrl(query, webFingerTemplate);
+ return await getJson(url, "application/jrd+json, application/json");
+}
+async function hostMetaToWebFingerTemplate(url) {
+ try {
+ const res = await getResponse({
+ url,
+ method: "GET",
+ headers: Object.assign({
+ "User-Agent": config.userAgent,
+ Accept: "application/xrd+xml"
+ }, {}),
+ timeout: 10000
+ });
+ const options = {
+ ignoreAttributes: false,
+ isArray: (_name, jpath)=>jpath === 'XRD.Link'
+ };
+ const parser = new XMLParser(options);
+ const hostMeta = parser.parse(await res.text());
+ const template = hostMeta['XRD']['Link'].filter((p)=>p['@_rel'] === 'lrdd')[0]['@_template'];
+ return template.indexOf('{uri}') < 0 ? null : template;
+ } catch {
+ return null;
+ }
+}
+function queryToWebFingerTemplate(query) {
+ if (query.match(/^https?:\/\//)) {
+ const u = new URL(query);
+ return `${u.protocol}//${u.hostname}/.well-known/webfinger?resource={uri}`;
+ }
+ const m = query.match(/^([^@]+)@(.*)/);
+ if (m) {
+ const hostname = m[2];
+ return `https://${hostname}/.well-known/webfinger?resource={uri}`;
+ }
+ throw new Error(`Invalid query (${query})`);
+}
+function queryToHostMetaUrl(query) {
+ if (query.match(/^https?:\/\//)) {
+ const u = new URL(query);
+ return `${u.protocol}//${u.hostname}/.well-known/host-meta`;
+ }
+ const m = query.match(/^([^@]+)@(.*)/);
+ if (m) {
+ const hostname = m[2];
+ return `https://${hostname}/.well-known/host-meta`;
+ }
+ throw new Error(`Invalid query (${query})`);
+}
+function genWebFingerUrl(query, webFingerTemplate) {
+ if (webFingerTemplate.indexOf('{uri}') < 0) throw new Error(`Invalid webFingerUrl: ${webFingerTemplate}`);
+ if (query.match(/^https?:\/\//)) {
+ return webFingerTemplate.replace('{uri}', encodeURIComponent(query));
+ }
+ const m = query.match(/^([^@]+)@(.*)/);
+ if (m) {
+ return webFingerTemplate.replace('{uri}', encodeURIComponent(`acct:${query}`));
+ }
+ throw new Error(`Invalid query (${query})`);
+}
diff --git a/packages/backend/built/server/activitypub.js b/packages/backend/built/server/activitypub.js
new file mode 100644
index 0000000..acd6e0e
--- /dev/null
+++ b/packages/backend/built/server/activitypub.js
@@ -0,0 +1,482 @@
+import Router from "@koa/router";
+import bodyParser from "koa-bodyparser";
+import httpSignature from "@peertube/http-signature";
+import { In, IsNull, Not } from "typeorm";
+import { renderActivity } from "../remote/activitypub/renderer/index.js";
+import renderNote from "../remote/activitypub/renderer/note.js";
+import renderKey from "../remote/activitypub/renderer/key.js";
+import { renderPerson } from "../remote/activitypub/renderer/person.js";
+import renderEmoji from "../remote/activitypub/renderer/emoji.js";
+import { inbox as processInbox } from "../queue/index.js";
+import { isSelfHost } from "../misc/convert-host.js";
+import { Notes, Users, Emojis, NoteReactions, FollowRequests, Bites, InteractionStamps } from "../models/index.js";
+import { renderLike } from "../remote/activitypub/renderer/like.js";
+import { getUserKeypair } from "../misc/keypair-store.js";
+import { checkFetch, getSignatureUser, verifyDigest } from "../remote/activitypub/check-fetch.js";
+import { getInstanceActor } from "../services/instance-actor.js";
+import { fetchMeta } from "../misc/fetch-meta.js";
+import renderFollow from "../remote/activitypub/renderer/follow.js";
+import Featured from "./activitypub/featured.js";
+import Following from "./activitypub/following.js";
+import Followers from "./activitypub/followers.js";
+import Outbox, { packActivity } from "./activitypub/outbox.js";
+import { serverLogger } from "./index.js";
+import config from "../config/index.js";
+import { tickFetch } from "../metrics.js";
+import renderBite from "../remote/activitypub/renderer/bite.js";
+import renderQuoteAuthorization from "../remote/activitypub/renderer/quote-authorization.js";
+// Init router
+const router = new Router();
+//#region Routing
+function inbox(ctx) {
+ if (ctx.req.headers.host !== config.host) {
+ ctx.status = 400;
+ return;
+ }
+ let signature;
+ try {
+ signature = httpSignature.parseRequest(ctx.req, {
+ headers: [
+ '(request-target)',
+ 'digest',
+ 'host',
+ 'date'
+ ],
+ authorizationHeaderName: 'signature'
+ });
+ } catch (e) {
+ ctx.status = 401;
+ return;
+ }
+ if (!verifyDigest(ctx.request.rawBody, ctx.headers.digest)) {
+ ctx.status = 401;
+ return;
+ }
+ processInbox(ctx.request.body, signature);
+ ctx.status = 202;
+}
+const ACTIVITY_JSON = "application/activity+json; charset=utf-8";
+const LD_JSON = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8';
+function isActivityPubReq(ctx) {
+ ctx.response.vary("Accept");
+ const accepted = ctx.accepts("html", ACTIVITY_JSON, LD_JSON);
+ return typeof accepted === "string" && !accepted.match(/html/);
+}
+export function setResponseType(ctx) {
+ const accept = ctx.accepts(ACTIVITY_JSON, LD_JSON);
+ if (accept === LD_JSON) {
+ ctx.response.type = LD_JSON;
+ } else {
+ ctx.response.type = ACTIVITY_JSON;
+ }
+}
+async function parseJsonBodyOrFail(ctx, next) {
+ const koaBodyParser = bodyParser({
+ enableTypes: [
+ "json"
+ ],
+ detectJSON: ()=>true
+ });
+ try {
+ await koaBodyParser(ctx, next);
+ } catch {
+ ctx.status = 400;
+ return;
+ }
+}
+// inbox
+router.post("/inbox", parseJsonBodyOrFail, inbox);
+router.post("/users/:user/inbox", parseJsonBodyOrFail, inbox);
+// note
+router.get("/notes/:note", async (ctx, next)=>{
+ if (!isActivityPubReq(ctx)) return await next();
+ const verify = await checkFetch(ctx.req);
+ if (verify !== 200) {
+ ctx.status = verify;
+ return;
+ }
+ const note = await Notes.findOneBy({
+ id: ctx.params.note,
+ visibility: In([
+ "public",
+ "home",
+ "followers"
+ ]),
+ localOnly: false
+ });
+ if (note == null) {
+ ctx.status = 404;
+ return;
+ }
+ // redirect if remote
+ if (note.userHost !== null) {
+ if (note.uri == null || isSelfHost(note.userHost)) {
+ ctx.status = 500;
+ return;
+ }
+ ctx.redirect(note.uri);
+ return;
+ }
+ if (note.visibility === "followers") {
+ serverLogger.debug("Responding to request for follower-only note, validating access...");
+ const remoteUser = await getSignatureUser(ctx.req);
+ serverLogger.debug("Local note author user:");
+ serverLogger.debug(JSON.stringify(note, null, 2));
+ serverLogger.debug("Authenticated remote user:");
+ serverLogger.debug(JSON.stringify(remoteUser, null, 2));
+ if (remoteUser == null) {
+ serverLogger.debug("Rejecting: no user");
+ ctx.status = 401;
+ return;
+ }
+ const relation = await Users.getRelation(remoteUser.user.id, note.userId);
+ serverLogger.debug("Relation:");
+ serverLogger.debug(JSON.stringify(relation, null, 2));
+ if (!relation.isFollowing || relation.isBlocked) {
+ serverLogger.debug("Rejecting: authenticated user is not following us or was blocked by us");
+ ctx.status = 403;
+ return;
+ }
+ serverLogger.debug("Accepting: access criteria met");
+ }
+ ctx.body = renderActivity(await renderNote(note, false));
+ const meta = await fetchMeta();
+ if (meta.secureMode || meta.privateMode) {
+ ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
+ } else {
+ ctx.set("Cache-Control", "public, max-age=180");
+ }
+ setResponseType(ctx);
+});
+// note activity
+router.get("/notes/:note/activity", async (ctx)=>{
+ const verify = await checkFetch(ctx.req);
+ if (verify !== 200) {
+ ctx.status = verify;
+ return;
+ }
+ const note = await Notes.findOneBy({
+ id: ctx.params.note,
+ userHost: IsNull(),
+ visibility: In([
+ "public",
+ "home"
+ ]),
+ localOnly: false
+ });
+ if (note == null) {
+ ctx.status = 404;
+ return;
+ }
+ ctx.body = renderActivity(await packActivity(note));
+ const meta = await fetchMeta();
+ if (meta.secureMode || meta.privateMode) {
+ ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
+ } else {
+ ctx.set("Cache-Control", "public, max-age=180");
+ }
+ setResponseType(ctx);
+});
+// outbox
+router.get("/users/:user/outbox", Outbox);
+// followers
+router.get("/users/:user/followers", Followers);
+// following
+router.get("/users/:user/following", Following);
+// featured
+router.get("/users/:user/collections/featured", Featured);
+// publickey
+router.get("/users/:user/publickey", async (ctx)=>{
+ const instanceActor = await getInstanceActor();
+ if (ctx.params.user === instanceActor.id) {
+ tickFetch();
+ ctx.body = renderActivity(renderKey(instanceActor, await getUserKeypair(instanceActor.id)));
+ ctx.set("Cache-Control", "public, max-age=180");
+ setResponseType(ctx);
+ return;
+ }
+ const verify = await checkFetch(ctx.req);
+ if (verify !== 200) {
+ ctx.status = verify;
+ return;
+ }
+ const userId = ctx.params.user;
+ const user = await Users.findOneBy({
+ id: userId,
+ host: IsNull()
+ });
+ if (user == null) {
+ ctx.status = 404;
+ return;
+ }
+ const keypair = await getUserKeypair(user.id);
+ if (Users.isLocalUser(user)) {
+ ctx.body = renderActivity(renderKey(user, keypair));
+ const meta = await fetchMeta();
+ if (meta.secureMode || meta.privateMode) {
+ ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
+ } else {
+ ctx.set("Cache-Control", "public, max-age=180");
+ }
+ setResponseType(ctx);
+ } else {
+ ctx.status = 400;
+ }
+});
+// user
+async function userInfo(ctx, user) {
+ if (user == null) {
+ ctx.status = 404;
+ return;
+ }
+ ctx.body = renderActivity(await renderPerson(user));
+ const meta = await fetchMeta();
+ if (meta.secureMode || meta.privateMode) {
+ ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
+ } else {
+ ctx.set("Cache-Control", "public, max-age=180");
+ }
+ setResponseType(ctx);
+}
+router.get("/users/:user", async (ctx, next)=>{
+ if (!isActivityPubReq(ctx)) return await next();
+ const instanceActor = await getInstanceActor();
+ if (ctx.params.user === instanceActor.id) {
+ tickFetch();
+ await userInfo(ctx, instanceActor);
+ return;
+ }
+ const verify = await checkFetch(ctx.req);
+ if (verify !== 200) {
+ ctx.status = verify;
+ return;
+ }
+ const userId = ctx.params.user;
+ const user = await Users.findOneBy({
+ id: userId,
+ host: IsNull(),
+ isSuspended: false
+ });
+ await userInfo(ctx, user);
+});
+router.get("/@:user", async (ctx, next)=>{
+ if (!isActivityPubReq(ctx)) return await next();
+ if (ctx.params.user === "instance.actor") {
+ tickFetch();
+ const instanceActor = await getInstanceActor();
+ await userInfo(ctx, instanceActor);
+ return;
+ }
+ const verify = await checkFetch(ctx.req);
+ if (verify !== 200) {
+ ctx.status = verify;
+ return;
+ }
+ const user = await Users.findOneBy({
+ usernameLower: ctx.params.user.toLowerCase(),
+ host: IsNull(),
+ isSuspended: false
+ });
+ await userInfo(ctx, user);
+});
+router.get("/actor", async (ctx, next)=>{
+ tickFetch();
+ const instanceActor = await getInstanceActor();
+ await userInfo(ctx, instanceActor);
+});
+//#endregion
+// emoji
+router.get("/emojis/:emoji", async (ctx)=>{
+ const verify = await checkFetch(ctx.req);
+ if (verify !== 200) {
+ ctx.status = verify;
+ return;
+ }
+ const emoji = await Emojis.findOneBy({
+ host: IsNull(),
+ name: ctx.params.emoji
+ });
+ if (emoji == null) {
+ ctx.status = 404;
+ return;
+ }
+ ctx.body = renderActivity(await renderEmoji(emoji));
+ const meta = await fetchMeta();
+ if (meta.secureMode || meta.privateMode) {
+ ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
+ } else {
+ ctx.set("Cache-Control", "public, max-age=180");
+ }
+ setResponseType(ctx);
+});
+// like
+router.get("/likes/:like", async (ctx)=>{
+ const verify = await checkFetch(ctx.req);
+ if (verify !== 200) {
+ ctx.status = verify;
+ return;
+ }
+ const reaction = await NoteReactions.findOneBy({
+ id: ctx.params.like
+ });
+ if (reaction == null) {
+ ctx.status = 404;
+ return;
+ }
+ const note = await Notes.findOneBy({
+ id: reaction.noteId
+ });
+ if (note == null) {
+ ctx.status = 404;
+ return;
+ }
+ ctx.body = renderActivity(await renderLike(reaction, note));
+ const meta = await fetchMeta();
+ if (meta.secureMode || meta.privateMode) {
+ ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
+ } else {
+ ctx.set("Cache-Control", "public, max-age=180");
+ }
+ setResponseType(ctx);
+});
+// follow
+router.get("/follows/:follower/:followee", async (ctx)=>{
+ const verify = await checkFetch(ctx.req);
+ if (verify !== 200) {
+ ctx.status = verify;
+ return;
+ }
+ // This may be used before the follow is completed, so we do not
+ // check if the following exists.
+ const [follower, followee] = await Promise.all([
+ Users.findOneBy({
+ id: ctx.params.follower,
+ host: IsNull()
+ }),
+ Users.findOneBy({
+ id: ctx.params.followee,
+ host: Not(IsNull())
+ })
+ ]);
+ if (follower == null || followee == null) {
+ ctx.status = 404;
+ return;
+ }
+ ctx.body = renderActivity(renderFollow(follower, followee));
+ const meta = await fetchMeta();
+ if (meta.secureMode || meta.privateMode) {
+ ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
+ } else {
+ ctx.set("Cache-Control", "public, max-age=180");
+ }
+ setResponseType(ctx);
+});
+// follow request
+router.get("/follows/:followRequestId", async (ctx)=>{
+ const verify = await checkFetch(ctx.req);
+ if (verify !== 200) {
+ ctx.status = verify;
+ return;
+ }
+ const followRequest = await FollowRequests.findOneBy({
+ id: ctx.params.followRequestId
+ });
+ if (followRequest == null) {
+ ctx.status = 404;
+ return;
+ }
+ const [follower, followee] = await Promise.all([
+ Users.findOneBy({
+ id: followRequest.followerId,
+ host: IsNull()
+ }),
+ Users.findOneBy({
+ id: followRequest.followeeId,
+ host: Not(IsNull())
+ })
+ ]);
+ if (follower == null || followee == null) {
+ ctx.status = 404;
+ return;
+ }
+ const meta = await fetchMeta();
+ if (meta.secureMode || meta.privateMode) {
+ ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
+ } else {
+ ctx.set("Cache-Control", "public, max-age=180");
+ }
+ ctx.body = renderActivity(renderFollow(follower, followee));
+ setResponseType(ctx);
+});
+// bite
+router.get("/bites/:biteId", async (ctx)=>{
+ const verify = await checkFetch(ctx.req);
+ if (verify !== 200) {
+ ctx.status = verify;
+ return;
+ }
+ const bite = await Bites.findOne({
+ where: {
+ id: ctx.params.biteId
+ },
+ relations: [
+ "targetUser",
+ "targetBite"
+ ]
+ });
+ if (bite === null) {
+ ctx.status = 404;
+ return;
+ }
+ const meta = await fetchMeta();
+ if (meta.secureMode || meta.privateMode) {
+ ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
+ } else {
+ ctx.set("Cache-Control", "public, max-age=180");
+ }
+ ctx.body = renderActivity(await renderBite(bite));
+ setResponseType(ctx);
+});
+// mastodon-style quote authorizations
+router.get("/stamp/:id", async (ctx)=>{
+ const verify = await checkFetch(ctx.req);
+ if (verify !== 200) {
+ ctx.status = verify;
+ return;
+ }
+ const stamp = await InteractionStamps.findOne({
+ where: {
+ id: ctx.params.id
+ },
+ relations: [
+ "note",
+ "targetNote"
+ ]
+ });
+ if (stamp === null) {
+ ctx.status = 404;
+ return;
+ }
+ // make sure the requester has access to the note, otherwise this endpoint can leak the existence of a private note.
+ // we can't directly validate that the requester can view the quoting post, but servers should only get this uri
+ // from the metadata on the quote's note object, so the possibility and scope of leakage is minor
+ const requester = await getSignatureUser(ctx.req);
+ if (requester === null) {
+ ctx.status = 404;
+ return;
+ }
+ const targetNoteVisibile = await Notes.isVisibleForMe(stamp.targetNote, requester.user.id);
+ if (!targetNoteVisibile) {
+ ctx.status = 404;
+ return;
+ }
+ const meta = await fetchMeta();
+ if (meta.secureMode || meta.privateMode) {
+ ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
+ } else {
+ ctx.set("Cache-Control", "public, max-age=180");
+ }
+ ctx.body = renderActivity(renderQuoteAuthorization(stamp));
+ setResponseType(ctx);
+});
+export default router;
diff --git a/packages/backend/built/server/activitypub/featured.js b/packages/backend/built/server/activitypub/featured.js
new file mode 100644
index 0000000..5a5c0a5
--- /dev/null
+++ b/packages/backend/built/server/activitypub/featured.js
@@ -0,0 +1,45 @@
+import { IsNull } from "typeorm";
+import config from "../../config/index.js";
+import { renderActivity } from "../../remote/activitypub/renderer/index.js";
+import renderOrderedCollection from "../../remote/activitypub/renderer/ordered-collection.js";
+import { Users, Notes, UserNotePinings } from "../../models/index.js";
+import { checkFetch } from "../../remote/activitypub/check-fetch.js";
+import { fetchMeta } from "../../misc/fetch-meta.js";
+import { setResponseType } from "../activitypub.js";
+export default (async (ctx)=>{
+ const verify = await checkFetch(ctx.req);
+ if (verify !== 200) {
+ ctx.status = verify;
+ return;
+ }
+ const userId = ctx.params.user;
+ const user = await Users.findOneBy({
+ id: userId,
+ host: IsNull()
+ });
+ if (user == null) {
+ ctx.status = 404;
+ return;
+ }
+ const pinings = await UserNotePinings.find({
+ where: {
+ userId: user.id
+ },
+ order: {
+ id: "DESC"
+ }
+ });
+ const pinnedNotes = await Promise.all(pinings.map((pining)=>Notes.findOneByOrFail({
+ id: pining.noteId
+ })));
+ const renderedNotes = pinnedNotes.map((note)=>`${config.url}/notes/${note.id}`);
+ const rendered = renderOrderedCollection(`${config.url}/users/${userId}/collections/featured`, renderedNotes.length, undefined, undefined, renderedNotes);
+ ctx.body = renderActivity(rendered);
+ const meta = await fetchMeta();
+ if (meta.secureMode || meta.privateMode) {
+ ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
+ } else {
+ ctx.set("Cache-Control", "public, max-age=180");
+ }
+ setResponseType(ctx);
+});
diff --git a/packages/backend/built/server/activitypub/followers.js b/packages/backend/built/server/activitypub/followers.js
new file mode 100644
index 0000000..e084a47
--- /dev/null
+++ b/packages/backend/built/server/activitypub/followers.js
@@ -0,0 +1,90 @@
+import { IsNull, LessThan } from "typeorm";
+import config from "../../config/index.js";
+import * as url from "../../prelude/url.js";
+import { renderActivity } from "../../remote/activitypub/renderer/index.js";
+import renderOrderedCollection from "../../remote/activitypub/renderer/ordered-collection.js";
+import renderOrderedCollectionPage from "../../remote/activitypub/renderer/ordered-collection-page.js";
+import renderFollowUser from "../../remote/activitypub/renderer/follow-user.js";
+import { Users, Followings, UserProfiles } from "../../models/index.js";
+import { checkFetch } from "../../remote/activitypub/check-fetch.js";
+import { fetchMeta } from "../../misc/fetch-meta.js";
+import { setResponseType } from "../activitypub.js";
+export default (async (ctx)=>{
+ const verify = await checkFetch(ctx.req);
+ if (verify !== 200) {
+ ctx.status = verify;
+ return;
+ }
+ const userId = ctx.params.user;
+ const cursor = ctx.request.query.cursor;
+ if (cursor != null && typeof cursor !== "string") {
+ ctx.status = 400;
+ return;
+ }
+ const page = ctx.request.query.page === "true";
+ const user = await Users.findOneBy({
+ id: userId,
+ host: IsNull()
+ });
+ if (user == null) {
+ ctx.status = 404;
+ return;
+ }
+ //#region Check ff visibility
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ if (profile.ffVisibility === "private") {
+ ctx.status = 403;
+ ctx.set("Cache-Control", "public, max-age=30");
+ return;
+ } else if (profile.ffVisibility === "followers") {
+ ctx.status = 403;
+ ctx.set("Cache-Control", "public, max-age=30");
+ return;
+ }
+ //#endregion
+ const limit = 10;
+ const partOf = `${config.url}/users/${userId}/followers`;
+ if (page) {
+ const query = {
+ followeeId: user.id
+ };
+ // カーソルが指定されている場合
+ if (cursor) {
+ query.id = LessThan(cursor);
+ }
+ // Get followers
+ const followings = await Followings.find({
+ where: query,
+ take: limit + 1,
+ order: {
+ id: -1
+ }
+ });
+ // 「次のページ」があるかどうか
+ const inStock = followings.length === limit + 1;
+ if (inStock) followings.pop();
+ const renderedFollowers = await Promise.all(followings.map((following)=>renderFollowUser(following.followerId)));
+ const rendered = renderOrderedCollectionPage(`${partOf}?${url.query({
+ page: "true",
+ cursor
+ })}`, user.followersCount, renderedFollowers, partOf, undefined, inStock ? `${partOf}?${url.query({
+ page: "true",
+ cursor: followings[followings.length - 1].id
+ })}` : undefined);
+ ctx.body = renderActivity(rendered);
+ setResponseType(ctx);
+ } else {
+ // index page
+ const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`);
+ ctx.body = renderActivity(rendered);
+ setResponseType(ctx);
+ }
+ const meta = await fetchMeta();
+ if (meta.secureMode || meta.privateMode) {
+ ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
+ } else {
+ ctx.set("Cache-Control", "public, max-age=180");
+ }
+});
diff --git a/packages/backend/built/server/activitypub/following.js b/packages/backend/built/server/activitypub/following.js
new file mode 100644
index 0000000..322eb32
--- /dev/null
+++ b/packages/backend/built/server/activitypub/following.js
@@ -0,0 +1,90 @@
+import { LessThan, IsNull } from "typeorm";
+import config from "../../config/index.js";
+import * as url from "../../prelude/url.js";
+import { renderActivity } from "../../remote/activitypub/renderer/index.js";
+import renderOrderedCollection from "../../remote/activitypub/renderer/ordered-collection.js";
+import renderOrderedCollectionPage from "../../remote/activitypub/renderer/ordered-collection-page.js";
+import renderFollowUser from "../../remote/activitypub/renderer/follow-user.js";
+import { Users, Followings, UserProfiles } from "../../models/index.js";
+import { checkFetch } from "../../remote/activitypub/check-fetch.js";
+import { fetchMeta } from "../../misc/fetch-meta.js";
+import { setResponseType } from "../activitypub.js";
+export default (async (ctx)=>{
+ const verify = await checkFetch(ctx.req);
+ if (verify !== 200) {
+ ctx.status = verify;
+ return;
+ }
+ const userId = ctx.params.user;
+ const cursor = ctx.request.query.cursor;
+ if (cursor != null && typeof cursor !== "string") {
+ ctx.status = 400;
+ return;
+ }
+ const page = ctx.request.query.page === "true";
+ const user = await Users.findOneBy({
+ id: userId,
+ host: IsNull()
+ });
+ if (user == null) {
+ ctx.status = 404;
+ return;
+ }
+ //#region Check ff visibility
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ if (profile.ffVisibility === "private") {
+ ctx.status = 403;
+ ctx.set("Cache-Control", "public, max-age=30");
+ return;
+ } else if (profile.ffVisibility === "followers") {
+ ctx.status = 403;
+ ctx.set("Cache-Control", "public, max-age=30");
+ return;
+ }
+ //#endregion
+ const limit = 10;
+ const partOf = `${config.url}/users/${userId}/following`;
+ if (page) {
+ const query = {
+ followerId: user.id
+ };
+ // If a cursor is specified
+ if (cursor) {
+ query.id = LessThan(cursor);
+ }
+ // Get followings
+ const followings = await Followings.find({
+ where: query,
+ take: limit + 1,
+ order: {
+ id: -1
+ }
+ });
+ // Whether there is a "next page" or not
+ const inStock = followings.length === limit + 1;
+ if (inStock) followings.pop();
+ const renderedFollowees = await Promise.all(followings.map((following)=>renderFollowUser(following.followeeId)));
+ const rendered = renderOrderedCollectionPage(`${partOf}?${url.query({
+ page: "true",
+ cursor
+ })}`, user.followingCount, renderedFollowees, partOf, undefined, inStock ? `${partOf}?${url.query({
+ page: "true",
+ cursor: followings[followings.length - 1].id
+ })}` : undefined);
+ ctx.body = renderActivity(rendered);
+ setResponseType(ctx);
+ } else {
+ // index page
+ const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`);
+ ctx.body = renderActivity(rendered);
+ setResponseType(ctx);
+ }
+ const meta = await fetchMeta();
+ if (meta.secureMode || meta.privateMode) {
+ ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
+ } else {
+ ctx.set("Cache-Control", "public, max-age=180");
+ }
+});
diff --git a/packages/backend/built/server/activitypub/outbox.js b/packages/backend/built/server/activitypub/outbox.js
new file mode 100644
index 0000000..7ea1058
--- /dev/null
+++ b/packages/backend/built/server/activitypub/outbox.js
@@ -0,0 +1,97 @@
+import { Brackets, IsNull } from "typeorm";
+import config from "../../config/index.js";
+import { renderActivity } from "../../remote/activitypub/renderer/index.js";
+import renderOrderedCollection from "../../remote/activitypub/renderer/ordered-collection.js";
+import renderOrderedCollectionPage from "../../remote/activitypub/renderer/ordered-collection-page.js";
+import renderNote from "../../remote/activitypub/renderer/note.js";
+import renderCreate from "../../remote/activitypub/renderer/create.js";
+import renderAnnounce from "../../remote/activitypub/renderer/announce.js";
+import { countIf } from "../../prelude/array.js";
+import * as url from "../../prelude/url.js";
+import { Users, Notes } from "../../models/index.js";
+import { checkFetch } from "../../remote/activitypub/check-fetch.js";
+import { fetchMeta } from "../../misc/fetch-meta.js";
+import { makePaginationQuery } from "../api/common/make-pagination-query.js";
+import { setResponseType } from "../activitypub.js";
+export default (async (ctx)=>{
+ const verify = await checkFetch(ctx.req);
+ if (verify !== 200) {
+ ctx.status = verify;
+ return;
+ }
+ const userId = ctx.params.user;
+ const sinceId = ctx.request.query.since_id;
+ if (sinceId != null && typeof sinceId !== "string") {
+ ctx.status = 400;
+ return;
+ }
+ const untilId = ctx.request.query.until_id;
+ if (untilId != null && typeof untilId !== "string") {
+ ctx.status = 400;
+ return;
+ }
+ const page = ctx.request.query.page === "true";
+ if (countIf((x)=>x != null, [
+ sinceId,
+ untilId
+ ]) > 1) {
+ ctx.status = 400;
+ return;
+ }
+ const user = await Users.findOneBy({
+ id: userId,
+ host: IsNull()
+ });
+ if (user == null) {
+ ctx.status = 404;
+ return;
+ }
+ const limit = 20;
+ const partOf = `${config.url}/users/${userId}/outbox`;
+ if (page) {
+ const query = makePaginationQuery(Notes.createQueryBuilder("note"), sinceId, untilId).andWhere("note.userId = :userId", {
+ userId: user.id
+ }).andWhere(new Brackets((qb)=>{
+ qb.where("note.visibility = 'public'").orWhere("note.visibility = 'home'");
+ })).andWhere("note.localOnly = FALSE");
+ const notes = await query.take(limit).getMany();
+ if (sinceId) notes.reverse();
+ const activities = await Promise.all(notes.map((note)=>packActivity(note)));
+ const rendered = renderOrderedCollectionPage(`${partOf}?${url.query({
+ page: "true",
+ since_id: sinceId,
+ until_id: untilId
+ })}`, user.notesCount, activities, partOf, notes.length ? `${partOf}?${url.query({
+ page: "true",
+ since_id: notes[0].id
+ })}` : undefined, notes.length ? `${partOf}?${url.query({
+ page: "true",
+ until_id: notes[notes.length - 1].id
+ })}` : undefined);
+ ctx.body = renderActivity(rendered);
+ setResponseType(ctx);
+ } else {
+ // index page
+ const rendered = renderOrderedCollection(partOf, user.notesCount, `${partOf}?page=true`, `${partOf}?page=true&since_id=000000000000000000000000`);
+ ctx.body = renderActivity(rendered);
+ setResponseType(ctx);
+ }
+ const meta = await fetchMeta();
+ if (meta.secureMode || meta.privateMode) {
+ ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
+ } else {
+ ctx.set("Cache-Control", "public, max-age=180");
+ }
+});
+/**
+ * Pack Create or Announce Activity
+ * @param note Note
+ */ export async function packActivity(note) {
+ if (note.renoteId && note.text == null && note.cw == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) {
+ const renote = await Notes.findOneByOrFail({
+ id: note.renoteId
+ });
+ return renderAnnounce(renote.uri ?? `${config.url}/notes/${renote.id}`, note);
+ }
+ return renderCreate(await renderNote(note, false), note);
+}
diff --git a/packages/backend/built/server/api/2fa.js b/packages/backend/built/server/api/2fa.js
new file mode 100644
index 0000000..d152903
--- /dev/null
+++ b/packages/backend/built/server/api/2fa.js
@@ -0,0 +1,278 @@
+import * as crypto from "node:crypto";
+import * as jsrsasign from "jsrsasign";
+import config from "../../config/index.js";
+const ECC_PRELUDE = Buffer.from([
+ 0x04
+]);
+const NULL_BYTE = Buffer.from([
+ 0
+]);
+const PEM_PRELUDE = Buffer.from("3059301306072a8648ce3d020106082a8648ce3d030107034200", "hex");
+// Android Safetynet attestations are signed with this cert:
+const GSR2 = `-----BEGIN CERTIFICATE-----
+MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G
+A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
+Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1
+MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG
+A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL
+v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8
+eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq
+tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd
+C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa
+zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB
+mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH
+V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n
+bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG
+3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs
+J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO
+291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS
+ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd
+AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
+TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
+-----END CERTIFICATE-----\n`;
+function base64URLDecode(source) {
+ return Buffer.from(source.replace(/\-/g, "+").replace(/_/g, "/"), "base64");
+}
+function getCertSubject(certificate) {
+ const subjectCert = new jsrsasign.X509();
+ subjectCert.readCertPEM(certificate);
+ const subjectString = subjectCert.getSubjectString();
+ const subjectFields = subjectString.slice(1).split("/");
+ const fields = {};
+ for (const field of subjectFields){
+ const eqIndex = field.indexOf("=");
+ fields[field.substring(0, eqIndex)] = field.substring(eqIndex + 1);
+ }
+ return fields;
+}
+function verifyCertificateChain(certificates) {
+ let valid = true;
+ for(let i = 0; i < certificates.length; i++){
+ const Cert = certificates[i];
+ const certificate = new jsrsasign.X509();
+ certificate.readCertPEM(Cert);
+ const CACert = i + 1 >= certificates.length ? Cert : certificates[i + 1];
+ const certStruct = jsrsasign.ASN1HEX.getTLVbyList(certificate.hex, 0, [
+ 0
+ ]);
+ const algorithm = certificate.getSignatureAlgorithmField();
+ const signatureHex = certificate.getSignatureValueHex();
+ // Verify against CA
+ const Signature = new jsrsasign.KJUR.crypto.Signature({
+ alg: algorithm
+ });
+ Signature.init(CACert);
+ Signature.updateHex(certStruct);
+ valid = valid && !!Signature.verify(signatureHex); // true if CA signed the certificate
+ }
+ return valid;
+}
+function PEMString(pemBuffer, type = "CERTIFICATE") {
+ if (pemBuffer.length === 65 && pemBuffer[0] === 0x04) {
+ pemBuffer = Buffer.concat([
+ PEM_PRELUDE,
+ pemBuffer
+ ], 91);
+ type = "PUBLIC KEY";
+ }
+ const cert = pemBuffer.toString("base64");
+ const keyParts = [];
+ const max = Math.ceil(cert.length / 64);
+ let start = 0;
+ for(let i = 0; i < max; i++){
+ keyParts.push(cert.substring(start, start + 64));
+ start += 64;
+ }
+ return `-----BEGIN ${type}-----\n${keyParts.join("\n")}\n-----END ${type}-----\n`;
+}
+export function hash(data) {
+ return crypto.createHash("sha256").update(data).digest();
+}
+export function verifyLogin({ publicKey, authenticatorData, clientDataJSON, clientData, signature, challenge }) {
+ if (clientData.type !== "webauthn.get") {
+ throw new Error("type is not webauthn.get");
+ }
+ if (hash(clientData.challenge).toString("hex") !== challenge) {
+ throw new Error("challenge mismatch");
+ }
+ if (clientData.origin !== `${config.scheme}://${config.host}`) {
+ throw new Error("origin mismatch");
+ }
+ const verificationData = Buffer.concat([
+ authenticatorData,
+ hash(clientDataJSON)
+ ], 32 + authenticatorData.length);
+ return crypto.createVerify("SHA256").update(verificationData).verify(PEMString(publicKey), signature);
+}
+export const procedures = {
+ none: {
+ verify ({ publicKey }) {
+ const negTwo = publicKey.get(-2);
+ if (!negTwo || negTwo.length !== 32) {
+ throw new Error("invalid or no -2 key given");
+ }
+ const negThree = publicKey.get(-3);
+ if (!negThree || negThree.length !== 32) {
+ throw new Error("invalid or no -3 key given");
+ }
+ const publicKeyU2F = Buffer.concat([
+ ECC_PRELUDE,
+ negTwo,
+ negThree
+ ], 1 + 32 + 32);
+ return {
+ publicKey: publicKeyU2F,
+ valid: true
+ };
+ }
+ },
+ "android-key": {
+ verify ({ attStmt, authenticatorData, clientDataHash, publicKey, rpIdHash, credentialId }) {
+ if (attStmt.alg !== -7) {
+ throw new Error("alg mismatch");
+ }
+ const verificationData = Buffer.concat([
+ authenticatorData,
+ clientDataHash
+ ]);
+ const attCert = attStmt.x5c[0];
+ const negTwo = publicKey.get(-2);
+ if (!negTwo || negTwo.length !== 32) {
+ throw new Error("invalid or no -2 key given");
+ }
+ const negThree = publicKey.get(-3);
+ if (!negThree || negThree.length !== 32) {
+ throw new Error("invalid or no -3 key given");
+ }
+ const publicKeyData = Buffer.concat([
+ ECC_PRELUDE,
+ negTwo,
+ negThree
+ ], 1 + 32 + 32);
+ if (!attCert.equals(publicKeyData)) {
+ throw new Error("public key mismatch");
+ }
+ const isValid = crypto.createVerify("SHA256").update(verificationData).verify(PEMString(attCert), attStmt.sig);
+ // TODO: Check 'attestationChallenge' field in extension of cert matches hash(clientDataJSON)
+ return {
+ valid: isValid,
+ publicKey: publicKeyData
+ };
+ }
+ },
+ // what a stupid attestation
+ "android-safetynet": {
+ verify ({ attStmt, authenticatorData, clientDataHash, publicKey, rpIdHash, credentialId }) {
+ const verificationData = hash(Buffer.concat([
+ authenticatorData,
+ clientDataHash
+ ]));
+ const jwsParts = attStmt.response.toString("utf-8").split(".");
+ const header = JSON.parse(base64URLDecode(jwsParts[0]).toString("utf-8"));
+ const response = JSON.parse(base64URLDecode(jwsParts[1]).toString("utf-8"));
+ const signature = jwsParts[2];
+ if (!verificationData.equals(Buffer.from(response.nonce, "base64"))) {
+ throw new Error("invalid nonce");
+ }
+ const certificateChain = header.x5c.map((key)=>PEMString(key)).concat([
+ GSR2
+ ]);
+ if (getCertSubject(certificateChain[0]).CN !== "attest.android.com") {
+ throw new Error("invalid common name");
+ }
+ if (!verifyCertificateChain(certificateChain)) {
+ throw new Error("Invalid certificate chain!");
+ }
+ const signatureBase = Buffer.from(`${jwsParts[0]}.${jwsParts[1]}`, "utf-8");
+ const valid = crypto.createVerify("sha256").update(signatureBase).verify(certificateChain[0], base64URLDecode(signature));
+ const negTwo = publicKey.get(-2);
+ if (!negTwo || negTwo.length !== 32) {
+ throw new Error("invalid or no -2 key given");
+ }
+ const negThree = publicKey.get(-3);
+ if (!negThree || negThree.length !== 32) {
+ throw new Error("invalid or no -3 key given");
+ }
+ const publicKeyData = Buffer.concat([
+ ECC_PRELUDE,
+ negTwo,
+ negThree
+ ], 1 + 32 + 32);
+ return {
+ valid,
+ publicKey: publicKeyData
+ };
+ }
+ },
+ packed: {
+ verify ({ attStmt, authenticatorData, clientDataHash, publicKey, rpIdHash, credentialId }) {
+ const verificationData = Buffer.concat([
+ authenticatorData,
+ clientDataHash
+ ]);
+ if (attStmt.x5c) {
+ const attCert = attStmt.x5c[0];
+ const validSignature = crypto.createVerify("SHA256").update(verificationData).verify(PEMString(attCert), attStmt.sig);
+ const negTwo = publicKey.get(-2);
+ if (!negTwo || negTwo.length !== 32) {
+ throw new Error("invalid or no -2 key given");
+ }
+ const negThree = publicKey.get(-3);
+ if (!negThree || negThree.length !== 32) {
+ throw new Error("invalid or no -3 key given");
+ }
+ const publicKeyData = Buffer.concat([
+ ECC_PRELUDE,
+ negTwo,
+ negThree
+ ], 1 + 32 + 32);
+ return {
+ valid: validSignature,
+ publicKey: publicKeyData
+ };
+ } else if (attStmt.ecdaaKeyId) {
+ // https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-ecdaa-algorithm-v2.0-id-20180227.html#ecdaa-verify-operation
+ throw new Error("ECDAA-Verify is not supported");
+ } else {
+ if (attStmt.alg !== -7) throw new Error("alg mismatch");
+ throw new Error("self attestation is not supported");
+ }
+ }
+ },
+ "fido-u2f": {
+ verify ({ attStmt, authenticatorData, clientDataHash, publicKey, rpIdHash, credentialId }) {
+ const x5c = attStmt.x5c;
+ if (x5c.length !== 1) {
+ throw new Error("x5c length does not match expectation");
+ }
+ const attCert = x5c[0];
+ // TODO: make sure attCert is an Elliptic Curve (EC) public key over the P-256 curve
+ const negTwo = publicKey.get(-2);
+ if (!negTwo || negTwo.length !== 32) {
+ throw new Error("invalid or no -2 key given");
+ }
+ const negThree = publicKey.get(-3);
+ if (!negThree || negThree.length !== 32) {
+ throw new Error("invalid or no -3 key given");
+ }
+ const publicKeyU2F = Buffer.concat([
+ ECC_PRELUDE,
+ negTwo,
+ negThree
+ ], 1 + 32 + 32);
+ const verificationData = Buffer.concat([
+ NULL_BYTE,
+ rpIdHash,
+ clientDataHash,
+ credentialId,
+ publicKeyU2F
+ ]);
+ const validSignature = crypto.createVerify("SHA256").update(verificationData).verify(PEMString(attCert), attStmt.sig);
+ return {
+ valid: validSignature,
+ publicKey: publicKeyU2F
+ };
+ }
+ }
+};
diff --git a/packages/backend/built/server/api/api-handler.js b/packages/backend/built/server/api/api-handler.js
new file mode 100644
index 0000000..98caf3b
--- /dev/null
+++ b/packages/backend/built/server/api/api-handler.js
@@ -0,0 +1,85 @@
+import { UserIps } from "../../models/index.js";
+import { fetchMeta } from "../../misc/fetch-meta.js";
+import authenticate, { AuthenticationError } from "./authenticate.js";
+import call from "./call.js";
+import { ApiError } from "./error.js";
+const userIpHistories = new Map();
+setInterval(()=>{
+ userIpHistories.clear();
+}, 1000 * 60 * 60);
+export default ((endpoint, ctx)=>new Promise((res)=>{
+ const body = ctx.is("multipart/form-data") ? ctx.request.body : ctx.method === "GET" ? ctx.query : ctx.request.body;
+ const reply = (x, y)=>{
+ if (x == null) {
+ ctx.status = 204;
+ } else if (typeof x === "number" && y) {
+ ctx.status = x;
+ ctx.body = {
+ error: {
+ message: y.message,
+ code: y.code,
+ id: y.id,
+ kind: y.kind,
+ ...y.info ? {
+ info: y.info
+ } : {}
+ }
+ };
+ } else {
+ // 文字列を返す場合は、JSON.stringify通さないとJSONと認識されない
+ ctx.body = typeof x === "string" ? JSON.stringify(x) : x;
+ }
+ res();
+ };
+ // Authentication
+ // for GET requests, do not even pass on the body parameter as it is considered unsafe
+ authenticate(ctx.headers.authorization, ctx.method === "GET" ? null : body["i"]).then(([user, app])=>{
+ // API invoking
+ call(endpoint.name, user, app, body, ctx).then((res)=>{
+ if (ctx.method === "GET" && endpoint.meta.cacheSec && !body["i"] && !user) {
+ ctx.set("Cache-Control", `public, max-age=${endpoint.meta.cacheSec}`);
+ }
+ reply(res);
+ }).catch((e)=>{
+ reply(e.httpStatusCode ? e.httpStatusCode : e.kind === "client" ? 400 : 500, e);
+ });
+ // Log IP
+ if (user) {
+ fetchMeta().then((meta)=>{
+ if (!meta.enableIpLogging) return;
+ const ip = ctx.ip;
+ const ips = userIpHistories.get(user.id);
+ if (ips == null || !ips.has(ip)) {
+ if (ips == null) {
+ userIpHistories.set(user.id, new Set([
+ ip
+ ]));
+ } else {
+ ips.add(ip);
+ }
+ try {
+ UserIps.createQueryBuilder().insert().values({
+ createdAt: new Date(),
+ userId: user.id,
+ ip: ip
+ }).orIgnore(true).execute();
+ } catch {}
+ }
+ });
+ }
+ }).catch((e)=>{
+ if (e instanceof AuthenticationError) {
+ ctx.response.status = 403;
+ ctx.response.set("WWW-Authenticate", "Bearer");
+ ctx.response.body = {
+ message: `Authentication failed: ${e.message}`,
+ code: "AUTHENTICATION_FAILED",
+ id: "b0a7f5f8-dc2f-4171-b91f-de88ad238e14",
+ kind: "client"
+ };
+ res();
+ } else {
+ reply(500, new ApiError());
+ }
+ });
+ }));
diff --git a/packages/backend/built/server/api/authenticate.js b/packages/backend/built/server/api/authenticate.js
new file mode 100644
index 0000000..371336a
--- /dev/null
+++ b/packages/backend/built/server/api/authenticate.js
@@ -0,0 +1,87 @@
+import isNativeToken from "./common/is-native-token.js";
+import { Users, AccessTokens, Apps } from "../../models/index.js";
+import { Cache } from "../../misc/cache.js";
+import { localUserByIdCache, localUserByNativeTokenCache } from "../../services/user-cache.js";
+const appCache = new Cache("app", 60 * 30);
+export class AuthenticationError extends Error {
+ constructor(message){
+ super(message);
+ this.name = "AuthenticationError";
+ }
+}
+export default (async (authorization, bodyToken, bypassUserCache = false)=>{
+ let token = null;
+ // check if there is an authorization header set
+ if (authorization != null) {
+ if (bodyToken != null) {
+ throw new AuthenticationError("using multiple authorization schemes");
+ }
+ // check if OAuth 2.0 Bearer tokens are being used
+ // Authorization schemes are case insensitive
+ if (authorization.substring(0, 7).toLowerCase() === "bearer ") {
+ token = authorization.substring(7);
+ } else {
+ throw new AuthenticationError("unsupported authentication scheme");
+ }
+ } else if (bodyToken != null) {
+ token = bodyToken;
+ } else {
+ return [
+ null,
+ null
+ ];
+ }
+ if (isNativeToken(token)) {
+ const user = bypassUserCache ? await Users.findOneBy({
+ token
+ }) : await localUserByNativeTokenCache.fetch(token, ()=>Users.findOneBy({
+ token: token ?? undefined
+ }), true);
+ if (user == null) {
+ throw new AuthenticationError("unknown token");
+ }
+ return [
+ user,
+ null
+ ];
+ } else {
+ const accessToken = await AccessTokens.findOne({
+ where: [
+ {
+ hash: token.toLowerCase()
+ },
+ {
+ token: token
+ }
+ ]
+ });
+ if (accessToken == null) {
+ throw new AuthenticationError("unknown token");
+ }
+ AccessTokens.update(accessToken.id, {
+ lastUsedAt: new Date()
+ });
+ const user = bypassUserCache ? await Users.findOneBy({
+ id: accessToken.userId
+ }) : await localUserByIdCache.fetch(accessToken.userId, ()=>Users.findOneBy({
+ id: accessToken.userId
+ }), true);
+ if (accessToken.appId) {
+ const app = await appCache.fetch(accessToken.appId, ()=>Apps.findOneByOrFail({
+ id: accessToken.appId
+ }), true);
+ return [
+ user,
+ {
+ id: accessToken.id,
+ permission: app.permission
+ }
+ ];
+ } else {
+ return [
+ user,
+ accessToken
+ ];
+ }
+ }
+});
diff --git a/packages/backend/built/server/api/call.js b/packages/backend/built/server/api/call.js
new file mode 100644
index 0000000..8fb2c3c
--- /dev/null
+++ b/packages/backend/built/server/api/call.js
@@ -0,0 +1,149 @@
+import { performance } from "perf_hooks";
+import { getIpHash } from "../../misc/get-ip-hash.js";
+import { limiter } from "./limiter.js";
+import endpoints from "./endpoints.js";
+import compatibility from "./compatibility.js";
+import { ApiError } from "./error.js";
+import { apiLogger } from "./logger.js";
+import { fetchMeta } from "../../misc/fetch-meta.js";
+const accessDenied = {
+ message: "Access denied.",
+ code: "ACCESS_DENIED",
+ id: "56f35758-7dd5-468b-8439-5d6fb8ec9b8e"
+};
+export default (async (endpoint, user, token, data, ctx)=>{
+ const isSecure = user != null && token == null;
+ const isModerator = user != null && (user.isModerator || user.isAdmin);
+ const ep = endpoints.find((e)=>e.name === endpoint) || compatibility.find((e)=>e.name === endpoint);
+ if (ep == null) {
+ throw new ApiError({
+ message: "No such endpoint.",
+ code: "NO_SUCH_ENDPOINT",
+ id: "f8080b67-5f9c-4eb7-8c18-7f1eeae8f709",
+ httpStatusCode: 404
+ });
+ }
+ if (ep.meta.secure && !isSecure) {
+ throw new ApiError(accessDenied);
+ }
+ if (ep.meta.limit) {
+ // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
+ let limitActor;
+ if (user) {
+ limitActor = user.id;
+ } else {
+ limitActor = getIpHash(ctx.ip);
+ }
+ const limit = Object.assign({}, ep.meta.limit);
+ if (!limit.key) {
+ limit.key = ep.name;
+ }
+ // Rate limit
+ await limiter(limit, limitActor).catch((e)=>{
+ const remainingTime = e.remainingTime ? `Please try again in ${e.remainingTime}.` : "Please try again later.";
+ throw new ApiError({
+ message: `Rate limit exceeded. ${remainingTime}`,
+ code: "RATE_LIMIT_EXCEEDED",
+ id: "d5826d14-3982-4d2e-8011-b9e9f02499ef",
+ httpStatusCode: 429
+ });
+ });
+ }
+ if (ep.meta.requireCredential && user == null) {
+ throw new ApiError({
+ message: "Credential required.",
+ code: "CREDENTIAL_REQUIRED",
+ id: "1384574d-a912-4b81-8601-c7b1c4085df1",
+ httpStatusCode: 401
+ });
+ }
+ if (ep.meta.requireCredential && user.isSuspended) {
+ throw new ApiError({
+ message: "Your account has been suspended.",
+ code: "YOUR_ACCOUNT_SUSPENDED",
+ id: "a8c724b3-6e9c-4b46-b1a8-bc3ed6258370",
+ httpStatusCode: 403
+ });
+ }
+ if (ep.meta.requireAdmin && !user.isAdmin) {
+ throw new ApiError(accessDenied, {
+ reason: "You are not an admin."
+ });
+ }
+ if (ep.meta.requireModerator && !isModerator) {
+ throw new ApiError(accessDenied, {
+ reason: "You are not a moderator."
+ });
+ }
+ if (token && ep.meta.kind && !token.permission.some((p)=>p === ep.meta.kind)) {
+ throw new ApiError({
+ message: "Your app does not have the necessary permissions to use this endpoint.",
+ code: "PERMISSION_DENIED",
+ id: "1370e5b7-d4eb-4566-bb1d-7748ee6a1838"
+ });
+ }
+ // private mode
+ const meta = await fetchMeta();
+ if (meta.privateMode && ep.meta.requireCredentialPrivateMode && user == null) {
+ throw new ApiError({
+ message: "Credential required.",
+ code: "CREDENTIAL_REQUIRED",
+ id: "1384574d-a912-4b81-8601-c7b1c4085df1",
+ httpStatusCode: 401
+ });
+ }
+ // Cast non JSON input
+ if ((ep.meta.requireFile || ctx?.method === "GET") && ep.params.properties) {
+ for (const k of Object.keys(ep.params.properties)){
+ const param = ep.params.properties[k];
+ if ([
+ "boolean",
+ "number",
+ "integer"
+ ].includes(param.type ?? "") && typeof data[k] === "string") {
+ try {
+ data[k] = JSON.parse(data[k]);
+ } catch (e) {
+ throw new ApiError({
+ message: "Invalid param.",
+ code: "INVALID_PARAM",
+ id: "0b5f1631-7c1a-41a6-b399-cce335f34d85"
+ }, {
+ param: k,
+ reason: `cannot cast to ${param.type}`
+ });
+ }
+ }
+ }
+ }
+ // API invoking
+ const before = performance.now();
+ return await ep.exec(data, user, token, ctx?.file, ctx?.ip, ctx?.headers).catch((e)=>{
+ if (e instanceof ApiError) {
+ throw e;
+ } else {
+ apiLogger.error(`Internal error occurred in ${ep.name}: ${e.message}`, {
+ ep: ep.name,
+ ps: data,
+ e: {
+ message: e.message,
+ code: e.name,
+ stack: e.stack
+ }
+ });
+ throw new ApiError(null, {
+ e: {
+ message: e.message,
+ code: e.name,
+ stack: e.stack
+ }
+ });
+ }
+ }).finally(()=>{
+ const after = performance.now();
+ const time = after - before;
+ if (time > 1000) {
+ apiLogger.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`);
+ }
+ });
+});
diff --git a/packages/backend/built/server/api/common/generate-block-query.js b/packages/backend/built/server/api/common/generate-block-query.js
new file mode 100644
index 0000000..be59ab1
--- /dev/null
+++ b/packages/backend/built/server/api/common/generate-block-query.js
@@ -0,0 +1,35 @@
+import { Blockings } from "../../../models/index.js";
+import { Brackets } from "typeorm";
+// ここでいうBlockedは被Blockedの意
+export function generateBlockedUserQuery(q, me) {
+ const blockingQuery = Blockings.createQueryBuilder("blocking").select("blocking.blockerId").where("blocking.blockeeId = :blockeeId", {
+ blockeeId: me.id
+ }).andWhere("blocking.groupId IS NULL");
+ const groupBlockingQuery = Blockings.createQueryBuilder("blocking").select("blocking.groupId").where("blocking.blockeeId = :groupBlockeeId", {
+ groupBlockeeId: me.id
+ }).andWhere("blocking.groupId IS NOT NULL");
+ // 投稿の作者にブロックされていない かつ
+ // 投稿の返信先の作者にブロックされていない かつ
+ // 投稿の引用元の作者にブロックされていない
+ q.andWhere(`note.userId NOT IN (${blockingQuery.getQuery()})`).andWhere(new Brackets((qb)=>{
+ qb.where("note.groupId IS NULL").orWhere(`note.groupId NOT IN (${groupBlockingQuery.getQuery()})`);
+ })).andWhere(new Brackets((qb)=>{
+ qb.where("note.replyUserId IS NULL").orWhere(`note.replyUserId NOT IN (${blockingQuery.getQuery()})`);
+ })).andWhere(new Brackets((qb)=>{
+ qb.where("note.renoteUserId IS NULL").orWhere(`note.renoteUserId NOT IN (${blockingQuery.getQuery()})`);
+ }));
+ q.setParameters(blockingQuery.getParameters());
+ q.setParameters(groupBlockingQuery.getParameters());
+}
+export function generateBlockQueryForUsers(q, me) {
+ const blockingQuery = Blockings.createQueryBuilder("blocking").select("blocking.blockeeId").where("blocking.blockerId = :blockerId", {
+ blockerId: me.id
+ });
+ const blockedQuery = Blockings.createQueryBuilder("blocking").select("blocking.blockerId").where("blocking.blockeeId = :blockeeId", {
+ blockeeId: me.id
+ });
+ q.andWhere(`user.id NOT IN (${blockingQuery.getQuery()})`);
+ q.setParameters(blockingQuery.getParameters());
+ q.andWhere(`user.id NOT IN (${blockedQuery.getQuery()})`);
+ q.setParameters(blockedQuery.getParameters());
+}
diff --git a/packages/backend/built/server/api/common/generate-channel-query.js b/packages/backend/built/server/api/common/generate-channel-query.js
new file mode 100644
index 0000000..39cc7f4
--- /dev/null
+++ b/packages/backend/built/server/api/common/generate-channel-query.js
@@ -0,0 +1,18 @@
+import { ChannelFollowings } from "../../../models/index.js";
+import { Brackets } from "typeorm";
+export function generateChannelQuery(q, me) {
+ if (me == null) {
+ q.andWhere("note.channelId IS NULL");
+ } else {
+ q.leftJoinAndSelect("note.channel", "channel");
+ const channelFollowingQuery = ChannelFollowings.createQueryBuilder("channelFollowing").select("channelFollowing.followeeId").where("channelFollowing.followerId = :followerId", {
+ followerId: me.id
+ });
+ q.andWhere(new Brackets((qb)=>{
+ qb// チャンネルのノートではない
+ .where("note.channelId IS NULL")// または自分がフォローしているチャンネルのノート
+ .orWhere(`note.channelId IN (${channelFollowingQuery.getQuery()})`);
+ }));
+ q.setParameters(channelFollowingQuery.getParameters());
+ }
+}
diff --git a/packages/backend/built/server/api/common/generate-exclude-memoriet-query.js b/packages/backend/built/server/api/common/generate-exclude-memoriet-query.js
new file mode 100644
index 0000000..13f115e
--- /dev/null
+++ b/packages/backend/built/server/api/common/generate-exclude-memoriet-query.js
@@ -0,0 +1,3 @@
+export function generateExcludeMemorietQuery(query) {
+ query.andWhere(`NOT EXISTS (SELECT 1 FROM "memoriet" "memoriet_exclude" WHERE "memoriet_exclude"."noteId" = note.id)`);
+}
diff --git a/packages/backend/built/server/api/common/generate-following-query.js b/packages/backend/built/server/api/common/generate-following-query.js
new file mode 100644
index 0000000..dde16b6
--- /dev/null
+++ b/packages/backend/built/server/api/common/generate-following-query.js
@@ -0,0 +1,40 @@
+import { Brackets } from "typeorm";
+import { Followings, Notes } from "../../../models/index.js";
+import { Cache } from "../../../misc/cache.js";
+import { apiLogger } from "../logger.js";
+export const cache = new Cache("homeTlQueryData", 60 * 60 * 24);
+const cutoff = 250; // 250 posts in the last 7 days, constant determined by comparing benchmarks for cutoff values between 100 and 2500
+const logger = apiLogger.createSubLogger("heuristics");
+export async function generateFollowingQuery(q, me) {
+ const followingQuery = Followings.createQueryBuilder("following").select("following.followeeId").where("following.followerId = :meId");
+ const heuristic = await cache.fetch(me.id, async ()=>{
+ let curr = new Date();
+ let prev = new Date();
+ prev.setDate(prev.getDate() - 7);
+ return Notes.createQueryBuilder('note').where(`note.createdAt > :prev`, {
+ prev
+ }).andWhere(`note.createdAt < :curr`, {
+ curr
+ }).andWhere(new Brackets((qb)=>{
+ qb.where(`note.userId IN (${followingQuery.getQuery()})`);
+ qb.orWhere(`note.userId = :meId`, {
+ meId: me.id
+ });
+ })).getCount().then((res)=>{
+ logger.info(`Calculating heuristics for user ${me.id} took ${new Date().getTime() - curr.getTime()}ms`);
+ return res;
+ });
+ });
+ const shouldUseUnion = heuristic < cutoff;
+ q.andWhere(new Brackets((qb)=>{
+ if (shouldUseUnion) {
+ qb.where(`note.userId = ANY(array(${followingQuery.getQuery()} UNION ALL VALUES (:meId)))`);
+ } else {
+ qb.where(`note.userId = :meId`);
+ qb.orWhere(`note.userId IN (${followingQuery.getQuery()})`);
+ }
+ }));
+ q.setParameters({
+ meId: me.id
+ });
+}
diff --git a/packages/backend/built/server/api/common/generate-fts-query.js b/packages/backend/built/server/api/common/generate-fts-query.js
new file mode 100644
index 0000000..58f9b54
--- /dev/null
+++ b/packages/backend/built/server/api/common/generate-fts-query.js
@@ -0,0 +1,234 @@
+import { Brackets } from "typeorm";
+import { sqlLikeEscape } from "../../../misc/sql-like-escape.js";
+import { sqlRegexEscape } from "../../../misc/sql-regex-escape.js";
+import { Followings, NoteFavorites, NoteReactions, Users } from "../../../models/index.js";
+const filters = {
+ "from": fromFilter,
+ "-from": fromFilterInverse,
+ "mention": mentionFilter,
+ "-mention": mentionFilterInverse,
+ "reply": replyFilter,
+ "-reply": replyFilterInverse,
+ "to": replyFilter,
+ "-to": replyFilterInverse,
+ "before": beforeFilter,
+ "until": beforeFilter,
+ "after": afterFilter,
+ "since": afterFilter,
+ "instance": instanceFilter,
+ "-instance": instanceFilterInverse,
+ "domain": instanceFilter,
+ "-domain": instanceFilterInverse,
+ "host": instanceFilter,
+ "-host": instanceFilterInverse,
+ "filter": miscFilter,
+ "-filter": miscFilterInverse,
+ "in": inFilter,
+ "-in": inFilterInverse,
+ "has": attachmentFilter
+};
+export function generateFtsQuery(query, q) {
+ const components = q.trim().split(" ");
+ const terms = [];
+ let finalTerms = [];
+ let counter = 0;
+ let caseSensitive = false;
+ let matchWords = false;
+ for (const component of components){
+ const split = component.split(":");
+ if (split.length > 1 && filters[split[0]] !== undefined) filters[split[0]](query, split.slice(1).join(":"), counter++);
+ else if (split.length > 1 && (split[0] === "search" || split[0] === "match")) matchWords = split[1] === 'word' || split[1] === 'words';
+ else if (split.length > 1 && split[0] === "case") caseSensitive = split[1] === 'sensitive';
+ else terms.push(component);
+ }
+ let idx = 0;
+ let state = 'idle';
+ for(let i = 0; i < terms.length; i++){
+ if (state === 'idle') {
+ if (terms[i].startsWith('"') && terms[i].endsWith('"') || terms[i].startsWith('(') && terms[i].endsWith(')')) {
+ finalTerms.push(trimStartAndEnd(terms[i]));
+ } else if (terms[i].startsWith('"')) {
+ idx = i;
+ state = 'quote';
+ } else if (terms[i].startsWith('(')) {
+ idx = i;
+ state = 'parenthesis';
+ } else {
+ finalTerms.push(terms[i]);
+ }
+ } else if (state === 'quote' && terms[i].endsWith('"')) {
+ finalTerms.push(extractToken(terms, idx, i));
+ state = 'idle';
+ } else if (state === 'parenthesis' && terms[i].endsWith(')')) {
+ query.andWhere(new Brackets((qb)=>{
+ for (const term of extractToken(terms, idx, i).split(' OR ')){
+ const id = counter++;
+ appendSearchQuery(term, 'or', query, qb, id, term.startsWith('-'), matchWords, caseSensitive);
+ }
+ }));
+ state = 'idle';
+ }
+ }
+ if (state != "idle") {
+ finalTerms.push(...extractToken(terms, idx, terms.length - 1, false).substring(1).split(' '));
+ }
+ for (const term of finalTerms){
+ const id = counter++;
+ appendSearchQuery(term, 'and', query, query, id, term.startsWith('-'), matchWords, caseSensitive);
+ }
+}
+function fromFilter(query, filter, id) {
+ const userQuery = generateUserSubquery(filter, id);
+ query.andWhere(`note.userId = (${userQuery.getQuery()})`);
+ query.setParameters(userQuery.getParameters());
+}
+function fromFilterInverse(query, filter, id) {
+ const userQuery = generateUserSubquery(filter, id);
+ query.andWhere(`note.userId <> (${userQuery.getQuery()})`);
+ query.setParameters(userQuery.getParameters());
+}
+function mentionFilter(query, filter, id) {
+ const userQuery = generateUserSubquery(filter, id);
+ query.addCommonTableExpression(userQuery.getQuery(), `cte_${id}`, {
+ materialized: true
+ });
+ query.andWhere(`note.mentions @> array[(SELECT * FROM cte_${id})]::varchar[]`);
+ query.setParameters(userQuery.getParameters());
+}
+function mentionFilterInverse(query, filter, id) {
+ const userQuery = generateUserSubquery(filter, id);
+ query.addCommonTableExpression(userQuery.getQuery(), `cte_${id}`, {
+ materialized: true
+ });
+ query.andWhere(`NOT (note.mentions @> array[(SELECT * FROM cte_${id})]::varchar[])`);
+ query.setParameters(userQuery.getParameters());
+}
+function replyFilter(query, filter, id) {
+ const userQuery = generateUserSubquery(filter, id);
+ query.andWhere(`note.replyUserId = (${userQuery.getQuery()})`);
+ query.setParameters(userQuery.getParameters());
+}
+function replyFilterInverse(query, filter, id) {
+ const userQuery = generateUserSubquery(filter, id);
+ query.andWhere(`note.replyUserId <> (${userQuery.getQuery()})`);
+ query.setParameters(userQuery.getParameters());
+}
+function beforeFilter(query, filter) {
+ query.andWhere('note.createdAt < :before', {
+ before: filter
+ });
+}
+function afterFilter(query, filter) {
+ query.andWhere('note.createdAt > :after', {
+ after: filter
+ });
+}
+function instanceFilter(query, filter, id) {
+ if (filter === 'local') {
+ query.andWhere(`note.userHost IS NULL`);
+ } else {
+ query.andWhere(`note.userHost = :instance_${id}`);
+ query.setParameter(`instance_${id}`, filter);
+ }
+}
+function instanceFilterInverse(query, filter, id) {
+ if (filter === 'local') {
+ query.andWhere(`note.userHost IS NOT NULL`);
+ } else {
+ query.andWhere(`note.userHost <> :instance_${id}`);
+ query.setParameter(`instance_${id}`, filter);
+ }
+}
+function miscFilter(query, filter) {
+ let subQuery = null;
+ if (filter === 'followers') {
+ subQuery = Followings.createQueryBuilder('following').select('following.followerId').where('following.followeeId = :meId');
+ } else if (filter === 'following') {
+ subQuery = Followings.createQueryBuilder('following').select('following.followeeId').where('following.followerId = :meId');
+ } else if (filter === 'replies' || filter === 'reply') {
+ query.andWhere('note.replyId IS NOT NULL');
+ } else if (filter === 'boosts' || filter === 'boost' || filter === 'renotes' || filter === 'renote') {
+ query.andWhere('note.renoteId IS NOT NULL');
+ }
+ if (subQuery !== null) query.andWhere(`note.userId IN (${subQuery.getQuery()})`);
+}
+function miscFilterInverse(query, filter) {
+ let subQuery = null;
+ if (filter === 'followers') {
+ subQuery = Followings.createQueryBuilder('following').select('following.followerId').where('following.followeeId = :meId');
+ } else if (filter === 'following') {
+ subQuery = Followings.createQueryBuilder('following').select('following.followeeId').where('following.followerId = :meId');
+ } else if (filter === 'replies' || filter === 'reply') {
+ query.andWhere('note.replyId IS NULL');
+ } else if (filter === 'boosts' || filter === 'boost' || filter === 'renotes' || filter === 'renote') {
+ query.andWhere('note.renoteId IS NULL');
+ }
+ if (subQuery !== null) query.andWhere(`note.userId NOT IN (${subQuery.getQuery()})`);
+}
+function inFilter(query, filter) {
+ let subQuery = null;
+ if (filter === 'bookmarks') {
+ subQuery = NoteFavorites.createQueryBuilder('bookmark').select('bookmark.noteId').where('bookmark.userId = :meId');
+ } else if (filter === 'favorites' || filter === 'favourites' || filter === 'reactions' || filter === 'likes') {
+ subQuery = NoteReactions.createQueryBuilder('react').select('react.noteId').where('react.userId = :meId');
+ }
+ if (subQuery !== null) query.andWhere(`note.id IN (${subQuery.getQuery()})`);
+}
+function inFilterInverse(query, filter) {
+ let subQuery = null;
+ if (filter === 'bookmarks') {
+ subQuery = NoteFavorites.createQueryBuilder('bookmark').select('bookmark.noteId').where('bookmark.userId = :meId');
+ } else if (filter === 'favorites' || filter === 'favourites' || filter === 'reactions' || filter === 'likes') {
+ subQuery = NoteReactions.createQueryBuilder('react').select('react.noteId').where('react.userId = :meId');
+ }
+ if (subQuery !== null) query.andWhere(`note.id NOT IN (${subQuery.getQuery()})`);
+}
+function attachmentFilter(query, filter) {
+ switch(filter){
+ case 'image':
+ query.andWhere(`note."attachedFileTypes"::varchar ILIKE '%image/%'`);
+ break;
+ case 'video':
+ query.andWhere(`note."attachedFileTypes"::varchar ILIKE '%video/%'`);
+ break;
+ case 'audio':
+ query.andWhere(`note."attachedFileTypes"::varchar ILIKE '%audio/%'`);
+ break;
+ case 'file':
+ query.andWhere(`note."attachedFileTypes" <> '{}'`);
+ query.andWhere(`NOT (note."attachedFileTypes"::varchar ILIKE '%image/%')`);
+ query.andWhere(`NOT (note."attachedFileTypes"::varchar ILIKE '%video/%')`);
+ query.andWhere(`NOT (note."attachedFileTypes"::varchar ILIKE '%audio/%')`);
+ break;
+ default:
+ break;
+ }
+}
+function generateUserSubquery(filter, id) {
+ if (filter.startsWith('@')) filter = filter.substring(1);
+ const split = filter.split('@');
+ const query = Users.createQueryBuilder('user').select('user.id').where(`user.usernameLower = :user_${id}`).andWhere(`user.host ${split[1] !== undefined ? `= :host_${id}` : 'IS NULL'}`);
+ query.setParameter(`user_${id}`, split[0].toLowerCase());
+ if (split[1] !== undefined) query.setParameter(`host_${id}`, split[1].toLowerCase());
+ return query;
+}
+function extractToken(array, start, end, trim = true) {
+ const slice = array.slice(start, end + 1).join(" ");
+ return trim ? trimStartAndEnd(slice) : slice;
+}
+function trimStartAndEnd(str) {
+ return str.substring(1, str.length - 1);
+}
+function appendSearchQuery(term, mode, query, qb, id, negate, matchWords, caseSensitive) {
+ const sql = `note.text ${getSearchMatchOperator(negate, matchWords, caseSensitive)} :q_${id}`;
+ if (mode === 'and') qb.andWhere(sql);
+ else if (mode === 'or') qb.orWhere(sql);
+ query.setParameter(`q_${id}`, escapeSqlSearchParam(term.substring(negate ? 1 : 0), matchWords));
+}
+function getSearchMatchOperator(negate, matchWords, caseSensitive) {
+ const negatePrefix = matchWords ? '!' : 'NOT ';
+ return `${negate ? negatePrefix : ''}${matchWords ? caseSensitive ? '~' : '~*' : caseSensitive ? 'LIKE' : 'ILIKE'}`;
+}
+function escapeSqlSearchParam(param, matchWords) {
+ return matchWords ? `\\y${sqlRegexEscape(param)}\\y` : `%${sqlLikeEscape(param)}%`;
+}
diff --git a/packages/backend/built/server/api/common/generate-list-query.js b/packages/backend/built/server/api/common/generate-list-query.js
new file mode 100644
index 0000000..0e56352
--- /dev/null
+++ b/packages/backend/built/server/api/common/generate-list-query.js
@@ -0,0 +1,13 @@
+import { Brackets } from "typeorm";
+import { UserListJoinings, UserLists } from "../../../models/index.js";
+export function generateListQuery(q, me) {
+ const listQuery = UserLists.createQueryBuilder("list").select("list.id").where("list.hideFromHomeTl = TRUE").andWhere("list.userId = :meId");
+ const memberQuery = UserListJoinings.createQueryBuilder("member").select("member.userId").where(`member.userListId IN (${listQuery.getQuery()})`);
+ q.andWhere(new Brackets((qb)=>{
+ qb.where(`note.userId = :meId`);
+ qb.orWhere(`note.userId NOT IN (${memberQuery.getQuery()})`);
+ }));
+ q.setParameters({
+ meId: me.id
+ });
+}
diff --git a/packages/backend/built/server/api/common/generate-minor-badge-visibility-query.js b/packages/backend/built/server/api/common/generate-minor-badge-visibility-query.js
new file mode 100644
index 0000000..7e62b78
--- /dev/null
+++ b/packages/backend/built/server/api/common/generate-minor-badge-visibility-query.js
@@ -0,0 +1,18 @@
+import { Brackets } from "typeorm";
+export function shouldHideEUsersFor(viewer) {
+ return !!viewer && !viewer.isAdmin && !viewer.isModerator && (viewer.minorBadges ?? []).some((badge)=>badge === "K" || badge === "T");
+}
+export function generateMinorBadgeUserVisibilityQuery(q, viewer, alias = "user") {
+ if (!shouldHideEUsersFor(viewer)) return;
+ q.andWhere(new Brackets((qb)=>{
+ qb.where(`${alias}.id = :minorBadgeViewerId`).orWhere(`NOT ('E' = ANY(${alias}."minorBadges"))`);
+ }));
+ q.setParameter("minorBadgeViewerId", viewer.id);
+}
+export function generateMinorBadgeNoteVisibilityQuery(q, viewer, noteAlias = "note") {
+ if (!shouldHideEUsersFor(viewer)) return;
+ q.andWhere(new Brackets((qb)=>{
+ qb.where(`${noteAlias}."userId" = :minorBadgeViewerId`).orWhere(`${noteAlias}."userId" NOT IN (` + `SELECT "id" FROM "user" WHERE 'E' = ANY("minorBadges")` + `)`);
+ }));
+ q.setParameter("minorBadgeViewerId", viewer.id);
+}
diff --git a/packages/backend/built/server/api/common/generate-muted-note-thread-query.js b/packages/backend/built/server/api/common/generate-muted-note-thread-query.js
new file mode 100644
index 0000000..a6a295c
--- /dev/null
+++ b/packages/backend/built/server/api/common/generate-muted-note-thread-query.js
@@ -0,0 +1,12 @@
+import { NoteThreadMutings } from "../../../models/index.js";
+import { Brackets } from "typeorm";
+export function generateMutedNoteThreadQuery(q, me) {
+ const mutedQuery = NoteThreadMutings.createQueryBuilder("threadMuted").select("threadMuted.threadId").where("threadMuted.userId = :userId", {
+ userId: me.id
+ });
+ q.andWhere(`note.id NOT IN (${mutedQuery.getQuery()})`);
+ q.andWhere(new Brackets((qb)=>{
+ qb.where("note.threadId IS NULL").orWhere(`note.threadId NOT IN (${mutedQuery.getQuery()})`);
+ }));
+ q.setParameters(mutedQuery.getParameters());
+}
diff --git a/packages/backend/built/server/api/common/generate-muted-user-query.js b/packages/backend/built/server/api/common/generate-muted-user-query.js
new file mode 100644
index 0000000..8ebced6
--- /dev/null
+++ b/packages/backend/built/server/api/common/generate-muted-user-query.js
@@ -0,0 +1,39 @@
+import { Brackets } from "typeorm";
+import { Mutings, UserProfiles } from "../../../models/index.js";
+export function generateMutedUserQuery(q, me, exclude) {
+ const mutingQuery = Mutings.createQueryBuilder("muting").select("muting.muteeId").where("muting.muterId = :muterId", {
+ muterId: me.id
+ });
+ if (exclude) {
+ mutingQuery.andWhere("muting.muteeId != :excludeId", {
+ excludeId: exclude.id
+ });
+ }
+ const mutingInstanceQuery = UserProfiles.createQueryBuilder("user_profile").select("user_profile.mutedInstances").where("user_profile.userId = :muterId", {
+ muterId: me.id
+ });
+ // 投稿の作者をミュートしていない かつ
+ // 投稿の返信先の作者をミュートしていない かつ
+ // 投稿の引用元の作者をミュートしていない
+ q.andWhere(`note.userId NOT IN (${mutingQuery.getQuery()})`).andWhere(new Brackets((qb)=>{
+ qb.where("note.replyUserId IS NULL").orWhere(`note.replyUserId NOT IN (${mutingQuery.getQuery()})`);
+ })).andWhere(new Brackets((qb)=>{
+ qb.where("note.renoteUserId IS NULL").orWhere(`note.renoteUserId NOT IN (${mutingQuery.getQuery()})`);
+ }))// mute instances
+ .andWhere(new Brackets((qb)=>{
+ qb.andWhere("note.userHost IS NULL").orWhere(`NOT ((${mutingInstanceQuery.getQuery()})::jsonb ? note.userHost)`);
+ })).andWhere(new Brackets((qb)=>{
+ qb.where("note.replyUserHost IS NULL").orWhere(`NOT ((${mutingInstanceQuery.getQuery()})::jsonb ? note.replyUserHost)`);
+ })).andWhere(new Brackets((qb)=>{
+ qb.where("note.renoteUserHost IS NULL").orWhere(`NOT ((${mutingInstanceQuery.getQuery()})::jsonb ? note.renoteUserHost)`);
+ }));
+ q.setParameters(mutingQuery.getParameters());
+ q.setParameters(mutingInstanceQuery.getParameters());
+}
+export function generateMutedUserQueryForUsers(q, me) {
+ const mutingQuery = Mutings.createQueryBuilder("muting").select("muting.muteeId").where("muting.muterId = :muterId", {
+ muterId: me.id
+ });
+ q.andWhere(`user.id NOT IN (${mutingQuery.getQuery()})`);
+ q.setParameters(mutingQuery.getParameters());
+}
diff --git a/packages/backend/built/server/api/common/generate-native-user-token.js b/packages/backend/built/server/api/common/generate-native-user-token.js
new file mode 100644
index 0000000..3af2bab
--- /dev/null
+++ b/packages/backend/built/server/api/common/generate-native-user-token.js
@@ -0,0 +1,2 @@
+import { secureRndstr } from "../../../misc/secure-rndstr.js";
+export default (()=>secureRndstr(16));
diff --git a/packages/backend/built/server/api/common/generate-replies-query.js b/packages/backend/built/server/api/common/generate-replies-query.js
new file mode 100644
index 0000000..8ac95eb
--- /dev/null
+++ b/packages/backend/built/server/api/common/generate-replies-query.js
@@ -0,0 +1,28 @@
+import { Brackets } from "typeorm";
+export function generateRepliesQuery(q, withReplies, me) {
+ if (me == null) {
+ q.andWhere(new Brackets((qb)=>{
+ qb.where("note.replyId IS NULL") // 返信ではない
+ .orWhere(new Brackets((qb)=>{
+ qb.where(// 返信だけど投稿者自身への返信
+ "note.replyId IS NOT NULL").andWhere("note.replyUserId = note.userId");
+ }));
+ }));
+ } else if (!withReplies) {
+ q.andWhere(new Brackets((qb)=>{
+ qb.where("note.replyId IS NULL") // 返信ではない
+ .orWhere("note.replyUserId = :meId", {
+ meId: me.id
+ }) // 返信だけど自分のノートへの返信
+ .orWhere(new Brackets((qb)=>{
+ qb.where("note.replyId IS NOT NULL") // 返信だけど自分の行った返信
+ .andWhere("note.userId = :meId", {
+ meId: me.id
+ });
+ })).orWhere(new Brackets((qb)=>{
+ qb.where("note.replyId IS NOT NULL") // 返信だけど投稿者自身への返信
+ .andWhere("note.replyUserId = note.userId");
+ }));
+ }));
+ }
+}
diff --git a/packages/backend/built/server/api/common/generate-visibility-query.js b/packages/backend/built/server/api/common/generate-visibility-query.js
new file mode 100644
index 0000000..c3541cb
--- /dev/null
+++ b/packages/backend/built/server/api/common/generate-visibility-query.js
@@ -0,0 +1,43 @@
+import { Followings } from "../../../models/index.js";
+import { Brackets } from "typeorm";
+import { generateMinorBadgeNoteVisibilityQuery } from "./generate-minor-badge-visibility-query.js";
+export function generateVisibilityQuery(q, me, options) {
+ // This code must always be synchronized with the checks in Notes.isVisibleForMe.
+ if (me == null) {
+ q.andWhere(new Brackets((qb)=>{
+ qb.where(`note.visibility = 'public'`).orWhere(`note.visibility = 'home'`);
+ })).andWhere('note.localOnly = FALSE');
+ } else {
+ const followingQuery = Followings.createQueryBuilder("following").select("following.followeeId").where("following.followerId = :meId");
+ q.andWhere(new Brackets((qb)=>{
+ qb// 公開投稿である
+ .where(new Brackets((qb)=>{
+ qb.where(`note.visibility = 'public'`).orWhere(`note.visibility = 'home'`);
+ }))// または 自分自身
+ .orWhere("note.userId = :meId")// または 自分宛て
+ .orWhere(":meId = ANY(note.visibleUserIds)").orWhere(":meId = ANY(note.mentions)").orWhere(new Brackets((qb)=>{
+ qb// または フォロワー宛ての投稿であり、
+ .where(`note.visibility = 'followers'`).andWhere(new Brackets((qb)=>{
+ qb// 自分がフォロワーである
+ .where(`note.userId IN (${followingQuery.getQuery()})`)// または 自分の投稿へのリプライ
+ .orWhere("note.replyUserId = :meId");
+ }));
+ }));
+ }));
+ q.andWhere(new Brackets((qb)=>{
+ qb.where(`note.visibility != 'hidden'`).orWhere(`note.userId = :meId`);
+ }));
+ q.setParameters({
+ meId: me.id
+ });
+ }
+ if (!options?.allowAdservice) {
+ q.andWhere(new Brackets((qb)=>{
+ qb.where(`NOT ('adservice' = ANY(note.tags))`);
+ if (me) {
+ qb.orWhere("note.userId = :meId");
+ }
+ }));
+ }
+ generateMinorBadgeNoteVisibilityQuery(q, me);
+}
diff --git a/packages/backend/built/server/api/common/generated-muted-renote-query.js b/packages/backend/built/server/api/common/generated-muted-renote-query.js
new file mode 100644
index 0000000..ce0210d
--- /dev/null
+++ b/packages/backend/built/server/api/common/generated-muted-renote-query.js
@@ -0,0 +1,15 @@
+import { Brackets } from "typeorm";
+import { RenoteMutings } from "../../../models/index.js";
+export function generateMutedUserRenotesQueryForNotes(q, me) {
+ const mutingQuery = RenoteMutings.createQueryBuilder("renote_muting").select("renote_muting.muteeId").where("renote_muting.muterId = :muterId", {
+ muterId: me.id
+ });
+ q.andWhere(new Brackets((qb)=>{
+ qb.where(new Brackets((qb)=>{
+ qb.where("note.renoteId IS NOT NULL");
+ qb.andWhere("note.text IS NULL");
+ qb.andWhere(`note.userId NOT IN (${mutingQuery.getQuery()})`);
+ })).orWhere("note.renoteId IS NULL").orWhere("note.text IS NOT NULL");
+ }));
+ q.setParameters(mutingQuery.getParameters());
+}
diff --git a/packages/backend/built/server/api/common/get-group-actor.js b/packages/backend/built/server/api/common/get-group-actor.js
new file mode 100644
index 0000000..16c873b
--- /dev/null
+++ b/packages/backend/built/server/api/common/get-group-actor.js
@@ -0,0 +1,14 @@
+import { UserGroupJoinings, UserGroups } from "../../../models/index.js";
+export async function getGroupActor(groupId, user) {
+ if (groupId == null) return null;
+ const group = await UserGroups.findOneBy({
+ id: groupId
+ });
+ if (group == null) return null;
+ if (group.userId === user.id) return group;
+ const joining = await UserGroupJoinings.findOneBy({
+ userId: user.id,
+ userGroupId: group.id
+ });
+ return joining == null ? null : group;
+}
diff --git a/packages/backend/built/server/api/common/getters.js b/packages/backend/built/server/api/common/getters.js
new file mode 100644
index 0000000..e583857
--- /dev/null
+++ b/packages/backend/built/server/api/common/getters.js
@@ -0,0 +1,45 @@
+import { IdentifiableError } from "../../../misc/identifiable-error.js";
+import { Notes, Users } from "../../../models/index.js";
+import { generateVisibilityQuery } from "./generate-visibility-query.js";
+/**
+ * Get note for API processing, taking into account visibility.
+ */ export async function getNote(noteId, me, options) {
+ const query = Notes.createQueryBuilder("note").where("note.id = :id", {
+ id: noteId
+ });
+ generateVisibilityQuery(query, me, options);
+ const note = await query.getOne();
+ if (note == null || me == null && note.localOnly) {
+ throw new IdentifiableError("9725d0ce-ba28-4dde-95a7-2cbb2c15de24", "No such note.");
+ }
+ return note;
+}
+/**
+ * Get user for API processing
+ */ export async function getUser(userId) {
+ const user = await Users.findOneBy({
+ id: userId
+ });
+ if (user == null) {
+ throw new IdentifiableError("15348ddd-432d-49c2-8a5a-8069753becff", "No such user.");
+ }
+ return user;
+}
+/**
+ * Get remote user for API processing
+ */ export async function getRemoteUser(userId) {
+ const user = await getUser(userId);
+ if (!Users.isRemoteUser(user)) {
+ throw new Error("user is not a remote user");
+ }
+ return user;
+}
+/**
+ * Get local user for API processing
+ */ export async function getLocalUser(userId) {
+ const user = await getUser(userId);
+ if (!Users.isLocalUser(user)) {
+ throw new Error("user is not a local user");
+ }
+ return user;
+}
diff --git a/packages/backend/built/server/api/common/inject-featured.js b/packages/backend/built/server/api/common/inject-featured.js
new file mode 100644
index 0000000..28fc28c
--- /dev/null
+++ b/packages/backend/built/server/api/common/inject-featured.js
@@ -0,0 +1,37 @@
+import rndstr from "rndstr";
+import { Notes, UserProfiles, NoteReactions } from "../../../models/index.js";
+import { generateMutedUserQuery } from "./generate-muted-user-query.js";
+import { generateBlockedUserQuery } from "./generate-block-query.js";
+// TODO: リアクション、Renote、返信などをしたノートは除外する
+export async function injectFeatured(timeline, user) {
+ if (timeline.length < 5) return;
+ if (user) {
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ if (!profile.injectFeaturedNote) return;
+ }
+ const max = 30;
+ const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで
+ const query = Notes.createQueryBuilder("note").addSelect("note.score").where("note.userHost IS NULL").andWhere("note.score > 0").andWhere("note.createdAt > :date", {
+ date: new Date(Date.now() - day)
+ }).andWhere(`note.visibility = 'public'`).innerJoinAndSelect("note.user", "user");
+ if (user) {
+ query.andWhere("note.userId != :userId", {
+ userId: user.id
+ });
+ generateMutedUserQuery(query, user);
+ generateBlockedUserQuery(query, user);
+ const reactionQuery = NoteReactions.createQueryBuilder("reaction").select("reaction.noteId").where("reaction.userId = :userId", {
+ userId: user.id
+ });
+ query.andWhere(`note.id NOT IN (${reactionQuery.getQuery()})`);
+ }
+ const notes = await query.orderBy("note.score", "DESC").take(max).getMany();
+ if (notes.length === 0) return;
+ // Pick random one
+ const featured = notes[Math.floor(Math.random() * notes.length)];
+ featured._featuredId_ = rndstr("a-z0-9", 8);
+ // Inject featured
+ timeline.splice(3, 0, featured);
+}
diff --git a/packages/backend/built/server/api/common/inject-promo.js b/packages/backend/built/server/api/common/inject-promo.js
new file mode 100644
index 0000000..47a57b5
--- /dev/null
+++ b/packages/backend/built/server/api/common/inject-promo.js
@@ -0,0 +1,53 @@
+import rndstr from "rndstr";
+import { PromoReads, PromoNotes, Notes, Users } from "../../../models/index.js";
+import { shouldHideEUsersFor } from "./generate-minor-badge-visibility-query.js";
+import { readPromo } from "./read-promo.js";
+const systemTags = new Set([
+ "adservice",
+ "videoservice",
+ "audioservice",
+ "imageservice",
+ "karaokeservice",
+ "lua4frozen"
+]);
+function isExplicitAd(note) {
+ return note.tags.includes("explicit") || note.user?.minorBadges?.includes("E") === true;
+}
+export async function injectPromo(timeline, user, preferredTag) {
+ // TODO: readやexpireフィルタはクエリ側でやる
+ const readDay = new Date().toISOString().slice(0, 10);
+ const reads = user ? await PromoReads.findBy({
+ userId: user.id,
+ readDay
+ }) : [];
+ let promos = await PromoNotes.find();
+ promos = promos.filter((n)=>n.expiresAt.getTime() > Date.now());
+ promos = promos.filter((n)=>n.remainingCredits > 0);
+ promos = promos.filter((n)=>!reads.map((r)=>r.noteId).includes(n.noteId));
+ if (promos.length === 0) return;
+ const candidates = [];
+ for (const promo of promos){
+ const note = await Notes.findOneBy({
+ id: promo.noteId
+ });
+ if (!note?.tags.includes("adservice")) continue;
+ note.user = await Users.findOneByOrFail({
+ id: note.userId
+ });
+ if (user && shouldHideEUsersFor(user) && isExplicitAd(note)) continue;
+ candidates.push(note);
+ }
+ if (candidates.length === 0) return;
+ const normalizedPreferredTag = preferredTag?.trim().toLowerCase().replace(/^#/, "");
+ const priority = normalizedPreferredTag && !systemTags.has(normalizedPreferredTag) ? candidates.filter((note)=>note.tags.includes(normalizedPreferredTag)) : [];
+ const pool = priority.length > 0 ? priority : candidates;
+ // Pick random promo
+ const note = pool[Math.floor(Math.random() * pool.length)];
+ const promo = promos.find((promo)=>promo.noteId === note.id);
+ note._prId_ = rndstr("a-z0-9", 8);
+ if (user && promo) {
+ await readPromo(note, promo, user);
+ }
+ // Inject promo
+ timeline.splice(Math.min(3, timeline.length), 0, note);
+}
diff --git a/packages/backend/built/server/api/common/is-native-token.js b/packages/backend/built/server/api/common/is-native-token.js
new file mode 100644
index 0000000..f4ca790
--- /dev/null
+++ b/packages/backend/built/server/api/common/is-native-token.js
@@ -0,0 +1 @@
+export default ((token)=>token.length === 16);
diff --git a/packages/backend/built/server/api/common/make-pagination-query.js b/packages/backend/built/server/api/common/make-pagination-query.js
new file mode 100644
index 0000000..82572cb
--- /dev/null
+++ b/packages/backend/built/server/api/common/make-pagination-query.js
@@ -0,0 +1,42 @@
+export function makePaginationQuery(q, sinceId, untilId, sinceDate, untilDate) {
+ if (sinceId && untilId) {
+ q.andWhere(`${q.alias}.id > :sinceId`, {
+ sinceId: sinceId
+ });
+ q.andWhere(`${q.alias}.id < :untilId`, {
+ untilId: untilId
+ });
+ q.orderBy(`${q.alias}.id`, "DESC");
+ } else if (sinceId) {
+ q.andWhere(`${q.alias}.id > :sinceId`, {
+ sinceId: sinceId
+ });
+ q.orderBy(`${q.alias}.id`, "ASC");
+ } else if (untilId) {
+ q.andWhere(`${q.alias}.id < :untilId`, {
+ untilId: untilId
+ });
+ q.orderBy(`${q.alias}.id`, "DESC");
+ } else if (sinceDate && untilDate) {
+ q.andWhere(`${q.alias}.createdAt > :sinceDate`, {
+ sinceDate: new Date(sinceDate)
+ });
+ q.andWhere(`${q.alias}.createdAt < :untilDate`, {
+ untilDate: new Date(untilDate)
+ });
+ q.orderBy(`${q.alias}.createdAt`, "DESC");
+ } else if (sinceDate) {
+ q.andWhere(`${q.alias}.createdAt > :sinceDate`, {
+ sinceDate: new Date(sinceDate)
+ });
+ q.orderBy(`${q.alias}.createdAt`, "ASC");
+ } else if (untilDate) {
+ q.andWhere(`${q.alias}.createdAt < :untilDate`, {
+ untilDate: new Date(untilDate)
+ });
+ q.orderBy(`${q.alias}.createdAt`, "DESC");
+ } else {
+ q.orderBy(`${q.alias}.id`, "DESC");
+ }
+ return q;
+}
diff --git a/packages/backend/built/server/api/common/read-messaging-message.js b/packages/backend/built/server/api/common/read-messaging-message.js
new file mode 100644
index 0000000..a3f03b7
--- /dev/null
+++ b/packages/backend/built/server/api/common/read-messaging-message.js
@@ -0,0 +1,125 @@
+import { publishMainStream, publishGroupMessagingStream } from "../../../services/stream.js";
+import { publishMessagingStream } from "../../../services/stream.js";
+import { publishMessagingIndexStream } from "../../../services/stream.js";
+import { pushNotification } from "../../../services/push-notification.js";
+import { MessagingMessages, UserGroupJoinings, Users } from "../../../models/index.js";
+import { In } from "typeorm";
+import { IdentifiableError } from "../../../misc/identifiable-error.js";
+import { toArray } from "../../../prelude/array.js";
+import { renderReadActivity } from "../../../remote/activitypub/renderer/read.js";
+import { renderActivity } from "../../../remote/activitypub/renderer/index.js";
+import { deliver } from "../../../queue/index.js";
+import orderedCollection from "../../../remote/activitypub/renderer/ordered-collection.js";
+/**
+ * Mark messages as read
+ */ export async function readUserMessagingMessage(userId, otherpartyId, messageIds) {
+ if (messageIds.length === 0) return;
+ const messages = await MessagingMessages.findBy({
+ id: In(messageIds)
+ });
+ for (const message of messages){
+ if (message.recipientId !== userId) {
+ throw new IdentifiableError("e140a4bf-49ce-4fb6-b67c-b78dadf6b52f", "Access denied (user).");
+ }
+ }
+ // Update documents
+ await MessagingMessages.update({
+ id: In(messageIds),
+ userId: otherpartyId,
+ recipientId: userId,
+ isRead: false
+ }, {
+ isRead: true
+ });
+ // Publish event
+ publishMessagingStream(otherpartyId, userId, "read", messageIds);
+ publishMessagingIndexStream(userId, "read", messageIds);
+ if (!await Users.getHasUnreadMessagingMessage(userId)) {
+ // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
+ publishMainStream(userId, "readAllMessagingMessages");
+ pushNotification(userId, "readAllMessagingMessages", undefined);
+ } else {
+ // そのユーザーとのメッセージで未読がなければイベント発行
+ const count = await MessagingMessages.count({
+ where: {
+ userId: otherpartyId,
+ recipientId: userId,
+ isRead: false
+ },
+ take: 1
+ });
+ if (!count) {
+ pushNotification(userId, "readAllMessagingMessagesOfARoom", {
+ userId: otherpartyId
+ });
+ }
+ }
+}
+/**
+ * Mark messages as read
+ */ export async function readGroupMessagingMessage(userId, groupId, messageIds) {
+ if (messageIds.length === 0) return;
+ // check joined
+ const joining = await UserGroupJoinings.findOneBy({
+ userId: userId,
+ userGroupId: groupId
+ });
+ if (joining == null) {
+ throw new IdentifiableError("930a270c-714a-46b2-b776-ad27276dc569", "Access denied (group).");
+ }
+ const messages = await MessagingMessages.findBy({
+ id: In(messageIds)
+ });
+ const reads = [];
+ for (const message of messages){
+ if (message.userId === userId) continue;
+ if (message.reads.includes(userId)) continue;
+ // Update document
+ await MessagingMessages.createQueryBuilder().update().set({
+ reads: ()=>`array_append("reads", '${joining.userId}')`
+ }).where("id = :id", {
+ id: message.id
+ }).execute();
+ reads.push(message.id);
+ }
+ // Publish event
+ publishGroupMessagingStream(groupId, "read", {
+ ids: reads,
+ userId: userId
+ });
+ publishMessagingIndexStream(userId, "read", reads);
+ if (!await Users.getHasUnreadMessagingMessage(userId)) {
+ // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
+ publishMainStream(userId, "readAllMessagingMessages");
+ pushNotification(userId, "readAllMessagingMessages", undefined);
+ } else {
+ // そのグループにおいて未読がなければイベント発行
+ const unreadExist = await MessagingMessages.createQueryBuilder("message").where("message.groupId = :groupId", {
+ groupId: groupId
+ }).andWhere("message.userId != :userId", {
+ userId: userId
+ }).andWhere("NOT (:userId = ANY(message.reads))", {
+ userId: userId
+ }).andWhere("message.createdAt > :joinedAt", {
+ joinedAt: joining.createdAt
+ }) // 自分が加入する前の会話については、未読扱いしない
+ .getOne().then((x)=>x != null);
+ if (!unreadExist) {
+ pushNotification(userId, "readAllMessagingMessagesOfARoom", {
+ groupId
+ });
+ }
+ }
+}
+export async function deliverReadActivity(user, recipient, messages) {
+ messages = toArray(messages).filter((x)=>x.uri);
+ const contents = messages.map((x)=>renderReadActivity(user, x));
+ if (contents.length > 1) {
+ const collection = orderedCollection(null, contents.length, undefined, undefined, contents);
+ deliver(user, renderActivity(collection), recipient.inbox);
+ } else {
+ for (const content of contents){
+ deliver(user, renderActivity(content), recipient.inbox);
+ }
+ }
+}
diff --git a/packages/backend/built/server/api/common/read-notification.js b/packages/backend/built/server/api/common/read-notification.js
new file mode 100644
index 0000000..52f47d1
--- /dev/null
+++ b/packages/backend/built/server/api/common/read-notification.js
@@ -0,0 +1,36 @@
+import { In } from "typeorm";
+import { publishMainStream } from "../../../services/stream.js";
+import { pushNotification } from "../../../services/push-notification.js";
+import { Notifications, Users } from "../../../models/index.js";
+export async function readNotification(userId, notificationIds) {
+ if (notificationIds.length === 0) return;
+ // Update documents
+ const result = await Notifications.update({
+ notifieeId: userId,
+ id: In(notificationIds),
+ isRead: false
+ }, {
+ isRead: true
+ });
+ if (result.affected === 0) return;
+ if (!await Users.getHasUnreadNotification(userId)) return postReadAllNotifications(userId);
+ else return postReadNotifications(userId, notificationIds);
+}
+export async function readNotificationByQuery(userId, query) {
+ const notificationIds = await Notifications.findBy({
+ ...query,
+ notifieeId: userId,
+ isRead: false
+ }).then((notifications)=>notifications.map((notification)=>notification.id));
+ return readNotification(userId, notificationIds);
+}
+function postReadAllNotifications(userId) {
+ publishMainStream(userId, "readAllNotifications");
+ return pushNotification(userId, "readAllNotifications", undefined);
+}
+function postReadNotifications(userId, notificationIds) {
+ publishMainStream(userId, "readNotifications", notificationIds);
+ return pushNotification(userId, "readNotifications", {
+ notificationIds
+ });
+}
diff --git a/packages/backend/built/server/api/common/read-promo.js b/packages/backend/built/server/api/common/read-promo.js
new file mode 100644
index 0000000..6889fc9
--- /dev/null
+++ b/packages/backend/built/server/api/common/read-promo.js
@@ -0,0 +1,23 @@
+import { PromoNotes, PromoReads } from "../../../models/index.js";
+import { genId } from "../../../misc/gen-id.js";
+export async function readPromo(note, promo, user) {
+ if (promo.expiresAt.getTime() <= Date.now() || promo.remainingCredits <= 0 || promo.userId === user.id || note.userId === user.id || user.isAdmin || user.isModerator || user.isBot) {
+ return;
+ }
+ const readDay = new Date().toISOString().slice(0, 10);
+ const result = await PromoReads.createQueryBuilder().insert().values({
+ id: genId(),
+ createdAt: new Date(),
+ noteId: note.id,
+ userId: user.id,
+ readDay
+ }).orIgnore().returning("id").execute();
+ if (result.raw.length === 0) {
+ return;
+ }
+ await PromoNotes.createQueryBuilder().update().set({
+ remainingCredits: ()=>`"remainingCredits" - 1`
+ }).where(`"noteId" = :noteId`, {
+ noteId: note.id
+ }).andWhere(`"remainingCredits" > 0`).execute();
+}
diff --git a/packages/backend/built/server/api/common/signin.js b/packages/backend/built/server/api/common/signin.js
new file mode 100644
index 0000000..94ee433
--- /dev/null
+++ b/packages/backend/built/server/api/common/signin.js
@@ -0,0 +1,37 @@
+import config from "../../../config/index.js";
+import { Signins } from "../../../models/index.js";
+import { genId } from "../../../misc/gen-id.js";
+import { publishMainStream } from "../../../services/stream.js";
+export default function(ctx, user, redirect = false) {
+ if (redirect) {
+ //#region Cookie
+ ctx.cookies.set("igi", user.token, {
+ path: "/",
+ // SEE: https://github.com/koajs/koa/issues/974
+ // When using a SSL proxy it should be configured to add the "X-Forwarded-Proto: https" header
+ secure: config.url.startsWith("https"),
+ httpOnly: false
+ });
+ //#endregion
+ ctx.redirect(config.url);
+ } else {
+ ctx.body = {
+ id: user.id,
+ i: user.token
+ };
+ ctx.status = 200;
+ }
+ (async ()=>{
+ // Append signin history
+ const record = await Signins.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ ip: ctx.ip,
+ headers: ctx.headers,
+ success: true
+ }).then((x)=>Signins.findOneByOrFail(x.identifiers[0]));
+ // Publish signin event
+ publishMainStream(user.id, "signin", await Signins.pack(record));
+ })();
+}
diff --git a/packages/backend/built/server/api/common/signup.js b/packages/backend/built/server/api/common/signup.js
new file mode 100644
index 0000000..0e190db
--- /dev/null
+++ b/packages/backend/built/server/api/common/signup.js
@@ -0,0 +1,128 @@
+import { generateKeyPair } from "node:crypto";
+import generateUserToken from "./generate-native-user-token.js";
+import { User } from "../../../models/entities/user.js";
+import { Users, UsedUsernames } from "../../../models/index.js";
+import { UserProfile } from "../../../models/entities/user-profile.js";
+import { IsNull } from "typeorm";
+import { genId } from "../../../misc/gen-id.js";
+import { toPunyNullable } from "../../../misc/convert-host.js";
+import { UserKeypair } from "../../../models/entities/user-keypair.js";
+import { usersChart } from "../../../services/chart/index.js";
+import { UsedUsername } from "../../../models/entities/used-username.js";
+import { db } from "../../../db/postgre.js";
+import config from "../../../config/index.js";
+import { hashPassword } from "../../../misc/password.js";
+import { fetchMeta } from "../../../misc/fetch-meta.js";
+import follow from "../../../services/following/create.js";
+export async function signup(opts) {
+ const { username, password, passwordHash, host } = opts;
+ let hash = passwordHash;
+ const userCount = await Users.countBy({
+ host: IsNull()
+ });
+ if (config.maxUserSignups != null && userCount > config.maxUserSignups) {
+ throw new Error("MAX_USERS_REACHED");
+ }
+ // Validate username
+ if (!Users.validateLocalUsername(username)) {
+ throw new Error("INVALID_USERNAME");
+ }
+ if (password != null && passwordHash == null) {
+ // Validate password
+ if (!Users.validatePassword(password)) {
+ throw new Error("INVALID_PASSWORD");
+ }
+ // Generate hash of password
+ hash = await hashPassword(password);
+ }
+ // Generate secret
+ const secret = generateUserToken();
+ // Check username duplication
+ if (await Users.findOneBy({
+ usernameLower: username.toLowerCase(),
+ host: IsNull()
+ })) {
+ throw new Error("DUPLICATED_USERNAME");
+ }
+ // Check deleted username duplication
+ if (await UsedUsernames.findOneBy({
+ username: username.toLowerCase()
+ })) {
+ throw new Error("USED_USERNAME");
+ }
+ const keyPair = await new Promise((res, rej)=>generateKeyPair("rsa", {
+ modulusLength: 4096,
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem"
+ },
+ privateKeyEncoding: {
+ type: "pkcs8",
+ format: "pem",
+ cipher: undefined,
+ passphrase: undefined
+ }
+ }, (err, publicKey, privateKey)=>err ? rej(err) : res([
+ publicKey,
+ privateKey
+ ])));
+ const exist = await Users.findOneBy({
+ usernameLower: username.toLowerCase(),
+ host: IsNull()
+ });
+ if (exist) throw new Error("The username is already in use");
+ // Prepare objects
+ const user = new User({
+ id: genId(),
+ createdAt: new Date(),
+ username: username,
+ usernameLower: username.toLowerCase(),
+ host: toPunyNullable(host),
+ token: secret,
+ isAdmin: await Users.countBy({
+ host: IsNull(),
+ isAdmin: true
+ }) === 0
+ });
+ const userKeypair = new UserKeypair({
+ publicKey: keyPair[0],
+ privateKey: keyPair[1],
+ userId: user.id
+ });
+ const userProfile = new UserProfile({
+ userId: user.id,
+ autoAcceptFollowed: true,
+ allowCalls: false,
+ password: hash
+ });
+ const usedUsername = new UsedUsername({
+ createdAt: new Date(),
+ username: username.toLowerCase()
+ });
+ // Save the objects atomically using a db transaction, note that we should never run any code in a transaction block directly
+ await db.transaction(async (transactionalEntityManager)=>{
+ await transactionalEntityManager.save(user);
+ await transactionalEntityManager.save(userKeypair);
+ await transactionalEntityManager.save(userProfile);
+ await transactionalEntityManager.save(usedUsername);
+ });
+ const account = await Users.findOneByOrFail({
+ id: user.id
+ });
+ const meta = await fetchMeta();
+ // If an autofollow account exists, follow it
+ if (meta.autofollowedAccount) {
+ const autofollowedAccount = await Users.findOneByOrFail({
+ usernameLower: meta.autofollowedAccount.toLowerCase(),
+ host: IsNull()
+ });
+ if (autofollowedAccount) {
+ await follow(account, autofollowedAccount);
+ }
+ }
+ usersChart.update(account, true);
+ return {
+ account,
+ secret
+ };
+}
diff --git a/packages/backend/built/server/api/compatibility.js b/packages/backend/built/server/api/compatibility.js
new file mode 100644
index 0000000..c4b02ff
--- /dev/null
+++ b/packages/backend/built/server/api/compatibility.js
@@ -0,0 +1,21 @@
+import * as cp___custom_emojis from "./endpoints/compatibility/custom-emojis.js";
+import * as ep___instance_peers from "./endpoints/compatibility/peers.js";
+const cps = [
+ [
+ "v1/custom_emojis",
+ cp___custom_emojis
+ ],
+ [
+ "v1/instance/peers",
+ ep___instance_peers
+ ]
+];
+const compatibility = cps.map(([name, cp])=>{
+ return {
+ name: name,
+ exec: cp.default,
+ meta: cp.meta || {},
+ params: cp.paramDef
+ };
+});
+export default compatibility;
diff --git a/packages/backend/built/server/api/define.js b/packages/backend/built/server/api/define.js
new file mode 100644
index 0000000..d4de54e
--- /dev/null
+++ b/packages/backend/built/server/api/define.js
@@ -0,0 +1,38 @@
+import * as fs from "node:fs";
+import Ajv from "ajv";
+import { ApiError } from "./error.js";
+const ajv = new Ajv({
+ useDefaults: true
+});
+ajv.addFormat("misskey:id", /^[a-zA-Z0-9]+$/);
+export default function(meta, paramDef, cb) {
+ const validate = ajv.compile(paramDef);
+ return (params, user, token, file, ip, headers)=>{
+ let cleanup = undefined;
+ if (meta.requireFile) {
+ cleanup = ()=>{
+ fs.unlink(file.path, ()=>{});
+ };
+ if (file == null) return Promise.reject(new ApiError({
+ message: "File required.",
+ code: "FILE_REQUIRED",
+ id: "4267801e-70d1-416a-b011-4ee502885d8b"
+ }));
+ }
+ const valid = validate(params);
+ if (!valid) {
+ if (file) cleanup();
+ const errors = validate.errors;
+ const err = new ApiError({
+ message: "Invalid param.",
+ code: "INVALID_PARAM",
+ id: "3d81ceae-475f-4600-b2a8-2bc116157532"
+ }, {
+ param: errors[0].schemaPath,
+ reason: errors[0].message
+ });
+ return Promise.reject(err);
+ }
+ return cb(params, user, token, file, cleanup, ip, headers);
+ };
+}
diff --git a/packages/backend/built/server/api/endpoints.js b/packages/backend/built/server/api/endpoints.js
new file mode 100644
index 0000000..4c98921
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints.js
@@ -0,0 +1,1975 @@
+import * as ep___admin_meta from "./endpoints/admin/meta.js";
+import * as ep___admin_abuseUserReports from "./endpoints/admin/abuse-user-reports.js";
+import * as ep___admin_accounts_create from "./endpoints/admin/accounts/create.js";
+import * as ep___admin_accounts_delete from "./endpoints/admin/accounts/delete.js";
+import * as ep___admin_accounts_hosted from "./endpoints/admin/accounts/hosted.js";
+import * as ep___admin_announcements_create from "./endpoints/admin/announcements/create.js";
+import * as ep___admin_announcements_delete from "./endpoints/admin/announcements/delete.js";
+import * as ep___admin_announcements_list from "./endpoints/admin/announcements/list.js";
+import * as ep___admin_announcements_update from "./endpoints/admin/announcements/update.js";
+import * as ep___admin_createBackup from "./endpoints/admin/create-backup.js";
+import * as ep___admin_deleteAllFilesOfAUser from "./endpoints/admin/delete-all-files-of-a-user.js";
+import * as ep___admin_drive_cleanRemoteFiles from "./endpoints/admin/drive/clean-remote-files.js";
+import * as ep___admin_drive_cleanup from "./endpoints/admin/drive/cleanup.js";
+import * as ep___admin_drive_files from "./endpoints/admin/drive/files.js";
+import * as ep___admin_drive_showFile from "./endpoints/admin/drive/show-file.js";
+import * as ep___admin_emoji_addAliasesBulk from "./endpoints/admin/emoji/add-aliases-bulk.js";
+import * as ep___admin_emoji_add from "./endpoints/admin/emoji/add.js";
+import * as ep___admin_emoji_copy from "./endpoints/admin/emoji/copy.js";
+import * as ep___admin_emoji_deleteBulk from "./endpoints/admin/emoji/delete-bulk.js";
+import * as ep___admin_emoji_delete from "./endpoints/admin/emoji/delete.js";
+import * as ep___admin_emoji_importZip from "./endpoints/admin/emoji/import-zip.js";
+import * as ep___admin_emoji_listRemote from "./endpoints/admin/emoji/list-remote.js";
+import * as ep___admin_emoji_list from "./endpoints/admin/emoji/list.js";
+import * as ep___admin_emoji_removeAliasesBulk from "./endpoints/admin/emoji/remove-aliases-bulk.js";
+import * as ep___admin_emoji_setAliasesBulk from "./endpoints/admin/emoji/set-aliases-bulk.js";
+import * as ep___admin_emoji_setCategoryBulk from "./endpoints/admin/emoji/set-category-bulk.js";
+import * as ep___admin_emoji_setLicenseBulk from "./endpoints/admin/emoji/set-license-bulk.js";
+import * as ep___admin_emoji_update from "./endpoints/admin/emoji/update.js";
+import * as ep___admin_federation_deleteAllFiles from "./endpoints/admin/federation/delete-all-files.js";
+import * as ep___admin_federation_refreshRemoteInstanceMetadata from "./endpoints/admin/federation/refresh-remote-instance-metadata.js";
+import * as ep___admin_federation_removeAllFollowing from "./endpoints/admin/federation/remove-all-following.js";
+import * as ep___admin_federation_updateInstance from "./endpoints/admin/federation/update-instance.js";
+import * as ep___admin_getIndexStats from "./endpoints/admin/get-index-stats.js";
+import * as ep___admin_getTableStats from "./endpoints/admin/get-table-stats.js";
+import * as ep___admin_getUserIps from "./endpoints/admin/get-user-ips.js";
+import * as ep___admin_invite from "./endpoints/admin/invite.js";
+import * as ep___admin_moderators_add from "./endpoints/admin/moderators/add.js";
+import * as ep___admin_moderators_remove from "./endpoints/admin/moderators/remove.js";
+import * as ep___admin_plans_create from "./endpoints/admin/plans/create.js";
+import * as ep___admin_plans_delete from "./endpoints/admin/plans/delete.js";
+import * as ep___admin_plans_list from "./endpoints/admin/plans/list.js";
+import * as ep___admin_plans_update from "./endpoints/admin/plans/update.js";
+import * as ep___admin_promo_create from "./endpoints/admin/promo/create.js";
+import * as ep___admin_promo_list from "./endpoints/admin/promo/list.js";
+import * as ep___admin_queue_clear from "./endpoints/admin/queue/clear.js";
+import * as ep___admin_queue_deliverDelayed from "./endpoints/admin/queue/deliver-delayed.js";
+import * as ep___admin_queue_inboxDelayed from "./endpoints/admin/queue/inbox-delayed.js";
+import * as ep___admin_queue_stats from "./endpoints/admin/queue/stats.js";
+import * as ep___admin_relays_add from "./endpoints/admin/relays/add.js";
+import * as ep___admin_relays_list from "./endpoints/admin/relays/list.js";
+import * as ep___admin_relays_remove from "./endpoints/admin/relays/remove.js";
+import * as ep___admin_resetPassword from "./endpoints/admin/reset-password.js";
+import * as ep___admin_resolveAbuseUserReport from "./endpoints/admin/resolve-abuse-user-report.js";
+import * as ep___admin_sendEmail from "./endpoints/admin/send-email.js";
+import * as ep___admin_sendModMail from "./endpoints/admin/send-mod-mail.js";
+import * as ep___admin_serverInfo from "./endpoints/admin/server-info.js";
+import * as ep___admin_setUserVerified from "./endpoints/admin/set-user-verified.js";
+import * as ep___admin_showModerationLogs from "./endpoints/admin/show-moderation-logs.js";
+import * as ep___admin_showUser from "./endpoints/admin/show-user.js";
+import * as ep___admin_showUsers from "./endpoints/admin/show-users.js";
+import * as ep___admin_silenceUser from "./endpoints/admin/silence-user.js";
+import * as ep___admin_suspendUser from "./endpoints/admin/suspend-user.js";
+import * as ep___admin_unsilenceUser from "./endpoints/admin/unsilence-user.js";
+import * as ep___admin_unsuspendUser from "./endpoints/admin/unsuspend-user.js";
+import * as ep___admin_updateMeta from "./endpoints/admin/update-meta.js";
+import * as ep___admin_vacuum from "./endpoints/admin/vacuum.js";
+import * as ep___admin_userPlans_add from "./endpoints/admin/user-plans/add.js";
+import * as ep___admin_userPlans_list from "./endpoints/admin/user-plans/list.js";
+import * as ep___admin_userPlans_remove from "./endpoints/admin/user-plans/remove.js";
+import * as ep___admin_verifiedBadgeRequests from "./endpoints/admin/verified-badge-requests.js";
+import * as ep___admin_resolveVerifiedBadgeRequest from "./endpoints/admin/resolve-verified-badge-request.js";
+import * as ep___admin_deleteAccount from "./endpoints/admin/delete-account.js";
+import * as ep___admin_updateUserNote from "./endpoints/admin/update-user-note.js";
+import * as ep___announcements from "./endpoints/announcements.js";
+import * as ep___antennas_create from "./endpoints/antennas/create.js";
+import * as ep___antennas_delete from "./endpoints/antennas/delete.js";
+import * as ep___antennas_list from "./endpoints/antennas/list.js";
+import * as ep___antennas_markRead from "./endpoints/antennas/markread.js";
+import * as ep___antennas_notes from "./endpoints/antennas/notes.js";
+import * as ep___antennas_show from "./endpoints/antennas/show.js";
+import * as ep___antennas_update from "./endpoints/antennas/update.js";
+import * as ep___ap_get from "./endpoints/ap/get.js";
+import * as ep___ap_show from "./endpoints/ap/show.js";
+import * as ep___app_create from "./endpoints/app/create.js";
+import * as ep___app_show from "./endpoints/app/show.js";
+import * as ep___auth_accept from "./endpoints/auth/accept.js";
+import * as ep___auth_session_generate from "./endpoints/auth/session/generate.js";
+import * as ep___auth_session_show from "./endpoints/auth/session/show.js";
+import * as ep___auth_session_userkey from "./endpoints/auth/session/userkey.js";
+import * as ep___blocking_create from "./endpoints/blocking/create.js";
+import * as ep___blocking_delete from "./endpoints/blocking/delete.js";
+import * as ep___blocking_list from "./endpoints/blocking/list.js";
+import * as ep___call_blocking_create from "./endpoints/call-blocking/create.js";
+import * as ep___call_blocking_delete from "./endpoints/call-blocking/delete.js";
+import * as ep___call_blocking_list from "./endpoints/call-blocking/list.js";
+import * as ep___channels_create from "./endpoints/channels/create.js";
+import * as ep___channels_featured from "./endpoints/channels/featured.js";
+import * as ep___channels_follow from "./endpoints/channels/follow.js";
+import * as ep___channels_followed from "./endpoints/channels/followed.js";
+import * as ep___channels_owned from "./endpoints/channels/owned.js";
+import * as ep___channels_search from "./endpoints/channels/search.js";
+import * as ep___channels_show from "./endpoints/channels/show.js";
+import * as ep___channels_timeline from "./endpoints/channels/timeline.js";
+import * as ep___channels_unfollow from "./endpoints/channels/unfollow.js";
+import * as ep___channels_update from "./endpoints/channels/update.js";
+import * as ep___charts_activeUsers from "./endpoints/charts/active-users.js";
+import * as ep___charts_apRequest from "./endpoints/charts/ap-request.js";
+import * as ep___charts_drive from "./endpoints/charts/drive.js";
+import * as ep___charts_federation from "./endpoints/charts/federation.js";
+import * as ep___charts_hashtag from "./endpoints/charts/hashtag.js";
+import * as ep___charts_instance from "./endpoints/charts/instance.js";
+import * as ep___charts_notes from "./endpoints/charts/notes.js";
+import * as ep___charts_user_drive from "./endpoints/charts/user/drive.js";
+import * as ep___charts_user_following from "./endpoints/charts/user/following.js";
+import * as ep___charts_user_notes from "./endpoints/charts/user/notes.js";
+import * as ep___charts_user_reactions from "./endpoints/charts/user/reactions.js";
+import * as ep___charts_users from "./endpoints/charts/users.js";
+import * as ep___clips_addNote from "./endpoints/clips/add-note.js";
+import * as ep___clips_removeNote from "./endpoints/clips/remove-note.js";
+import * as ep___clips_create from "./endpoints/clips/create.js";
+import * as ep___clips_delete from "./endpoints/clips/delete.js";
+import * as ep___clips_list from "./endpoints/clips/list.js";
+import * as ep___clips_notes from "./endpoints/clips/notes.js";
+import * as ep___clips_show from "./endpoints/clips/show.js";
+import * as ep___clips_update from "./endpoints/clips/update.js";
+import * as ep___drive from "./endpoints/drive.js";
+import * as ep___drive_files from "./endpoints/drive/files.js";
+import * as ep___drive_files_attachedNotes from "./endpoints/drive/files/attached-notes.js";
+import * as ep___drive_files_checkExistence from "./endpoints/drive/files/check-existence.js";
+import * as ep___drive_files_captionImage from "./endpoints/drive/files/caption-image.js";
+import * as ep___drive_files_create from "./endpoints/drive/files/create.js";
+import * as ep___drive_files_delete from "./endpoints/drive/files/delete.js";
+import * as ep___drive_files_findByHash from "./endpoints/drive/files/find-by-hash.js";
+import * as ep___drive_files_find from "./endpoints/drive/files/find.js";
+import * as ep___drive_files_show from "./endpoints/drive/files/show.js";
+import * as ep___drive_files_update from "./endpoints/drive/files/update.js";
+import * as ep___drive_files_uploadFromUrl from "./endpoints/drive/files/upload-from-url.js";
+import * as ep___drive_folders from "./endpoints/drive/folders.js";
+import * as ep___drive_folders_create from "./endpoints/drive/folders/create.js";
+import * as ep___drive_folders_delete from "./endpoints/drive/folders/delete.js";
+import * as ep___drive_folders_find from "./endpoints/drive/folders/find.js";
+import * as ep___drive_folders_show from "./endpoints/drive/folders/show.js";
+import * as ep___drive_folders_update from "./endpoints/drive/folders/update.js";
+import * as ep___drive_stream from "./endpoints/drive/stream.js";
+import * as ep___emailAddress_available from "./endpoints/email-address/available.js";
+import * as ep___emoji from "./endpoints/emoji.js";
+import * as ep___endpoint from "./endpoints/endpoint.js";
+import * as ep___endpoints from "./endpoints/endpoints.js";
+import * as ep___exportCustomEmojis from "./endpoints/export-custom-emojis.js";
+import * as ep___federation_followers from "./endpoints/federation/followers.js";
+import * as ep___federation_following from "./endpoints/federation/following.js";
+import * as ep___federation_instances from "./endpoints/federation/instances.js";
+import * as ep___federation_showInstance from "./endpoints/federation/show-instance.js";
+import * as ep___federation_updateRemoteUser from "./endpoints/federation/update-remote-user.js";
+import * as ep___federation_users from "./endpoints/federation/users.js";
+import * as ep___federation_stats from "./endpoints/federation/stats.js";
+import * as ep___following_create from "./endpoints/following/create.js";
+import * as ep___following_delete from "./endpoints/following/delete.js";
+import * as ep___following_invalidate from "./endpoints/following/invalidate.js";
+import * as ep___following_requests_accept from "./endpoints/following/requests/accept.js";
+import * as ep___following_requests_cancel from "./endpoints/following/requests/cancel.js";
+import * as ep___following_requests_list from "./endpoints/following/requests/list.js";
+import * as ep___following_requests_reject from "./endpoints/following/requests/reject.js";
+import * as ep___gallery_featured from "./endpoints/gallery/featured.js";
+import * as ep___gallery_popular from "./endpoints/gallery/popular.js";
+import * as ep___gallery_posts from "./endpoints/gallery/posts.js";
+import * as ep___gallery_posts_create from "./endpoints/gallery/posts/create.js";
+import * as ep___gallery_posts_delete from "./endpoints/gallery/posts/delete.js";
+import * as ep___gallery_posts_like from "./endpoints/gallery/posts/like.js";
+import * as ep___gallery_posts_show from "./endpoints/gallery/posts/show.js";
+import * as ep___gallery_posts_unlike from "./endpoints/gallery/posts/unlike.js";
+import * as ep___gallery_posts_update from "./endpoints/gallery/posts/update.js";
+import * as ep___getOnlineUsersCount from "./endpoints/get-online-users-count.js";
+import * as ep___hashtags_list from "./endpoints/hashtags/list.js";
+import * as ep___hashtags_search from "./endpoints/hashtags/search.js";
+import * as ep___hashtags_show from "./endpoints/hashtags/show.js";
+import * as ep___hashtags_trend from "./endpoints/hashtags/trend.js";
+import * as ep___hashtags_users from "./endpoints/hashtags/users.js";
+import * as ep___i from "./endpoints/i.js";
+import * as ep___i_2fa_done from "./endpoints/i/2fa/done.js";
+import * as ep___i_2fa_keyDone from "./endpoints/i/2fa/key-done.js";
+import * as ep___i_2fa_passwordLess from "./endpoints/i/2fa/password-less.js";
+import * as ep___i_2fa_registerKey from "./endpoints/i/2fa/register-key.js";
+import * as ep___i_2fa_register from "./endpoints/i/2fa/register.js";
+import * as ep___i_2fa_updateKey from "./endpoints/i/2fa/update-key.js";
+import * as ep___i_2fa_removeKey from "./endpoints/i/2fa/remove-key.js";
+import * as ep___i_2fa_unregister from "./endpoints/i/2fa/unregister.js";
+import * as ep___i_apps from "./endpoints/i/apps.js";
+import * as ep___i_authorizedApps from "./endpoints/i/authorized-apps.js";
+import * as ep___i_changePassword from "./endpoints/i/change-password.js";
+import * as ep___i_deleteAccount from "./endpoints/i/delete-account.js";
+import * as ep___i_exportBlocking from "./endpoints/i/export-blocking.js";
+import * as ep___i_exportFollowing from "./endpoints/i/export-following.js";
+import * as ep___i_exportMute from "./endpoints/i/export-mute.js";
+import * as ep___i_exportNotes from "./endpoints/i/export-notes.js";
+import * as ep___i_importPosts from "./endpoints/i/import-posts.js";
+import * as ep___i_exportUserLists from "./endpoints/i/export-user-lists.js";
+import * as ep___i_favorites from "./endpoints/i/favorites.js";
+import * as ep___i_gallery_likes from "./endpoints/i/gallery/likes.js";
+import * as ep___i_gallery_posts from "./endpoints/i/gallery/posts.js";
+import * as ep___i_importBlocking from "./endpoints/i/import-blocking.js";
+import * as ep___i_importFollowing from "./endpoints/i/import-following.js";
+import * as ep___i_importMuting from "./endpoints/i/import-muting.js";
+import * as ep___i_importUserLists from "./endpoints/i/import-user-lists.js";
+import * as ep___i_notifications from "./endpoints/i/notifications.js";
+import * as ep___i_pageLikes from "./endpoints/i/page-likes.js";
+import * as ep___i_pages from "./endpoints/i/pages.js";
+import * as ep___i_pin from "./endpoints/i/pin.js";
+import * as ep___i_readAllMessagingMessages from "./endpoints/i/read-all-messaging-messages.js";
+import * as ep___i_readAllUnreadNotes from "./endpoints/i/read-all-unread-notes.js";
+import * as ep___i_readAnnouncement from "./endpoints/i/read-announcement.js";
+import * as ep___i_requestVerifiedBadge from "./endpoints/i/request-verified-badge.js";
+import * as ep___i_regenerateToken from "./endpoints/i/regenerate-token.js";
+import * as ep___i_registry_getAll from "./endpoints/i/registry/get-all.js";
+import * as ep___i_registry_getDetail from "./endpoints/i/registry/get-detail.js";
+import * as ep___i_registry_get from "./endpoints/i/registry/get.js";
+import * as ep___i_registry_keysWithType from "./endpoints/i/registry/keys-with-type.js";
+import * as ep___i_registry_keys from "./endpoints/i/registry/keys.js";
+import * as ep___i_registry_remove from "./endpoints/i/registry/remove.js";
+import * as ep___i_registry_scopes from "./endpoints/i/registry/scopes.js";
+import * as ep___i_registry_set from "./endpoints/i/registry/set.js";
+import * as ep___i_revokeToken from "./endpoints/i/revoke-token.js";
+import * as ep___i_signinHistory from "./endpoints/i/signin-history.js";
+import * as ep___i_unpin from "./endpoints/i/unpin.js";
+import * as ep___i_updateEmail from "./endpoints/i/update-email.js";
+import * as ep___i_update from "./endpoints/i/update.js";
+import * as ep___i_userEmojis_create from "./endpoints/i/user-emojis/create.js";
+import * as ep___i_userEmojis_delete from "./endpoints/i/user-emojis/delete.js";
+import * as ep___i_userEmojis_list from "./endpoints/i/user-emojis/list.js";
+import * as ep___i_userGroupInvites from "./endpoints/i/user-group-invites.js";
+import * as ep___i_webhooks_create from "./endpoints/i/webhooks/create.js";
+import * as ep___i_webhooks_show from "./endpoints/i/webhooks/show.js";
+import * as ep___i_webhooks_list from "./endpoints/i/webhooks/list.js";
+import * as ep___i_webhooks_update from "./endpoints/i/webhooks/update.js";
+import * as ep___i_webhooks_delete from "./endpoints/i/webhooks/delete.js";
+import * as ep___messaging_history from "./endpoints/messaging/history.js";
+import * as ep___messaging_messages from "./endpoints/messaging/messages.js";
+import * as ep___messaging_messages_create from "./endpoints/messaging/messages/create.js";
+import * as ep___messaging_messages_delete from "./endpoints/messaging/messages/delete.js";
+import * as ep___messaging_messages_read from "./endpoints/messaging/messages/read.js";
+import * as ep___meta from "./endpoints/meta.js";
+import * as ep___sounds from "./endpoints/get-sounds.js";
+import * as ep___miauth_genToken from "./endpoints/miauth/gen-token.js";
+import * as ep___mute_create from "./endpoints/mute/create.js";
+import * as ep___mute_delete from "./endpoints/mute/delete.js";
+import * as ep___mute_list from "./endpoints/mute/list.js";
+import * as ep___renote_mute_create from "./endpoints/renote-mute/create.js";
+import * as ep___renote_mute_delete from "./endpoints/renote-mute/delete.js";
+import * as ep___renote_mute_list from "./endpoints/renote-mute/list.js";
+import * as ep___my_apps from "./endpoints/my/apps.js";
+import * as ep___notes from "./endpoints/notes.js";
+import * as ep___notes_children from "./endpoints/notes/children.js";
+import * as ep___notes_clips from "./endpoints/notes/clips.js";
+import * as ep___notes_conversation from "./endpoints/notes/conversation.js";
+import * as ep___notes_create from "./endpoints/notes/create.js";
+import * as ep___notes_delete from "./endpoints/notes/delete.js";
+import * as ep___notes_edit from "./endpoints/notes/edit.js";
+import * as ep___notes_favorites_create from "./endpoints/notes/favorites/create.js";
+import * as ep___notes_favorites_delete from "./endpoints/notes/favorites/delete.js";
+import * as ep___notes_featured from "./endpoints/notes/featured.js";
+import * as ep___notes_globalTimeline from "./endpoints/notes/global-timeline.js";
+import * as ep___notes_hybridTimeline from "./endpoints/notes/hybrid-timeline.js";
+import * as ep___notes_localTimeline from "./endpoints/notes/local-timeline.js";
+import * as ep___notes_recommendedTimeline from "./endpoints/notes/recommended-timeline.js";
+import * as ep___notes_mentions from "./endpoints/notes/mentions.js";
+import * as ep___notes_polls_recommendation from "./endpoints/notes/polls/recommendation.js";
+import * as ep___notes_polls_vote from "./endpoints/notes/polls/vote.js";
+import * as ep___notes_reactions from "./endpoints/notes/reactions.js";
+import * as ep___notes_reactions_create from "./endpoints/notes/reactions/create.js";
+import * as ep___notes_reactions_delete from "./endpoints/notes/reactions/delete.js";
+import * as ep___notes_renotes from "./endpoints/notes/renotes.js";
+import * as ep___notes_replies from "./endpoints/notes/replies.js";
+import * as ep___notes_incrementServiceView from "./endpoints/notes/increment-service-view.js";
+import * as ep___notes_lua4frozenSearch from "./endpoints/notes/lua4frozen-search.js";
+import * as ep___notes_mediaServiceSearch from "./endpoints/notes/media-service-search.js";
+import * as ep___notes_karaokeServiceSearch from "./endpoints/notes/karaoke-service-search.js";
+import * as ep___memoriet_create from "./endpoints/memoriet/create.js";
+import * as ep___memoriet_deletedList from "./endpoints/memoriet/deleted-list.js";
+import * as ep___memoriet_list from "./endpoints/memoriet/list.js";
+import * as ep___memoriet_repost from "./endpoints/memoriet/repost.js";
+import * as ep___memoriet_viewers from "./endpoints/memoriet/viewers.js";
+import * as ep___notes_searchByTag from "./endpoints/notes/search-by-tag.js";
+import * as ep___notes_search from "./endpoints/notes/search.js";
+import * as ep___notes_show from "./endpoints/notes/show.js";
+import * as ep___notes_state from "./endpoints/notes/state.js";
+import * as ep___notes_threadMuting_create from "./endpoints/notes/thread-muting/create.js";
+import * as ep___notes_threadMuting_delete from "./endpoints/notes/thread-muting/delete.js";
+import * as ep___notes_timeline from "./endpoints/notes/timeline.js";
+import * as ep___notes_translate from "./endpoints/notes/translate.js";
+import * as ep___notes_unrenote from "./endpoints/notes/unrenote.js";
+import * as ep___notes_userListTimeline from "./endpoints/notes/user-list-timeline.js";
+import * as ep___notes_watching_create from "./endpoints/notes/watching/create.js";
+import * as ep___notes_watching_delete from "./endpoints/notes/watching/delete.js";
+import * as ep___notifications_create from "./endpoints/notifications/create.js";
+import * as ep___notifications_markAllAsRead from "./endpoints/notifications/mark-all-as-read.js";
+import * as ep___notifications_read from "./endpoints/notifications/read.js";
+import * as ep___pagePush from "./endpoints/page-push.js";
+import * as ep___pages_create from "./endpoints/pages/create.js";
+import * as ep___pages_delete from "./endpoints/pages/delete.js";
+import * as ep___pages_featured from "./endpoints/pages/featured.js";
+import * as ep___pages_like from "./endpoints/pages/like.js";
+import * as ep___pages_show from "./endpoints/pages/show.js";
+import * as ep___pages_unlike from "./endpoints/pages/unlike.js";
+import * as ep___pages_update from "./endpoints/pages/update.js";
+import * as ep___ping from "./endpoints/ping.js";
+import * as ep___recommendedInstances from "./endpoints/recommended-instances.js";
+import * as ep___pinnedUsers from "./endpoints/pinned-users.js";
+import * as ep___customMOTD from "./endpoints/custom-motd.js";
+import * as ep___customSplashIcons from "./endpoints/custom-splash-icons.js";
+import * as ep___latestVersion from "./endpoints/latest-version.js";
+import * as ep___release from "./endpoints/release.js";
+import * as ep___reversi_cancelMatch from "./endpoints/reversi/cancel-match.js";
+import * as ep___reversi_games from "./endpoints/reversi/games.js";
+import * as ep___reversi_invitations from "./endpoints/reversi/invitations.js";
+import * as ep___reversi_match from "./endpoints/reversi/match.js";
+import * as ep___reversi_showGame from "./endpoints/reversi/show-game.js";
+import * as ep___reversi_surrender from "./endpoints/reversi/surrender.js";
+import * as ep___reversi_verify from "./endpoints/reversi/verify.js";
+import * as ep___shogi_cancelMatch from "./endpoints/shogi/cancel-match.js";
+import * as ep___shogi_games from "./endpoints/shogi/games.js";
+import * as ep___shogi_invitations from "./endpoints/shogi/invitations.js";
+import * as ep___shogi_match from "./endpoints/shogi/match.js";
+import * as ep___shogi_showGame from "./endpoints/shogi/show-game.js";
+import * as ep___shogi_surrender from "./endpoints/shogi/surrender.js";
+import * as ep___promo_read from "./endpoints/promo/read.js";
+import * as ep___promo_show from "./endpoints/promo/show.js";
+import * as ep___requestResetPassword from "./endpoints/request-reset-password.js";
+import * as ep___resetDb from "./endpoints/reset-db.js";
+import * as ep___resetPassword from "./endpoints/reset-password.js";
+import * as ep___serverInfo from "./endpoints/server-info.js";
+import * as ep___stats from "./endpoints/stats.js";
+import * as ep___sw_show_registration from "./endpoints/sw/show-registration.js";
+import * as ep___sw_update_registration from "./endpoints/sw/update-registration.js";
+import * as ep___sw_register from "./endpoints/sw/register.js";
+import * as ep___sw_unregister from "./endpoints/sw/unregister.js";
+import * as ep___test from "./endpoints/test.js";
+import * as ep___username_available from "./endpoints/username/available.js";
+import * as ep___users from "./endpoints/users.js";
+import * as ep___users_clips from "./endpoints/users/clips.js";
+import * as ep___users_followers from "./endpoints/users/followers.js";
+import * as ep___users_following from "./endpoints/users/following.js";
+import * as ep___users_gallery_posts from "./endpoints/users/gallery/posts.js";
+import * as ep___users_getFrequentlyRepliedUsers from "./endpoints/users/get-frequently-replied-users.js";
+import * as ep___users_groups_create from "./endpoints/users/groups/create.js";
+import * as ep___users_groups_delete from "./endpoints/users/groups/delete.js";
+import * as ep___users_groups_invitations_accept from "./endpoints/users/groups/invitations/accept.js";
+import * as ep___users_groups_invitations_reject from "./endpoints/users/groups/invitations/reject.js";
+import * as ep___users_groups_invite from "./endpoints/users/groups/invite.js";
+import * as ep___users_groups_joined from "./endpoints/users/groups/joined.js";
+import * as ep___users_groups_leave from "./endpoints/users/groups/leave.js";
+import * as ep___users_groups_owned from "./endpoints/users/groups/owned.js";
+import * as ep___users_groups_pull from "./endpoints/users/groups/pull.js";
+import * as ep___users_groups_emojis_create from "./endpoints/users/groups/emojis/create.js";
+import * as ep___users_groups_emojis_delete from "./endpoints/users/groups/emojis/delete.js";
+import * as ep___users_groups_emojis_list from "./endpoints/users/groups/emojis/list.js";
+import * as ep___users_groups_show from "./endpoints/users/groups/show.js";
+import * as ep___users_groups_showByUsername from "./endpoints/users/groups/show-by-username.js";
+import * as ep___users_groups_transfer from "./endpoints/users/groups/transfer.js";
+import * as ep___users_groups_update from "./endpoints/users/groups/update.js";
+import * as ep___users_lists_create from "./endpoints/users/lists/create.js";
+import * as ep___users_lists_delete from "./endpoints/users/lists/delete.js";
+import * as ep___users_lists_delete_all from "./endpoints/users/lists/delete-all.js";
+import * as ep___users_lists_list from "./endpoints/users/lists/list.js";
+import * as ep___users_lists_pull from "./endpoints/users/lists/pull.js";
+import * as ep___users_lists_push from "./endpoints/users/lists/push.js";
+import * as ep___users_lists_show from "./endpoints/users/lists/show.js";
+import * as ep___users_lists_update from "./endpoints/users/lists/update.js";
+import * as ep___users_notes from "./endpoints/users/notes.js";
+import * as ep___users_pages from "./endpoints/users/pages.js";
+import * as ep___users_reactions from "./endpoints/users/reactions.js";
+import * as ep___users_recommendation from "./endpoints/users/recommendation.js";
+import * as ep___users_relation from "./endpoints/users/relation.js";
+import * as ep___users_reportAbuse from "./endpoints/users/report-abuse.js";
+import * as ep___users_searchByUsernameAndHost from "./endpoints/users/search-by-username-and-host.js";
+import * as ep___users_search from "./endpoints/users/search.js";
+import * as ep___users_show from "./endpoints/users/show.js";
+import * as ep___users_stats from "./endpoints/users/stats.js";
+import * as ep___fetchRss from "./endpoints/fetch-rss.js";
+import * as ep___admin_driveCapOverride from "./endpoints/admin/drive-capacity-override.js";
+import * as ep___bites_create from "./endpoints/bites/create.js";
+import * as ep___bites_show from "./endpoints/bites/show.js";
+//Iceshrimp Move
+import * as ep___i_move from "./endpoints/i/move.js";
+import * as ep___i_known_as from "./endpoints/i/known-as.js";
+const eps = [
+ [
+ "admin/meta",
+ ep___admin_meta
+ ],
+ [
+ "admin/abuse-user-reports",
+ ep___admin_abuseUserReports
+ ],
+ [
+ "admin/accounts/create",
+ ep___admin_accounts_create
+ ],
+ [
+ "admin/accounts/delete",
+ ep___admin_accounts_delete
+ ],
+ [
+ "admin/accounts/hosted",
+ ep___admin_accounts_hosted
+ ],
+ [
+ "admin/announcements/create",
+ ep___admin_announcements_create
+ ],
+ [
+ "admin/announcements/delete",
+ ep___admin_announcements_delete
+ ],
+ [
+ "admin/announcements/list",
+ ep___admin_announcements_list
+ ],
+ [
+ "admin/announcements/update",
+ ep___admin_announcements_update
+ ],
+ [
+ "admin/create-backup",
+ ep___admin_createBackup
+ ],
+ [
+ "admin/delete-all-files-of-a-user",
+ ep___admin_deleteAllFilesOfAUser
+ ],
+ [
+ "admin/drive/clean-remote-files",
+ ep___admin_drive_cleanRemoteFiles
+ ],
+ [
+ "admin/drive/cleanup",
+ ep___admin_drive_cleanup
+ ],
+ [
+ "admin/drive/files",
+ ep___admin_drive_files
+ ],
+ [
+ "admin/drive/show-file",
+ ep___admin_drive_showFile
+ ],
+ [
+ "admin/emoji/add-aliases-bulk",
+ ep___admin_emoji_addAliasesBulk
+ ],
+ [
+ "admin/emoji/add",
+ ep___admin_emoji_add
+ ],
+ [
+ "admin/emoji/copy",
+ ep___admin_emoji_copy
+ ],
+ [
+ "admin/emoji/delete-bulk",
+ ep___admin_emoji_deleteBulk
+ ],
+ [
+ "admin/emoji/delete",
+ ep___admin_emoji_delete
+ ],
+ [
+ "admin/emoji/import-zip",
+ ep___admin_emoji_importZip
+ ],
+ [
+ "admin/emoji/list-remote",
+ ep___admin_emoji_listRemote
+ ],
+ [
+ "admin/emoji/list",
+ ep___admin_emoji_list
+ ],
+ [
+ "admin/emoji/remove-aliases-bulk",
+ ep___admin_emoji_removeAliasesBulk
+ ],
+ [
+ "admin/emoji/set-aliases-bulk",
+ ep___admin_emoji_setAliasesBulk
+ ],
+ [
+ "admin/emoji/set-category-bulk",
+ ep___admin_emoji_setCategoryBulk
+ ],
+ [
+ "admin/emoji/set-license-bulk",
+ ep___admin_emoji_setLicenseBulk
+ ],
+ [
+ "admin/emoji/update",
+ ep___admin_emoji_update
+ ],
+ [
+ "admin/federation/delete-all-files",
+ ep___admin_federation_deleteAllFiles
+ ],
+ [
+ "admin/federation/refresh-remote-instance-metadata",
+ ep___admin_federation_refreshRemoteInstanceMetadata
+ ],
+ [
+ "admin/federation/remove-all-following",
+ ep___admin_federation_removeAllFollowing
+ ],
+ [
+ "admin/federation/update-instance",
+ ep___admin_federation_updateInstance
+ ],
+ [
+ "admin/get-index-stats",
+ ep___admin_getIndexStats
+ ],
+ [
+ "admin/get-table-stats",
+ ep___admin_getTableStats
+ ],
+ [
+ "admin/get-user-ips",
+ ep___admin_getUserIps
+ ],
+ [
+ "admin/invite",
+ ep___admin_invite
+ ],
+ [
+ "admin/moderators/add",
+ ep___admin_moderators_add
+ ],
+ [
+ "admin/moderators/remove",
+ ep___admin_moderators_remove
+ ],
+ [
+ "admin/plans/create",
+ ep___admin_plans_create
+ ],
+ [
+ "admin/plans/delete",
+ ep___admin_plans_delete
+ ],
+ [
+ "admin/plans/list",
+ ep___admin_plans_list
+ ],
+ [
+ "admin/plans/update",
+ ep___admin_plans_update
+ ],
+ [
+ "admin/promo/create",
+ ep___admin_promo_create
+ ],
+ [
+ "admin/promo/list",
+ ep___admin_promo_list
+ ],
+ [
+ "admin/queue/clear",
+ ep___admin_queue_clear
+ ],
+ [
+ "admin/queue/deliver-delayed",
+ ep___admin_queue_deliverDelayed
+ ],
+ [
+ "admin/queue/inbox-delayed",
+ ep___admin_queue_inboxDelayed
+ ],
+ [
+ "admin/queue/stats",
+ ep___admin_queue_stats
+ ],
+ [
+ "admin/relays/add",
+ ep___admin_relays_add
+ ],
+ [
+ "admin/relays/list",
+ ep___admin_relays_list
+ ],
+ [
+ "admin/relays/remove",
+ ep___admin_relays_remove
+ ],
+ [
+ "admin/reset-password",
+ ep___admin_resetPassword
+ ],
+ [
+ "admin/resolve-abuse-user-report",
+ ep___admin_resolveAbuseUserReport
+ ],
+ [
+ "admin/send-email",
+ ep___admin_sendEmail
+ ],
+ [
+ "admin/send-mod-mail",
+ ep___admin_sendModMail
+ ],
+ [
+ "admin/server-info",
+ ep___admin_serverInfo
+ ],
+ [
+ "admin/set-user-verified",
+ ep___admin_setUserVerified
+ ],
+ [
+ "admin/show-moderation-logs",
+ ep___admin_showModerationLogs
+ ],
+ [
+ "admin/show-user",
+ ep___admin_showUser
+ ],
+ [
+ "admin/show-users",
+ ep___admin_showUsers
+ ],
+ [
+ "admin/silence-user",
+ ep___admin_silenceUser
+ ],
+ [
+ "admin/suspend-user",
+ ep___admin_suspendUser
+ ],
+ [
+ "admin/unsilence-user",
+ ep___admin_unsilenceUser
+ ],
+ [
+ "admin/unsuspend-user",
+ ep___admin_unsuspendUser
+ ],
+ [
+ "admin/update-meta",
+ ep___admin_updateMeta
+ ],
+ [
+ "admin/vacuum",
+ ep___admin_vacuum
+ ],
+ [
+ "admin/user-plans/add",
+ ep___admin_userPlans_add
+ ],
+ [
+ "admin/user-plans/list",
+ ep___admin_userPlans_list
+ ],
+ [
+ "admin/user-plans/remove",
+ ep___admin_userPlans_remove
+ ],
+ [
+ "admin/verified-badge-requests",
+ ep___admin_verifiedBadgeRequests
+ ],
+ [
+ "admin/resolve-verified-badge-request",
+ ep___admin_resolveVerifiedBadgeRequest
+ ],
+ [
+ "admin/delete-account",
+ ep___admin_deleteAccount
+ ],
+ [
+ "admin/update-user-note",
+ ep___admin_updateUserNote
+ ],
+ [
+ "announcements",
+ ep___announcements
+ ],
+ [
+ "antennas/create",
+ ep___antennas_create
+ ],
+ [
+ "antennas/delete",
+ ep___antennas_delete
+ ],
+ [
+ "antennas/list",
+ ep___antennas_list
+ ],
+ [
+ "antennas/mark-read",
+ ep___antennas_markRead
+ ],
+ [
+ "antennas/notes",
+ ep___antennas_notes
+ ],
+ [
+ "antennas/show",
+ ep___antennas_show
+ ],
+ [
+ "antennas/update",
+ ep___antennas_update
+ ],
+ [
+ "ap/get",
+ ep___ap_get
+ ],
+ [
+ "ap/show",
+ ep___ap_show
+ ],
+ [
+ "app/create",
+ ep___app_create
+ ],
+ [
+ "app/show",
+ ep___app_show
+ ],
+ [
+ "auth/accept",
+ ep___auth_accept
+ ],
+ [
+ "auth/session/generate",
+ ep___auth_session_generate
+ ],
+ [
+ "auth/session/show",
+ ep___auth_session_show
+ ],
+ [
+ "auth/session/userkey",
+ ep___auth_session_userkey
+ ],
+ [
+ "blocking/create",
+ ep___blocking_create
+ ],
+ [
+ "blocking/delete",
+ ep___blocking_delete
+ ],
+ [
+ "blocking/list",
+ ep___blocking_list
+ ],
+ [
+ "call-blocking/create",
+ ep___call_blocking_create
+ ],
+ [
+ "call-blocking/delete",
+ ep___call_blocking_delete
+ ],
+ [
+ "call-blocking/list",
+ ep___call_blocking_list
+ ],
+ [
+ "channels/create",
+ ep___channels_create
+ ],
+ [
+ "channels/featured",
+ ep___channels_featured
+ ],
+ [
+ "channels/follow",
+ ep___channels_follow
+ ],
+ [
+ "channels/followed",
+ ep___channels_followed
+ ],
+ [
+ "channels/owned",
+ ep___channels_owned
+ ],
+ [
+ "channels/search",
+ ep___channels_search
+ ],
+ [
+ "channels/show",
+ ep___channels_show
+ ],
+ [
+ "channels/timeline",
+ ep___channels_timeline
+ ],
+ [
+ "channels/unfollow",
+ ep___channels_unfollow
+ ],
+ [
+ "channels/update",
+ ep___channels_update
+ ],
+ [
+ "charts/active-users",
+ ep___charts_activeUsers
+ ],
+ [
+ "charts/ap-request",
+ ep___charts_apRequest
+ ],
+ [
+ "charts/drive",
+ ep___charts_drive
+ ],
+ [
+ "charts/federation",
+ ep___charts_federation
+ ],
+ [
+ "charts/hashtag",
+ ep___charts_hashtag
+ ],
+ [
+ "charts/instance",
+ ep___charts_instance
+ ],
+ [
+ "charts/notes",
+ ep___charts_notes
+ ],
+ [
+ "charts/user/drive",
+ ep___charts_user_drive
+ ],
+ [
+ "charts/user/following",
+ ep___charts_user_following
+ ],
+ [
+ "charts/user/notes",
+ ep___charts_user_notes
+ ],
+ [
+ "charts/user/reactions",
+ ep___charts_user_reactions
+ ],
+ [
+ "charts/users",
+ ep___charts_users
+ ],
+ [
+ "clips/add-note",
+ ep___clips_addNote
+ ],
+ [
+ "clips/remove-note",
+ ep___clips_removeNote
+ ],
+ [
+ "clips/create",
+ ep___clips_create
+ ],
+ [
+ "clips/delete",
+ ep___clips_delete
+ ],
+ [
+ "clips/list",
+ ep___clips_list
+ ],
+ [
+ "clips/notes",
+ ep___clips_notes
+ ],
+ [
+ "clips/show",
+ ep___clips_show
+ ],
+ [
+ "clips/update",
+ ep___clips_update
+ ],
+ [
+ "drive",
+ ep___drive
+ ],
+ [
+ "drive/files",
+ ep___drive_files
+ ],
+ [
+ "drive/files/attached-notes",
+ ep___drive_files_attachedNotes
+ ],
+ [
+ "drive/files/caption-image",
+ ep___drive_files_captionImage
+ ],
+ [
+ "drive/files/check-existence",
+ ep___drive_files_checkExistence
+ ],
+ [
+ "drive/files/create",
+ ep___drive_files_create
+ ],
+ [
+ "drive/files/delete",
+ ep___drive_files_delete
+ ],
+ [
+ "drive/files/find-by-hash",
+ ep___drive_files_findByHash
+ ],
+ [
+ "drive/files/find",
+ ep___drive_files_find
+ ],
+ [
+ "drive/files/show",
+ ep___drive_files_show
+ ],
+ [
+ "drive/files/update",
+ ep___drive_files_update
+ ],
+ [
+ "drive/files/upload-from-url",
+ ep___drive_files_uploadFromUrl
+ ],
+ [
+ "drive/folders",
+ ep___drive_folders
+ ],
+ [
+ "drive/folders/create",
+ ep___drive_folders_create
+ ],
+ [
+ "drive/folders/delete",
+ ep___drive_folders_delete
+ ],
+ [
+ "drive/folders/find",
+ ep___drive_folders_find
+ ],
+ [
+ "drive/folders/show",
+ ep___drive_folders_show
+ ],
+ [
+ "drive/folders/update",
+ ep___drive_folders_update
+ ],
+ [
+ "drive/stream",
+ ep___drive_stream
+ ],
+ [
+ "email-address/available",
+ ep___emailAddress_available
+ ],
+ [
+ "emoji",
+ ep___emoji
+ ],
+ [
+ "endpoint",
+ ep___endpoint
+ ],
+ [
+ "endpoints",
+ ep___endpoints
+ ],
+ [
+ "export-custom-emojis",
+ ep___exportCustomEmojis
+ ],
+ [
+ "federation/followers",
+ ep___federation_followers
+ ],
+ [
+ "federation/following",
+ ep___federation_following
+ ],
+ [
+ "federation/instances",
+ ep___federation_instances
+ ],
+ [
+ "federation/show-instance",
+ ep___federation_showInstance
+ ],
+ [
+ "federation/update-remote-user",
+ ep___federation_updateRemoteUser
+ ],
+ [
+ "federation/users",
+ ep___federation_users
+ ],
+ [
+ "federation/stats",
+ ep___federation_stats
+ ],
+ [
+ "following/create",
+ ep___following_create
+ ],
+ [
+ "following/delete",
+ ep___following_delete
+ ],
+ [
+ "following/invalidate",
+ ep___following_invalidate
+ ],
+ [
+ "following/requests/accept",
+ ep___following_requests_accept
+ ],
+ [
+ "following/requests/cancel",
+ ep___following_requests_cancel
+ ],
+ [
+ "following/requests/list",
+ ep___following_requests_list
+ ],
+ [
+ "following/requests/reject",
+ ep___following_requests_reject
+ ],
+ [
+ "gallery/featured",
+ ep___gallery_featured
+ ],
+ [
+ "gallery/popular",
+ ep___gallery_popular
+ ],
+ [
+ "gallery/posts",
+ ep___gallery_posts
+ ],
+ [
+ "gallery/posts/create",
+ ep___gallery_posts_create
+ ],
+ [
+ "gallery/posts/delete",
+ ep___gallery_posts_delete
+ ],
+ [
+ "gallery/posts/like",
+ ep___gallery_posts_like
+ ],
+ [
+ "gallery/posts/show",
+ ep___gallery_posts_show
+ ],
+ [
+ "gallery/posts/unlike",
+ ep___gallery_posts_unlike
+ ],
+ [
+ "gallery/posts/update",
+ ep___gallery_posts_update
+ ],
+ [
+ "get-online-users-count",
+ ep___getOnlineUsersCount
+ ],
+ [
+ "hashtags/list",
+ ep___hashtags_list
+ ],
+ [
+ "hashtags/search",
+ ep___hashtags_search
+ ],
+ [
+ "hashtags/show",
+ ep___hashtags_show
+ ],
+ [
+ "hashtags/trend",
+ ep___hashtags_trend
+ ],
+ [
+ "hashtags/users",
+ ep___hashtags_users
+ ],
+ [
+ "i",
+ ep___i
+ ],
+ [
+ "i/known-as",
+ ep___i_known_as
+ ],
+ [
+ "i/move",
+ ep___i_move
+ ],
+ [
+ "i/2fa/done",
+ ep___i_2fa_done
+ ],
+ [
+ "i/2fa/key-done",
+ ep___i_2fa_keyDone
+ ],
+ [
+ "i/2fa/password-less",
+ ep___i_2fa_passwordLess
+ ],
+ [
+ "i/2fa/register-key",
+ ep___i_2fa_registerKey
+ ],
+ [
+ "i/2fa/register",
+ ep___i_2fa_register
+ ],
+ [
+ "i/2fa/update-key",
+ ep___i_2fa_updateKey
+ ],
+ [
+ "i/2fa/remove-key",
+ ep___i_2fa_removeKey
+ ],
+ [
+ "i/2fa/unregister",
+ ep___i_2fa_unregister
+ ],
+ [
+ "i/apps",
+ ep___i_apps
+ ],
+ [
+ "i/authorized-apps",
+ ep___i_authorizedApps
+ ],
+ [
+ "i/change-password",
+ ep___i_changePassword
+ ],
+ [
+ "i/delete-account",
+ ep___i_deleteAccount
+ ],
+ [
+ "i/export-blocking",
+ ep___i_exportBlocking
+ ],
+ [
+ "i/export-following",
+ ep___i_exportFollowing
+ ],
+ [
+ "i/export-mute",
+ ep___i_exportMute
+ ],
+ [
+ "i/export-notes",
+ ep___i_exportNotes
+ ],
+ [
+ "i/import-posts",
+ ep___i_importPosts
+ ],
+ [
+ "i/export-user-lists",
+ ep___i_exportUserLists
+ ],
+ [
+ "i/favorites",
+ ep___i_favorites
+ ],
+ [
+ "i/gallery/likes",
+ ep___i_gallery_likes
+ ],
+ [
+ "i/gallery/posts",
+ ep___i_gallery_posts
+ ],
+ [
+ "i/import-blocking",
+ ep___i_importBlocking
+ ],
+ [
+ "i/import-following",
+ ep___i_importFollowing
+ ],
+ [
+ "i/import-muting",
+ ep___i_importMuting
+ ],
+ [
+ "i/import-user-lists",
+ ep___i_importUserLists
+ ],
+ [
+ "i/notifications",
+ ep___i_notifications
+ ],
+ [
+ "i/page-likes",
+ ep___i_pageLikes
+ ],
+ [
+ "i/pages",
+ ep___i_pages
+ ],
+ [
+ "i/pin",
+ ep___i_pin
+ ],
+ [
+ "i/read-all-messaging-messages",
+ ep___i_readAllMessagingMessages
+ ],
+ [
+ "i/read-all-unread-notes",
+ ep___i_readAllUnreadNotes
+ ],
+ [
+ "i/read-announcement",
+ ep___i_readAnnouncement
+ ],
+ [
+ "i/request-verified-badge",
+ ep___i_requestVerifiedBadge
+ ],
+ [
+ "i/regenerate-token",
+ ep___i_regenerateToken
+ ],
+ [
+ "i/registry/get-all",
+ ep___i_registry_getAll
+ ],
+ [
+ "i/registry/get-detail",
+ ep___i_registry_getDetail
+ ],
+ [
+ "i/registry/get",
+ ep___i_registry_get
+ ],
+ [
+ "i/registry/keys-with-type",
+ ep___i_registry_keysWithType
+ ],
+ [
+ "i/registry/keys",
+ ep___i_registry_keys
+ ],
+ [
+ "i/registry/remove",
+ ep___i_registry_remove
+ ],
+ [
+ "i/registry/scopes",
+ ep___i_registry_scopes
+ ],
+ [
+ "i/registry/set",
+ ep___i_registry_set
+ ],
+ [
+ "i/revoke-token",
+ ep___i_revokeToken
+ ],
+ [
+ "i/signin-history",
+ ep___i_signinHistory
+ ],
+ [
+ "i/unpin",
+ ep___i_unpin
+ ],
+ [
+ "i/update-email",
+ ep___i_updateEmail
+ ],
+ [
+ "i/update",
+ ep___i_update
+ ],
+ [
+ "i/user-emojis/create",
+ ep___i_userEmojis_create
+ ],
+ [
+ "i/user-emojis/delete",
+ ep___i_userEmojis_delete
+ ],
+ [
+ "i/user-emojis/list",
+ ep___i_userEmojis_list
+ ],
+ [
+ "i/user-group-invites",
+ ep___i_userGroupInvites
+ ],
+ [
+ "i/webhooks/create",
+ ep___i_webhooks_create
+ ],
+ [
+ "i/webhooks/list",
+ ep___i_webhooks_list
+ ],
+ [
+ "i/webhooks/show",
+ ep___i_webhooks_show
+ ],
+ [
+ "i/webhooks/update",
+ ep___i_webhooks_update
+ ],
+ [
+ "i/webhooks/delete",
+ ep___i_webhooks_delete
+ ],
+ [
+ "messaging/history",
+ ep___messaging_history
+ ],
+ [
+ "messaging/messages",
+ ep___messaging_messages
+ ],
+ [
+ "messaging/messages/create",
+ ep___messaging_messages_create
+ ],
+ [
+ "messaging/messages/delete",
+ ep___messaging_messages_delete
+ ],
+ [
+ "messaging/messages/read",
+ ep___messaging_messages_read
+ ],
+ [
+ "meta",
+ ep___meta
+ ],
+ [
+ "miauth/gen-token",
+ ep___miauth_genToken
+ ],
+ [
+ "mute/create",
+ ep___mute_create
+ ],
+ [
+ "mute/delete",
+ ep___mute_delete
+ ],
+ [
+ "mute/list",
+ ep___mute_list
+ ],
+ [
+ "my/apps",
+ ep___my_apps
+ ],
+ [
+ "notes",
+ ep___notes
+ ],
+ [
+ "notes/children",
+ ep___notes_children
+ ],
+ [
+ "notes/clips",
+ ep___notes_clips
+ ],
+ [
+ "notes/conversation",
+ ep___notes_conversation
+ ],
+ [
+ "notes/create",
+ ep___notes_create
+ ],
+ [
+ "notes/delete",
+ ep___notes_delete
+ ],
+ [
+ "notes/edit",
+ ep___notes_edit
+ ],
+ [
+ "notes/favorites/create",
+ ep___notes_favorites_create
+ ],
+ [
+ "notes/favorites/delete",
+ ep___notes_favorites_delete
+ ],
+ [
+ "notes/featured",
+ ep___notes_featured
+ ],
+ [
+ "notes/global-timeline",
+ ep___notes_globalTimeline
+ ],
+ [
+ "notes/hybrid-timeline",
+ ep___notes_hybridTimeline
+ ],
+ [
+ "notes/local-timeline",
+ ep___notes_localTimeline
+ ],
+ [
+ "notes/recommended-timeline",
+ ep___notes_recommendedTimeline
+ ],
+ [
+ "notes/mentions",
+ ep___notes_mentions
+ ],
+ [
+ "notes/polls/recommendation",
+ ep___notes_polls_recommendation
+ ],
+ [
+ "notes/polls/vote",
+ ep___notes_polls_vote
+ ],
+ [
+ "notes/reactions",
+ ep___notes_reactions
+ ],
+ [
+ "notes/reactions/create",
+ ep___notes_reactions_create
+ ],
+ [
+ "notes/reactions/delete",
+ ep___notes_reactions_delete
+ ],
+ [
+ "notes/renotes",
+ ep___notes_renotes
+ ],
+ [
+ "notes/replies",
+ ep___notes_replies
+ ],
+ [
+ "notes/increment-service-view",
+ ep___notes_incrementServiceView
+ ],
+ [
+ "notes/lua4frozen-search",
+ ep___notes_lua4frozenSearch
+ ],
+ [
+ "notes/media-service-search",
+ ep___notes_mediaServiceSearch
+ ],
+ [
+ "notes/karaoke-service-search",
+ ep___notes_karaokeServiceSearch
+ ],
+ [
+ "memoriet/create",
+ ep___memoriet_create
+ ],
+ [
+ "memoriet/deleted-list",
+ ep___memoriet_deletedList
+ ],
+ [
+ "memoriet/list",
+ ep___memoriet_list
+ ],
+ [
+ "memoriet/repost",
+ ep___memoriet_repost
+ ],
+ [
+ "memoriet/viewers",
+ ep___memoriet_viewers
+ ],
+ [
+ "notes/search-by-tag",
+ ep___notes_searchByTag
+ ],
+ [
+ "notes/search",
+ ep___notes_search
+ ],
+ [
+ "notes/show",
+ ep___notes_show
+ ],
+ [
+ "notes/state",
+ ep___notes_state
+ ],
+ [
+ "notes/thread-muting/create",
+ ep___notes_threadMuting_create
+ ],
+ [
+ "notes/thread-muting/delete",
+ ep___notes_threadMuting_delete
+ ],
+ [
+ "notes/timeline",
+ ep___notes_timeline
+ ],
+ [
+ "notes/translate",
+ ep___notes_translate
+ ],
+ [
+ "notes/unrenote",
+ ep___notes_unrenote
+ ],
+ [
+ "notes/user-list-timeline",
+ ep___notes_userListTimeline
+ ],
+ [
+ "notes/watching/create",
+ ep___notes_watching_create
+ ],
+ [
+ "notes/watching/delete",
+ ep___notes_watching_delete
+ ],
+ [
+ "notifications/create",
+ ep___notifications_create
+ ],
+ [
+ "notifications/mark-all-as-read",
+ ep___notifications_markAllAsRead
+ ],
+ [
+ "notifications/read",
+ ep___notifications_read
+ ],
+ [
+ "page-push",
+ ep___pagePush
+ ],
+ [
+ "pages/create",
+ ep___pages_create
+ ],
+ [
+ "pages/delete",
+ ep___pages_delete
+ ],
+ [
+ "pages/featured",
+ ep___pages_featured
+ ],
+ [
+ "pages/like",
+ ep___pages_like
+ ],
+ [
+ "pages/show",
+ ep___pages_show
+ ],
+ [
+ "pages/unlike",
+ ep___pages_unlike
+ ],
+ [
+ "pages/update",
+ ep___pages_update
+ ],
+ [
+ "ping",
+ ep___ping
+ ],
+ [
+ "pinned-users",
+ ep___pinnedUsers
+ ],
+ [
+ "recommended-instances",
+ ep___recommendedInstances
+ ],
+ [
+ "renote-mute/create",
+ ep___renote_mute_create
+ ],
+ [
+ "renote-mute/delete",
+ ep___renote_mute_delete
+ ],
+ [
+ "renote-mute/list",
+ ep___renote_mute_list
+ ],
+ [
+ "custom-motd",
+ ep___customMOTD
+ ],
+ [
+ "custom-splash-icons",
+ ep___customSplashIcons
+ ],
+ [
+ "latest-version",
+ ep___latestVersion
+ ],
+ [
+ "release",
+ ep___release
+ ],
+ [
+ "reversi/cancel-match",
+ ep___reversi_cancelMatch
+ ],
+ [
+ "reversi/games",
+ ep___reversi_games
+ ],
+ [
+ "reversi/invitations",
+ ep___reversi_invitations
+ ],
+ [
+ "reversi/match",
+ ep___reversi_match
+ ],
+ [
+ "reversi/show-game",
+ ep___reversi_showGame
+ ],
+ [
+ "reversi/surrender",
+ ep___reversi_surrender
+ ],
+ [
+ "reversi/verify",
+ ep___reversi_verify
+ ],
+ [
+ "games/reversi/games",
+ ep___reversi_games
+ ],
+ [
+ "games/reversi/games/show",
+ ep___reversi_showGame
+ ],
+ [
+ "games/reversi/games/surrender",
+ ep___reversi_surrender
+ ],
+ [
+ "games/reversi/invitations",
+ ep___reversi_invitations
+ ],
+ [
+ "games/reversi/match",
+ ep___reversi_match
+ ],
+ [
+ "games/reversi/match/cancel",
+ ep___reversi_cancelMatch
+ ],
+ [
+ "shogi/cancel-match",
+ ep___shogi_cancelMatch
+ ],
+ [
+ "shogi/games",
+ ep___shogi_games
+ ],
+ [
+ "shogi/invitations",
+ ep___shogi_invitations
+ ],
+ [
+ "shogi/match",
+ ep___shogi_match
+ ],
+ [
+ "shogi/show-game",
+ ep___shogi_showGame
+ ],
+ [
+ "shogi/surrender",
+ ep___shogi_surrender
+ ],
+ [
+ "games/shogi/games",
+ ep___shogi_games
+ ],
+ [
+ "games/shogi/games/show",
+ ep___shogi_showGame
+ ],
+ [
+ "games/shogi/games/surrender",
+ ep___shogi_surrender
+ ],
+ [
+ "games/shogi/invitations",
+ ep___shogi_invitations
+ ],
+ [
+ "games/shogi/match",
+ ep___shogi_match
+ ],
+ [
+ "games/shogi/match/cancel",
+ ep___shogi_cancelMatch
+ ],
+ [
+ "promo/read",
+ ep___promo_read
+ ],
+ [
+ "promo/show",
+ ep___promo_show
+ ],
+ [
+ "request-reset-password",
+ ep___requestResetPassword
+ ],
+ [
+ "reset-db",
+ ep___resetDb
+ ],
+ [
+ "reset-password",
+ ep___resetPassword
+ ],
+ [
+ "server-info",
+ ep___serverInfo
+ ],
+ [
+ "stats",
+ ep___stats
+ ],
+ [
+ "sw/register",
+ ep___sw_register
+ ],
+ [
+ "sw/unregister",
+ ep___sw_unregister
+ ],
+ [
+ "sw/show-registration",
+ ep___sw_show_registration
+ ],
+ [
+ "sw/update-registration",
+ ep___sw_update_registration
+ ],
+ [
+ "test",
+ ep___test
+ ],
+ [
+ "username/available",
+ ep___username_available
+ ],
+ [
+ "users",
+ ep___users
+ ],
+ [
+ "users/clips",
+ ep___users_clips
+ ],
+ [
+ "users/followers",
+ ep___users_followers
+ ],
+ [
+ "users/following",
+ ep___users_following
+ ],
+ [
+ "users/gallery/posts",
+ ep___users_gallery_posts
+ ],
+ [
+ "users/get-frequently-replied-users",
+ ep___users_getFrequentlyRepliedUsers
+ ],
+ [
+ "users/groups/create",
+ ep___users_groups_create
+ ],
+ [
+ "users/groups/delete",
+ ep___users_groups_delete
+ ],
+ [
+ "users/groups/invitations/accept",
+ ep___users_groups_invitations_accept
+ ],
+ [
+ "users/groups/invitations/reject",
+ ep___users_groups_invitations_reject
+ ],
+ [
+ "users/groups/invite",
+ ep___users_groups_invite
+ ],
+ [
+ "users/groups/joined",
+ ep___users_groups_joined
+ ],
+ [
+ "users/groups/leave",
+ ep___users_groups_leave
+ ],
+ [
+ "users/groups/owned",
+ ep___users_groups_owned
+ ],
+ [
+ "users/groups/pull",
+ ep___users_groups_pull
+ ],
+ [
+ "users/groups/emojis/create",
+ ep___users_groups_emojis_create
+ ],
+ [
+ "users/groups/emojis/delete",
+ ep___users_groups_emojis_delete
+ ],
+ [
+ "users/groups/emojis/list",
+ ep___users_groups_emojis_list
+ ],
+ [
+ "users/groups/show",
+ ep___users_groups_show
+ ],
+ [
+ "users/groups/show-by-username",
+ ep___users_groups_showByUsername
+ ],
+ [
+ "users/groups/transfer",
+ ep___users_groups_transfer
+ ],
+ [
+ "users/groups/update",
+ ep___users_groups_update
+ ],
+ [
+ "users/lists/create",
+ ep___users_lists_create
+ ],
+ [
+ "users/lists/delete",
+ ep___users_lists_delete
+ ],
+ [
+ "users/lists/delete-all",
+ ep___users_lists_delete_all
+ ],
+ [
+ "users/lists/list",
+ ep___users_lists_list
+ ],
+ [
+ "users/lists/pull",
+ ep___users_lists_pull
+ ],
+ [
+ "users/lists/push",
+ ep___users_lists_push
+ ],
+ [
+ "users/lists/show",
+ ep___users_lists_show
+ ],
+ [
+ "users/lists/update",
+ ep___users_lists_update
+ ],
+ [
+ "users/notes",
+ ep___users_notes
+ ],
+ [
+ "users/pages",
+ ep___users_pages
+ ],
+ [
+ "users/reactions",
+ ep___users_reactions
+ ],
+ [
+ "users/recommendation",
+ ep___users_recommendation
+ ],
+ [
+ "users/relation",
+ ep___users_relation
+ ],
+ [
+ "users/report-abuse",
+ ep___users_reportAbuse
+ ],
+ [
+ "users/search-by-username-and-host",
+ ep___users_searchByUsernameAndHost
+ ],
+ [
+ "users/search",
+ ep___users_search
+ ],
+ [
+ "users/show",
+ ep___users_show
+ ],
+ [
+ "users/stats",
+ ep___users_stats
+ ],
+ [
+ "admin/drive-capacity-override",
+ ep___admin_driveCapOverride
+ ],
+ [
+ "fetch-rss",
+ ep___fetchRss
+ ],
+ [
+ "get-sounds",
+ ep___sounds
+ ],
+ [
+ "bites/create",
+ ep___bites_create
+ ],
+ [
+ "bites/show",
+ ep___bites_show
+ ]
+];
+const endpoints = eps.map(([name, ep])=>{
+ return {
+ name: name,
+ exec: ep.default,
+ meta: ep.meta ?? {},
+ params: ep.paramDef
+ };
+});
+export default endpoints;
diff --git a/packages/backend/built/server/api/endpoints/admin/abuse-user-reports.js b/packages/backend/built/server/api/endpoints/admin/abuse-user-reports.js
new file mode 100644
index 0000000..0c7354e
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/abuse-user-reports.js
@@ -0,0 +1,158 @@
+import define from "../../define.js";
+import { AbuseUserReports } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ nullable: false,
+ optional: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ nullable: false,
+ optional: false,
+ format: "date-time"
+ },
+ comment: {
+ type: "string",
+ nullable: false,
+ optional: false
+ },
+ resolved: {
+ type: "boolean",
+ nullable: false,
+ optional: false,
+ example: false
+ },
+ reporterId: {
+ type: "string",
+ nullable: false,
+ optional: false,
+ format: "id"
+ },
+ targetUserId: {
+ type: "string",
+ nullable: false,
+ optional: false,
+ format: "id"
+ },
+ assigneeId: {
+ type: "string",
+ nullable: true,
+ optional: false,
+ format: "id"
+ },
+ reporter: {
+ type: "object",
+ nullable: false,
+ optional: false,
+ ref: "User"
+ },
+ targetUser: {
+ type: "object",
+ nullable: false,
+ optional: false,
+ ref: "User"
+ },
+ assignee: {
+ type: "object",
+ nullable: true,
+ optional: true,
+ ref: "User"
+ }
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ state: {
+ type: "string",
+ nullable: true,
+ default: null
+ },
+ reporterOrigin: {
+ type: "string",
+ enum: [
+ "combined",
+ "local",
+ "remote"
+ ],
+ default: "combined"
+ },
+ targetUserOrigin: {
+ type: "string",
+ enum: [
+ "combined",
+ "local",
+ "remote"
+ ],
+ default: "combined"
+ },
+ forwarded: {
+ type: "boolean",
+ default: false
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps)=>{
+ const query = makePaginationQuery(AbuseUserReports.createQueryBuilder("report"), ps.sinceId, ps.untilId);
+ switch(ps.state){
+ case "resolved":
+ query.andWhere("report.resolved = TRUE");
+ break;
+ case "unresolved":
+ query.andWhere("report.resolved = FALSE");
+ break;
+ }
+ switch(ps.reporterOrigin){
+ case "local":
+ query.andWhere("report.reporterHost IS NULL");
+ break;
+ case "remote":
+ query.andWhere("report.reporterHost IS NOT NULL");
+ break;
+ }
+ switch(ps.targetUserOrigin){
+ case "local":
+ query.andWhere("report.targetUserHost IS NULL");
+ break;
+ case "remote":
+ query.andWhere("report.targetUserHost IS NOT NULL");
+ break;
+ }
+ const reports = await query.take(ps.limit).getMany();
+ return await AbuseUserReports.packMany(reports);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/accounts/create.js b/packages/backend/built/server/api/endpoints/admin/accounts/create.js
new file mode 100644
index 0000000..ebc8c66
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/accounts/create.js
@@ -0,0 +1,53 @@
+import define from "../../../define.js";
+import { Users } from "../../../../../models/index.js";
+import { signup } from "../../../common/signup.js";
+import { IsNull } from "typeorm";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "User",
+ properties: {
+ token: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ username: Users.localUsernameSchema,
+ password: Users.passwordSchema
+ },
+ required: [
+ "username",
+ "password"
+ ]
+};
+export default define(meta, paramDef, async (ps, _me)=>{
+ const me = _me ? await Users.findOneByOrFail({
+ id: _me.id
+ }) : null;
+ const noUsers = await Users.countBy({
+ host: IsNull(),
+ isAdmin: true
+ }) === 0;
+ if (!(noUsers || me?.isAdmin)) throw new Error("access denied");
+ const { account, secret } = await signup({
+ username: ps.username,
+ password: ps.password
+ });
+ const res = await Users.pack(account, account, {
+ detail: true,
+ includeSecrets: true
+ });
+ res.token = secret;
+ return res;
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/accounts/delete.js b/packages/backend/built/server/api/endpoints/admin/accounts/delete.js
new file mode 100644
index 0000000..8424b44
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/accounts/delete.js
@@ -0,0 +1,56 @@
+import define from "../../../define.js";
+import { Users } from "../../../../../models/index.js";
+import { doPostSuspend } from "../../../../../services/suspend-user.js";
+import { publishUserEvent } from "../../../../../services/stream.js";
+import { createDeleteAccountJob } from "../../../../../queue/index.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const user = await Users.findOneBy({
+ id: ps.userId
+ });
+ if (user == null) {
+ throw new Error("user not found");
+ }
+ if (user.isAdmin) {
+ throw new Error("cannot suspend admin");
+ }
+ if (user.isModerator) {
+ throw new Error("cannot suspend moderator");
+ }
+ if (Users.isLocalUser(user)) {
+ // 物理削除する前にDelete activityを送信する
+ await doPostSuspend(user).catch((e)=>{});
+ createDeleteAccountJob(user, {
+ soft: false
+ });
+ } else {
+ createDeleteAccountJob(user, {
+ soft: true
+ });
+ }
+ await Users.update(user.id, {
+ isDeleted: true
+ });
+ if (Users.isLocalUser(user)) {
+ // Terminate streaming
+ publishUserEvent(user.id, "terminate", {});
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/accounts/hosted.js b/packages/backend/built/server/api/endpoints/admin/accounts/hosted.js
new file mode 100644
index 0000000..a4434df
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/accounts/hosted.js
@@ -0,0 +1,109 @@
+import config from "../../../../../config/index.js";
+import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
+import define from "../../../define.js";
+import { Metas } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireAdmin: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const hostedConfig = config.isManagedHosting;
+ const hosted = hostedConfig != null && hostedConfig === true;
+ if (hosted) {
+ const set = {};
+ if (config.deepl.managed != null && config.deepl.managed === true) {
+ if (typeof config.deepl.authKey === "boolean") {
+ set.deeplAuthKey = config.deepl.authKey;
+ }
+ if (typeof config.deepl.isPro === "boolean") {
+ set.deeplIsPro = config.deepl.isPro;
+ }
+ }
+ if (config.libreTranslate.managed != null && config.libreTranslate.managed === true) {
+ if (typeof config.libreTranslate.apiUrl === "string") {
+ set.libreTranslateApiUrl = config.libreTranslate.apiUrl;
+ }
+ if (typeof config.libreTranslate.apiKey === "string") {
+ set.libreTranslateApiKey = config.libreTranslate.apiKey;
+ }
+ }
+ if (config.email.managed != null && config.email.managed === true) {
+ set.enableEmail = true;
+ if (typeof config.email.address === "string") {
+ set.email = config.email.address;
+ }
+ if (typeof config.email.host === "string") {
+ set.smtpHost = config.email.host;
+ }
+ if (typeof config.email.port === "number") {
+ set.smtpPort = config.email.port;
+ }
+ if (typeof config.email.user === "string") {
+ set.smtpUser = config.email.user;
+ }
+ if (typeof config.email.pass === "string") {
+ set.smtpPass = config.email.pass;
+ }
+ if (typeof config.email.useImplicitSslTls === "boolean") {
+ set.smtpSecure = config.email.useImplicitSslTls;
+ }
+ }
+ if (config.objectStorage.managed != null && config.objectStorage.managed === true) {
+ set.useObjectStorage = true;
+ if (typeof config.objectStorage.baseUrl === "string") {
+ set.objectStorageBaseUrl = config.objectStorage.baseUrl;
+ }
+ if (typeof config.objectStorage.bucket === "string") {
+ set.objectStorageBucket = config.objectStorage.bucket;
+ }
+ if (typeof config.objectStorage.prefix === "string") {
+ set.objectStoragePrefix = config.objectStorage.prefix;
+ }
+ if (typeof config.objectStorage.endpoint === "string") {
+ set.objectStorageEndpoint = config.objectStorage.endpoint;
+ }
+ if (typeof config.objectStorage.region === "string") {
+ set.objectStorageRegion = config.objectStorage.region;
+ }
+ if (typeof config.objectStorage.accessKey === "string") {
+ set.objectStorageAccessKey = config.objectStorage.accessKey;
+ }
+ if (typeof config.objectStorage.secretKey === "string") {
+ set.objectStorageSecretKey = config.objectStorage.secretKey;
+ }
+ if (typeof config.objectStorage.useSsl === "boolean") {
+ set.objectStorageUseSSL = config.objectStorage.useSsl;
+ }
+ if (typeof config.objectStorage.connnectOverProxy === "boolean") {
+ set.objectStorageUseProxy = config.objectStorage.connnectOverProxy;
+ }
+ if (typeof config.objectStorage.setPublicReadOnUpload === "boolean") {
+ set.objectStorageSetPublicRead = config.objectStorage.setPublicReadOnUpload;
+ }
+ if (typeof config.objectStorage.s3ForcePathStyle === "boolean") {
+ set.objectStorageS3ForcePathStyle = config.objectStorage.s3ForcePathStyle;
+ }
+ }
+ if (config.summalyProxyUrl !== undefined) {
+ set.summalyProxy = config.summalyProxyUrl;
+ }
+ const meta = await Metas.findOne({
+ where: {},
+ order: {
+ id: "DESC"
+ }
+ });
+ if (meta) await Metas.update(meta.id, set);
+ else await Metas.save(set);
+ insertModerationLog(me, "updateMeta");
+ }
+ return hosted;
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/announcements/create.js b/packages/backend/built/server/api/endpoints/admin/announcements/create.js
new file mode 100644
index 0000000..8527dbe
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/announcements/create.js
@@ -0,0 +1,108 @@
+import define from "../../../define.js";
+import { Announcements } from "../../../../../models/index.js";
+import { genId } from "../../../../../misc/gen-id.js";
+import { publishBroadcastStream } from "../../../../../services/stream.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ updatedAt: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "date-time"
+ },
+ title: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ text: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ imageUrl: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ showPopup: {
+ type: "boolean",
+ optional: true,
+ nullable: false
+ },
+ isGoodNews: {
+ type: "boolean",
+ optional: true,
+ nullable: false
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ title: {
+ type: "string",
+ minLength: 1
+ },
+ text: {
+ type: "string",
+ minLength: 1
+ },
+ imageUrl: {
+ type: "string",
+ nullable: true,
+ minLength: 1
+ },
+ showPopup: {
+ type: "boolean"
+ },
+ isGoodNews: {
+ type: "boolean"
+ }
+ },
+ required: [
+ "title",
+ "text",
+ "imageUrl"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const announcement = await Announcements.insert({
+ id: genId(),
+ createdAt: new Date(),
+ updatedAt: null,
+ title: ps.title,
+ text: ps.text,
+ imageUrl: ps.imageUrl,
+ showPopup: ps.showPopup ?? false,
+ isGoodNews: ps.isGoodNews ?? false
+ }).then((x)=>Announcements.findOneByOrFail(x.identifiers[0]));
+ publishBroadcastStream("announcementAdded", announcement);
+ return Object.assign({}, announcement, {
+ createdAt: announcement.createdAt.toISOString(),
+ updatedAt: null
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/announcements/delete.js b/packages/backend/built/server/api/endpoints/admin/announcements/delete.js
new file mode 100644
index 0000000..6f03196
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/announcements/delete.js
@@ -0,0 +1,38 @@
+import define from "../../../define.js";
+import { Announcements } from "../../../../../models/index.js";
+import { ApiError } from "../../../error.js";
+import { publishBroadcastStream } from "../../../../../services/stream.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ errors: {
+ noSuchAnnouncement: {
+ message: "No such announcement.",
+ code: "NO_SUCH_ANNOUNCEMENT",
+ id: "ecad8040-a276-4e85-bda9-015a708d291e"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "id"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const announcement = await Announcements.findOneBy({
+ id: ps.id
+ });
+ if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
+ publishBroadcastStream("announcementDeleted", announcement.id);
+ await Announcements.delete(announcement.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/announcements/list.js b/packages/backend/built/server/api/endpoints/admin/announcements/list.js
new file mode 100644
index 0000000..26a0f68
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/announcements/list.js
@@ -0,0 +1,112 @@
+import { Announcements, AnnouncementReads } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { makePaginationQuery } from "../../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ updatedAt: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "date-time"
+ },
+ text: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ title: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ imageUrl: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ reads: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ showPopup: {
+ type: "boolean",
+ optional: true,
+ nullable: false
+ },
+ isGoodNews: {
+ type: "boolean",
+ optional: true,
+ nullable: false
+ }
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps)=>{
+ const query = makePaginationQuery(Announcements.createQueryBuilder("announcement"), ps.sinceId, ps.untilId);
+ const announcements = await query.take(ps.limit).getMany();
+ const reads = new Map();
+ for (const announcement of announcements){
+ reads.set(announcement, await AnnouncementReads.countBy({
+ announcementId: announcement.id
+ }));
+ }
+ return announcements.map((announcement)=>({
+ id: announcement.id,
+ createdAt: announcement.createdAt.toISOString(),
+ updatedAt: announcement.updatedAt?.toISOString() ?? null,
+ title: announcement.title,
+ text: announcement.text,
+ imageUrl: announcement.imageUrl,
+ reads: reads.get(announcement),
+ showPopup: announcement.showPopup,
+ isGoodNews: announcement.isGoodNews
+ }));
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/announcements/update.js b/packages/backend/built/server/api/endpoints/admin/announcements/update.js
new file mode 100644
index 0000000..2ac3fa1
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/announcements/update.js
@@ -0,0 +1,65 @@
+import define from "../../../define.js";
+import { Announcements } from "../../../../../models/index.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ errors: {
+ noSuchAnnouncement: {
+ message: "No such announcement.",
+ code: "NO_SUCH_ANNOUNCEMENT",
+ id: "d3aae5a7-6372-4cb4-b61c-f511ffc2d7cc"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ format: "misskey:id"
+ },
+ title: {
+ type: "string",
+ minLength: 1
+ },
+ text: {
+ type: "string",
+ minLength: 1
+ },
+ imageUrl: {
+ type: "string",
+ nullable: true,
+ minLength: 1
+ },
+ showPopup: {
+ type: "boolean"
+ },
+ isGoodNews: {
+ type: "boolean"
+ }
+ },
+ required: [
+ "id",
+ "title",
+ "text",
+ "imageUrl"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const announcement = await Announcements.findOneBy({
+ id: ps.id
+ });
+ if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
+ await Announcements.update(announcement.id, {
+ updatedAt: new Date(),
+ title: ps.title,
+ text: ps.text,
+ imageUrl: ps.imageUrl,
+ showPopup: ps.showPopup ?? false,
+ isGoodNews: ps.isGoodNews ?? false
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/create-backup.js b/packages/backend/built/server/api/endpoints/admin/create-backup.js
new file mode 100644
index 0000000..2f24ea1
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/create-backup.js
@@ -0,0 +1,194 @@
+import * as fs from "node:fs";
+import { mkdir, rm, stat, writeFile } from "node:fs/promises";
+import { spawn } from "node:child_process";
+import { dirname, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+import archiver from "archiver";
+import config from "../../../../config/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
+import { createTempDir } from "../../../../misc/create-temp.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireAdmin: true,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ path: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ fileName: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ size: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ includedMedia: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ }
+ }
+ },
+ errors: {
+ backupFailed: {
+ message: "Failed to create backup.",
+ code: "BACKUP_FAILED",
+ id: "7498ab9f-4e1d-40d0-96d8-d8d1d82bd621"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), "../../../../../../..");
+function timestamp() {
+ const now = new Date();
+ const pad = (value)=>value.toString().padStart(2, "0");
+ return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
+}
+function run(command, args, env) {
+ return new Promise((resolvePromise, reject)=>{
+ const child = spawn(command, args, {
+ stdio: [
+ "ignore",
+ "ignore",
+ "pipe"
+ ],
+ env
+ });
+ let stderr = "";
+ child.stderr.on("data", (chunk)=>{
+ stderr += chunk.toString();
+ });
+ child.on("error", reject);
+ child.on("exit", (code)=>{
+ if (code === 0) {
+ resolvePromise();
+ } else {
+ reject(new Error(`${command} exited with code ${code}: ${stderr.trim()}`));
+ }
+ });
+ });
+}
+function archiveDirectory(sourceDir, outFile) {
+ return new Promise((resolvePromise, reject)=>{
+ const output = fs.createWriteStream(outFile);
+ const archive = archiver("tar", {
+ gzip: true,
+ gzipOptions: {
+ level: 6
+ }
+ });
+ output.on("close", ()=>resolvePromise());
+ archive.on("error", reject);
+ archive.pipe(output);
+ archive.directory(sourceDir, false);
+ archive.finalize();
+ });
+}
+async function pathExists(path) {
+ return stat(path).then(()=>true).catch(()=>false);
+}
+export default define(meta, paramDef, async (_ps, me)=>{
+ const [workDir, cleanup] = await createTempDir();
+ const fileName = `iceshrimp-full-${timestamp()}.tar.gz`;
+ const outDir = resolve(process.env.ICESHRIMP_BACKUP_DIR ?? `${rootDir}/backups`);
+ const outFile = resolve(outDir, fileName);
+ const dbDumpPath = resolve(workDir, "database.dump");
+ const mediaDir = resolve(config.mediaDir);
+ let includedMedia = false;
+ try {
+ await mkdir(outDir, {
+ recursive: true
+ });
+ const env = {
+ ...process.env,
+ PGHOST: config.db.host,
+ PGPORT: String(config.db.port),
+ PGDATABASE: config.db.db,
+ PGUSER: config.db.user,
+ PGPASSWORD: config.db.pass
+ };
+ await run("pg_dump", [
+ "--format=custom",
+ "--blobs",
+ "--no-owner",
+ "--file",
+ dbDumpPath,
+ config.db.db
+ ], env);
+ const configDir = resolve(workDir, "config");
+ await mkdir(configDir, {
+ recursive: true
+ });
+ const configFiles = [
+ process.env.ICESHRIMP_CONFIG ? resolve(process.env.ICESHRIMP_CONFIG) : resolve(rootDir, ".config/default.yml"),
+ ...process.env.ICESHRIMP_SECRETS ? [
+ resolve(process.env.ICESHRIMP_SECRETS)
+ ] : []
+ ];
+ for (const configFile of configFiles){
+ if (await pathExists(configFile)) {
+ fs.copyFileSync(configFile, resolve(configDir, configFile.split("/").pop()));
+ }
+ }
+ if (await pathExists(mediaDir) && !outDir.startsWith(mediaDir + "/") && outDir !== mediaDir) {
+ fs.cpSync(mediaDir, resolve(workDir, "files"), {
+ recursive: true,
+ dereference: false,
+ errorOnExist: false
+ });
+ includedMedia = true;
+ }
+ await writeFile(resolve(workDir, "manifest.json"), `${JSON.stringify({
+ type: "iceshrimp-full-backup",
+ version: config.version,
+ createdAt: new Date().toISOString(),
+ host: config.host,
+ database: config.db.db,
+ included: {
+ database: true,
+ config: true,
+ media: includedMedia
+ },
+ restore: "yarn full:restore "
+ }, null, 2)}\n`, "utf8");
+ await archiveDirectory(workDir, outFile);
+ const outStat = await stat(outFile);
+ await insertModerationLog(me, "createBackup", {
+ path: outFile,
+ size: outStat.size,
+ includedMedia
+ });
+ return {
+ path: outFile,
+ fileName,
+ size: outStat.size,
+ includedMedia
+ };
+ } catch (e) {
+ await rm(outFile, {
+ force: true
+ }).catch(()=>{});
+ throw new ApiError(meta.errors.backupFailed, {
+ message: e instanceof Error ? e.message : String(e)
+ });
+ } finally{
+ cleanup();
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/delete-account.js b/packages/backend/built/server/api/endpoints/admin/delete-account.js
new file mode 100644
index 0000000..1749e44
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/delete-account.js
@@ -0,0 +1,32 @@
+import { Users } from "../../../../models/index.js";
+import { deleteAccount } from "../../../../services/delete-account.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireAdmin: true,
+ res: {}
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const user = await Users.findOneByOrFail({
+ id: ps.userId
+ });
+ if (user.isDeleted) {
+ return;
+ }
+ await deleteAccount(user);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/delete-all-files-of-a-user.js b/packages/backend/built/server/api/endpoints/admin/delete-all-files-of-a-user.js
new file mode 100644
index 0000000..97326bf
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/delete-all-files-of-a-user.js
@@ -0,0 +1,30 @@
+import define from "../../define.js";
+import { deleteFile } from "../../../../services/drive/delete-file.js";
+import { DriveFiles } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const files = await DriveFiles.findBy({
+ userId: ps.userId
+ });
+ for (const file of files){
+ deleteFile(file);
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/drive-capacity-override.js b/packages/backend/built/server/api/endpoints/admin/drive-capacity-override.js
new file mode 100644
index 0000000..9ce5605
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/drive-capacity-override.js
@@ -0,0 +1,48 @@
+import define from "../../define.js";
+import { Users } from "../../../../models/index.js";
+import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
+import { publishInternalEvent } from "../../../../services/stream.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ overrideMb: {
+ type: "number",
+ nullable: true
+ }
+ },
+ required: [
+ "userId",
+ "overrideMb"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const user = await Users.findOneBy({
+ id: ps.userId
+ });
+ if (user == null) {
+ throw new Error("user not found");
+ }
+ if (!Users.isLocalUser(user)) {
+ throw new Error("user is not local user");
+ }
+ await Users.update(user.id, {
+ driveCapacityOverrideMb: ps.overrideMb
+ });
+ publishInternalEvent("localUserUpdated", {
+ id: user.id
+ });
+ insertModerationLog(me, "change-drive-capacity-override", {
+ targetId: user.id
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/drive/clean-remote-files.js b/packages/backend/built/server/api/endpoints/admin/drive/clean-remote-files.js
new file mode 100644
index 0000000..2b96d55
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/drive/clean-remote-files.js
@@ -0,0 +1,17 @@
+import define from "../../../define.js";
+import { createCleanRemoteFilesJob } from "../../../../../queue/index.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ createCleanRemoteFilesJob();
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/drive/cleanup.js b/packages/backend/built/server/api/endpoints/admin/drive/cleanup.js
new file mode 100644
index 0000000..99db164
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/drive/cleanup.js
@@ -0,0 +1,24 @@
+import { IsNull } from "typeorm";
+import define from "../../../define.js";
+import { deleteFile } from "../../../../../services/drive/delete-file.js";
+import { DriveFiles } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const files = await DriveFiles.findBy({
+ userId: IsNull()
+ });
+ for (const file of files){
+ deleteFile(file);
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/drive/files.js b/packages/backend/built/server/api/endpoints/admin/drive/files.js
new file mode 100644
index 0000000..4aaa683
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/drive/files.js
@@ -0,0 +1,102 @@
+import { DriveFiles } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { makePaginationQuery } from "../../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: false,
+ requireModerator: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "DriveFile"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ userId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ type: {
+ type: "string",
+ nullable: true,
+ pattern: /^[a-zA-Z0-9\/\-*]+$/.toString().slice(1, -1)
+ },
+ origin: {
+ type: "string",
+ enum: [
+ "combined",
+ "local",
+ "remote"
+ ],
+ default: "local"
+ },
+ hostname: {
+ type: "string",
+ nullable: true,
+ default: null,
+ description: "The local host is represented with `null`."
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = makePaginationQuery(DriveFiles.createQueryBuilder("file"), ps.sinceId, ps.untilId);
+ if (ps.userId) {
+ query.andWhere("file.userId = :userId", {
+ userId: ps.userId
+ });
+ } else {
+ if (ps.origin === "local") {
+ query.andWhere("file.userHost IS NULL");
+ } else if (ps.origin === "remote") {
+ query.andWhere("file.userHost IS NOT NULL");
+ }
+ if (ps.hostname) {
+ query.andWhere("file.userHost = :hostname", {
+ hostname: ps.hostname
+ });
+ }
+ }
+ if (ps.type) {
+ if (ps.type.endsWith("/*")) {
+ query.andWhere("file.type like :type", {
+ type: `${ps.type.replace("/*", "/")}%`
+ });
+ } else {
+ query.andWhere("file.type = :type", {
+ type: ps.type
+ });
+ }
+ }
+ const files = await query.take(ps.limit).getMany();
+ return await DriveFiles.packMany(files, {
+ detail: true,
+ withUser: true,
+ self: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/drive/show-file.js b/packages/backend/built/server/api/endpoints/admin/drive/show-file.js
new file mode 100644
index 0000000..c4493ee
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/drive/show-file.js
@@ -0,0 +1,227 @@
+import { DriveFiles } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ errors: {
+ noSuchFile: {
+ message: "No such file.",
+ code: "NO_SUCH_FILE",
+ id: "caf3ca38-c6e5-472e-a30c-b05377dcc240"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ userId: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ userHost: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ description: "The local host is represented with `null`."
+ },
+ md5: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "md5",
+ example: "15eca7fba0480996e2245f5185bf39f2"
+ },
+ name: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ example: "lenna.jpg"
+ },
+ type: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ example: "image/jpeg"
+ },
+ size: {
+ type: "number",
+ optional: false,
+ nullable: false,
+ example: 51469
+ },
+ comment: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ blurhash: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ properties: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ width: {
+ type: "number",
+ optional: false,
+ nullable: false,
+ example: 1280
+ },
+ height: {
+ type: "number",
+ optional: false,
+ nullable: false,
+ example: 720
+ },
+ avgColor: {
+ type: "string",
+ optional: true,
+ nullable: false,
+ example: "rgb(40,65,87)"
+ }
+ }
+ },
+ storedInternal: {
+ type: "boolean",
+ optional: false,
+ nullable: true,
+ example: true
+ },
+ url: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "url"
+ },
+ thumbnailUrl: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "url"
+ },
+ webpublicUrl: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "url"
+ },
+ accessKey: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ thumbnailAccessKey: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ webpublicAccessKey: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ uri: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ src: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ folderId: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ isSensitive: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ isLink: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ anyOf: [
+ {
+ properties: {
+ fileId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "fileId"
+ ]
+ },
+ {
+ properties: {
+ url: {
+ type: "string"
+ }
+ },
+ required: [
+ "url"
+ ]
+ }
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const file = ps.fileId ? await DriveFiles.findOneBy({
+ id: ps.fileId
+ }) : await DriveFiles.findOne({
+ where: [
+ {
+ url: ps.url
+ },
+ {
+ thumbnailUrl: ps.url
+ },
+ {
+ webpublicUrl: ps.url
+ }
+ ]
+ });
+ if (file == null) {
+ throw new ApiError(meta.errors.noSuchFile);
+ }
+ if (!me.isAdmin) {
+ file.requestIp = undefined;
+ file.requestHeaders = undefined;
+ }
+ return file;
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/emoji/add-aliases-bulk.js b/packages/backend/built/server/api/endpoints/admin/emoji/add-aliases-bulk.js
new file mode 100644
index 0000000..87ad286
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/emoji/add-aliases-bulk.js
@@ -0,0 +1,49 @@
+import define from "../../../define.js";
+import { Emojis } from "../../../../../models/index.js";
+import { In } from "typeorm";
+import { db } from "../../../../../db/postgre.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ ids: {
+ type: "array",
+ items: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ aliases: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ }
+ },
+ required: [
+ "ids",
+ "aliases"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const emojis = await Emojis.findBy({
+ id: In(ps.ids)
+ });
+ for (const emoji of emojis){
+ await Emojis.update(emoji.id, {
+ updatedAt: new Date(),
+ aliases: [
+ ...new Set(emoji.aliases.concat(ps.aliases))
+ ]
+ });
+ }
+ await db.queryResultCache.remove([
+ "meta_emojis"
+ ]);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/emoji/add.js b/packages/backend/built/server/api/endpoints/admin/emoji/add.js
new file mode 100644
index 0000000..59c37b1
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/emoji/add.js
@@ -0,0 +1,70 @@
+import define from "../../../define.js";
+import { Emojis, DriveFiles } from "../../../../../models/index.js";
+import { genId } from "../../../../../misc/gen-id.js";
+import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
+import { ApiError } from "../../../error.js";
+import rndstr from "rndstr";
+import { publishBroadcastStream } from "../../../../../services/stream.js";
+import { db } from "../../../../../db/postgre.js";
+import { getEmojiSize } from "../../../../../misc/emoji-meta.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ errors: {
+ noSuchFile: {
+ message: "No such file.",
+ code: "MO_SUCH_FILE",
+ id: "fc46b5a4-6b92-4c33-ac66-b806659bb5cf"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ fileId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "fileId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const file = await DriveFiles.findOneBy({
+ id: ps.fileId
+ });
+ if (file == null) throw new ApiError(meta.errors.noSuchFile);
+ const name = file.name.split(".")[0].match(/^[a-z0-9_]+$/) ? file.name.split(".")[0] : `_${rndstr("a-z0-9", 8)}_`;
+ const size = await getEmojiSize(file.url);
+ const emoji = await Emojis.insert({
+ id: genId(),
+ updatedAt: new Date(),
+ name: name,
+ category: null,
+ host: null,
+ aliases: [],
+ originalUrl: file.url,
+ publicUrl: file.webpublicUrl ?? file.url,
+ type: file.webpublicType ?? file.type,
+ license: null,
+ glyph: file.type === "image/svg+xml",
+ width: size.width || null,
+ height: size.height || null
+ }).then((x)=>Emojis.findOneByOrFail(x.identifiers[0]));
+ await db.queryResultCache.remove([
+ "meta_emojis"
+ ]);
+ publishBroadcastStream("emojiAdded", {
+ emoji: await Emojis.pack(emoji.id)
+ });
+ insertModerationLog(me, "addEmoji", {
+ emojiId: emoji.id
+ });
+ return {
+ id: emoji.id
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/emoji/copy.js b/packages/backend/built/server/api/endpoints/admin/emoji/copy.js
new file mode 100644
index 0000000..35f3b42
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/emoji/copy.js
@@ -0,0 +1,90 @@
+import define from "../../../define.js";
+import { Emojis } from "../../../../../models/index.js";
+import { genId } from "../../../../../misc/gen-id.js";
+import { ApiError } from "../../../error.js";
+import { uploadFromUrl } from "../../../../../services/drive/upload-from-url.js";
+import { publishBroadcastStream } from "../../../../../services/stream.js";
+import { db } from "../../../../../db/postgre.js";
+import { getEmojiSize } from "../../../../../misc/emoji-meta.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ errors: {
+ noSuchEmoji: {
+ message: "No such emoji.",
+ code: "NO_SUCH_EMOJI",
+ id: "e2785b66-dca3-4087-9cac-b93c541cc425"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ emojiId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "emojiId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const emoji = await Emojis.findOneBy({
+ id: ps.emojiId
+ });
+ if (emoji == null) {
+ throw new ApiError(meta.errors.noSuchEmoji);
+ }
+ let driveFile;
+ try {
+ // Create file
+ driveFile = await uploadFromUrl({
+ url: emoji.originalUrl,
+ user: null,
+ force: true
+ });
+ } catch (e) {
+ throw new ApiError();
+ }
+ const size = await getEmojiSize(driveFile.url);
+ const copied = await Emojis.insert({
+ id: genId(),
+ updatedAt: new Date(),
+ name: emoji.name,
+ host: null,
+ aliases: [],
+ originalUrl: driveFile.url,
+ publicUrl: driveFile.webpublicUrl ?? driveFile.url,
+ type: driveFile.webpublicType ?? driveFile.type,
+ license: emoji.license,
+ glyph: emoji.glyph,
+ width: size.width || null,
+ height: size.height || null
+ }).then((x)=>Emojis.findOneByOrFail(x.identifiers[0]));
+ await db.queryResultCache.remove([
+ "meta_emojis"
+ ]);
+ publishBroadcastStream("emojiAdded", {
+ emoji: await Emojis.pack(copied.id)
+ });
+ return {
+ id: copied.id
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/emoji/delete-bulk.js b/packages/backend/built/server/api/endpoints/admin/emoji/delete-bulk.js
new file mode 100644
index 0000000..020272c
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/emoji/delete-bulk.js
@@ -0,0 +1,41 @@
+import define from "../../../define.js";
+import { Emojis } from "../../../../../models/index.js";
+import { In } from "typeorm";
+import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
+import { db } from "../../../../../db/postgre.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ ids: {
+ type: "array",
+ items: {
+ type: "string",
+ format: "misskey:id"
+ }
+ }
+ },
+ required: [
+ "ids"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const emojis = await Emojis.findBy({
+ id: In(ps.ids)
+ });
+ for (const emoji of emojis){
+ await Emojis.delete(emoji.id);
+ await db.queryResultCache.remove([
+ "meta_emojis"
+ ]);
+ insertModerationLog(me, "deleteEmoji", {
+ emoji: emoji
+ });
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/emoji/delete.js b/packages/backend/built/server/api/endpoints/admin/emoji/delete.js
new file mode 100644
index 0000000..bb3a35b
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/emoji/delete.js
@@ -0,0 +1,44 @@
+import define from "../../../define.js";
+import { Emojis } from "../../../../../models/index.js";
+import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
+import { ApiError } from "../../../error.js";
+import { db } from "../../../../../db/postgre.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ errors: {
+ noSuchEmoji: {
+ message: "No such emoji.",
+ code: "NO_SUCH_EMOJI",
+ id: "be83669b-773a-44b7-b1f8-e5e5170ac3c2"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "id"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const emoji = await Emojis.findOneBy({
+ id: ps.id
+ });
+ if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji);
+ await Emojis.delete(emoji.id);
+ await db.queryResultCache.remove([
+ "meta_emojis"
+ ]);
+ insertModerationLog(me, "deleteEmoji", {
+ emoji: emoji
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/emoji/import-zip.js b/packages/backend/built/server/api/endpoints/admin/emoji/import-zip.js
new file mode 100644
index 0000000..0e1297e
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/emoji/import-zip.js
@@ -0,0 +1,22 @@
+import define from "../../../define.js";
+import { createImportCustomEmojisJob } from "../../../../../queue/index.js";
+export const meta = {
+ secure: true,
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ fileId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "fileId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ createImportCustomEmojisJob(user, ps.fileId);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/emoji/list-remote.js b/packages/backend/built/server/api/endpoints/admin/emoji/list-remote.js
new file mode 100644
index 0000000..37fce41
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/emoji/list-remote.js
@@ -0,0 +1,134 @@
+import define from "../../../define.js";
+import { Emojis } from "../../../../../models/index.js";
+import { toPuny } from "../../../../../misc/convert-host.js";
+import { makePaginationQuery } from "../../../common/make-pagination-query.js";
+import { sqlLikeEscape } from "../../../../../misc/sql-like-escape.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ aliases: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ },
+ name: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ category: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ host: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ description: "The local host is represented with `null`."
+ },
+ url: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ license: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ glyph: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ glyphUrl: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ width: {
+ type: "number",
+ optional: false,
+ nullable: true
+ },
+ height: {
+ type: "number",
+ optional: false,
+ nullable: true
+ }
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ query: {
+ type: "string",
+ nullable: true,
+ default: null
+ },
+ host: {
+ type: "string",
+ nullable: true,
+ default: null,
+ description: "Use `null` to represent the local host."
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps)=>{
+ const q = makePaginationQuery(Emojis.createQueryBuilder("emoji"), ps.sinceId, ps.untilId);
+ if (ps.host == null) {
+ q.andWhere("emoji.host IS NOT NULL");
+ } else {
+ q.andWhere("emoji.host = :host", {
+ host: toPuny(ps.host)
+ });
+ }
+ if (ps.query) {
+ q.andWhere("emoji.name like :query", {
+ query: `%${sqlLikeEscape(ps.query)}%`
+ });
+ }
+ const emojis = await q.orderBy("emoji.id", "DESC").take(ps.limit).getMany();
+ return Emojis.packMany(emojis);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/emoji/list.js b/packages/backend/built/server/api/endpoints/admin/emoji/list.js
new file mode 100644
index 0000000..559f458
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/emoji/list.js
@@ -0,0 +1,123 @@
+import define from "../../../define.js";
+import { Emojis } from "../../../../../models/index.js";
+import { makePaginationQuery } from "../../../common/make-pagination-query.js";
+//import { sqlLikeEscape } from "@/misc/sql-like-escape.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ aliases: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ },
+ name: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ category: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ host: {
+ type: "null",
+ optional: false,
+ description: "The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files."
+ },
+ url: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ license: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ glyph: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ glyphUrl: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ width: {
+ type: "number",
+ optional: false,
+ nullable: true
+ },
+ height: {
+ type: "number",
+ optional: false,
+ nullable: true
+ }
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ query: {
+ type: "string",
+ nullable: true,
+ default: null
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps)=>{
+ const q = makePaginationQuery(Emojis.createQueryBuilder("emoji"), ps.sinceId, ps.untilId).andWhere("emoji.host IS NULL");
+ let emojis;
+ if (ps.query) {
+ //q.andWhere('emoji.name ILIKE :q', { q: `%${sqlLikeEscape(ps.query)}%` });
+ //const emojis = await q.take(ps.limit).getMany();
+ emojis = await q.getMany();
+ emojis = emojis.filter((emoji)=>emoji.name.includes(ps.query) || emoji.aliases.some((a)=>a.includes(ps.query)) || emoji.category?.includes(ps.query));
+ emojis.splice(ps.limit + 1);
+ } else {
+ emojis = await q.take(ps.limit).getMany();
+ }
+ return Emojis.packMany(emojis);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/emoji/remove-aliases-bulk.js b/packages/backend/built/server/api/endpoints/admin/emoji/remove-aliases-bulk.js
new file mode 100644
index 0000000..60f0650
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/emoji/remove-aliases-bulk.js
@@ -0,0 +1,47 @@
+import define from "../../../define.js";
+import { Emojis } from "../../../../../models/index.js";
+import { In } from "typeorm";
+import { db } from "../../../../../db/postgre.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ ids: {
+ type: "array",
+ items: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ aliases: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ }
+ },
+ required: [
+ "ids",
+ "aliases"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const emojis = await Emojis.findBy({
+ id: In(ps.ids)
+ });
+ for (const emoji of emojis){
+ await Emojis.update(emoji.id, {
+ updatedAt: new Date(),
+ aliases: emoji.aliases.filter((x)=>!ps.aliases.includes(x))
+ });
+ }
+ await db.queryResultCache.remove([
+ "meta_emojis"
+ ]);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/emoji/set-aliases-bulk.js b/packages/backend/built/server/api/endpoints/admin/emoji/set-aliases-bulk.js
new file mode 100644
index 0000000..3a0862d
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/emoji/set-aliases-bulk.js
@@ -0,0 +1,44 @@
+import define from "../../../define.js";
+import { Emojis } from "../../../../../models/index.js";
+import { In } from "typeorm";
+import { db } from "../../../../../db/postgre.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ ids: {
+ type: "array",
+ items: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ aliases: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ }
+ },
+ required: [
+ "ids",
+ "aliases"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ await Emojis.update({
+ id: In(ps.ids)
+ }, {
+ updatedAt: new Date(),
+ aliases: ps.aliases
+ });
+ await db.queryResultCache.remove([
+ "meta_emojis"
+ ]);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/emoji/set-category-bulk.js b/packages/backend/built/server/api/endpoints/admin/emoji/set-category-bulk.js
new file mode 100644
index 0000000..be02412
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/emoji/set-category-bulk.js
@@ -0,0 +1,42 @@
+import define from "../../../define.js";
+import { Emojis } from "../../../../../models/index.js";
+import { In } from "typeorm";
+import { db } from "../../../../../db/postgre.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ ids: {
+ type: "array",
+ items: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ category: {
+ type: "string",
+ nullable: true,
+ description: "Use `null` to reset the category."
+ }
+ },
+ required: [
+ "ids"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ await Emojis.update({
+ id: In(ps.ids)
+ }, {
+ updatedAt: new Date(),
+ category: ps.category
+ });
+ await db.queryResultCache.remove([
+ "meta_emojis"
+ ]);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/emoji/set-license-bulk.js b/packages/backend/built/server/api/endpoints/admin/emoji/set-license-bulk.js
new file mode 100644
index 0000000..8447f10
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/emoji/set-license-bulk.js
@@ -0,0 +1,42 @@
+import define from "../../../define.js";
+import { Emojis } from "../../../../../models/index.js";
+import { In } from "typeorm";
+import { db } from "../../../../../db/postgre.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ ids: {
+ type: "array",
+ items: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ license: {
+ type: "string",
+ nullable: true,
+ description: "Use `null` to reset the license."
+ }
+ },
+ required: [
+ "ids"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ await Emojis.update({
+ id: In(ps.ids)
+ }, {
+ updatedAt: new Date(),
+ license: ps.license
+ });
+ await db.queryResultCache.remove([
+ "meta_emojis"
+ ]);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/emoji/update.js b/packages/backend/built/server/api/endpoints/admin/emoji/update.js
new file mode 100644
index 0000000..ccad645
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/emoji/update.js
@@ -0,0 +1,72 @@
+import define from "../../../define.js";
+import { Emojis } from "../../../../../models/index.js";
+import { ApiError } from "../../../error.js";
+import { db } from "../../../../../db/postgre.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ errors: {
+ noSuchEmoji: {
+ message: "No such emoji.",
+ code: "NO_SUCH_EMOJI",
+ id: "684dec9d-a8c2-4364-9aa8-456c49cb1dc8"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ format: "misskey:id"
+ },
+ name: {
+ type: "string"
+ },
+ category: {
+ type: "string",
+ nullable: true,
+ description: "Use `null` to reset the category."
+ },
+ aliases: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ },
+ license: {
+ type: "string",
+ nullable: true
+ },
+ glyph: {
+ type: "boolean"
+ }
+ },
+ required: [
+ "id",
+ "name",
+ "aliases"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const emoji = await Emojis.findOneBy({
+ id: ps.id
+ });
+ if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji);
+ await Emojis.update(emoji.id, {
+ updatedAt: new Date(),
+ name: ps.name,
+ category: ps.category,
+ aliases: ps.aliases,
+ license: ps.license,
+ ...typeof ps.glyph === "boolean" ? {
+ glyph: ps.glyph
+ } : {}
+ });
+ await db.queryResultCache.remove([
+ "meta_emojis"
+ ]);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/federation/delete-all-files.js b/packages/backend/built/server/api/endpoints/admin/federation/delete-all-files.js
new file mode 100644
index 0000000..c74a5dd
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/federation/delete-all-files.js
@@ -0,0 +1,29 @@
+import define from "../../../define.js";
+import { deleteFile } from "../../../../../services/drive/delete-file.js";
+import { DriveFiles } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ host: {
+ type: "string"
+ }
+ },
+ required: [
+ "host"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const files = await DriveFiles.findBy({
+ userHost: ps.host
+ });
+ for (const file of files){
+ deleteFile(file);
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.js b/packages/backend/built/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.js
new file mode 100644
index 0000000..0efe304
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.js
@@ -0,0 +1,31 @@
+import define from "../../../define.js";
+import { Instances } from "../../../../../models/index.js";
+import { toPuny } from "../../../../../misc/convert-host.js";
+import { fetchInstanceMetadata } from "../../../../../services/fetch-instance-metadata.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ host: {
+ type: "string"
+ }
+ },
+ required: [
+ "host"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const instance = await Instances.findOneBy({
+ host: toPuny(ps.host)
+ });
+ if (instance == null) {
+ throw new Error("instance not found");
+ }
+ fetchInstanceMetadata(instance, true);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/federation/remove-all-following.js b/packages/backend/built/server/api/endpoints/admin/federation/remove-all-following.js
new file mode 100644
index 0000000..2dab1a0
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/federation/remove-all-following.js
@@ -0,0 +1,37 @@
+import define from "../../../define.js";
+import deleteFollowing from "../../../../../services/following/delete.js";
+import { Followings, Users } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ host: {
+ type: "string"
+ }
+ },
+ required: [
+ "host"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const followings = await Followings.findBy({
+ followerHost: ps.host
+ });
+ const pairs = await Promise.all(followings.map((f)=>Promise.all([
+ Users.findOneByOrFail({
+ id: f.followerId
+ }),
+ Users.findOneByOrFail({
+ id: f.followeeId
+ })
+ ])));
+ for (const pair of pairs){
+ deleteFollowing(pair[0], pair[1]);
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/federation/update-instance.js b/packages/backend/built/server/api/endpoints/admin/federation/update-instance.js
new file mode 100644
index 0000000..a1e9ed8
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/federation/update-instance.js
@@ -0,0 +1,38 @@
+import define from "../../../define.js";
+import { Instances } from "../../../../../models/index.js";
+import { toPuny } from "../../../../../misc/convert-host.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ host: {
+ type: "string"
+ },
+ isSuspended: {
+ type: "boolean"
+ }
+ },
+ required: [
+ "host",
+ "isSuspended"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const instance = await Instances.findOneBy({
+ host: toPuny(ps.host)
+ });
+ if (instance == null) {
+ throw new Error("instance not found");
+ }
+ Instances.update({
+ host: toPuny(ps.host)
+ }, {
+ isSuspended: ps.isSuspended
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/get-index-stats.js b/packages/backend/built/server/api/endpoints/admin/get-index-stats.js
new file mode 100644
index 0000000..4d72180
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/get-index-stats.js
@@ -0,0 +1,24 @@
+import define from "../../define.js";
+import { db } from "../../../../db/postgre.js";
+export const meta = {
+ requireCredential: true,
+ requireModerator: true,
+ tags: [
+ "admin"
+ ]
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async ()=>{
+ const stats = await db.query("SELECT * FROM pg_indexes;").then((recs)=>{
+ const res = [];
+ for (const rec of recs){
+ res.push(rec);
+ }
+ return res;
+ });
+ return stats;
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/get-table-stats.js b/packages/backend/built/server/api/endpoints/admin/get-table-stats.js
new file mode 100644
index 0000000..b749f76
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/get-table-stats.js
@@ -0,0 +1,43 @@
+import { db } from "../../../../db/postgre.js";
+import define from "../../define.js";
+export const meta = {
+ requireCredential: true,
+ requireModerator: true,
+ tags: [
+ "admin"
+ ],
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ example: {
+ migrations: {
+ count: 66,
+ size: 32768
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async ()=>{
+ const sizes = await db.query(`
+ SELECT relname AS "table", reltuples as "count", pg_total_relation_size(C.oid) AS "size"
+ FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
+ WHERE nspname NOT IN ('pg_catalog', 'information_schema')
+ AND C.relkind <> 'i'
+ AND nspname !~ '^pg_toast';`).then((recs)=>{
+ const res = {};
+ for (const rec of recs){
+ res[rec.table] = {
+ count: parseInt(rec.count, 10),
+ size: parseInt(rec.size, 10)
+ };
+ }
+ return res;
+ });
+ return sizes;
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/get-user-ips.js b/packages/backend/built/server/api/endpoints/admin/get-user-ips.js
new file mode 100644
index 0000000..2784f0a
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/get-user-ips.js
@@ -0,0 +1,36 @@
+import { UserIps } from "../../../../models/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireAdmin: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const ips = await UserIps.find({
+ where: {
+ userId: ps.userId
+ },
+ order: {
+ createdAt: "DESC"
+ },
+ take: 30
+ });
+ return ips.map((x)=>({
+ ip: x.ip,
+ createdAt: x.createdAt.toISOString()
+ }));
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/invite.js b/packages/backend/built/server/api/endpoints/admin/invite.js
new file mode 100644
index 0000000..9b1e40a
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/invite.js
@@ -0,0 +1,45 @@
+import rndstr from "rndstr";
+import define from "../../define.js";
+import { RegistrationTickets } from "../../../../models/index.js";
+import { genId } from "../../../../misc/gen-id.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ code: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ example: "2ERUA5VR",
+ maxLength: 8,
+ minLength: 8
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async ()=>{
+ const code = rndstr({
+ length: 8,
+ chars: "2-9A-HJ-NP-Z"
+ });
+ await RegistrationTickets.insert({
+ id: genId(),
+ createdAt: new Date(),
+ code
+ });
+ return {
+ code
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/meta.js b/packages/backend/built/server/api/endpoints/admin/meta.js
new file mode 100644
index 0000000..b51132d
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/meta.js
@@ -0,0 +1,503 @@
+import config from "../../../../config/index.js";
+import { fetchMeta } from "../../../../misc/fetch-meta.js";
+import { MAX_NOTE_TEXT_LENGTH, MAX_CAPTION_TEXT_LENGTH } from "../../../../const.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "meta"
+ ],
+ requireCredential: true,
+ requireAdmin: true,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ driveCapacityPerLocalUserMb: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ driveCapacityPerRemoteUserMb: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ lua4frozenDatabaseCapacityMb: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ cacheRemoteFiles: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ emailRequiredForSignup: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ enableHcaptcha: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ hcaptchaSiteKey: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ enableRecaptcha: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ recaptchaSiteKey: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ swPublickey: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ mascotImageUrl: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ default: "/twemoji/1f440.svg"
+ },
+ bannerUrl: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ errorImageUrl: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ default: "/twemoji/1f480.svg"
+ },
+ iconUrl: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ maxNoteTextLength: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ maxCaptionTextLength: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ emojis: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ aliases: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ },
+ category: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ host: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ url: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "url"
+ }
+ }
+ }
+ },
+ enableEmail: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ enableGithubIntegration: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ enableDiscordIntegration: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ translatorAvailable: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ recommendedInstances: {
+ type: "array",
+ optional: true,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ },
+ pinnedUsers: {
+ type: "array",
+ optional: true,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ },
+ customMOTD: {
+ type: "array",
+ optional: true,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ },
+ customSplashIcons: {
+ type: "array",
+ optional: true,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ },
+ hiddenTags: {
+ type: "array",
+ optional: true,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ },
+ blockedHosts: {
+ type: "array",
+ optional: true,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ },
+ silencedHosts: {
+ type: "array",
+ optional: true,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ },
+ allowedHosts: {
+ type: "array",
+ optional: true,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ },
+ privateMode: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ secureMode: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ hcaptchaSecretKey: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ recaptchaSecretKey: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ summaryProxy: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ email: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ smtpSecure: {
+ type: "boolean",
+ optional: true,
+ nullable: false
+ },
+ smtpHost: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ smtpPort: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ smtpUser: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ smtpPass: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ swPrivateKey: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ useObjectStorage: {
+ type: "boolean",
+ optional: true,
+ nullable: false
+ },
+ objectStorageBaseUrl: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ objectStorageBucket: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ objectStoragePrefix: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ objectStorageEndpoint: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ objectStorageRegion: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ objectStoragePort: {
+ type: "number",
+ optional: true,
+ nullable: true
+ },
+ objectStorageAccessKey: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ objectStorageSecretKey: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ objectStorageUseSSL: {
+ type: "boolean",
+ optional: true,
+ nullable: false
+ },
+ objectStorageUseProxy: {
+ type: "boolean",
+ optional: true,
+ nullable: false
+ },
+ objectStorageSetPublicRead: {
+ type: "boolean",
+ optional: true,
+ nullable: false
+ },
+ enableIpLogging: {
+ type: "boolean",
+ optional: true,
+ nullable: false
+ },
+ enableActiveEmailValidation: {
+ type: "boolean",
+ optional: true,
+ nullable: false
+ },
+ defaultReaction: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ experimentalFeatures: {
+ type: "object",
+ optional: true,
+ nullable: true,
+ properties: {
+ postImports: {
+ type: "boolean"
+ }
+ }
+ },
+ enableServerMachineStats: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ enableIdenticonGeneration: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ donationLink: {
+ type: "string",
+ optional: true,
+ nullable: true
+ },
+ autofollowedAccount: {
+ type: "string",
+ optional: true,
+ nullable: true
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const instance = await fetchMeta(true);
+ return {
+ maintainerName: instance.maintainerName,
+ maintainerEmail: instance.maintainerEmail,
+ version: config.version,
+ name: instance.name,
+ uri: config.url,
+ description: instance.description,
+ langs: instance.langs,
+ tosUrl: instance.ToSUrl,
+ repositoryUrl: instance.repositoryUrl,
+ feedbackUrl: instance.feedbackUrl,
+ disableRegistration: instance.disableRegistration,
+ disableLocalTimeline: instance.disableLocalTimeline,
+ disableRecommendedTimeline: instance.disableRecommendedTimeline,
+ disableGlobalTimeline: instance.disableGlobalTimeline,
+ driveCapacityPerLocalUserMb: instance.localDriveCapacityMb,
+ driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb,
+ lua4frozenDatabaseCapacityMb: instance.lua4frozenDatabaseCapacityMb,
+ emailRequiredForSignup: instance.emailRequiredForSignup,
+ enableHcaptcha: instance.enableHcaptcha,
+ hcaptchaSiteKey: instance.hcaptchaSiteKey,
+ enableRecaptcha: instance.enableRecaptcha,
+ recaptchaSiteKey: instance.recaptchaSiteKey,
+ swPublickey: instance.swPublicKey,
+ themeColor: instance.themeColor,
+ mascotImageUrl: instance.mascotImageUrl,
+ bannerUrl: instance.bannerUrl,
+ errorImageUrl: instance.errorImageUrl,
+ iconUrl: instance.iconUrl,
+ backgroundImageUrl: instance.backgroundImageUrl,
+ logoImageUrl: instance.logoImageUrl,
+ maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
+ maxCaptionTextLength: MAX_CAPTION_TEXT_LENGTH,
+ defaultLightTheme: instance.defaultLightTheme,
+ defaultDarkTheme: instance.defaultDarkTheme,
+ enableEmail: instance.enableEmail,
+ translatorAvailable: instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null,
+ pinnedPages: instance.pinnedPages,
+ pinnedClipId: instance.pinnedClipId,
+ cacheRemoteFiles: instance.cacheRemoteFiles,
+ defaultReaction: instance.defaultReaction,
+ recommendedInstances: instance.recommendedInstances,
+ pinnedUsers: instance.pinnedUsers,
+ customMOTD: instance.customMOTD,
+ customSplashIcons: instance.customSplashIcons,
+ hiddenTags: instance.hiddenTags,
+ blockedHosts: instance.blockedHosts,
+ silencedHosts: instance.silencedHosts,
+ allowedHosts: instance.allowedHosts,
+ privateMode: instance.privateMode,
+ secureMode: instance.secureMode,
+ hcaptchaSecretKey: instance.hcaptchaSecretKey,
+ recaptchaSecretKey: instance.recaptchaSecretKey,
+ summalyProxy: instance.summalyProxy,
+ email: instance.email,
+ smtpSecure: instance.smtpSecure,
+ smtpHost: instance.smtpHost,
+ smtpPort: instance.smtpPort,
+ smtpUser: instance.smtpUser,
+ smtpPass: instance.smtpPass,
+ swPrivateKey: instance.swPrivateKey,
+ useObjectStorage: instance.useObjectStorage,
+ objectStorageBaseUrl: instance.objectStorageBaseUrl,
+ objectStorageBucket: instance.objectStorageBucket,
+ objectStoragePrefix: instance.objectStoragePrefix,
+ objectStorageEndpoint: instance.objectStorageEndpoint,
+ objectStorageRegion: instance.objectStorageRegion,
+ objectStoragePort: instance.objectStoragePort,
+ objectStorageAccessKey: instance.objectStorageAccessKey,
+ objectStorageSecretKey: instance.objectStorageSecretKey,
+ objectStorageUseSSL: instance.objectStorageUseSSL,
+ objectStorageUseProxy: instance.objectStorageUseProxy,
+ objectStorageSetPublicRead: instance.objectStorageSetPublicRead,
+ objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle,
+ deeplAuthKey: instance.deeplAuthKey,
+ deeplIsPro: instance.deeplIsPro,
+ libreTranslateApiUrl: instance.libreTranslateApiUrl,
+ libreTranslateApiKey: instance.libreTranslateApiKey,
+ enableIpLogging: instance.enableIpLogging,
+ enableActiveEmailValidation: instance.enableActiveEmailValidation,
+ experimentalFeatures: instance.experimentalFeatures,
+ enableServerMachineStats: instance.enableServerMachineStats,
+ enableIdenticonGeneration: instance.enableIdenticonGeneration,
+ donationLink: instance.donationLink,
+ autofollowedAccount: instance.autofollowedAccount
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/moderators/add.js b/packages/backend/built/server/api/endpoints/admin/moderators/add.js
new file mode 100644
index 0000000..b547027
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/moderators/add.js
@@ -0,0 +1,40 @@
+import define from "../../../define.js";
+import { Users } from "../../../../../models/index.js";
+import { publishInternalEvent } from "../../../../../services/stream.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireAdmin: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const user = await Users.findOneBy({
+ id: ps.userId
+ });
+ if (user == null) {
+ throw new Error("user not found");
+ }
+ if (user.isAdmin) {
+ throw new Error("cannot mark as moderator if admin user");
+ }
+ await Users.update(user.id, {
+ isModerator: true
+ });
+ publishInternalEvent("userChangeModeratorState", {
+ id: user.id,
+ isModerator: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/moderators/remove.js b/packages/backend/built/server/api/endpoints/admin/moderators/remove.js
new file mode 100644
index 0000000..bc1a7af
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/moderators/remove.js
@@ -0,0 +1,37 @@
+import define from "../../../define.js";
+import { Users } from "../../../../../models/index.js";
+import { publishInternalEvent } from "../../../../../services/stream.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireAdmin: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const user = await Users.findOneBy({
+ id: ps.userId
+ });
+ if (user == null) {
+ throw new Error("user not found");
+ }
+ await Users.update(user.id, {
+ isModerator: false
+ });
+ publishInternalEvent("userChangeModeratorState", {
+ id: user.id,
+ isModerator: false
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/plans/create.js b/packages/backend/built/server/api/endpoints/admin/plans/create.js
new file mode 100644
index 0000000..4d9e730
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/plans/create.js
@@ -0,0 +1,59 @@
+import define from "../../../define.js";
+import { Plans } from "../../../../../models/index.js";
+import { genId } from "../../../../../misc/gen-id.js";
+import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireAdmin: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ name: {
+ type: "string",
+ minLength: 1,
+ maxLength: 128
+ },
+ icon: {
+ type: "string",
+ minLength: 1,
+ maxLength: 64
+ },
+ description: {
+ type: "string",
+ maxLength: 512,
+ default: ""
+ }
+ },
+ required: [
+ "name",
+ "icon"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const name = ps.name.trim();
+ const icon = ps.icon.trim();
+ const description = ps.description.trim();
+ if (name === "") throw new Error("name is empty");
+ if (icon === "") throw new Error("icon is empty");
+ const exists = await Plans.findOneBy({
+ name
+ });
+ if (exists) throw new Error("plan name already exists");
+ const plan = await Plans.insert({
+ id: genId(),
+ createdAt: new Date(),
+ updatedAt: null,
+ name,
+ icon,
+ description
+ }).then((x)=>Plans.findOneByOrFail(x.identifiers[0]));
+ insertModerationLog(me, "createPlan", {
+ planId: plan.id,
+ name
+ });
+ return await Plans.pack(plan);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/plans/delete.js b/packages/backend/built/server/api/endpoints/admin/plans/delete.js
new file mode 100644
index 0000000..20d026e
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/plans/delete.js
@@ -0,0 +1,32 @@
+import define from "../../../define.js";
+import { Plans } from "../../../../../models/index.js";
+import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireAdmin: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ planId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "planId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const plan = await Plans.findOneByOrFail({
+ id: ps.planId
+ });
+ await Plans.delete(plan.id);
+ insertModerationLog(me, "deletePlan", {
+ planId: plan.id,
+ name: plan.name
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/plans/list.js b/packages/backend/built/server/api/endpoints/admin/plans/list.js
new file mode 100644
index 0000000..bb62c68
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/plans/list.js
@@ -0,0 +1,22 @@
+import define from "../../../define.js";
+import { Plans } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireAdmin: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async ()=>{
+ const plans = await Plans.find({
+ order: {
+ createdAt: "ASC"
+ }
+ });
+ return await Plans.packMany(plans);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/plans/update.js b/packages/backend/built/server/api/endpoints/admin/plans/update.js
new file mode 100644
index 0000000..4bb852e
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/plans/update.js
@@ -0,0 +1,64 @@
+import define from "../../../define.js";
+import { Plans } from "../../../../../models/index.js";
+import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireAdmin: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ planId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ name: {
+ type: "string",
+ minLength: 1,
+ maxLength: 128
+ },
+ icon: {
+ type: "string",
+ minLength: 1,
+ maxLength: 64
+ },
+ description: {
+ type: "string",
+ maxLength: 512,
+ default: ""
+ }
+ },
+ required: [
+ "planId",
+ "name",
+ "icon"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const plan = await Plans.findOneByOrFail({
+ id: ps.planId
+ });
+ const name = ps.name.trim();
+ const icon = ps.icon.trim();
+ const description = ps.description.trim();
+ if (name === "") throw new Error("name is empty");
+ if (icon === "") throw new Error("icon is empty");
+ const exists = await Plans.findOneBy({
+ name
+ });
+ if (exists && exists.id !== plan.id) throw new Error("plan name already exists");
+ await Plans.update(plan.id, {
+ updatedAt: new Date(),
+ name,
+ icon,
+ description
+ });
+ insertModerationLog(me, "updatePlan", {
+ planId: plan.id,
+ name
+ });
+ return await Plans.pack(plan.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/promo/create.js b/packages/backend/built/server/api/endpoints/admin/promo/create.js
new file mode 100644
index 0000000..e7f4ebb
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/promo/create.js
@@ -0,0 +1,79 @@
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { Notes, PromoNotes } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "ee449fbe-af2a-453b-9cae-cf2fe7c895fc"
+ },
+ alreadyPromoted: {
+ message: "The note has already promoted.",
+ code: "ALREADY_PROMOTED",
+ id: "ae427aa2-7a41-484f-a18c-2c1104051604"
+ },
+ notAdService: {
+ message: "The note must have #AdService.",
+ code: "NOT_AD_SERVICE",
+ id: "970a4d57-7b67-4540-80ea-f210041724ef"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ expiresAt: {
+ type: "integer"
+ },
+ credits: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100000,
+ default: 1
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const note = await Notes.findOneBy({
+ id: ps.noteId
+ });
+ if (note == null) {
+ throw new ApiError(meta.errors.noSuchNote);
+ }
+ if (!note.tags.includes("adservice")) {
+ throw new ApiError(meta.errors.notAdService);
+ }
+ const expiresAt = ps.expiresAt ? new Date(ps.expiresAt) : new Date(Date.now() + 30 * 86400000);
+ const credits = ps.credits ?? 1;
+ const exist = await PromoNotes.findOneBy({
+ noteId: note.id
+ });
+ if (exist) {
+ await PromoNotes.update(note.id, {
+ expiresAt: exist.expiresAt.getTime() > expiresAt.getTime() ? exist.expiresAt : expiresAt,
+ totalCredits: exist.totalCredits + credits,
+ remainingCredits: exist.remainingCredits + credits
+ });
+ return;
+ }
+ await PromoNotes.insert({
+ noteId: note.id,
+ expiresAt,
+ totalCredits: credits,
+ remainingCredits: credits,
+ userId: note.userId
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/promo/list.js b/packages/backend/built/server/api/endpoints/admin/promo/list.js
new file mode 100644
index 0000000..7bdb28e
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/promo/list.js
@@ -0,0 +1,52 @@
+import rndstr from "rndstr";
+import { Notes, PromoNotes } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { makePaginationQuery } from "../../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId).andWhere(`'adservice' = ANY(note.tags)`).innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser");
+ const notes = await query.take(ps.limit).getMany();
+ if (notes.length === 0) return [];
+ const promos = await PromoNotes.findBy(notes.map((note)=>({
+ noteId: note.id
+ })));
+ return Promise.all(notes.map(async (note)=>{
+ const promo = promos.find((item)=>item.noteId === note.id);
+ const expiredCredits = promo && promo.expiresAt.getTime() <= Date.now() ? promo.remainingCredits : 0;
+ note._prId_ = rndstr("a-z0-9", 8);
+ return {
+ id: note.id,
+ note: await Notes.pack(note, user),
+ expiresAt: promo?.expiresAt.toISOString() ?? null,
+ totalCredits: promo?.totalCredits ?? 0,
+ remainingCredits: promo?.remainingCredits ?? 0,
+ expiredCredits
+ };
+ }));
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/queue/clear.js b/packages/backend/built/server/api/endpoints/admin/queue/clear.js
new file mode 100644
index 0000000..07fbd3b
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/queue/clear.js
@@ -0,0 +1,19 @@
+import define from "../../../define.js";
+import { destroy } from "../../../../../queue/index.js";
+import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ destroy();
+ insertModerationLog(me, "clearQueue");
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/queue/deliver-delayed.js b/packages/backend/built/server/api/endpoints/admin/queue/deliver-delayed.js
new file mode 100644
index 0000000..c0f1251
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/queue/deliver-delayed.js
@@ -0,0 +1,60 @@
+import { deliverQueue } from "../../../../../queue/queues.js";
+import { URL } from "node:url";
+import define from "../../../define.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ anyOf: [
+ {
+ type: "string"
+ },
+ {
+ type: "number"
+ }
+ ]
+ }
+ },
+ example: [
+ [
+ "example.com",
+ 12
+ ]
+ ]
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps)=>{
+ const jobs = await deliverQueue.getJobs([
+ "delayed"
+ ]);
+ const res = [];
+ for (const job of jobs){
+ const host = new URL(job.data.to).host;
+ if (res.find((x)=>x[0] === host)) {
+ res.find((x)=>x[0] === host)[1]++;
+ } else {
+ res.push([
+ host,
+ 1
+ ]);
+ }
+ }
+ res.sort((a, b)=>b[1] - a[1]);
+ return res;
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/queue/inbox-delayed.js b/packages/backend/built/server/api/endpoints/admin/queue/inbox-delayed.js
new file mode 100644
index 0000000..9cbf6e7
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/queue/inbox-delayed.js
@@ -0,0 +1,60 @@
+import { URL } from "node:url";
+import define from "../../../define.js";
+import { inboxQueue } from "../../../../../queue/queues.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ anyOf: [
+ {
+ type: "string"
+ },
+ {
+ type: "number"
+ }
+ ]
+ }
+ },
+ example: [
+ [
+ "example.com",
+ 12
+ ]
+ ]
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps)=>{
+ const jobs = await inboxQueue.getJobs([
+ "delayed"
+ ]);
+ const res = [];
+ for (const job of jobs){
+ const host = new URL(job.data.signature.keyId).host;
+ if (res.find((x)=>x[0] === host)) {
+ res.find((x)=>x[0] === host)[1]++;
+ } else {
+ res.push([
+ host,
+ 1
+ ]);
+ }
+ }
+ res.sort((a, b)=>b[1] - a[1]);
+ return res;
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/queue/stats.js b/packages/backend/built/server/api/endpoints/admin/queue/stats.js
new file mode 100644
index 0000000..425b9e6
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/queue/stats.js
@@ -0,0 +1,53 @@
+import { deliverQueue, inboxQueue, dbQueue, objectStorageQueue } from "../../../../../queue/queues.js";
+import define from "../../../define.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ deliver: {
+ optional: false,
+ nullable: false,
+ ref: "QueueCount"
+ },
+ inbox: {
+ optional: false,
+ nullable: false,
+ ref: "QueueCount"
+ },
+ db: {
+ optional: false,
+ nullable: false,
+ ref: "QueueCount"
+ },
+ objectStorage: {
+ optional: false,
+ nullable: false,
+ ref: "QueueCount"
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps)=>{
+ const deliverJobCounts = await deliverQueue.getJobCounts();
+ const inboxJobCounts = await inboxQueue.getJobCounts();
+ const dbJobCounts = await dbQueue.getJobCounts();
+ const objectStorageJobCounts = await objectStorageQueue.getJobCounts();
+ return {
+ deliver: deliverJobCounts,
+ inbox: inboxJobCounts,
+ db: dbJobCounts,
+ objectStorage: objectStorageJobCounts
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/relays/add.js b/packages/backend/built/server/api/endpoints/admin/relays/add.js
new file mode 100644
index 0000000..bebc99e
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/relays/add.js
@@ -0,0 +1,68 @@
+import { URL } from "node:url";
+import define from "../../../define.js";
+import { addRelay } from "../../../../../services/relay.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ errors: {
+ invalidUrl: {
+ message: "Invalid URL",
+ code: "INVALID_URL",
+ id: "fb8c92d3-d4e5-44e7-b3d4-800d5cef8b2c"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ inbox: {
+ description: "URL of the inbox, must be a https scheme URL",
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "url"
+ },
+ status: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ default: "requesting",
+ enum: [
+ "requesting",
+ "accepted",
+ "rejected"
+ ]
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ inbox: {
+ type: "string"
+ }
+ },
+ required: [
+ "inbox"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ try {
+ if (new URL(ps.inbox).protocol !== "https:") throw new Error("https only");
+ } catch {
+ throw new ApiError(meta.errors.invalidUrl);
+ }
+ return await addRelay(ps.inbox);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/relays/list.js b/packages/backend/built/server/api/endpoints/admin/relays/list.js
new file mode 100644
index 0000000..34c31b0
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/relays/list.js
@@ -0,0 +1,52 @@
+import define from "../../../define.js";
+import { listRelay } from "../../../../../services/relay.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ inbox: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "url"
+ },
+ status: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ default: "requesting",
+ enum: [
+ "requesting",
+ "accepted",
+ "rejected"
+ ]
+ }
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ return await listRelay();
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/relays/remove.js b/packages/backend/built/server/api/endpoints/admin/relays/remove.js
new file mode 100644
index 0000000..5ae8bf2
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/relays/remove.js
@@ -0,0 +1,23 @@
+import define from "../../../define.js";
+import { removeRelay } from "../../../../../services/relay.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ inbox: {
+ type: "string"
+ }
+ },
+ required: [
+ "inbox"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ return await removeRelay(ps.inbox);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/reset-password.js b/packages/backend/built/server/api/endpoints/admin/reset-password.js
new file mode 100644
index 0000000..4159375
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/reset-password.js
@@ -0,0 +1,61 @@
+import define from "../../define.js";
+// import bcrypt from "bcryptjs";
+import rndstr from "rndstr";
+import { Users, UserProfiles } from "../../../../models/index.js";
+import { hashPassword } from "../../../../misc/password.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ password: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ minLength: 8,
+ maxLength: 8
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const user = await Users.findOneBy({
+ id: ps.userId
+ });
+ if (user == null) {
+ throw new Error("user not found");
+ }
+ if (user.isAdmin) {
+ throw new Error("cannot reset password of admin");
+ }
+ const passwd = rndstr("a-zA-Z0-9", 8);
+ // Generate hash of password
+ // const hash = bcrypt.hashSync(passwd);
+ const hash = await hashPassword(passwd);
+ await UserProfiles.update({
+ userId: user.id
+ }, {
+ password: hash
+ });
+ return {
+ password: passwd
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/resolve-abuse-user-report.js b/packages/backend/built/server/api/endpoints/admin/resolve-abuse-user-report.js
new file mode 100644
index 0000000..7a0922b
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/resolve-abuse-user-report.js
@@ -0,0 +1,51 @@
+import define from "../../define.js";
+import { AbuseUserReports, Users } from "../../../../models/index.js";
+import { getInstanceActor } from "../../../../services/instance-actor.js";
+import { deliver } from "../../../../queue/index.js";
+import { renderActivity } from "../../../../remote/activitypub/renderer/index.js";
+import { renderFlag } from "../../../../remote/activitypub/renderer/flag.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ reportId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ forward: {
+ type: "boolean",
+ default: false
+ }
+ },
+ required: [
+ "reportId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const report = await AbuseUserReports.findOneByOrFail({
+ id: ps.reportId
+ });
+ if (report == null) {
+ throw new Error("report not found");
+ }
+ if (ps.forward && report.targetUserHost != null) {
+ const actor = await getInstanceActor();
+ const targetUser = await Users.findOneByOrFail({
+ id: report.targetUserId
+ });
+ deliver(actor, renderActivity(renderFlag(actor, [
+ targetUser.uri
+ ], report.comment)), targetUser.inbox);
+ }
+ await AbuseUserReports.update(report.id, {
+ resolved: true,
+ assigneeId: me.id,
+ forwarded: ps.forward && report.targetUserHost != null
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/resolve-verified-badge-request.js b/packages/backend/built/server/api/endpoints/admin/resolve-verified-badge-request.js
new file mode 100644
index 0000000..b8fa52f
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/resolve-verified-badge-request.js
@@ -0,0 +1,48 @@
+import define from "../../define.js";
+import { Users, VerifiedBadgeRequests } from "../../../../models/index.js";
+import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireAdmin: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ requestId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ approve: {
+ type: "boolean"
+ }
+ },
+ required: [
+ "requestId",
+ "approve"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const request = await VerifiedBadgeRequests.findOneByOrFail({
+ id: ps.requestId
+ });
+ if (request.status !== "pending") {
+ throw new Error("request already resolved");
+ }
+ if (ps.approve) {
+ await Users.update(request.userId, {
+ isVerified: true
+ });
+ }
+ await VerifiedBadgeRequests.update(request.id, {
+ status: ps.approve ? "approved" : "rejected",
+ resolvedAt: new Date(),
+ resolverId: me.id
+ });
+ insertModerationLog(me, ps.approve ? "approveVerifiedBadgeRequest" : "rejectVerifiedBadgeRequest", {
+ targetId: request.userId,
+ requestId: request.id
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/send-email.js b/packages/backend/built/server/api/endpoints/admin/send-email.js
new file mode 100644
index 0000000..6c81757
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/send-email.js
@@ -0,0 +1,31 @@
+import define from "../../define.js";
+import { sendEmail } from "../../../../services/send-email.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ to: {
+ type: "string"
+ },
+ subject: {
+ type: "string"
+ },
+ text: {
+ type: "string"
+ }
+ },
+ required: [
+ "to",
+ "subject",
+ "text"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ await sendEmail(ps.to, ps.subject, ps.text, ps.text);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/send-mod-mail.js b/packages/backend/built/server/api/endpoints/admin/send-mod-mail.js
new file mode 100644
index 0000000..7ad6d29
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/send-mod-mail.js
@@ -0,0 +1,70 @@
+import * as sanitizeHtml from "sanitize-html";
+import define from "../../define.js";
+import { Users, UserProfiles } from "../../../../models/index.js";
+import { ApiError } from "../../error.js";
+import { sendEmail } from "../../../../services/send-email.js";
+import { createNotification } from "../../../../services/create-notification.js";
+import config from "../../../../config/index.js";
+export const meta = {
+ tags: [
+ "users"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ description: "Send a moderation notice.",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "1acefcb5-0959-43fd-9685-b48305736cb5"
+ },
+ noEmail: {
+ message: "No email for user.",
+ code: "NO_EMAIL",
+ id: "ac9d2d22-ef73-11ed-a05b-0242ac120003"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ comment: {
+ type: "string",
+ minLength: 1,
+ maxLength: 2048
+ }
+ },
+ required: [
+ "userId",
+ "comment"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const [user, profile] = await Promise.all([
+ Users.findOneBy({
+ id: ps.userId
+ }),
+ UserProfiles.findOneBy({
+ userId: ps.userId
+ })
+ ]);
+ if (user == null || profile == null) {
+ throw new ApiError(meta.errors.noSuchUser);
+ }
+ createNotification(user.id, "app", {
+ customBody: ps.comment,
+ customHeader: "Moderation Notice",
+ customIcon: config?.images?.info
+ });
+ setImmediate(async ()=>{
+ const email = profile.email;
+ if (email == null) {
+ throw new ApiError(meta.errors.noEmail);
+ }
+ sendEmail(email, "Moderation notice", sanitizeHtml(ps.comment), sanitizeHtml(ps.comment));
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/server-info.js b/packages/backend/built/server/api/endpoints/admin/server-info.js
new file mode 100644
index 0000000..fb4ea08
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/server-info.js
@@ -0,0 +1,137 @@
+import * as os from "node:os";
+import si from "systeminformation";
+import define from "../../define.js";
+import { redisClient } from "../../../../db/redis.js";
+import { db } from "../../../../db/postgre.js";
+export const meta = {
+ requireCredential: true,
+ requireModerator: true,
+ tags: [
+ "admin",
+ "meta"
+ ],
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ machine: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ os: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ example: "linux"
+ },
+ node: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ psql: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ cpu: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ model: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ cores: {
+ type: "number",
+ optional: false,
+ nullable: false
+ }
+ }
+ },
+ mem: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ total: {
+ type: "number",
+ optional: false,
+ nullable: false,
+ format: "bytes"
+ }
+ }
+ },
+ fs: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ total: {
+ type: "number",
+ optional: false,
+ nullable: false,
+ format: "bytes"
+ },
+ used: {
+ type: "number",
+ optional: false,
+ nullable: false,
+ format: "bytes"
+ }
+ }
+ },
+ net: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ interface: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ example: "eth0"
+ }
+ }
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async ()=>{
+ const memStats = await si.mem();
+ const fsStats = await si.fsSize();
+ const netInterface = await si.networkInterfaceDefault();
+ const redisServerInfo = await redisClient.info("Server");
+ const m = redisServerInfo.match(new RegExp("^redis_version:(.*)", "m"));
+ const redis_version = m?.[1];
+ return {
+ machine: os.hostname(),
+ os: os.platform(),
+ node: process.version,
+ psql: await db.query("SHOW server_version").then((x)=>x[0].server_version),
+ redis: redis_version,
+ cpu: {
+ model: os.cpus()[0].model,
+ cores: os.cpus().length
+ },
+ mem: {
+ total: memStats.total
+ },
+ fs: {
+ total: fsStats[0].size,
+ used: fsStats[0].used
+ },
+ net: {
+ interface: netInterface
+ }
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/set-user-verified.js b/packages/backend/built/server/api/endpoints/admin/set-user-verified.js
new file mode 100644
index 0000000..721a220
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/set-user-verified.js
@@ -0,0 +1,40 @@
+import define from "../../define.js";
+import { Users } from "../../../../models/index.js";
+import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireAdmin: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ isVerified: {
+ type: "boolean"
+ }
+ },
+ required: [
+ "userId",
+ "isVerified"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const user = await Users.findOneBy({
+ id: ps.userId
+ });
+ if (user == null) {
+ throw new Error("user not found");
+ }
+ await Users.update(user.id, {
+ isVerified: ps.isVerified
+ });
+ insertModerationLog(me, ps.isVerified ? "markAsVerified" : "unmarkAsVerified", {
+ targetId: user.id
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/show-moderation-logs.js b/packages/backend/built/server/api/endpoints/admin/show-moderation-logs.js
new file mode 100644
index 0000000..1c680ac
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/show-moderation-logs.js
@@ -0,0 +1,81 @@
+import define from "../../define.js";
+import { ModerationLogs } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ type: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ info: {
+ type: "object",
+ optional: false,
+ nullable: false
+ },
+ userId: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ user: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserDetailed"
+ }
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps)=>{
+ const query = makePaginationQuery(ModerationLogs.createQueryBuilder("report"), ps.sinceId, ps.untilId);
+ const reports = await query.take(ps.limit).getMany();
+ return await ModerationLogs.packMany(reports);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/show-user.js b/packages/backend/built/server/api/endpoints/admin/show-user.js
new file mode 100644
index 0000000..af4c910
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/show-user.js
@@ -0,0 +1,86 @@
+import { Signins, UserProfiles, Users } from "../../../../models/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ res: {
+ type: "object",
+ nullable: false,
+ optional: false
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const [user, profile] = await Promise.all([
+ Users.findOneBy({
+ id: ps.userId
+ }),
+ UserProfiles.findOneBy({
+ userId: ps.userId
+ })
+ ]);
+ if (user == null || profile == null) {
+ throw new Error("user not found");
+ }
+ const _me = await Users.findOneByOrFail({
+ id: me.id
+ });
+ if (_me.isModerator && !_me.isAdmin && user.isAdmin) {
+ throw new Error("cannot show info of admin");
+ }
+ if (!_me.isAdmin) {
+ return {
+ isModerator: user.isModerator,
+ isSilenced: user.isSilenced,
+ isSuspended: user.isSuspended,
+ moderationNote: profile.moderationNote
+ };
+ }
+ const maskedKeys = [
+ "accessToken",
+ "accessTokenSecret",
+ "refreshToken"
+ ];
+ Object.keys(profile.integrations).forEach((integration)=>{
+ maskedKeys.forEach((key)=>profile.integrations[integration][key] = "");
+ });
+ const signins = await Signins.findBy({
+ userId: user.id
+ });
+ return {
+ email: profile.email,
+ emailVerified: profile.emailVerified,
+ autoAcceptFollowed: profile.autoAcceptFollowed,
+ noCrawle: profile.noCrawle,
+ preventAiLearning: profile.preventAiLearning,
+ alwaysMarkNsfw: profile.alwaysMarkNsfw,
+ carefulBot: profile.carefulBot,
+ injectFeaturedNote: profile.injectFeaturedNote,
+ receiveAnnouncementEmail: profile.receiveAnnouncementEmail,
+ integrations: profile.integrations,
+ mutedWords: profile.mutedWords,
+ mutedInstances: profile.mutedInstances,
+ mutingNotificationTypes: profile.mutingNotificationTypes,
+ isModerator: user.isModerator,
+ isVerified: user.isVerified,
+ isSilenced: user.isSilenced,
+ isSuspended: user.isSuspended,
+ lastActiveDate: user.lastActiveDate,
+ moderationNote: profile.moderationNote,
+ signins
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/show-users.js b/packages/backend/built/server/api/endpoints/admin/show-users.js
new file mode 100644
index 0000000..ae0606e
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/show-users.js
@@ -0,0 +1,161 @@
+import { Users } from "../../../../models/index.js";
+import define from "../../define.js";
+import { sqlLikeEscape } from "../../../../misc/sql-like-escape.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true,
+ res: {
+ type: "array",
+ nullable: false,
+ optional: false,
+ items: {
+ type: "object",
+ nullable: false,
+ optional: false,
+ ref: "UserDetailed"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ offset: {
+ type: "integer",
+ default: 0
+ },
+ sort: {
+ type: "string",
+ enum: [
+ "+follower",
+ "-follower",
+ "+createdAt",
+ "-createdAt",
+ "+updatedAt",
+ "-updatedAt"
+ ]
+ },
+ state: {
+ type: "string",
+ enum: [
+ "all",
+ "alive",
+ "available",
+ "admin",
+ "moderator",
+ "adminOrModerator",
+ "silenced",
+ "suspended",
+ "verified"
+ ],
+ default: "all"
+ },
+ origin: {
+ type: "string",
+ enum: [
+ "combined",
+ "local",
+ "remote"
+ ],
+ default: "combined"
+ },
+ username: {
+ type: "string",
+ nullable: true,
+ default: null
+ },
+ hostname: {
+ type: "string",
+ nullable: true,
+ default: null,
+ description: "The local host is represented with `null`."
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = Users.createQueryBuilder("user");
+ switch(ps.state){
+ case "available":
+ query.where("user.isSuspended = FALSE");
+ break;
+ case "admin":
+ query.where("user.isAdmin = TRUE");
+ break;
+ case "moderator":
+ query.where("user.isModerator = TRUE");
+ break;
+ case "adminOrModerator":
+ query.where("user.isAdmin = TRUE OR user.isModerator = TRUE");
+ break;
+ case "alive":
+ query.where("user.updatedAt > :date", {
+ date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5)
+ });
+ break;
+ case "silenced":
+ query.where("user.isSilenced = TRUE");
+ break;
+ case "suspended":
+ query.where("user.isSuspended = TRUE");
+ break;
+ case "verified":
+ query.where("user.isVerified = TRUE");
+ break;
+ }
+ switch(ps.origin){
+ case "local":
+ query.andWhere("user.host IS NULL");
+ break;
+ case "remote":
+ query.andWhere("user.host IS NOT NULL");
+ break;
+ }
+ if (ps.username) {
+ query.andWhere("user.usernameLower like :username", {
+ username: `${sqlLikeEscape(ps.username.toLowerCase())}%`
+ });
+ }
+ if (ps.hostname) {
+ query.andWhere("user.host = :hostname", {
+ hostname: ps.hostname.toLowerCase()
+ });
+ }
+ switch(ps.sort){
+ case "+follower":
+ query.orderBy("user.followersCount", "DESC");
+ break;
+ case "-follower":
+ query.orderBy("user.followersCount", "ASC");
+ break;
+ case "+createdAt":
+ query.orderBy("user.createdAt", "DESC");
+ break;
+ case "-createdAt":
+ query.orderBy("user.createdAt", "ASC");
+ break;
+ case "+updatedAt":
+ query.orderBy("user.updatedAt", "DESC", "NULLS LAST");
+ break;
+ case "-updatedAt":
+ query.orderBy("user.updatedAt", "ASC", "NULLS FIRST");
+ break;
+ default:
+ query.orderBy("user.id", "ASC");
+ break;
+ }
+ query.take(ps.limit);
+ query.skip(ps.offset);
+ const users = await query.getMany();
+ return await Users.packMany(users, me, {
+ detail: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/silence-user.js b/packages/backend/built/server/api/endpoints/admin/silence-user.js
new file mode 100644
index 0000000..d18dbd7
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/silence-user.js
@@ -0,0 +1,44 @@
+import define from "../../define.js";
+import { Users } from "../../../../models/index.js";
+import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
+import { publishInternalEvent } from "../../../../services/stream.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const user = await Users.findOneBy({
+ id: ps.userId
+ });
+ if (user == null) {
+ throw new Error("user not found");
+ }
+ if (user.isAdmin) {
+ throw new Error("cannot silence admin");
+ }
+ await Users.update(user.id, {
+ isSilenced: true
+ });
+ publishInternalEvent("userChangeSilencedState", {
+ id: user.id,
+ isSilenced: true
+ });
+ insertModerationLog(me, "silence", {
+ targetId: user.id
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/suspend-user.js b/packages/backend/built/server/api/endpoints/admin/suspend-user.js
new file mode 100644
index 0000000..bcdf6da
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/suspend-user.js
@@ -0,0 +1,76 @@
+import define from "../../define.js";
+import deleteFollowing from "../../../../services/following/delete.js";
+import { Users, Followings, Notifications } from "../../../../models/index.js";
+import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
+import { doPostSuspend } from "../../../../services/suspend-user.js";
+import { publishUserEvent } from "../../../../services/stream.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const user = await Users.findOneBy({
+ id: ps.userId
+ });
+ if (user == null) {
+ throw new Error("user not found");
+ }
+ if (user.isAdmin) {
+ throw new Error("cannot suspend admin");
+ }
+ if (user.isModerator) {
+ throw new Error("cannot suspend moderator");
+ }
+ await Users.update(user.id, {
+ isSuspended: true
+ });
+ insertModerationLog(me, "suspend", {
+ targetId: user.id
+ });
+ // Terminate streaming
+ if (Users.isLocalUser(user)) {
+ publishUserEvent(user.id, "terminate", {});
+ }
+ (async ()=>{
+ await doPostSuspend(user).catch((e)=>{});
+ await unFollowAll(user).catch((e)=>{});
+ await readAllNotify(user).catch((e)=>{});
+ })();
+});
+async function unFollowAll(follower) {
+ const followings = await Followings.findBy({
+ followerId: follower.id
+ });
+ for (const following of followings){
+ const followee = await Users.findOneBy({
+ id: following.followeeId
+ });
+ if (followee == null) {
+ throw new Error(`Cant find followee ${following.followeeId}`);
+ }
+ await deleteFollowing(follower, followee, true);
+ }
+}
+async function readAllNotify(notifier) {
+ await Notifications.update({
+ notifierId: notifier.id,
+ isRead: false
+ }, {
+ isRead: true
+ });
+}
diff --git a/packages/backend/built/server/api/endpoints/admin/unsilence-user.js b/packages/backend/built/server/api/endpoints/admin/unsilence-user.js
new file mode 100644
index 0000000..0faa93a
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/unsilence-user.js
@@ -0,0 +1,41 @@
+import define from "../../define.js";
+import { Users } from "../../../../models/index.js";
+import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
+import { publishInternalEvent } from "../../../../services/stream.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const user = await Users.findOneBy({
+ id: ps.userId
+ });
+ if (user == null) {
+ throw new Error("user not found");
+ }
+ await Users.update(user.id, {
+ isSilenced: false
+ });
+ publishInternalEvent("userChangeSilencedState", {
+ id: user.id,
+ isSilenced: false
+ });
+ insertModerationLog(me, "unsilence", {
+ targetId: user.id
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/unsuspend-user.js b/packages/backend/built/server/api/endpoints/admin/unsuspend-user.js
new file mode 100644
index 0000000..f11b946
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/unsuspend-user.js
@@ -0,0 +1,38 @@
+import define from "../../define.js";
+import { Users } from "../../../../models/index.js";
+import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
+import { doPostUnsuspend } from "../../../../services/unsuspend-user.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const user = await Users.findOneBy({
+ id: ps.userId
+ });
+ if (user == null) {
+ throw new Error("user not found");
+ }
+ await Users.update(user.id, {
+ isSuspended: false
+ });
+ insertModerationLog(me, "unsuspend", {
+ targetId: user.id
+ });
+ doPostUnsuspend(user);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/update-meta.js b/packages/backend/built/server/api/endpoints/admin/update-meta.js
new file mode 100644
index 0000000..8ec51fe
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/update-meta.js
@@ -0,0 +1,671 @@
+import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
+import define from "../../define.js";
+import { Metas } from "../../../../models/index.js";
+import { Users } from "../../../../models/index.js";
+import { IsNull } from "typeorm";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireAdmin: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ disableRegistration: {
+ type: "boolean",
+ nullable: true
+ },
+ disableLocalTimeline: {
+ type: "boolean",
+ nullable: true
+ },
+ disableRecommendedTimeline: {
+ type: "boolean",
+ nullable: true
+ },
+ disableGlobalTimeline: {
+ type: "boolean",
+ nullable: true
+ },
+ defaultReaction: {
+ type: "string",
+ nullable: true
+ },
+ recommendedInstances: {
+ type: "array",
+ nullable: true,
+ items: {
+ type: "string"
+ }
+ },
+ pinnedUsers: {
+ type: "array",
+ nullable: true,
+ items: {
+ type: "string"
+ }
+ },
+ customMOTD: {
+ type: "array",
+ nullable: true,
+ items: {
+ type: "string"
+ }
+ },
+ customSplashIcons: {
+ type: "array",
+ nullable: true,
+ items: {
+ type: "string"
+ }
+ },
+ hiddenTags: {
+ type: "array",
+ nullable: true,
+ items: {
+ type: "string"
+ }
+ },
+ blockedHosts: {
+ type: "array",
+ nullable: true,
+ items: {
+ type: "string"
+ }
+ },
+ silencedHosts: {
+ type: "array",
+ nullable: true,
+ items: {
+ type: "string"
+ }
+ },
+ allowedHosts: {
+ type: "array",
+ nullable: true,
+ items: {
+ type: "string"
+ }
+ },
+ secureMode: {
+ type: "boolean",
+ nullable: true
+ },
+ privateMode: {
+ type: "boolean",
+ nullable: true
+ },
+ themeColor: {
+ type: "string",
+ nullable: true,
+ pattern: "^#[0-9a-fA-F]{6}$"
+ },
+ mascotImageUrl: {
+ type: "string",
+ nullable: true
+ },
+ bannerUrl: {
+ type: "string",
+ nullable: true
+ },
+ logoImageUrl: {
+ type: "string",
+ nullable: true
+ },
+ errorImageUrl: {
+ type: "string",
+ nullable: true
+ },
+ iconUrl: {
+ type: "string",
+ nullable: true
+ },
+ backgroundImageUrl: {
+ type: "string",
+ nullable: true
+ },
+ name: {
+ type: "string",
+ nullable: true
+ },
+ description: {
+ type: "string",
+ nullable: true
+ },
+ defaultLightTheme: {
+ type: "string",
+ nullable: true
+ },
+ defaultDarkTheme: {
+ type: "string",
+ nullable: true
+ },
+ localDriveCapacityMb: {
+ type: "integer"
+ },
+ remoteDriveCapacityMb: {
+ type: "integer"
+ },
+ lua4frozenDatabaseCapacityMb: {
+ type: "integer"
+ },
+ cacheRemoteFiles: {
+ type: "boolean"
+ },
+ emailRequiredForSignup: {
+ type: "boolean"
+ },
+ enableHcaptcha: {
+ type: "boolean"
+ },
+ hcaptchaSiteKey: {
+ type: "string",
+ nullable: true
+ },
+ hcaptchaSecretKey: {
+ type: "string",
+ nullable: true
+ },
+ enableRecaptcha: {
+ type: "boolean"
+ },
+ recaptchaSiteKey: {
+ type: "string",
+ nullable: true
+ },
+ recaptchaSecretKey: {
+ type: "string",
+ nullable: true
+ },
+ maintainerName: {
+ type: "string",
+ nullable: true
+ },
+ maintainerEmail: {
+ type: "string",
+ nullable: true
+ },
+ pinnedPages: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ },
+ pinnedClipId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ langs: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ },
+ summalyProxy: {
+ type: "string",
+ nullable: true
+ },
+ deeplAuthKey: {
+ type: "string",
+ nullable: true
+ },
+ deeplIsPro: {
+ type: "boolean"
+ },
+ libreTranslateApiUrl: {
+ type: "string",
+ nullable: true
+ },
+ libreTranslateApiKey: {
+ type: "string",
+ nullable: true
+ },
+ enableGithubIntegration: {
+ type: "boolean"
+ },
+ githubClientId: {
+ type: "string",
+ nullable: true
+ },
+ githubClientSecret: {
+ type: "string",
+ nullable: true
+ },
+ enableDiscordIntegration: {
+ type: "boolean"
+ },
+ discordClientId: {
+ type: "string",
+ nullable: true
+ },
+ discordClientSecret: {
+ type: "string",
+ nullable: true
+ },
+ enableEmail: {
+ type: "boolean"
+ },
+ email: {
+ type: "string",
+ nullable: true
+ },
+ smtpSecure: {
+ type: "boolean"
+ },
+ smtpHost: {
+ type: "string",
+ nullable: true
+ },
+ smtpPort: {
+ type: "integer",
+ nullable: true
+ },
+ smtpUser: {
+ type: "string",
+ nullable: true
+ },
+ smtpPass: {
+ type: "string",
+ nullable: true
+ },
+ tosUrl: {
+ type: "string",
+ nullable: true
+ },
+ repositoryUrl: {
+ type: "string"
+ },
+ feedbackUrl: {
+ type: "string"
+ },
+ useObjectStorage: {
+ type: "boolean"
+ },
+ objectStorageBaseUrl: {
+ type: "string",
+ nullable: true
+ },
+ objectStorageBucket: {
+ type: "string",
+ nullable: true
+ },
+ objectStoragePrefix: {
+ type: "string",
+ nullable: true
+ },
+ objectStorageEndpoint: {
+ type: "string",
+ nullable: true
+ },
+ objectStorageRegion: {
+ type: "string",
+ nullable: true
+ },
+ objectStoragePort: {
+ type: "integer",
+ nullable: true
+ },
+ objectStorageAccessKey: {
+ type: "string",
+ nullable: true
+ },
+ objectStorageSecretKey: {
+ type: "string",
+ nullable: true
+ },
+ objectStorageUseSSL: {
+ type: "boolean"
+ },
+ objectStorageUseProxy: {
+ type: "boolean"
+ },
+ objectStorageSetPublicRead: {
+ type: "boolean"
+ },
+ objectStorageS3ForcePathStyle: {
+ type: "boolean"
+ },
+ enableIpLogging: {
+ type: "boolean"
+ },
+ enableActiveEmailValidation: {
+ type: "boolean"
+ },
+ experimentalFeatures: {
+ type: "object",
+ nullable: true,
+ properties: {
+ postImports: {
+ type: "boolean"
+ }
+ }
+ },
+ enableServerMachineStats: {
+ type: "boolean"
+ },
+ enableIdenticonGeneration: {
+ type: "boolean"
+ },
+ donationLink: {
+ type: "string",
+ nullable: true
+ },
+ autofollowedAccount: {
+ type: "string",
+ nullable: true
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const set = {};
+ if (typeof ps.disableRegistration === "boolean") {
+ set.disableRegistration = ps.disableRegistration;
+ }
+ if (typeof ps.disableLocalTimeline === "boolean") {
+ set.disableLocalTimeline = ps.disableLocalTimeline;
+ }
+ if (typeof ps.disableRecommendedTimeline === "boolean") {
+ set.disableRecommendedTimeline = ps.disableRecommendedTimeline;
+ }
+ if (typeof ps.disableGlobalTimeline === "boolean") {
+ set.disableGlobalTimeline = ps.disableGlobalTimeline;
+ }
+ if (typeof ps.defaultReaction === "string") {
+ set.defaultReaction = ps.defaultReaction;
+ }
+ if (Array.isArray(ps.pinnedUsers)) {
+ set.pinnedUsers = ps.pinnedUsers.filter(Boolean);
+ }
+ if (Array.isArray(ps.customMOTD)) {
+ set.customMOTD = ps.customMOTD.filter(Boolean);
+ }
+ if (Array.isArray(ps.customSplashIcons)) {
+ set.customSplashIcons = ps.customSplashIcons.filter(Boolean);
+ }
+ if (Array.isArray(ps.recommendedInstances)) {
+ set.recommendedInstances = ps.recommendedInstances.filter(Boolean);
+ if (set.recommendedInstances?.length > 0) {
+ set.recommendedInstances.forEach((instance, index)=>{
+ if (/^https?:\/\//i.test(instance)) {
+ set.recommendedInstances[index] = instance.replace(/^https?:\/\//i, "").replace(/\/$/, "");
+ }
+ });
+ }
+ }
+ if (Array.isArray(ps.hiddenTags)) {
+ set.hiddenTags = ps.hiddenTags.filter(Boolean);
+ }
+ if (Array.isArray(ps.blockedHosts)) {
+ let lastValue = "";
+ set.blockedHosts = ps.blockedHosts.sort().filter((h)=>{
+ const lv = lastValue;
+ lastValue = h;
+ return h !== "" && h !== lv;
+ });
+ }
+ if (Array.isArray(ps.silencedHosts)) {
+ let lastValue = "";
+ set.silencedHosts = ps.silencedHosts.sort().filter((h)=>{
+ const lv = lastValue;
+ lastValue = h;
+ return h !== "" && h !== lv;
+ });
+ }
+ if (ps.themeColor !== undefined) {
+ set.themeColor = ps.themeColor;
+ }
+ if (Array.isArray(ps.allowedHosts)) {
+ set.allowedHosts = ps.allowedHosts.filter(Boolean);
+ }
+ if (typeof ps.privateMode === "boolean") {
+ set.privateMode = ps.privateMode;
+ }
+ if (typeof ps.secureMode === "boolean") {
+ set.secureMode = ps.secureMode;
+ }
+ if (ps.mascotImageUrl !== undefined) {
+ set.mascotImageUrl = ps.mascotImageUrl;
+ }
+ if (ps.bannerUrl !== undefined) {
+ set.bannerUrl = ps.bannerUrl;
+ }
+ if (ps.logoImageUrl !== undefined) {
+ set.logoImageUrl = ps.logoImageUrl;
+ }
+ if (ps.iconUrl !== undefined) {
+ set.iconUrl = ps.iconUrl;
+ }
+ if (ps.backgroundImageUrl !== undefined) {
+ set.backgroundImageUrl = ps.backgroundImageUrl;
+ }
+ if (ps.logoImageUrl !== undefined) {
+ set.logoImageUrl = ps.logoImageUrl;
+ }
+ if (ps.name !== undefined) {
+ set.name = ps.name;
+ }
+ if (ps.description !== undefined) {
+ set.description = ps.description;
+ }
+ if (ps.defaultLightTheme !== undefined) {
+ set.defaultLightTheme = ps.defaultLightTheme;
+ }
+ if (ps.defaultDarkTheme !== undefined) {
+ set.defaultDarkTheme = ps.defaultDarkTheme;
+ }
+ if (ps.localDriveCapacityMb !== undefined) {
+ set.localDriveCapacityMb = ps.localDriveCapacityMb;
+ }
+ if (ps.remoteDriveCapacityMb !== undefined) {
+ set.remoteDriveCapacityMb = ps.remoteDriveCapacityMb;
+ }
+ if (ps.lua4frozenDatabaseCapacityMb !== undefined) {
+ set.lua4frozenDatabaseCapacityMb = ps.lua4frozenDatabaseCapacityMb;
+ }
+ if (ps.cacheRemoteFiles !== undefined) {
+ set.cacheRemoteFiles = ps.cacheRemoteFiles;
+ }
+ if (ps.emailRequiredForSignup !== undefined) {
+ set.emailRequiredForSignup = ps.emailRequiredForSignup;
+ }
+ if (ps.enableHcaptcha !== undefined) {
+ set.enableHcaptcha = ps.enableHcaptcha;
+ }
+ if (ps.hcaptchaSiteKey !== undefined) {
+ set.hcaptchaSiteKey = ps.hcaptchaSiteKey;
+ }
+ if (ps.hcaptchaSecretKey !== undefined) {
+ set.hcaptchaSecretKey = ps.hcaptchaSecretKey;
+ }
+ if (ps.enableRecaptcha !== undefined) {
+ set.enableRecaptcha = ps.enableRecaptcha;
+ }
+ if (ps.recaptchaSiteKey !== undefined) {
+ set.recaptchaSiteKey = ps.recaptchaSiteKey;
+ }
+ if (ps.recaptchaSecretKey !== undefined) {
+ set.recaptchaSecretKey = ps.recaptchaSecretKey;
+ }
+ if (ps.maintainerName !== undefined) {
+ set.maintainerName = ps.maintainerName;
+ }
+ if (ps.maintainerEmail !== undefined) {
+ set.maintainerEmail = ps.maintainerEmail;
+ }
+ if (Array.isArray(ps.langs)) {
+ set.langs = ps.langs.filter(Boolean);
+ }
+ if (Array.isArray(ps.pinnedPages)) {
+ set.pinnedPages = ps.pinnedPages.filter(Boolean);
+ }
+ if (ps.pinnedClipId !== undefined) {
+ set.pinnedClipId = ps.pinnedClipId;
+ }
+ if (ps.summalyProxy !== undefined) {
+ set.summalyProxy = ps.summalyProxy;
+ }
+ if (ps.enableGithubIntegration !== undefined) {
+ set.enableGithubIntegration = ps.enableGithubIntegration;
+ }
+ if (ps.githubClientId !== undefined) {
+ set.githubClientId = ps.githubClientId;
+ }
+ if (ps.githubClientSecret !== undefined) {
+ set.githubClientSecret = ps.githubClientSecret;
+ }
+ if (ps.enableDiscordIntegration !== undefined) {
+ set.enableDiscordIntegration = ps.enableDiscordIntegration;
+ }
+ if (ps.discordClientId !== undefined) {
+ set.discordClientId = ps.discordClientId;
+ }
+ if (ps.discordClientSecret !== undefined) {
+ set.discordClientSecret = ps.discordClientSecret;
+ }
+ if (ps.enableEmail !== undefined) {
+ set.enableEmail = ps.enableEmail;
+ }
+ if (ps.email !== undefined) {
+ set.email = ps.email;
+ }
+ if (ps.smtpSecure !== undefined) {
+ set.smtpSecure = ps.smtpSecure;
+ }
+ if (ps.smtpHost !== undefined) {
+ set.smtpHost = ps.smtpHost;
+ }
+ if (ps.smtpPort !== undefined) {
+ set.smtpPort = ps.smtpPort;
+ }
+ if (ps.smtpUser !== undefined) {
+ set.smtpUser = ps.smtpUser;
+ }
+ if (ps.smtpPass !== undefined) {
+ set.smtpPass = ps.smtpPass;
+ }
+ if (ps.errorImageUrl !== undefined) {
+ set.errorImageUrl = ps.errorImageUrl;
+ }
+ if (ps.tosUrl !== undefined) {
+ set.ToSUrl = ps.tosUrl;
+ }
+ if (ps.repositoryUrl !== undefined) {
+ set.repositoryUrl = ps.repositoryUrl;
+ }
+ if (ps.feedbackUrl !== undefined) {
+ set.feedbackUrl = ps.feedbackUrl;
+ }
+ if (ps.useObjectStorage !== undefined) {
+ set.useObjectStorage = ps.useObjectStorage;
+ }
+ if (ps.objectStorageBaseUrl !== undefined) {
+ set.objectStorageBaseUrl = ps.objectStorageBaseUrl;
+ }
+ if (ps.objectStorageBucket !== undefined) {
+ set.objectStorageBucket = ps.objectStorageBucket;
+ }
+ if (ps.objectStoragePrefix !== undefined) {
+ set.objectStoragePrefix = ps.objectStoragePrefix;
+ }
+ if (ps.objectStorageEndpoint !== undefined) {
+ set.objectStorageEndpoint = ps.objectStorageEndpoint;
+ }
+ if (ps.objectStorageRegion !== undefined) {
+ set.objectStorageRegion = ps.objectStorageRegion;
+ }
+ if (ps.objectStoragePort !== undefined) {
+ set.objectStoragePort = ps.objectStoragePort;
+ }
+ if (ps.objectStorageAccessKey !== undefined) {
+ set.objectStorageAccessKey = ps.objectStorageAccessKey;
+ }
+ if (ps.objectStorageSecretKey !== undefined) {
+ set.objectStorageSecretKey = ps.objectStorageSecretKey;
+ }
+ if (ps.objectStorageUseSSL !== undefined) {
+ set.objectStorageUseSSL = ps.objectStorageUseSSL;
+ }
+ if (ps.objectStorageUseProxy !== undefined) {
+ set.objectStorageUseProxy = ps.objectStorageUseProxy;
+ }
+ if (ps.objectStorageSetPublicRead !== undefined) {
+ set.objectStorageSetPublicRead = ps.objectStorageSetPublicRead;
+ }
+ if (ps.objectStorageS3ForcePathStyle !== undefined) {
+ set.objectStorageS3ForcePathStyle = ps.objectStorageS3ForcePathStyle;
+ }
+ if (ps.deeplAuthKey !== undefined) {
+ if (ps.deeplAuthKey === "") {
+ set.deeplAuthKey = null;
+ } else {
+ set.deeplAuthKey = ps.deeplAuthKey;
+ }
+ }
+ if (ps.deeplIsPro !== undefined) {
+ set.deeplIsPro = ps.deeplIsPro;
+ }
+ if (ps.libreTranslateApiUrl !== undefined) {
+ if (ps.libreTranslateApiUrl === "") {
+ set.libreTranslateApiUrl = null;
+ } else {
+ set.libreTranslateApiUrl = ps.libreTranslateApiUrl;
+ }
+ }
+ if (ps.libreTranslateApiKey !== undefined) {
+ if (ps.libreTranslateApiKey === "") {
+ set.libreTranslateApiKey = null;
+ } else {
+ set.libreTranslateApiKey = ps.libreTranslateApiKey;
+ }
+ }
+ if (ps.enableIpLogging !== undefined) {
+ set.enableIpLogging = ps.enableIpLogging;
+ }
+ if (ps.enableActiveEmailValidation !== undefined) {
+ set.enableActiveEmailValidation = ps.enableActiveEmailValidation;
+ }
+ if (ps.experimentalFeatures !== undefined) {
+ set.experimentalFeatures = ps.experimentalFeatures || undefined;
+ }
+ if (ps.enableServerMachineStats !== undefined) {
+ set.enableServerMachineStats = ps.enableServerMachineStats;
+ }
+ if (ps.enableIdenticonGeneration !== undefined) {
+ set.enableIdenticonGeneration = ps.enableIdenticonGeneration;
+ }
+ if (ps.donationLink !== undefined) {
+ set.donationLink = ps.donationLink;
+ if (set.donationLink && !/^https?:\/\//i.test(set.donationLink)) {
+ set.donationLink = `https://${set.donationLink}`;
+ }
+ }
+ if (ps.autofollowedAccount !== undefined) {
+ if (ps.autofollowedAccount === null) {
+ set.autofollowedAccount = null;
+ } else {
+ // Verify account exists and is a local account
+ const user = await Users.findOneBy({
+ username: ps.autofollowedAccount,
+ host: IsNull()
+ });
+ if (user !== null) {
+ set.autofollowedAccount = user.username;
+ } else {
+ set.autofollowedAccount = null;
+ }
+ }
+ }
+ const meta = await Metas.findOne({
+ where: {},
+ order: {
+ id: "DESC"
+ }
+ });
+ if (meta) await Metas.update(meta.id, set);
+ else await Metas.save(set);
+ insertModerationLog(me, "updateMeta");
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/update-user-note.js b/packages/backend/built/server/api/endpoints/admin/update-user-note.js
new file mode 100644
index 0000000..2c106bf
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/update-user-note.js
@@ -0,0 +1,38 @@
+import { UserProfiles, Users } from "../../../../models/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ text: {
+ type: "string"
+ }
+ },
+ required: [
+ "userId",
+ "text"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const user = await Users.findOneBy({
+ id: ps.userId
+ });
+ if (user == null) {
+ throw new Error("user not found");
+ }
+ await UserProfiles.update({
+ userId: user.id
+ }, {
+ moderationNote: ps.text
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/user-plans/add.js b/packages/backend/built/server/api/endpoints/admin/user-plans/add.js
new file mode 100644
index 0000000..31cc9d3
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/user-plans/add.js
@@ -0,0 +1,51 @@
+import define from "../../../define.js";
+import { Plans, UserPlans, Users } from "../../../../../models/index.js";
+import { genId } from "../../../../../misc/gen-id.js";
+import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireAdmin: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ planId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId",
+ "planId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ await Users.findOneByOrFail({
+ id: ps.userId
+ });
+ await Plans.findOneByOrFail({
+ id: ps.planId
+ });
+ const exists = await UserPlans.findOneBy({
+ userId: ps.userId,
+ planId: ps.planId
+ });
+ if (exists) return;
+ await UserPlans.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: ps.userId,
+ planId: ps.planId
+ });
+ insertModerationLog(me, "addUserPlan", {
+ targetId: ps.userId,
+ planId: ps.planId
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/user-plans/list.js b/packages/backend/built/server/api/endpoints/admin/user-plans/list.js
new file mode 100644
index 0000000..eeb7254
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/user-plans/list.js
@@ -0,0 +1,38 @@
+import define from "../../../define.js";
+import { Plans, UserPlans, Users } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireAdmin: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ await Users.findOneByOrFail({
+ id: ps.userId
+ });
+ const joins = await UserPlans.find({
+ where: {
+ userId: ps.userId
+ },
+ relations: [
+ "plan"
+ ],
+ order: {
+ createdAt: "ASC"
+ }
+ });
+ return await Plans.packMany(joins.map((join)=>join.plan).filter((plan)=>plan != null));
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/user-plans/remove.js b/packages/backend/built/server/api/endpoints/admin/user-plans/remove.js
new file mode 100644
index 0000000..c5cf234
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/user-plans/remove.js
@@ -0,0 +1,40 @@
+import define from "../../../define.js";
+import { UserPlans, Users } from "../../../../../models/index.js";
+import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireAdmin: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ planId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId",
+ "planId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ await Users.findOneByOrFail({
+ id: ps.userId
+ });
+ await UserPlans.delete({
+ userId: ps.userId,
+ planId: ps.planId
+ });
+ insertModerationLog(me, "removeUserPlan", {
+ targetId: ps.userId,
+ planId: ps.planId
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/vacuum.js b/packages/backend/built/server/api/endpoints/admin/vacuum.js
new file mode 100644
index 0000000..b8bf507
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/vacuum.js
@@ -0,0 +1,36 @@
+import define from "../../define.js";
+import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
+import { db } from "../../../../db/postgre.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireModerator: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ full: {
+ type: "boolean"
+ },
+ analyze: {
+ type: "boolean"
+ }
+ },
+ required: [
+ "full",
+ "analyze"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const params = [];
+ if (ps.full) {
+ params.push("FULL");
+ }
+ if (ps.analyze) {
+ params.push("ANALYZE");
+ }
+ db.query(`VACUUM ${params.join(" ")}`);
+ insertModerationLog(me, "vacuum", ps);
+});
diff --git a/packages/backend/built/server/api/endpoints/admin/verified-badge-requests.js b/packages/backend/built/server/api/endpoints/admin/verified-badge-requests.js
new file mode 100644
index 0000000..bf71163
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/admin/verified-badge-requests.js
@@ -0,0 +1,50 @@
+import define from "../../define.js";
+import { VerifiedBadgeRequests } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "admin"
+ ],
+ requireCredential: true,
+ requireAdmin: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ state: {
+ type: "string",
+ enum: [
+ "all",
+ "pending",
+ "approved",
+ "rejected"
+ ],
+ default: "pending"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps)=>{
+ const query = makePaginationQuery(VerifiedBadgeRequests.createQueryBuilder("request"), ps.sinceId, ps.untilId);
+ if (ps.state !== "all") {
+ query.andWhere("request.status = :status", {
+ status: ps.state
+ });
+ }
+ const requests = await query.take(ps.limit).getMany();
+ return await VerifiedBadgeRequests.packMany(requests);
+});
diff --git a/packages/backend/built/server/api/endpoints/announcements.js b/packages/backend/built/server/api/endpoints/announcements.js
new file mode 100644
index 0000000..6a8177c
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/announcements.js
@@ -0,0 +1,112 @@
+import { Announcements, AnnouncementReads } from "../../../models/index.js";
+import define from "../define.js";
+import { makePaginationQuery } from "../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "meta"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id",
+ example: "xxxxxxxxxx"
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "date-time"
+ },
+ updatedAt: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ format: "date-time"
+ },
+ text: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ title: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ imageUrl: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ isRead: {
+ type: "boolean",
+ optional: true,
+ nullable: false
+ },
+ showPopup: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ isGoodNews: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ }
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ withUnreads: {
+ type: "boolean",
+ default: false
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(Announcements.createQueryBuilder("announcement"), ps.sinceId, ps.untilId);
+ const announcements = await query.take(ps.limit).getMany();
+ if (user) {
+ const reads = (await AnnouncementReads.findBy({
+ userId: user.id
+ })).map((x)=>x.announcementId);
+ for (const announcement of announcements){
+ announcement.isRead = reads.includes(announcement.id);
+ }
+ }
+ return (ps.withUnreads ? announcements.filter((a)=>!a.isRead) : announcements).map((a)=>({
+ ...a,
+ createdAt: a.createdAt.toISOString(),
+ updatedAt: a.updatedAt?.toISOString() ?? null
+ }));
+});
diff --git a/packages/backend/built/server/api/endpoints/antennas/create.js b/packages/backend/built/server/api/endpoints/antennas/create.js
new file mode 100644
index 0000000..fe6c298
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/antennas/create.js
@@ -0,0 +1,173 @@
+import define from "../../define.js";
+import { genId } from "../../../../misc/gen-id.js";
+import { Antennas, UserLists, UserGroupJoinings } from "../../../../models/index.js";
+import { ApiError } from "../../error.js";
+import { publishInternalEvent } from "../../../../services/stream.js";
+export const meta = {
+ tags: [
+ "antennas"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchUserList: {
+ message: "No such user list.",
+ code: "NO_SUCH_USER_LIST",
+ id: "95063e93-a283-4b8b-9aa5-bcdb8df69a7f"
+ },
+ noSuchUserGroup: {
+ message: "No such user group.",
+ code: "NO_SUCH_USER_GROUP",
+ id: "aa3c0b9a-8cae-47c0-92ac-202ce5906682"
+ },
+ tooManyAntennas: {
+ message: "Too many antennas.",
+ code: "TOO_MANY_ANTENNAS",
+ id: "c3a5a51e-04d4-11ee-be56-0242ac120002"
+ },
+ noKeywords: {
+ message: "No keywords.",
+ code: "NO_KEYWORDS",
+ id: "aa975b74-1ddb-11ee-be56-0242ac120002"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Antenna"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ name: {
+ type: "string",
+ minLength: 1,
+ maxLength: 100
+ },
+ src: {
+ type: "string",
+ enum: [
+ "home",
+ "all",
+ "users",
+ "list",
+ "group",
+ "instances"
+ ]
+ },
+ userListId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ userGroupId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ keywords: {
+ type: "array",
+ items: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ }
+ },
+ excludeKeywords: {
+ type: "array",
+ items: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ }
+ },
+ users: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ },
+ instances: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ },
+ caseSensitive: {
+ type: "boolean"
+ },
+ withReplies: {
+ type: "boolean"
+ },
+ withFile: {
+ type: "boolean"
+ },
+ notify: {
+ type: "boolean"
+ }
+ },
+ required: [
+ "name",
+ "src",
+ "keywords",
+ "excludeKeywords",
+ "users",
+ "instances",
+ "caseSensitive",
+ "withReplies",
+ "withFile",
+ "notify"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ if (user.movedToUri != null) throw new ApiError(meta.errors.noSuchUserGroup);
+ if (ps.keywords.length === 0) throw new ApiError(meta.errors.noKeywords);
+ let userList;
+ let userGroupJoining;
+ const antennas = await Antennas.findBy({
+ userId: user.id
+ });
+ if (antennas.length > 5 && !user.isAdmin) {
+ throw new ApiError(meta.errors.tooManyAntennas);
+ }
+ if (ps.src === "list" && ps.userListId) {
+ userList = await UserLists.findOneBy({
+ id: ps.userListId,
+ userId: user.id
+ });
+ if (userList == null) {
+ throw new ApiError(meta.errors.noSuchUserList);
+ }
+ } else if (ps.src === "group" && ps.userGroupId) {
+ userGroupJoining = await UserGroupJoinings.findOneBy({
+ userGroupId: ps.userGroupId,
+ userId: user.id
+ });
+ if (userGroupJoining == null) {
+ throw new ApiError(meta.errors.noSuchUserGroup);
+ }
+ }
+ const antenna = await Antennas.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ name: ps.name,
+ src: ps.src,
+ userListId: userList ? userList.id : null,
+ userGroupJoiningId: userGroupJoining ? userGroupJoining.id : null,
+ keywords: ps.keywords,
+ excludeKeywords: ps.excludeKeywords,
+ users: ps.users,
+ instances: ps.instances,
+ caseSensitive: ps.caseSensitive,
+ withReplies: ps.withReplies,
+ withFile: ps.withFile,
+ notify: ps.notify
+ }).then((x)=>Antennas.findOneByOrFail(x.identifiers[0]));
+ publishInternalEvent("antennaCreated", antenna);
+ return await Antennas.pack(antenna);
+});
diff --git a/packages/backend/built/server/api/endpoints/antennas/delete.js b/packages/backend/built/server/api/endpoints/antennas/delete.js
new file mode 100644
index 0000000..4986fa6
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/antennas/delete.js
@@ -0,0 +1,41 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { Antennas } from "../../../../models/index.js";
+import { publishInternalEvent } from "../../../../services/stream.js";
+export const meta = {
+ tags: [
+ "antennas"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchAntenna: {
+ message: "No such antenna.",
+ code: "NO_SUCH_ANTENNA",
+ id: "b34dcf9d-348f-44bb-99d0-6c9314cfe2df"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ antennaId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "antennaId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const antenna = await Antennas.findOneBy({
+ id: ps.antennaId,
+ userId: user.id
+ });
+ if (antenna == null) {
+ throw new ApiError(meta.errors.noSuchAntenna);
+ }
+ await Antennas.delete(antenna.id);
+ publishInternalEvent("antennaDeleted", antenna);
+});
diff --git a/packages/backend/built/server/api/endpoints/antennas/list.js b/packages/backend/built/server/api/endpoints/antennas/list.js
new file mode 100644
index 0000000..0e69245
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/antennas/list.js
@@ -0,0 +1,32 @@
+import define from "../../define.js";
+import { Antennas } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "antennas",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "read:account",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Antenna"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const antennas = await Antennas.findBy({
+ userId: me.id
+ });
+ return await Promise.all(antennas.map((x)=>Antennas.pack(x)));
+});
diff --git a/packages/backend/built/server/api/endpoints/antennas/markread.js b/packages/backend/built/server/api/endpoints/antennas/markread.js
new file mode 100644
index 0000000..55f936e
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/antennas/markread.js
@@ -0,0 +1,41 @@
+import define from "../../define.js";
+import { Antennas } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "antennas",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "write:account"
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ antennaId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "antennaId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const antenna = await Antennas.findOneBy({
+ userId: me.id,
+ id: ps.antennaId
+ });
+ if (!antenna) {
+ return null;
+ }
+ // await AntennaNotes.update(
+ // {
+ // antennaId: antenna.id,
+ // read: false,
+ // },
+ // {
+ // read: true,
+ // },
+ // );
+ return true;
+});
diff --git a/packages/backend/built/server/api/endpoints/antennas/notes.js b/packages/backend/built/server/api/endpoints/antennas/notes.js
new file mode 100644
index 0000000..74a6acb
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/antennas/notes.js
@@ -0,0 +1,125 @@
+import define from "../../define.js";
+import readNote from "../../../../services/note/read.js";
+import { Antennas, Notes } from "../../../../models/index.js";
+import { redisClient } from "../../../../db/redis.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { ApiError } from "../../error.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+import { generateExcludeMemorietQuery } from "../../common/generate-exclude-memoriet-query.js";
+export const meta = {
+ tags: [
+ "antennas",
+ "account",
+ "notes"
+ ],
+ requireCredential: true,
+ kind: "read:account",
+ errors: {
+ noSuchAntenna: {
+ message: "No such antenna.",
+ code: "NO_SUCH_ANTENNA",
+ id: "850926e0-fd3b-49b6-b69a-b28a5dbd82fe"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ pagination: {
+ type: "string",
+ nullable: false,
+ optional: false
+ },
+ notes: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ antennaId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ pagination: {
+ type: "string",
+ default: "+"
+ }
+ },
+ required: [
+ "antennaId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const antenna = await Antennas.findOneBy({
+ id: ps.antennaId,
+ userId: user.id
+ });
+ let pagination = ps.pagination || "+";
+ if (antenna == null) {
+ throw new ApiError(meta.errors.noSuchAntenna);
+ }
+ let notes = [];
+ let paginationMap = [];
+ while(notes.length < ps.limit && pagination !== "-1"){
+ // exclusive range
+ if (pagination != "+" && !pagination.startsWith("(")) pagination = `(${pagination}`;
+ const noteIdsRes = await redisClient.xrevrange(`antennaTimeline:${antenna.id}`, pagination, "-", "COUNT", ps.limit - notes.length);
+ const noteIds = noteIdsRes.map((x)=>x[1][1]);
+ if (noteIds.length === 0) {
+ pagination = "-1";
+ break;
+ }
+ const query = makePaginationQuery(Notes.createQueryBuilder("note")).where("note.id IN (:...noteIds)", {
+ noteIds: noteIds
+ }).innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser").andWhere("note.visibility != 'home'");
+ generateVisibilityQuery(query, user);
+ generateMutedUserQuery(query, user);
+ generateBlockedUserQuery(query, user);
+ generateExcludeMemorietQuery(query);
+ pagination = noteIdsRes[noteIdsRes.length - 1][0];
+ paginationMap = paginationMap.concat(noteIdsRes.map((x)=>[
+ x[1][1],
+ x[0]
+ ]));
+ notes = notes.concat(await query.take(ps.limit - notes.length).getMany());
+ }
+ if (notes.length === 0) {
+ return {
+ pagination: "-1",
+ notes: []
+ };
+ } else {
+ readNote(user.id, notes);
+ }
+ const packedNotes = (await Notes.packMany(notes, user)).sort((a, b)=>paginationMap.findIndex((p)=>p[0] == a.id) - paginationMap.findIndex((p)=>p[0] == b.id));
+ if (notes.length < ps.limit) {
+ pagination = "-1";
+ } else {
+ // I'm so sorry, FIXME: rewrite pagination system
+ pagination = paginationMap.find((p)=>p[0] == packedNotes[packedNotes.length - (packedNotes.length > 1 ? 2 : 1)].id)[1];
+ }
+ return {
+ pagination: pagination,
+ notes: packedNotes
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/antennas/show.js b/packages/backend/built/server/api/endpoints/antennas/show.js
new file mode 100644
index 0000000..ae74434
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/antennas/show.js
@@ -0,0 +1,47 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { Antennas } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "antennas",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "read:account",
+ errors: {
+ noSuchAntenna: {
+ message: "No such antenna.",
+ code: "NO_SUCH_ANTENNA",
+ id: "c06569fb-b025-4f23-b22d-1fcd20d2816b"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Antenna"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ antennaId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "antennaId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ // Fetch the antenna
+ const antenna = await Antennas.findOneBy({
+ id: ps.antennaId,
+ userId: me.id
+ });
+ if (antenna == null) {
+ throw new ApiError(meta.errors.noSuchAntenna);
+ }
+ return await Antennas.pack(antenna);
+});
diff --git a/packages/backend/built/server/api/endpoints/antennas/update.js b/packages/backend/built/server/api/endpoints/antennas/update.js
new file mode 100644
index 0000000..385c5cc
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/antennas/update.js
@@ -0,0 +1,171 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { Antennas, UserLists, UserGroupJoinings } from "../../../../models/index.js";
+import { publishInternalEvent } from "../../../../services/stream.js";
+export const meta = {
+ tags: [
+ "antennas"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchAntenna: {
+ message: "No such antenna.",
+ code: "NO_SUCH_ANTENNA",
+ id: "10c673ac-8852-48eb-aa1f-f5b67f069290"
+ },
+ noSuchUserList: {
+ message: "No such user list.",
+ code: "NO_SUCH_USER_LIST",
+ id: "1c6b35c9-943e-48c2-81e4-2844989407f7"
+ },
+ noSuchUserGroup: {
+ message: "No such user group.",
+ code: "NO_SUCH_USER_GROUP",
+ id: "109ed789-b6eb-456e-b8a9-6059d567d385"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Antenna"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ antennaId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ name: {
+ type: "string",
+ minLength: 1,
+ maxLength: 100
+ },
+ src: {
+ type: "string",
+ enum: [
+ "home",
+ "all",
+ "users",
+ "list",
+ "group",
+ "instances"
+ ]
+ },
+ userListId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ userGroupId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ keywords: {
+ type: "array",
+ items: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ }
+ },
+ excludeKeywords: {
+ type: "array",
+ items: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ }
+ },
+ users: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ },
+ instances: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ },
+ caseSensitive: {
+ type: "boolean"
+ },
+ withReplies: {
+ type: "boolean"
+ },
+ withFile: {
+ type: "boolean"
+ },
+ notify: {
+ type: "boolean"
+ }
+ },
+ required: [
+ "antennaId",
+ "name",
+ "src",
+ "keywords",
+ "excludeKeywords",
+ "users",
+ "instances",
+ "caseSensitive",
+ "withReplies",
+ "withFile",
+ "notify"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Fetch the antenna
+ const antenna = await Antennas.findOneBy({
+ id: ps.antennaId,
+ userId: user.id
+ });
+ if (antenna == null) {
+ throw new ApiError(meta.errors.noSuchAntenna);
+ }
+ let userList;
+ let userGroupJoining;
+ if (ps.src === "list" && ps.userListId) {
+ userList = await UserLists.findOneBy({
+ id: ps.userListId,
+ userId: user.id
+ });
+ if (userList == null) {
+ throw new ApiError(meta.errors.noSuchUserList);
+ }
+ } else if (ps.src === "group" && ps.userGroupId) {
+ userGroupJoining = await UserGroupJoinings.findOneBy({
+ userGroupId: ps.userGroupId,
+ userId: user.id
+ });
+ if (userGroupJoining == null) {
+ throw new ApiError(meta.errors.noSuchUserGroup);
+ }
+ }
+ await Antennas.update(antenna.id, {
+ name: ps.name,
+ src: ps.src,
+ userListId: userList ? userList.id : null,
+ userGroupJoiningId: userGroupJoining ? userGroupJoining.id : null,
+ keywords: ps.keywords,
+ excludeKeywords: ps.excludeKeywords,
+ users: ps.users,
+ instances: ps.instances,
+ caseSensitive: ps.caseSensitive,
+ withReplies: ps.withReplies,
+ withFile: ps.withFile,
+ notify: ps.notify
+ });
+ publishInternalEvent("antennaUpdated", await Antennas.findOneByOrFail({
+ id: antenna.id
+ }));
+ return await Antennas.pack(antenna.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/ap/get.js b/packages/backend/built/server/api/endpoints/ap/get.js
new file mode 100644
index 0000000..4609917
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/ap/get.js
@@ -0,0 +1,36 @@
+import define from "../../define.js";
+import Resolver from "../../../../remote/activitypub/resolver.js";
+import { HOUR } from "../../../../const.js";
+export const meta = {
+ tags: [
+ "federation"
+ ],
+ requireCredential: true,
+ requireAdmin: true,
+ limit: {
+ duration: HOUR,
+ max: 30
+ },
+ errors: {},
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ uri: {
+ type: "string"
+ }
+ },
+ required: [
+ "uri"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const resolver = new Resolver();
+ const object = await resolver.resolve(ps.uri);
+ return object;
+});
diff --git a/packages/backend/built/server/api/endpoints/ap/show.js b/packages/backend/built/server/api/endpoints/ap/show.js
new file mode 100644
index 0000000..e2921f2
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/ap/show.js
@@ -0,0 +1,157 @@
+import define from "../../define.js";
+import { createPerson } from "../../../../remote/activitypub/models/person.js";
+import { createNote } from "../../../../remote/activitypub/models/note.js";
+import DbResolver from "../../../../remote/activitypub/db-resolver.js";
+import Resolver from "../../../../remote/activitypub/resolver.js";
+import { ApiError } from "../../error.js";
+import { extractDbHost } from "../../../../misc/convert-host.js";
+import { Users, Notes } from "../../../../models/index.js";
+import { isActor, isPost, getApId } from "../../../../remote/activitypub/type.js";
+import { MINUTE } from "../../../../const.js";
+import { shouldBlockInstance } from "../../../../misc/should-block-instance.js";
+import { updateQuestion } from "../../../../remote/activitypub/models/question.js";
+import { populatePoll } from "../../../../models/repositories/note.js";
+import { redisClient } from "../../../../db/redis.js";
+export const meta = {
+ tags: [
+ "federation"
+ ],
+ requireCredential: true,
+ limit: {
+ duration: MINUTE,
+ max: 10
+ },
+ errors: {
+ noSuchObject: {
+ message: "No such object.",
+ code: "NO_SUCH_OBJECT",
+ id: "dc94d745-1262-4e63-a17d-fecaa57efc82"
+ }
+ },
+ res: {
+ optional: false,
+ nullable: false,
+ oneOf: [
+ {
+ type: "object",
+ properties: {
+ type: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ enum: [
+ "User"
+ ]
+ },
+ object: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserDetailedNotMe"
+ }
+ }
+ },
+ {
+ type: "object",
+ properties: {
+ type: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ enum: [
+ "Note"
+ ]
+ },
+ object: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ }
+ }
+ ]
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ uri: {
+ type: "string"
+ }
+ },
+ required: [
+ "uri"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const object = await fetchAny(ps.uri, me);
+ if (object) {
+ return object;
+ } else {
+ throw new ApiError(meta.errors.noSuchObject);
+ }
+});
+/***
+ * Resolve User or Note from URI
+ */ async function fetchAny(uri, me) {
+ // Wait if blocked.
+ if (await shouldBlockInstance(extractDbHost(uri))) return null;
+ const dbResolver = new DbResolver();
+ const resolver = new Resolver();
+ resolver.setUser(me);
+ const [user, note] = await Promise.all([
+ dbResolver.getUserFromApId(uri),
+ dbResolver.getNoteFromApId(uri)
+ ]);
+ let local = await mergePack(me, user, note);
+ if (local) {
+ if (local.type === "Note" && note?.uri && note.hasPoll) {
+ // Update questions if the stored (remote) note contains the poll
+ const key = `pollFetched:${note.uri}`;
+ if (await redisClient.exists(key) === 0) {
+ if (await updateQuestion(note.uri, resolver)) {
+ local.object.poll = await populatePoll(note, me?.id ?? null);
+ }
+ // Allow fetching the poll again after 1 minute
+ await redisClient.set(key, 1, "EX", 60);
+ }
+ }
+ return local;
+ }
+ // fetching Object once from remote
+ const object = await resolver.resolve(uri);
+ // /@user If a URI other than the id is specified,
+ // the URI is determined here
+ if (uri !== object.id) {
+ local = await mergePack(me, ...await Promise.all([
+ dbResolver.getUserFromApId(getApId(object)),
+ dbResolver.getNoteFromApId(getApId(object))
+ ]));
+ if (local != null) return local;
+ }
+ return await mergePack(me, isActor(object) ? await createPerson(getApId(object), resolver.reset()) : null, isPost(object) ? await createNote(getApId(object), resolver.reset(), true) : null);
+}
+async function mergePack(me, user, note) {
+ if (user != null) {
+ return {
+ type: "User",
+ object: await Users.pack(user, me, {
+ detail: true
+ })
+ };
+ } else if (note != null) {
+ try {
+ const object = await Notes.pack(note, me, {
+ detail: true
+ });
+ return {
+ type: "Note",
+ object
+ };
+ } catch (e) {
+ return null;
+ }
+ }
+ return null;
+}
diff --git a/packages/backend/built/server/api/endpoints/app/create.js b/packages/backend/built/server/api/endpoints/app/create.js
new file mode 100644
index 0000000..bda1aa2
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/app/create.js
@@ -0,0 +1,69 @@
+import define from "../../define.js";
+import { Apps } from "../../../../models/index.js";
+import { genId } from "../../../../misc/gen-id.js";
+import { unique } from "../../../../prelude/array.js";
+import { secureRndstr } from "../../../../misc/secure-rndstr.js";
+export const meta = {
+ tags: [
+ "app"
+ ],
+ requireCredential: false,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "App"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ name: {
+ type: "string"
+ },
+ description: {
+ type: "string"
+ },
+ permission: {
+ type: "array",
+ uniqueItems: true,
+ items: {
+ type: "string"
+ }
+ },
+ callbackUrl: {
+ type: "string",
+ nullable: true
+ }
+ },
+ required: [
+ "name",
+ "description",
+ "permission"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ if (user?.movedToUri != null) return await Apps.pack("", null, {
+ detail: true,
+ includeSecret: true
+ });
+ // Generate secret
+ const secret = secureRndstr(32);
+ // for backward compatibility
+ const permission = unique(ps.permission.map((v)=>v.replace(/^(.+)(\/|-)(read|write)$/, "$3:$1")));
+ // Create account
+ const app = await Apps.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user ? user.id : null,
+ name: ps.name,
+ description: ps.description,
+ permission,
+ callbackUrl: ps.callbackUrl,
+ secret: secret
+ }).then((x)=>Apps.findOneByOrFail(x.identifiers[0]));
+ return await Apps.pack(app, null, {
+ detail: true,
+ includeSecret: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/app/show.js b/packages/backend/built/server/api/endpoints/app/show.js
new file mode 100644
index 0000000..2c18412
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/app/show.js
@@ -0,0 +1,47 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { Apps } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "app"
+ ],
+ errors: {
+ noSuchApp: {
+ message: "No such app.",
+ code: "NO_SUCH_APP",
+ id: "dce83913-2dc6-4093-8a7b-71dbb11718a3"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "App"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ appId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "appId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user, token)=>{
+ const isSecure = user != null && token == null;
+ // Lookup app
+ const ap = await Apps.findOneBy({
+ id: ps.appId
+ });
+ if (ap == null) {
+ throw new ApiError(meta.errors.noSuchApp);
+ }
+ return await Apps.pack(ap, user, {
+ detail: true,
+ includeSecret: isSecure && ap.userId === user.id
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/auth/accept.js b/packages/backend/built/server/api/endpoints/auth/accept.js
new file mode 100644
index 0000000..e8b766e
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/auth/accept.js
@@ -0,0 +1,74 @@
+import * as crypto from "node:crypto";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { AuthSessions, AccessTokens, Apps } from "../../../../models/index.js";
+import { genId } from "../../../../misc/gen-id.js";
+import { secureRndstr } from "../../../../misc/secure-rndstr.js";
+export const meta = {
+ tags: [
+ "auth"
+ ],
+ requireCredential: true,
+ secure: true,
+ errors: {
+ noSuchSession: {
+ message: "No such session.",
+ code: "NO_SUCH_SESSION",
+ id: "9c72d8de-391a-43c1-9d06-08d29efde8df"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ token: {
+ type: "string"
+ }
+ },
+ required: [
+ "token"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Fetch token
+ const session = await AuthSessions.findOneBy({
+ token: ps.token
+ });
+ if (session == null) {
+ throw new ApiError(meta.errors.noSuchSession);
+ }
+ // Generate access token
+ const accessToken = secureRndstr(32);
+ // Fetch exist access token
+ const exist = await AccessTokens.exist({
+ where: {
+ appId: session.appId,
+ userId: user.id
+ }
+ });
+ if (!exist) {
+ // Lookup app
+ const app = await Apps.findOneByOrFail({
+ id: session.appId
+ });
+ // Generate Hash
+ const sha256 = crypto.createHash("sha256");
+ sha256.update(accessToken + app.secret);
+ const hash = sha256.digest("hex");
+ const now = new Date();
+ // Insert access token doc
+ await AccessTokens.insert({
+ id: genId(),
+ createdAt: now,
+ lastUsedAt: now,
+ appId: session.appId,
+ userId: user.id,
+ token: accessToken,
+ hash: hash
+ });
+ }
+ // Update session
+ await AuthSessions.update(session.id, {
+ userId: user.id
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/auth/session/generate.js b/packages/backend/built/server/api/endpoints/auth/session/generate.js
new file mode 100644
index 0000000..d5cd3c7
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/auth/session/generate.js
@@ -0,0 +1,70 @@
+import { v4 as uuid } from "uuid";
+import config from "../../../../../config/index.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { Apps, AuthSessions } from "../../../../../models/index.js";
+import { genId } from "../../../../../misc/gen-id.js";
+export const meta = {
+ tags: [
+ "auth"
+ ],
+ requireCredential: false,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ token: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ url: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "url"
+ }
+ }
+ },
+ errors: {
+ noSuchApp: {
+ message: "No such app.",
+ code: "NO_SUCH_APP",
+ id: "92f93e63-428e-4f2f-a5a4-39e1407fe998"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ appSecret: {
+ type: "string"
+ }
+ },
+ required: [
+ "appSecret"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ // Lookup app
+ const app = await Apps.findOneBy({
+ secret: ps.appSecret
+ });
+ if (app == null) {
+ throw new ApiError(meta.errors.noSuchApp);
+ }
+ // Generate token
+ const token = uuid();
+ // Create session token document
+ const doc = await AuthSessions.insert({
+ id: genId(),
+ createdAt: new Date(),
+ appId: app.id,
+ token: token
+ }).then((x)=>AuthSessions.findOneByOrFail(x.identifiers[0]));
+ return {
+ token: doc.token,
+ url: `${config.authUrl}/${doc.token}`
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/auth/session/show.js b/packages/backend/built/server/api/endpoints/auth/session/show.js
new file mode 100644
index 0000000..6a64c2b
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/auth/session/show.js
@@ -0,0 +1,61 @@
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { AuthSessions } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "auth"
+ ],
+ requireCredential: false,
+ errors: {
+ noSuchSession: {
+ message: "No such session.",
+ code: "NO_SUCH_SESSION",
+ id: "bd72c97d-eba7-4adb-a467-f171b8847250"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ app: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "App"
+ },
+ token: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ token: {
+ type: "string"
+ }
+ },
+ required: [
+ "token"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Lookup session
+ const session = await AuthSessions.findOneBy({
+ token: ps.token
+ });
+ if (session == null) {
+ throw new ApiError(meta.errors.noSuchSession);
+ }
+ return await AuthSessions.pack(session, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/auth/session/userkey.js b/packages/backend/built/server/api/endpoints/auth/session/userkey.js
new file mode 100644
index 0000000..ced9585
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/auth/session/userkey.js
@@ -0,0 +1,92 @@
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { Apps, AuthSessions, AccessTokens, Users } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "auth"
+ ],
+ requireCredential: false,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ accessToken: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ user: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserDetailedNotMe"
+ }
+ }
+ },
+ errors: {
+ noSuchApp: {
+ message: "No such app.",
+ code: "NO_SUCH_APP",
+ id: "fcab192a-2c5a-43b7-8ad8-9b7054d8d40d"
+ },
+ noSuchSession: {
+ message: "No such session.",
+ code: "NO_SUCH_SESSION",
+ id: "5b5a1503-8bc8-4bd0-8054-dc189e8cdcb3"
+ },
+ pendingSession: {
+ message: "This session is not completed yet.",
+ code: "PENDING_SESSION",
+ id: "8c8a4145-02cc-4cca-8e66-29ba60445a8e"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ appSecret: {
+ type: "string"
+ },
+ token: {
+ type: "string"
+ }
+ },
+ required: [
+ "appSecret",
+ "token"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ // Lookup app
+ const app = await Apps.findOneBy({
+ secret: ps.appSecret
+ });
+ if (app == null) {
+ throw new ApiError(meta.errors.noSuchApp);
+ }
+ // Fetch token
+ const session = await AuthSessions.findOneBy({
+ token: ps.token,
+ appId: app.id
+ });
+ if (session == null) {
+ throw new ApiError(meta.errors.noSuchSession);
+ }
+ if (session.userId == null) {
+ throw new ApiError(meta.errors.pendingSession);
+ }
+ // Lookup access token
+ const accessToken = await AccessTokens.findOneByOrFail({
+ appId: app.id,
+ userId: session.userId
+ });
+ // Delete session
+ AuthSessions.delete(session.id);
+ return {
+ accessToken: accessToken.token,
+ user: await Users.pack(session.userId, null, {
+ detail: true
+ })
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/bites/create.js b/packages/backend/built/server/api/endpoints/bites/create.js
new file mode 100644
index 0000000..0e6978a
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/bites/create.js
@@ -0,0 +1,77 @@
+import { Bites } from "../../../../models/index.js";
+import define from "../../define.js";
+import { createBite } from "../../../../services/create-bite.js";
+import { MINUTE } from "../../../../const.js";
+import { ApiError } from "../../error.js";
+export const meta = {
+ tags: [
+ "bites"
+ ],
+ requireCredential: true,
+ limit: {
+ duration: MINUTE,
+ max: 30
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Bite"
+ },
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "7a80aef8-e4ca-43c2-a997-a6e9b0198374"
+ },
+ bitesDisabled: {
+ message: "User doesn't allow bites.",
+ code: "BITES_DISABLED",
+ id: "a8cfcada-42e1-4ef4-b15e-6fcd903458b7"
+ },
+ bitesFollowersOnly: {
+ message: "User only lets followers bite them.",
+ code: "BITES_FOLLOWERS_ONLY",
+ id: "26a2ed34-a1df-408c-9f75-d7459380fb60"
+ },
+ youHaveBeenBlocked: {
+ message: "You cannot bite because you have been blocked by this user.",
+ code: "YOU_HAVE_BEEN_BLOCKED",
+ id: "c15a5199-7422-4968-941a-2a462c478f7d"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ targetType: {
+ type: "string",
+ enum: [
+ "user",
+ "bite",
+ "note"
+ ]
+ },
+ targetId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "targetType",
+ "targetId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ let biteId;
+ try {
+ biteId = await createBite(me, ps.targetType, ps.targetId);
+ } catch (err) {
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ if (err.id === "f82d8d34-beaf-42f3-9135-477d32288213") throw new ApiError(meta.errors.youHaveBeenBlocked);
+ if (err.id === "35363f14-f489-45e2-81a9-558450710dfe") throw new ApiError(meta.errors.bitesFollowersOnly);
+ if (err.id === "92ce0141-760d-4163-a7a2-73b349e3d133") throw new ApiError(meta.errors.bitesDisabled);
+ throw err;
+ }
+ return await Bites.pack(biteId, me);
+});
diff --git a/packages/backend/built/server/api/endpoints/bites/show.js b/packages/backend/built/server/api/endpoints/bites/show.js
new file mode 100644
index 0000000..8d20376
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/bites/show.js
@@ -0,0 +1,28 @@
+import { Bites } from "../../../../models/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "bites"
+ ],
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Bite"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ biteId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "biteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ return await Bites.pack(ps.biteId, me);
+});
diff --git a/packages/backend/built/server/api/endpoints/blocking/create.js b/packages/backend/built/server/api/endpoints/blocking/create.js
new file mode 100644
index 0000000..b4d4c74
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/blocking/create.js
@@ -0,0 +1,104 @@
+import create from "../../../../services/blocking/create.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { getUser } from "../../common/getters.js";
+import { Blockings, NoteWatchings, Users } from "../../../../models/index.js";
+import { HOUR } from "../../../../const.js";
+import { getGroupActor } from "../../common/get-group-actor.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ limit: {
+ duration: HOUR,
+ max: 100
+ },
+ requireCredential: true,
+ kind: "write:blocks",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "7cc4f851-e2f1-4621-9633-ec9e1d00c01e"
+ },
+ blockeeIsYourself: {
+ message: "Blockee is yourself.",
+ code: "BLOCKEE_IS_YOURSELF",
+ id: "88b19138-f28d-42c0-8499-6a31bbd0fdc6"
+ },
+ alreadyBlocking: {
+ message: "You are already blocking that user.",
+ code: "ALREADY_BLOCKING",
+ id: "787fed64-acb9-464a-82eb-afbd745b9614"
+ },
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "8193cd43-8319-4383-9591-a093cbb4aa3a"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserDetailedNotMe"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ groupId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const blocker = await Users.findOneByOrFail({
+ id: user.id
+ });
+ const group = await getGroupActor(ps.groupId, user);
+ if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
+ // 自分自身
+ if (group == null && user.id === ps.userId) {
+ throw new ApiError(meta.errors.blockeeIsYourself);
+ }
+ // Get blockee
+ const blockee = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ // Check if already blocking
+ const exist = await Blockings.exist({
+ where: {
+ blockeeId: blockee.id,
+ ...group ? {
+ groupId: group.id
+ } : {
+ blockerId: blocker.id,
+ groupId: null
+ }
+ }
+ });
+ if (exist) {
+ throw new ApiError(meta.errors.alreadyBlocking);
+ }
+ await create(blocker, blockee, group?.id ?? null);
+ if (group == null) {
+ NoteWatchings.delete({
+ userId: blocker.id,
+ noteUserId: blockee.id
+ });
+ }
+ return await Users.pack(blockee.id, blocker, {
+ detail: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/blocking/delete.js b/packages/backend/built/server/api/endpoints/blocking/delete.js
new file mode 100644
index 0000000..2b88d0b
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/blocking/delete.js
@@ -0,0 +1,99 @@
+import deleteBlocking from "../../../../services/blocking/delete.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { getUser } from "../../common/getters.js";
+import { Blockings, Users } from "../../../../models/index.js";
+import { HOUR } from "../../../../const.js";
+import { getGroupActor } from "../../common/get-group-actor.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ limit: {
+ duration: HOUR,
+ max: 100
+ },
+ requireCredential: true,
+ kind: "write:blocks",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "8621d8bf-c358-4303-a066-5ea78610eb3f"
+ },
+ blockeeIsYourself: {
+ message: "Blockee is yourself.",
+ code: "BLOCKEE_IS_YOURSELF",
+ id: "06f6fac6-524b-473c-a354-e97a40ae6eac"
+ },
+ notBlocking: {
+ message: "You are not blocking that user.",
+ code: "NOT_BLOCKING",
+ id: "291b2efa-60c6-45c0-9f6a-045c8f9b02cd"
+ },
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "b797376f-f8cc-405b-a1d6-250b84cc80c2"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserDetailedNotMe"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ groupId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const blocker = await Users.findOneByOrFail({
+ id: user.id
+ });
+ const group = await getGroupActor(ps.groupId, user);
+ if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
+ // Check if the blockee is yourself
+ if (group == null && user.id === ps.userId) {
+ throw new ApiError(meta.errors.blockeeIsYourself);
+ }
+ // Get blockee
+ const blockee = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ // Check not blocking
+ const exist = await Blockings.exist({
+ where: {
+ blockeeId: blockee.id,
+ ...group ? {
+ groupId: group.id
+ } : {
+ blockerId: blocker.id,
+ groupId: null
+ }
+ }
+ });
+ if (!exist) {
+ throw new ApiError(meta.errors.notBlocking);
+ }
+ // Delete blocking
+ await deleteBlocking(blocker, blockee, group?.id ?? null);
+ return await Users.pack(blockee.id, blocker, {
+ detail: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/blocking/list.js b/packages/backend/built/server/api/endpoints/blocking/list.js
new file mode 100644
index 0000000..96f889b
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/blocking/list.js
@@ -0,0 +1,72 @@
+import define from "../../define.js";
+import { Blockings } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { ApiError } from "../../error.js";
+import { getGroupActor } from "../../common/get-group-actor.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ kind: "read:blocks",
+ errors: {
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "b31c1162-4adf-4b4b-b3e3-3f2fb8ffcd5f"
+ }
+ },
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Blocking"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 30
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ groupId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const group = await getGroupActor(ps.groupId, me);
+ if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
+ const query = makePaginationQuery(Blockings.createQueryBuilder("blocking"), ps.sinceId, ps.untilId);
+ if (group) {
+ query.andWhere("blocking.groupId = :groupId", {
+ groupId: group.id
+ });
+ } else {
+ query.andWhere("blocking.blockerId = :meId", {
+ meId: me.id
+ });
+ query.andWhere("blocking.groupId IS NULL");
+ }
+ const blockings = await query.take(ps.limit).getMany();
+ return await Blockings.packMany(blockings, me);
+});
diff --git a/packages/backend/built/server/api/endpoints/call-blocking/create.js b/packages/backend/built/server/api/endpoints/call-blocking/create.js
new file mode 100644
index 0000000..cf0b3bd
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/call-blocking/create.js
@@ -0,0 +1,103 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { getUser } from "../../common/getters.js";
+import { CallBlockings, Users } from "../../../../models/index.js";
+import { HOUR } from "../../../../const.js";
+import { genId } from "../../../../misc/gen-id.js";
+import { getGroupActor } from "../../common/get-group-actor.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ limit: {
+ duration: HOUR,
+ max: 100
+ },
+ requireCredential: true,
+ kind: "write:blocks",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "e476b7c0-03fd-44d3-8de2-efc43b15b7e0"
+ },
+ blockeeIsYourself: {
+ message: "Blockee is yourself.",
+ code: "BLOCKEE_IS_YOURSELF",
+ id: "ee4c68c6-2a3d-4e13-8842-d4e7a341f109"
+ },
+ alreadyBlocking: {
+ message: "You are already rejecting calls from that user.",
+ code: "ALREADY_CALL_BLOCKING",
+ id: "9601ea36-97cd-4232-b2d8-326e42e15df4"
+ },
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "1d7a15c6-41e5-4e5c-9ce5-a59b83a3b1ee"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserDetailedNotMe"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ groupId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const blocker = await Users.findOneByOrFail({
+ id: user.id
+ });
+ const group = await getGroupActor(ps.groupId, user);
+ if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
+ if (group == null && user.id === ps.userId) {
+ throw new ApiError(meta.errors.blockeeIsYourself);
+ }
+ const blockee = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ const exist = await CallBlockings.exist({
+ where: {
+ blockeeId: blockee.id,
+ ...group ? {
+ groupId: group.id
+ } : {
+ blockerId: blocker.id,
+ groupId: null
+ }
+ }
+ });
+ if (exist) {
+ throw new ApiError(meta.errors.alreadyBlocking);
+ }
+ await CallBlockings.insert({
+ id: genId(),
+ createdAt: new Date(),
+ blockerId: blocker.id,
+ blockeeId: blockee.id,
+ groupId: group?.id ?? null,
+ blocker,
+ blockee
+ });
+ return await Users.pack(blockee.id, blocker, {
+ detail: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/call-blocking/delete.js b/packages/backend/built/server/api/endpoints/call-blocking/delete.js
new file mode 100644
index 0000000..1b3027b
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/call-blocking/delete.js
@@ -0,0 +1,92 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { getUser } from "../../common/getters.js";
+import { CallBlockings, Users } from "../../../../models/index.js";
+import { HOUR } from "../../../../const.js";
+import { getGroupActor } from "../../common/get-group-actor.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ limit: {
+ duration: HOUR,
+ max: 100
+ },
+ requireCredential: true,
+ kind: "write:blocks",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "84d4f8bd-c78f-4b64-a542-76daefbd2338"
+ },
+ blockeeIsYourself: {
+ message: "Blockee is yourself.",
+ code: "BLOCKEE_IS_YOURSELF",
+ id: "8b347b91-c674-45af-8f4f-1fbbbcbd6f08"
+ },
+ notBlocking: {
+ message: "You are not rejecting calls from that user.",
+ code: "NOT_CALL_BLOCKING",
+ id: "7bd363a2-278f-46f8-a03a-ee1220302a3c"
+ },
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "27f63aa2-58cc-418a-8e9b-83d13f46048f"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserDetailedNotMe"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ groupId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const blocker = await Users.findOneByOrFail({
+ id: user.id
+ });
+ const group = await getGroupActor(ps.groupId, user);
+ if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
+ if (group == null && user.id === ps.userId) {
+ throw new ApiError(meta.errors.blockeeIsYourself);
+ }
+ const blockee = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ const blocking = await CallBlockings.findOneBy({
+ blockeeId: blockee.id,
+ ...group ? {
+ groupId: group.id
+ } : {
+ blockerId: blocker.id,
+ groupId: null
+ }
+ });
+ if (!blocking) {
+ throw new ApiError(meta.errors.notBlocking);
+ }
+ await CallBlockings.delete(blocking.id);
+ return await Users.pack(blockee.id, blocker, {
+ detail: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/call-blocking/list.js b/packages/backend/built/server/api/endpoints/call-blocking/list.js
new file mode 100644
index 0000000..c8b4357
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/call-blocking/list.js
@@ -0,0 +1,71 @@
+import define from "../../define.js";
+import { CallBlockings } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { ApiError } from "../../error.js";
+import { getGroupActor } from "../../common/get-group-actor.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ kind: "read:blocks",
+ errors: {
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "a863670f-a52a-4bf7-8ddf-d64a83a22dda"
+ }
+ },
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 30
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ groupId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const group = await getGroupActor(ps.groupId, me);
+ if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
+ const query = makePaginationQuery(CallBlockings.createQueryBuilder("call_blocking"), ps.sinceId, ps.untilId);
+ if (group) {
+ query.andWhere("call_blocking.groupId = :groupId", {
+ groupId: group.id
+ });
+ } else {
+ query.andWhere("call_blocking.blockerId = :meId", {
+ meId: me.id
+ });
+ query.andWhere("call_blocking.groupId IS NULL");
+ }
+ const blockings = await query.take(ps.limit).getMany();
+ return await CallBlockings.packMany(blockings, me);
+});
diff --git a/packages/backend/built/server/api/endpoints/channels/create.js b/packages/backend/built/server/api/endpoints/channels/create.js
new file mode 100644
index 0000000..6d9fb43
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/channels/create.js
@@ -0,0 +1,69 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { Channels, DriveFiles } from "../../../../models/index.js";
+import { genId } from "../../../../misc/gen-id.js";
+export const meta = {
+ tags: [
+ "channels"
+ ],
+ requireCredential: true,
+ kind: "write:channels",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Channel"
+ },
+ errors: {
+ noSuchFile: {
+ message: "No such file.",
+ code: "NO_SUCH_FILE",
+ id: "cd1e9f3e-5a12-4ab4-96f6-5d0a2cc32050"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ name: {
+ type: "string",
+ minLength: 1,
+ maxLength: 128
+ },
+ description: {
+ type: "string",
+ nullable: true,
+ minLength: 1,
+ maxLength: 2048
+ },
+ bannerId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: [
+ "name"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ let banner = null;
+ if (ps.bannerId != null) {
+ banner = await DriveFiles.findOneBy({
+ id: ps.bannerId,
+ userId: user.id
+ });
+ if (banner == null) {
+ throw new ApiError(meta.errors.noSuchFile);
+ }
+ }
+ const channel = await Channels.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ name: ps.name,
+ description: ps.description || null,
+ bannerId: banner ? banner.id : null
+ }).then((x)=>Channels.findOneByOrFail(x.identifiers[0]));
+ return await Channels.pack(channel, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/channels/featured.js b/packages/backend/built/server/api/endpoints/channels/featured.js
new file mode 100644
index 0000000..5a3d762
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/channels/featured.js
@@ -0,0 +1,29 @@
+import define from "../../define.js";
+import { Channels } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "channels"
+ ],
+ requireCredential: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Channel"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = Channels.createQueryBuilder("channel").where("channel.lastNotedAt IS NOT NULL").orderBy("channel.lastNotedAt", "DESC");
+ const channels = await query.take(10).getMany();
+ return await Promise.all(channels.map((x)=>Channels.pack(x, me)));
+});
diff --git a/packages/backend/built/server/api/endpoints/channels/follow.js b/packages/backend/built/server/api/endpoints/channels/follow.js
new file mode 100644
index 0000000..a252d9f
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/channels/follow.js
@@ -0,0 +1,46 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { Channels, ChannelFollowings } from "../../../../models/index.js";
+import { genId } from "../../../../misc/gen-id.js";
+import { publishUserEvent } from "../../../../services/stream.js";
+export const meta = {
+ tags: [
+ "channels"
+ ],
+ requireCredential: true,
+ kind: "write:channels",
+ errors: {
+ noSuchChannel: {
+ message: "No such channel.",
+ code: "NO_SUCH_CHANNEL",
+ id: "c0031718-d573-4e85-928e-10039f1fbb68"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ channelId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "channelId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const channel = await Channels.findOneBy({
+ id: ps.channelId
+ });
+ if (channel == null) {
+ throw new ApiError(meta.errors.noSuchChannel);
+ }
+ await ChannelFollowings.insert({
+ id: genId(),
+ createdAt: new Date(),
+ followerId: user.id,
+ followeeId: channel.id
+ });
+ publishUserEvent(user.id, "followChannel", channel);
+});
diff --git a/packages/backend/built/server/api/endpoints/channels/followed.js b/packages/backend/built/server/api/endpoints/channels/followed.js
new file mode 100644
index 0000000..bf602fe
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/channels/followed.js
@@ -0,0 +1,63 @@
+import define from "../../define.js";
+import { Channels, ChannelFollowings } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "channels",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "read:channels",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Channel"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 5
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = ChannelFollowings.createQueryBuilder("following").andWhere({
+ followerId: me.id
+ });
+ if (ps.sinceId) {
+ query.andWhere('following."followeeId" > :sinceId', {
+ sinceId: ps.sinceId
+ });
+ }
+ if (ps.untilId) {
+ query.andWhere('following."followeeId" < :untilId', {
+ untilId: ps.untilId
+ });
+ }
+ if (ps.sinceId && !ps.untilId) {
+ query.orderBy('following."followeeId"', "ASC");
+ } else {
+ query.orderBy('following."followeeId"', "DESC");
+ }
+ const followings = await query.take(ps.limit).getMany();
+ return await Promise.all(followings.map((x)=>Channels.pack(x.followeeId, me)));
+});
diff --git a/packages/backend/built/server/api/endpoints/channels/owned.js b/packages/backend/built/server/api/endpoints/channels/owned.js
new file mode 100644
index 0000000..42832f9
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/channels/owned.js
@@ -0,0 +1,49 @@
+import define from "../../define.js";
+import { Channels } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "channels",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "read:channels",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Channel"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 5
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = makePaginationQuery(Channels.createQueryBuilder(), ps.sinceId, ps.untilId).andWhere({
+ userId: me.id
+ });
+ const channels = await query.take(ps.limit).getMany();
+ return await Promise.all(channels.map((x)=>Channels.pack(x, me)));
+});
diff --git a/packages/backend/built/server/api/endpoints/channels/search.js b/packages/backend/built/server/api/endpoints/channels/search.js
new file mode 100644
index 0000000..0d885d5
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/channels/search.js
@@ -0,0 +1,73 @@
+import define from "../../define.js";
+import { Brackets } from "typeorm";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { Channels } from "../../../../models/index.js";
+import { sqlLikeEscape } from "../../../../misc/sql-like-escape.js";
+export const meta = {
+ tags: [
+ "channels"
+ ],
+ requireCredential: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Channel"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ query: {
+ type: "string"
+ },
+ type: {
+ type: "string",
+ enum: [
+ "nameAndDescription",
+ "nameOnly"
+ ],
+ default: "nameAndDescription"
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 5
+ }
+ },
+ required: [
+ "query"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = makePaginationQuery(Channels.createQueryBuilder("channel"), ps.sinceId, ps.untilId);
+ if (ps.type === "nameAndDescription") {
+ query.andWhere(new Brackets((qb)=>{
+ qb.where("channel.name ILIKE :q", {
+ q: `%${sqlLikeEscape(ps.query)}%`
+ }).orWhere("channel.description ILIKE :q", {
+ q: `%${sqlLikeEscape(ps.query)}%`
+ });
+ }));
+ } else {
+ query.andWhere("channel.name ILIKE :q", {
+ q: `%${sqlLikeEscape(ps.query)}%`
+ });
+ }
+ const channels = await query.take(ps.limit).getMany();
+ return await Promise.all(channels.map((x)=>Channels.pack(x, me)));
+});
diff --git a/packages/backend/built/server/api/endpoints/channels/show.js b/packages/backend/built/server/api/endpoints/channels/show.js
new file mode 100644
index 0000000..0939698
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/channels/show.js
@@ -0,0 +1,43 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { Channels } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "channels"
+ ],
+ requireCredential: true,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Channel"
+ },
+ errors: {
+ noSuchChannel: {
+ message: "No such channel.",
+ code: "NO_SUCH_CHANNEL",
+ id: "6f6c314b-7486-4897-8966-c04a66a02923"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ channelId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "channelId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const channel = await Channels.findOneBy({
+ id: ps.channelId
+ });
+ if (channel == null) {
+ throw new ApiError(meta.errors.noSuchChannel);
+ }
+ return await Channels.pack(channel, me);
+});
diff --git a/packages/backend/built/server/api/endpoints/channels/timeline.js b/packages/backend/built/server/api/endpoints/channels/timeline.js
new file mode 100644
index 0000000..47b8b62
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/channels/timeline.js
@@ -0,0 +1,80 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { Notes, Channels } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { activeUsersChart } from "../../../../services/chart/index.js";
+import { generateExcludeMemorietQuery } from "../../common/generate-exclude-memoriet-query.js";
+export const meta = {
+ tags: [
+ "notes",
+ "channels"
+ ],
+ requireCredential: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ },
+ errors: {
+ noSuchChannel: {
+ message: "No such channel.",
+ code: "NO_SUCH_CHANNEL",
+ id: "4d0eeeba-a02c-4c3c-9966-ef60d38d2e7f"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ channelId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ sinceDate: {
+ type: "integer"
+ },
+ untilDate: {
+ type: "integer"
+ }
+ },
+ required: [
+ "channelId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const channel = await Channels.findOneBy({
+ id: ps.channelId
+ });
+ if (channel == null) {
+ throw new ApiError(meta.errors.noSuchChannel);
+ }
+ //#region Construct query
+ const query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate).andWhere("note.channelId = :channelId", {
+ channelId: channel.id
+ }).innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser").leftJoinAndSelect("note.channel", "channel");
+ generateExcludeMemorietQuery(query);
+ //#endregion
+ const timeline = await query.take(ps.limit).getMany();
+ if (user) activeUsersChart.read(user);
+ return await Notes.packMany(timeline, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/channels/unfollow.js b/packages/backend/built/server/api/endpoints/channels/unfollow.js
new file mode 100644
index 0000000..b346d93
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/channels/unfollow.js
@@ -0,0 +1,43 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { Channels, ChannelFollowings } from "../../../../models/index.js";
+import { publishUserEvent } from "../../../../services/stream.js";
+export const meta = {
+ tags: [
+ "channels"
+ ],
+ requireCredential: true,
+ kind: "write:channels",
+ errors: {
+ noSuchChannel: {
+ message: "No such channel.",
+ code: "NO_SUCH_CHANNEL",
+ id: "19959ee9-0153-4c51-bbd9-a98c49dc59d6"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ channelId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "channelId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const channel = await Channels.findOneBy({
+ id: ps.channelId
+ });
+ if (channel == null) {
+ throw new ApiError(meta.errors.noSuchChannel);
+ }
+ await ChannelFollowings.delete({
+ followerId: user.id,
+ followeeId: channel.id
+ });
+ publishUserEvent(user.id, "unfollowChannel", channel);
+});
diff --git a/packages/backend/built/server/api/endpoints/channels/update.js b/packages/backend/built/server/api/endpoints/channels/update.js
new file mode 100644
index 0000000..603870b
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/channels/update.js
@@ -0,0 +1,96 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { Channels, DriveFiles } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "channels"
+ ],
+ requireCredential: true,
+ kind: "write:channels",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Channel"
+ },
+ errors: {
+ noSuchChannel: {
+ message: "No such channel.",
+ code: "NO_SUCH_CHANNEL",
+ id: "f9c5467f-d492-4c3c-9a8d-a70dacc86512"
+ },
+ accessDenied: {
+ message: "You do not have edit privilege of the channel.",
+ code: "ACCESS_DENIED",
+ id: "1fb7cb09-d46a-4fdf-b8df-057788cce513"
+ },
+ noSuchFile: {
+ message: "No such file.",
+ code: "NO_SUCH_FILE",
+ id: "e86c14a4-0da2-4032-8df3-e737a04c7f3b"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ channelId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ name: {
+ type: "string",
+ minLength: 1,
+ maxLength: 128
+ },
+ description: {
+ type: "string",
+ nullable: true,
+ minLength: 1,
+ maxLength: 2048
+ },
+ bannerId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: [
+ "channelId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const channel = await Channels.findOneBy({
+ id: ps.channelId
+ });
+ if (channel == null) {
+ throw new ApiError(meta.errors.noSuchChannel);
+ }
+ if (channel.userId !== me.id) {
+ throw new ApiError(meta.errors.accessDenied);
+ }
+ let banner = undefined;
+ if (ps.bannerId != null) {
+ banner = await DriveFiles.findOneBy({
+ id: ps.bannerId,
+ userId: me.id
+ });
+ if (banner == null) {
+ throw new ApiError(meta.errors.noSuchFile);
+ }
+ } else if (ps.bannerId === null) {
+ banner = null;
+ }
+ await Channels.update(channel.id, {
+ ...ps.name !== undefined ? {
+ name: ps.name
+ } : {},
+ ...ps.description !== undefined ? {
+ description: ps.description
+ } : {},
+ ...banner ? {
+ bannerId: banner.id
+ } : {}
+ });
+ return await Channels.pack(channel.id, me);
+});
diff --git a/packages/backend/built/server/api/endpoints/charts/active-users.js b/packages/backend/built/server/api/endpoints/charts/active-users.js
new file mode 100644
index 0000000..163ecdb
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/charts/active-users.js
@@ -0,0 +1,42 @@
+import { getJsonSchema } from "../../../../services/chart/core.js";
+import { activeUsersChart } from "../../../../services/chart/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "charts",
+ "users"
+ ],
+ requireCredentialPrivateMode: true,
+ res: getJsonSchema(activeUsersChart.schema),
+ allowGet: true,
+ cacheSec: 60 * 60
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ span: {
+ type: "string",
+ enum: [
+ "day",
+ "hour"
+ ]
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 500,
+ default: 30
+ },
+ offset: {
+ type: "integer",
+ nullable: true,
+ default: null
+ }
+ },
+ required: [
+ "span"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ return await activeUsersChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null);
+});
diff --git a/packages/backend/built/server/api/endpoints/charts/ap-request.js b/packages/backend/built/server/api/endpoints/charts/ap-request.js
new file mode 100644
index 0000000..13e0d92
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/charts/ap-request.js
@@ -0,0 +1,41 @@
+import { getJsonSchema } from "../../../../services/chart/core.js";
+import { apRequestChart } from "../../../../services/chart/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "charts"
+ ],
+ requireCredentialPrivateMode: true,
+ res: getJsonSchema(apRequestChart.schema),
+ allowGet: true,
+ cacheSec: 60 * 60
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ span: {
+ type: "string",
+ enum: [
+ "day",
+ "hour"
+ ]
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 500,
+ default: 30
+ },
+ offset: {
+ type: "integer",
+ nullable: true,
+ default: null
+ }
+ },
+ required: [
+ "span"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ return await apRequestChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null);
+});
diff --git a/packages/backend/built/server/api/endpoints/charts/drive.js b/packages/backend/built/server/api/endpoints/charts/drive.js
new file mode 100644
index 0000000..bb57c50
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/charts/drive.js
@@ -0,0 +1,42 @@
+import { getJsonSchema } from "../../../../services/chart/core.js";
+import { driveChart } from "../../../../services/chart/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "charts",
+ "drive"
+ ],
+ requireCredentialPrivateMode: true,
+ res: getJsonSchema(driveChart.schema),
+ allowGet: true,
+ cacheSec: 60 * 60
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ span: {
+ type: "string",
+ enum: [
+ "day",
+ "hour"
+ ]
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 500,
+ default: 30
+ },
+ offset: {
+ type: "integer",
+ nullable: true,
+ default: null
+ }
+ },
+ required: [
+ "span"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ return await driveChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null);
+});
diff --git a/packages/backend/built/server/api/endpoints/charts/federation.js b/packages/backend/built/server/api/endpoints/charts/federation.js
new file mode 100644
index 0000000..7aea8f3
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/charts/federation.js
@@ -0,0 +1,41 @@
+import { getJsonSchema } from "../../../../services/chart/core.js";
+import { federationChart } from "../../../../services/chart/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "charts"
+ ],
+ requireCredentialPrivateMode: true,
+ res: getJsonSchema(federationChart.schema),
+ allowGet: true,
+ cacheSec: 60 * 60
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ span: {
+ type: "string",
+ enum: [
+ "day",
+ "hour"
+ ]
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 500,
+ default: 30
+ },
+ offset: {
+ type: "integer",
+ nullable: true,
+ default: null
+ }
+ },
+ required: [
+ "span"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ return await federationChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null);
+});
diff --git a/packages/backend/built/server/api/endpoints/charts/hashtag.js b/packages/backend/built/server/api/endpoints/charts/hashtag.js
new file mode 100644
index 0000000..8916394
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/charts/hashtag.js
@@ -0,0 +1,46 @@
+import { getJsonSchema } from "../../../../services/chart/core.js";
+import { hashtagChart } from "../../../../services/chart/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "charts",
+ "hashtags"
+ ],
+ requireCredentialPrivateMode: true,
+ res: getJsonSchema(hashtagChart.schema),
+ allowGet: true,
+ cacheSec: 60 * 60
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ span: {
+ type: "string",
+ enum: [
+ "day",
+ "hour"
+ ]
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 500,
+ default: 30
+ },
+ offset: {
+ type: "integer",
+ nullable: true,
+ default: null
+ },
+ tag: {
+ type: "string"
+ }
+ },
+ required: [
+ "span",
+ "tag"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ return await hashtagChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.tag);
+});
diff --git a/packages/backend/built/server/api/endpoints/charts/instance.js b/packages/backend/built/server/api/endpoints/charts/instance.js
new file mode 100644
index 0000000..f753d6c
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/charts/instance.js
@@ -0,0 +1,45 @@
+import { getJsonSchema } from "../../../../services/chart/core.js";
+import { instanceChart } from "../../../../services/chart/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "charts"
+ ],
+ requireCredentialPrivateMode: true,
+ res: getJsonSchema(instanceChart.schema),
+ allowGet: true,
+ cacheSec: 60 * 60
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ span: {
+ type: "string",
+ enum: [
+ "day",
+ "hour"
+ ]
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 500,
+ default: 30
+ },
+ offset: {
+ type: "integer",
+ nullable: true,
+ default: null
+ },
+ host: {
+ type: "string"
+ }
+ },
+ required: [
+ "span",
+ "host"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ return await instanceChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.host);
+});
diff --git a/packages/backend/built/server/api/endpoints/charts/notes.js b/packages/backend/built/server/api/endpoints/charts/notes.js
new file mode 100644
index 0000000..5ee3c8b
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/charts/notes.js
@@ -0,0 +1,42 @@
+import { getJsonSchema } from "../../../../services/chart/core.js";
+import { notesChart } from "../../../../services/chart/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "charts",
+ "notes"
+ ],
+ requireCredentialPrivateMode: true,
+ res: getJsonSchema(notesChart.schema),
+ allowGet: true,
+ cacheSec: 60 * 60
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ span: {
+ type: "string",
+ enum: [
+ "day",
+ "hour"
+ ]
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 500,
+ default: 30
+ },
+ offset: {
+ type: "integer",
+ nullable: true,
+ default: null
+ }
+ },
+ required: [
+ "span"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ return await notesChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null);
+});
diff --git a/packages/backend/built/server/api/endpoints/charts/user/drive.js b/packages/backend/built/server/api/endpoints/charts/user/drive.js
new file mode 100644
index 0000000..d9b3db1
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/charts/user/drive.js
@@ -0,0 +1,48 @@
+import { getJsonSchema } from "../../../../../services/chart/core.js";
+import { perUserDriveChart } from "../../../../../services/chart/index.js";
+import define from "../../../define.js";
+export const meta = {
+ tags: [
+ "charts",
+ "drive",
+ "users"
+ ],
+ requireCredentialPrivateMode: true,
+ res: getJsonSchema(perUserDriveChart.schema),
+ allowGet: true,
+ cacheSec: 60 * 60
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ span: {
+ type: "string",
+ enum: [
+ "day",
+ "hour"
+ ]
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 500,
+ default: 30
+ },
+ offset: {
+ type: "integer",
+ nullable: true,
+ default: null
+ },
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "span",
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ return await perUserDriveChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId);
+});
diff --git a/packages/backend/built/server/api/endpoints/charts/user/following.js b/packages/backend/built/server/api/endpoints/charts/user/following.js
new file mode 100644
index 0000000..97a3396
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/charts/user/following.js
@@ -0,0 +1,48 @@
+import define from "../../../define.js";
+import { getJsonSchema } from "../../../../../services/chart/core.js";
+import { perUserFollowingChart } from "../../../../../services/chart/index.js";
+export const meta = {
+ tags: [
+ "charts",
+ "users",
+ "following"
+ ],
+ requireCredentialPrivateMode: true,
+ res: getJsonSchema(perUserFollowingChart.schema),
+ allowGet: true,
+ cacheSec: 60 * 60
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ span: {
+ type: "string",
+ enum: [
+ "day",
+ "hour"
+ ]
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 500,
+ default: 30
+ },
+ offset: {
+ type: "integer",
+ nullable: true,
+ default: null
+ },
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "span",
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ return await perUserFollowingChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId);
+});
diff --git a/packages/backend/built/server/api/endpoints/charts/user/notes.js b/packages/backend/built/server/api/endpoints/charts/user/notes.js
new file mode 100644
index 0000000..499d586
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/charts/user/notes.js
@@ -0,0 +1,48 @@
+import { getJsonSchema } from "../../../../../services/chart/core.js";
+import { perUserNotesChart } from "../../../../../services/chart/index.js";
+import define from "../../../define.js";
+export const meta = {
+ tags: [
+ "charts",
+ "users",
+ "notes"
+ ],
+ requireCredentialPrivateMode: true,
+ res: getJsonSchema(perUserNotesChart.schema),
+ allowGet: true,
+ cacheSec: 60 * 60
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ span: {
+ type: "string",
+ enum: [
+ "day",
+ "hour"
+ ]
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 500,
+ default: 30
+ },
+ offset: {
+ type: "integer",
+ nullable: true,
+ default: null
+ },
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "span",
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ return await perUserNotesChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId);
+});
diff --git a/packages/backend/built/server/api/endpoints/charts/user/reactions.js b/packages/backend/built/server/api/endpoints/charts/user/reactions.js
new file mode 100644
index 0000000..0c9259c
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/charts/user/reactions.js
@@ -0,0 +1,48 @@
+import { getJsonSchema } from "../../../../../services/chart/core.js";
+import { perUserReactionsChart } from "../../../../../services/chart/index.js";
+import define from "../../../define.js";
+export const meta = {
+ tags: [
+ "charts",
+ "users",
+ "reactions"
+ ],
+ requireCredentialPrivateMode: true,
+ res: getJsonSchema(perUserReactionsChart.schema),
+ allowGet: true,
+ cacheSec: 60 * 60
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ span: {
+ type: "string",
+ enum: [
+ "day",
+ "hour"
+ ]
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 500,
+ default: 30
+ },
+ offset: {
+ type: "integer",
+ nullable: true,
+ default: null
+ },
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "span",
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ return await perUserReactionsChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null, ps.userId);
+});
diff --git a/packages/backend/built/server/api/endpoints/charts/users.js b/packages/backend/built/server/api/endpoints/charts/users.js
new file mode 100644
index 0000000..32c02ae
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/charts/users.js
@@ -0,0 +1,42 @@
+import { getJsonSchema } from "../../../../services/chart/core.js";
+import { usersChart } from "../../../../services/chart/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "charts",
+ "users"
+ ],
+ requireCredentialPrivateMode: true,
+ res: getJsonSchema(usersChart.schema),
+ allowGet: true,
+ cacheSec: 60 * 60
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ span: {
+ type: "string",
+ enum: [
+ "day",
+ "hour"
+ ]
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 500,
+ default: 30
+ },
+ offset: {
+ type: "integer",
+ nullable: true,
+ default: null
+ }
+ },
+ required: [
+ "span"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ return await usersChart.getChart(ps.span, ps.limit, ps.offset ? new Date(ps.offset) : null);
+});
diff --git a/packages/backend/built/server/api/endpoints/clips/add-note.js b/packages/backend/built/server/api/endpoints/clips/add-note.js
new file mode 100644
index 0000000..bcec1ce
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/clips/add-note.js
@@ -0,0 +1,75 @@
+import define from "../../define.js";
+import { ClipNotes, Clips } from "../../../../models/index.js";
+import { ApiError } from "../../error.js";
+import { genId } from "../../../../misc/gen-id.js";
+import { getNote } from "../../common/getters.js";
+export const meta = {
+ tags: [
+ "account",
+ "notes",
+ "clips"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchClip: {
+ message: "No such clip.",
+ code: "NO_SUCH_CLIP",
+ id: "d6e76cc0-a1b5-4c7c-a287-73fa9c716dcf"
+ },
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "fc8c0b49-c7a3-4664-a0a6-b418d386bb8b"
+ },
+ alreadyClipped: {
+ message: "The note has already been clipped.",
+ code: "ALREADY_CLIPPED",
+ id: "734806c4-542c-463a-9311-15c512803965"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ clipId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "clipId",
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const clip = await Clips.findOneBy({
+ id: ps.clipId,
+ userId: user.id
+ });
+ if (clip == null) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
+ const note = await getNote(ps.noteId, user).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ const exist = await ClipNotes.exist({
+ where: {
+ noteId: note.id,
+ clipId: clip.id
+ }
+ });
+ if (exist) {
+ throw new ApiError(meta.errors.alreadyClipped);
+ }
+ await ClipNotes.insert({
+ id: genId(),
+ noteId: note.id,
+ clipId: clip.id
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/clips/create.js b/packages/backend/built/server/api/endpoints/clips/create.js
new file mode 100644
index 0000000..1744736
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/clips/create.js
@@ -0,0 +1,50 @@
+import define from "../../define.js";
+import { genId } from "../../../../misc/gen-id.js";
+import { Clips } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "clips"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Clip"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ name: {
+ type: "string",
+ minLength: 1,
+ maxLength: 100
+ },
+ isPublic: {
+ type: "boolean",
+ default: false
+ },
+ description: {
+ type: "string",
+ nullable: true,
+ minLength: 1,
+ maxLength: 2048
+ }
+ },
+ required: [
+ "name"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const clip = await Clips.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ name: ps.name,
+ isPublic: ps.isPublic,
+ description: ps.description
+ }).then((x)=>Clips.findOneByOrFail(x.identifiers[0]));
+ return await Clips.pack(clip);
+});
diff --git a/packages/backend/built/server/api/endpoints/clips/delete.js b/packages/backend/built/server/api/endpoints/clips/delete.js
new file mode 100644
index 0000000..21b2ec2
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/clips/delete.js
@@ -0,0 +1,39 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { Clips } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "clips"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchClip: {
+ message: "No such clip.",
+ code: "NO_SUCH_CLIP",
+ id: "70ca08ba-6865-4630-b6fb-8494759aa754"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ clipId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "clipId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const clip = await Clips.findOneBy({
+ id: ps.clipId,
+ userId: user.id
+ });
+ if (clip == null) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
+ await Clips.delete(clip.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/clips/list.js b/packages/backend/built/server/api/endpoints/clips/list.js
new file mode 100644
index 0000000..fde0125
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/clips/list.js
@@ -0,0 +1,32 @@
+import define from "../../define.js";
+import { Clips } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "clips",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "read:account",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Clip"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const clips = await Clips.findBy({
+ userId: me.id
+ });
+ return await Promise.all(clips.map((x)=>Clips.pack(x)));
+});
diff --git a/packages/backend/built/server/api/endpoints/clips/notes.js b/packages/backend/built/server/api/endpoints/clips/notes.js
new file mode 100644
index 0000000..5f651c8
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/clips/notes.js
@@ -0,0 +1,82 @@
+import define from "../../define.js";
+import { ClipNotes, Clips, Notes } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { ApiError } from "../../error.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+export const meta = {
+ tags: [
+ "account",
+ "notes",
+ "clips"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ kind: "read:account",
+ errors: {
+ noSuchClip: {
+ message: "No such clip.",
+ code: "NO_SUCH_CLIP",
+ id: "1d7645e6-2b6d-4635-b0fe-fe22b0e72e00"
+ }
+ },
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ clipId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "clipId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const clip = await Clips.findOneBy({
+ id: ps.clipId
+ });
+ if (clip == null) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
+ if (!clip.isPublic && (user == null || clip.userId !== user.id)) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
+ const query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId).innerJoin(ClipNotes.metadata.targetName, "clipNote", "clipNote.noteId = note.id").innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser").andWhere("clipNote.clipId = :clipId", {
+ clipId: clip.id
+ });
+ if (user) {
+ generateVisibilityQuery(query, user);
+ generateMutedUserQuery(query, user);
+ generateBlockedUserQuery(query, user);
+ }
+ const notes = await query.take(ps.limit).getMany();
+ return await Notes.packMany(notes, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/clips/remove-note.js b/packages/backend/built/server/api/endpoints/clips/remove-note.js
new file mode 100644
index 0000000..c2db398
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/clips/remove-note.js
@@ -0,0 +1,59 @@
+import define from "../../define.js";
+import { ClipNotes, Clips } from "../../../../models/index.js";
+import { ApiError } from "../../error.js";
+import { getNote } from "../../common/getters.js";
+export const meta = {
+ tags: [
+ "account",
+ "notes",
+ "clips"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchClip: {
+ message: "No such clip.",
+ code: "NO_SUCH_CLIP",
+ id: "b80525c6-97f7-49d7-a42d-ebccd49cfd52"
+ },
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "aff017de-190e-434b-893e-33a9ff5049d8"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ clipId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "clipId",
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const clip = await Clips.findOneBy({
+ id: ps.clipId,
+ userId: user.id
+ });
+ if (clip == null) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
+ const note = await getNote(ps.noteId).catch((e)=>{
+ if (e.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw e;
+ });
+ await ClipNotes.delete({
+ noteId: note.id,
+ clipId: clip.id
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/clips/show.js b/packages/backend/built/server/api/endpoints/clips/show.js
new file mode 100644
index 0000000..acac516
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/clips/show.js
@@ -0,0 +1,50 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { Clips } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "clips",
+ "account"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ kind: "read:account",
+ errors: {
+ noSuchClip: {
+ message: "No such clip.",
+ code: "NO_SUCH_CLIP",
+ id: "c3c5fe33-d62c-44d2-9ea5-d997703f5c20"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Clip"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ clipId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "clipId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ // Fetch the clip
+ const clip = await Clips.findOneBy({
+ id: ps.clipId
+ });
+ if (clip == null) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
+ if (!clip.isPublic && (me == null || clip.userId !== me.id)) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
+ return await Clips.pack(clip);
+});
diff --git a/packages/backend/built/server/api/endpoints/clips/update.js b/packages/backend/built/server/api/endpoints/clips/update.js
new file mode 100644
index 0000000..00ed812
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/clips/update.js
@@ -0,0 +1,66 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { Clips } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "clips"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchClip: {
+ message: "No such clip.",
+ code: "NO_SUCH_CLIP",
+ id: "b4d92d70-b216-46fa-9a3f-a8c811699257"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Clip"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ clipId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ name: {
+ type: "string",
+ minLength: 1,
+ maxLength: 100
+ },
+ isPublic: {
+ type: "boolean"
+ },
+ description: {
+ type: "string",
+ nullable: true,
+ minLength: 1,
+ maxLength: 2048
+ }
+ },
+ required: [
+ "clipId",
+ "name"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Fetch the clip
+ const clip = await Clips.findOneBy({
+ id: ps.clipId,
+ userId: user.id
+ });
+ if (clip == null) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
+ await Clips.update(clip.id, {
+ name: ps.name,
+ description: ps.description,
+ isPublic: ps.isPublic
+ });
+ return await Clips.pack(clip.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/compatibility/custom-emojis.js b/packages/backend/built/server/api/endpoints/compatibility/custom-emojis.js
new file mode 100644
index 0000000..644ad34
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/compatibility/custom-emojis.js
@@ -0,0 +1,40 @@
+import { Emojis } from "../../../../models/index.js";
+import { IsNull, In } from "typeorm";
+import { FILE_TYPE_BROWSERSAFE } from "../../../../const.js";
+import define from "../../define.js";
+export const meta = {
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ allowGet: true,
+ tags: [
+ "meta"
+ ]
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async ()=>{
+ const now = Date.now();
+ const emojis = await Emojis.find({
+ where: {
+ host: IsNull(),
+ type: In(FILE_TYPE_BROWSERSAFE)
+ },
+ select: [
+ "name",
+ "originalUrl",
+ "publicUrl",
+ "category"
+ ]
+ });
+ const emojiList = emojis.map((emoji)=>({
+ shortcode: emoji.name,
+ url: emoji.originalUrl,
+ static_url: emoji.publicUrl,
+ visible_in_picker: true,
+ category: emoji.category
+ }));
+ return emojiList;
+});
diff --git a/packages/backend/built/server/api/endpoints/compatibility/peers.js b/packages/backend/built/server/api/endpoints/compatibility/peers.js
new file mode 100644
index 0000000..1113bb4
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/compatibility/peers.js
@@ -0,0 +1,25 @@
+import { Instances } from "../../../../models/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "meta"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ allowGet: true,
+ cacheSec: 60
+};
+export const paramDef = {
+ type: "object"
+};
+export default define(meta, paramDef, async (ps)=>{
+ const instances = await Instances.find({
+ select: [
+ "host"
+ ],
+ where: {
+ isSuspended: false
+ }
+ });
+ return instances.map((instance)=>instance.host);
+});
diff --git a/packages/backend/built/server/api/endpoints/custom-motd.js b/packages/backend/built/server/api/endpoints/custom-motd.js
new file mode 100644
index 0000000..df3906a
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/custom-motd.js
@@ -0,0 +1,30 @@
+// import { IsNull } from 'typeorm';
+import { fetchMeta } from "../../../misc/fetch-meta.js";
+import define from "../define.js";
+export const meta = {
+ tags: [
+ "meta"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async ()=>{
+ const meta = await fetchMeta();
+ const motd = await Promise.all(meta.customMOTD.map((x)=>x));
+ return motd;
+});
diff --git a/packages/backend/built/server/api/endpoints/custom-splash-icons.js b/packages/backend/built/server/api/endpoints/custom-splash-icons.js
new file mode 100644
index 0000000..3a239b5
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/custom-splash-icons.js
@@ -0,0 +1,30 @@
+// import { IsNull } from 'typeorm';
+import { fetchMeta } from "../../../misc/fetch-meta.js";
+import define from "../define.js";
+export const meta = {
+ tags: [
+ "meta"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async ()=>{
+ const meta = await fetchMeta();
+ const icons = await Promise.all(meta.customSplashIcons.map((x)=>x));
+ return icons;
+});
diff --git a/packages/backend/built/server/api/endpoints/drive.js b/packages/backend/built/server/api/endpoints/drive.js
new file mode 100644
index 0000000..dff25e4
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/drive.js
@@ -0,0 +1,42 @@
+import { fetchMeta } from "../../../misc/fetch-meta.js";
+import { DriveFiles } from "../../../models/index.js";
+import define from "../define.js";
+export const meta = {
+ tags: [
+ "drive",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "read:drive",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ capacity: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ usage: {
+ type: "number",
+ optional: false,
+ nullable: false
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const instance = await fetchMeta(true);
+ // Calculate drive usage
+ const usage = await DriveFiles.calcDriveUsageOf(user.id);
+ return {
+ capacity: 1024 * 1024 * (user.driveCapacityOverrideMb || instance.localDriveCapacityMb),
+ usage: usage
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/drive/files.js b/packages/backend/built/server/api/endpoints/drive/files.js
new file mode 100644
index 0000000..6e40e44
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/drive/files.js
@@ -0,0 +1,80 @@
+import define from "../../define.js";
+import { DriveFiles } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "drive"
+ ],
+ requireCredential: true,
+ kind: "read:drive",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "DriveFile"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ folderId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true,
+ default: null
+ },
+ type: {
+ type: "string",
+ nullable: true,
+ pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1)
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(DriveFiles.createQueryBuilder("file"), ps.sinceId, ps.untilId).andWhere("file.userId = :userId", {
+ userId: user.id
+ });
+ if (ps.folderId) {
+ query.andWhere("file.folderId = :folderId", {
+ folderId: ps.folderId
+ });
+ } else {
+ query.andWhere("file.folderId IS NULL");
+ }
+ if (ps.type) {
+ if (ps.type.endsWith("/*")) {
+ query.andWhere("file.type like :type", {
+ type: `${ps.type.replace("/*", "/")}%`
+ });
+ } else {
+ query.andWhere("file.type = :type", {
+ type: ps.type
+ });
+ }
+ }
+ const files = await query.take(ps.limit).getMany();
+ return await DriveFiles.packMany(files, {
+ detail: false,
+ self: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/drive/folders.js b/packages/backend/built/server/api/endpoints/drive/folders.js
new file mode 100644
index 0000000..8439c06
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/drive/folders.js
@@ -0,0 +1,61 @@
+import define from "../../define.js";
+import { DriveFolders } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "drive"
+ ],
+ requireCredential: true,
+ kind: "read:drive",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "DriveFolder"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ folderId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true,
+ default: null
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(DriveFolders.createQueryBuilder("folder"), ps.sinceId, ps.untilId).andWhere("folder.userId = :userId", {
+ userId: user.id
+ });
+ if (ps.folderId) {
+ query.andWhere("folder.parentId = :parentId", {
+ parentId: ps.folderId
+ });
+ } else {
+ query.andWhere("folder.parentId IS NULL");
+ }
+ const folders = await query.take(ps.limit).getMany();
+ return await Promise.all(folders.map((folder)=>DriveFolders.pack(folder)));
+});
diff --git a/packages/backend/built/server/api/endpoints/drive/folders/create.js b/packages/backend/built/server/api/endpoints/drive/folders/create.js
new file mode 100644
index 0000000..7cc1022
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/drive/folders/create.js
@@ -0,0 +1,67 @@
+import { publishDriveStream } from "../../../../../services/stream.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { DriveFolders } from "../../../../../models/index.js";
+import { genId } from "../../../../../misc/gen-id.js";
+export const meta = {
+ tags: [
+ "drive"
+ ],
+ requireCredential: true,
+ kind: "write:drive",
+ errors: {
+ noSuchFolder: {
+ message: "No such folder.",
+ code: "NO_SUCH_FOLDER",
+ id: "53326628-a00d-40a6-a3cd-8975105c0f95"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "DriveFolder"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ name: {
+ type: "string",
+ default: "Untitled",
+ maxLength: 200
+ },
+ parentId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // If the parent folder is specified
+ let parent = null;
+ if (ps.parentId) {
+ // Fetch parent folder
+ parent = await DriveFolders.findOneBy({
+ id: ps.parentId,
+ userId: user.id
+ });
+ if (parent == null) {
+ throw new ApiError(meta.errors.noSuchFolder);
+ }
+ }
+ // Create folder
+ const folder = await DriveFolders.insert({
+ id: genId(),
+ createdAt: new Date(),
+ name: ps.name,
+ parentId: parent !== null ? parent.id : null,
+ userId: user.id
+ }).then((x)=>DriveFolders.findOneByOrFail(x.identifiers[0]));
+ const folderObj = await DriveFolders.pack(folder);
+ // Publish folderCreated event
+ publishDriveStream(user.id, "folderCreated", folderObj);
+ return folderObj;
+});
diff --git a/packages/backend/built/server/api/endpoints/drive/folders/delete.js b/packages/backend/built/server/api/endpoints/drive/folders/delete.js
new file mode 100644
index 0000000..4e90f49
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/drive/folders/delete.js
@@ -0,0 +1,59 @@
+import define from "../../../define.js";
+import { publishDriveStream } from "../../../../../services/stream.js";
+import { ApiError } from "../../../error.js";
+import { DriveFolders, DriveFiles } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "drive"
+ ],
+ requireCredential: true,
+ kind: "write:drive",
+ errors: {
+ noSuchFolder: {
+ message: "No such folder.",
+ code: "NO_SUCH_FOLDER",
+ id: "1069098f-c281-440f-b085-f9932edbe091"
+ },
+ hasChildFilesOrFolders: {
+ message: "This folder has child files or folders.",
+ code: "HAS_CHILD_FILES_OR_FOLDERS",
+ id: "b0fc8a17-963c-405d-bfbc-859a487295e1"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ folderId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "folderId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Get folder
+ const folder = await DriveFolders.findOneBy({
+ id: ps.folderId,
+ userId: user.id
+ });
+ if (folder == null) {
+ throw new ApiError(meta.errors.noSuchFolder);
+ }
+ const [childFoldersCount, childFilesCount] = await Promise.all([
+ DriveFolders.countBy({
+ parentId: folder.id
+ }),
+ DriveFiles.countBy({
+ folderId: folder.id
+ })
+ ]);
+ if (childFoldersCount !== 0 || childFilesCount !== 0) {
+ throw new ApiError(meta.errors.hasChildFilesOrFolders);
+ }
+ await DriveFolders.delete(folder.id);
+ // Publish folderCreated event
+ publishDriveStream(user.id, "folderDeleted", folder.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/drive/folders/find.js b/packages/backend/built/server/api/endpoints/drive/folders/find.js
new file mode 100644
index 0000000..2564414
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/drive/folders/find.js
@@ -0,0 +1,46 @@
+import define from "../../../define.js";
+import { DriveFolders } from "../../../../../models/index.js";
+import { IsNull } from "typeorm";
+export const meta = {
+ tags: [
+ "drive"
+ ],
+ requireCredential: true,
+ kind: "read:drive",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "DriveFolder"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ name: {
+ type: "string"
+ },
+ parentId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true,
+ default: null
+ }
+ },
+ required: [
+ "name"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const folders = await DriveFolders.findBy({
+ name: ps.name,
+ userId: user.id,
+ parentId: ps.parentId ?? IsNull()
+ });
+ return await Promise.all(folders.map((folder)=>DriveFolders.pack(folder)));
+});
diff --git a/packages/backend/built/server/api/endpoints/drive/folders/show.js b/packages/backend/built/server/api/endpoints/drive/folders/show.js
new file mode 100644
index 0000000..7ecde15
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/drive/folders/show.js
@@ -0,0 +1,48 @@
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { DriveFolders } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "drive"
+ ],
+ requireCredential: true,
+ kind: "read:drive",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "DriveFolder"
+ },
+ errors: {
+ noSuchFolder: {
+ message: "No such folder.",
+ code: "NO_SUCH_FOLDER",
+ id: "d74ab9eb-bb09-4bba-bf24-fb58f761e1e9"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ folderId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "folderId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Get folder
+ const folder = await DriveFolders.findOneBy({
+ id: ps.folderId,
+ userId: user.id
+ });
+ if (folder == null) {
+ throw new ApiError(meta.errors.noSuchFolder);
+ }
+ return await DriveFolders.pack(folder, {
+ detail: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/drive/folders/update.js b/packages/backend/built/server/api/endpoints/drive/folders/update.js
new file mode 100644
index 0000000..f39e3b6
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/drive/folders/update.js
@@ -0,0 +1,111 @@
+import { publishDriveStream } from "../../../../../services/stream.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { DriveFolders } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "drive"
+ ],
+ requireCredential: true,
+ kind: "write:drive",
+ errors: {
+ noSuchFolder: {
+ message: "No such folder.",
+ code: "NO_SUCH_FOLDER",
+ id: "f7974dac-2c0d-4a27-926e-23583b28e98e"
+ },
+ noSuchParentFolder: {
+ message: "No such parent folder.",
+ code: "NO_SUCH_PARENT_FOLDER",
+ id: "ce104e3a-faaf-49d5-b459-10ff0cbbcaa1"
+ },
+ recursiveNesting: {
+ message: "It can not be structured like nesting folders recursively.",
+ code: "NO_SUCH_PARENT_FOLDER",
+ id: "ce104e3a-faaf-49d5-b459-10ff0cbbcaa1"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "DriveFolder"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ folderId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ name: {
+ type: "string",
+ maxLength: 200
+ },
+ parentId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: [
+ "folderId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Fetch folder
+ const folder = await DriveFolders.findOneBy({
+ id: ps.folderId,
+ userId: user.id
+ });
+ if (folder == null) {
+ throw new ApiError(meta.errors.noSuchFolder);
+ }
+ if (ps.name) folder.name = ps.name;
+ if (ps.parentId !== undefined) {
+ if (ps.parentId === folder.id) {
+ throw new ApiError(meta.errors.recursiveNesting);
+ } else if (ps.parentId === null) {
+ folder.parentId = null;
+ } else {
+ // Get parent folder
+ const parent = await DriveFolders.findOneBy({
+ id: ps.parentId,
+ userId: user.id
+ });
+ if (parent == null) {
+ throw new ApiError(meta.errors.noSuchParentFolder);
+ }
+ // Check if the circular reference will occur
+ async function checkCircle(folderId) {
+ // Fetch folder
+ const folder2 = await DriveFolders.findOneBy({
+ id: folderId
+ });
+ if (folder2.id === folder.id) {
+ return true;
+ } else if (folder2.parentId) {
+ return await checkCircle(folder2.parentId);
+ } else {
+ return false;
+ }
+ }
+ if (parent.parentId !== null) {
+ if (await checkCircle(parent.parentId)) {
+ throw new ApiError(meta.errors.recursiveNesting);
+ }
+ }
+ folder.parentId = parent.id;
+ }
+ }
+ // Update
+ DriveFolders.update(folder.id, {
+ name: folder.name,
+ parentId: folder.parentId
+ });
+ const folderObj = await DriveFolders.pack(folder);
+ // Publish folderUpdated event
+ publishDriveStream(user.id, "folderUpdated", folderObj);
+ return folderObj;
+});
diff --git a/packages/backend/built/server/api/endpoints/drive/stream.js b/packages/backend/built/server/api/endpoints/drive/stream.js
new file mode 100644
index 0000000..d7b4987
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/drive/stream.js
@@ -0,0 +1,66 @@
+import define from "../../define.js";
+import { DriveFiles } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "drive"
+ ],
+ requireCredential: true,
+ kind: "read:drive",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "DriveFile"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ type: {
+ type: "string",
+ pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1)
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(DriveFiles.createQueryBuilder("file"), ps.sinceId, ps.untilId).andWhere("file.userId = :userId", {
+ userId: user.id
+ });
+ if (ps.type) {
+ if (ps.type.endsWith("/*")) {
+ query.andWhere("file.type like :type", {
+ type: `${ps.type.replace("/*", "/")}%`
+ });
+ } else {
+ query.andWhere("file.type = :type", {
+ type: ps.type
+ });
+ }
+ }
+ const files = await query.take(ps.limit).getMany();
+ return await DriveFiles.packMany(files, {
+ detail: false,
+ self: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/email-address/available.js b/packages/backend/built/server/api/endpoints/email-address/available.js
new file mode 100644
index 0000000..24be0e1
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/email-address/available.js
@@ -0,0 +1,39 @@
+import define from "../../define.js";
+import { validateEmailForAccount } from "../../../../services/validate-email-for-account.js";
+export const meta = {
+ tags: [
+ "users"
+ ],
+ requireCredential: false,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ available: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ reason: {
+ type: "string",
+ optional: false,
+ nullable: true
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ emailAddress: {
+ type: "string"
+ }
+ },
+ required: [
+ "emailAddress"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ return await validateEmailForAccount(ps.emailAddress);
+});
diff --git a/packages/backend/built/server/api/endpoints/emoji.js b/packages/backend/built/server/api/endpoints/emoji.js
new file mode 100644
index 0000000..ca53d0b
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/emoji.js
@@ -0,0 +1,79 @@
+import define from "../define.js";
+import { ApiError } from "../error.js";
+import { populateEmojiOrUserEmoji } from "../../../misc/populate-emojis.js";
+export const meta = {
+ tags: [
+ "meta"
+ ],
+ requireCredential: false,
+ allowGet: true,
+ cacheSec: 3600,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ name: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ url: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ glyph: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ glyphUrl: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ width: {
+ type: "number",
+ optional: false,
+ nullable: true
+ },
+ height: {
+ type: "number",
+ optional: false,
+ nullable: true
+ }
+ }
+ },
+ errors: {
+ noSuchEmoji: {
+ message: "No such emoji.",
+ code: "NO_SUCH_EMOJI",
+ id: "fc46b5a4-6b92-49e7-9e24-8d9a974f31a4"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ name: {
+ type: "string"
+ }
+ },
+ required: [
+ "name"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const name = /^([:;]).+\1$/.test(ps.name) ? ps.name.slice(1, -1) : ps.name;
+ const emoji = await populateEmojiOrUserEmoji(name, null);
+ if (!emoji) throw new ApiError(meta.errors.noSuchEmoji);
+ return {
+ name: emoji.name,
+ url: emoji.url,
+ glyph: emoji.glyph,
+ glyphUrl: emoji.glyphUrl,
+ width: emoji.width,
+ height: emoji.height
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/endpoint.js b/packages/backend/built/server/api/endpoints/endpoint.js
new file mode 100644
index 0000000..e9dc2df
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/endpoint.js
@@ -0,0 +1,29 @@
+import define from "../define.js";
+import endpoints from "../endpoints.js";
+export const meta = {
+ requireCredential: false,
+ tags: [
+ "meta"
+ ]
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ endpoint: {
+ type: "string"
+ }
+ },
+ required: [
+ "endpoint"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const ep = endpoints.find((x)=>x.name === ps.endpoint);
+ if (ep == null) return null;
+ return {
+ params: Object.entries(ep.params.properties || {}).map(([k, v])=>({
+ name: k,
+ type: v.type.charAt(0).toUpperCase() + v.type.slice(1)
+ }))
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/endpoints.js b/packages/backend/built/server/api/endpoints/endpoints.js
new file mode 100644
index 0000000..bfd256e
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/endpoints.js
@@ -0,0 +1,32 @@
+import define from "../define.js";
+import endpoints from "../endpoints.js";
+export const meta = {
+ requireCredential: false,
+ tags: [
+ "meta"
+ ],
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ example: [
+ "admin/abuse-user-reports",
+ "admin/accounts/create",
+ "admin/announcements/create",
+ "..."
+ ]
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async ()=>{
+ return endpoints.map((x)=>x.name);
+});
diff --git a/packages/backend/built/server/api/endpoints/export-custom-emojis.js b/packages/backend/built/server/api/endpoints/export-custom-emojis.js
new file mode 100644
index 0000000..3519dd4
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/export-custom-emojis.js
@@ -0,0 +1,19 @@
+import { createExportCustomEmojisJob } from "../../../queue/index.js";
+import define from "../define.js";
+import { HOUR } from "../../../const.js";
+export const meta = {
+ secure: true,
+ requireCredential: true,
+ limit: {
+ duration: HOUR,
+ max: 1
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ createExportCustomEmojisJob(user);
+});
diff --git a/packages/backend/built/server/api/endpoints/federation/followers.js b/packages/backend/built/server/api/endpoints/federation/followers.js
new file mode 100644
index 0000000..a656273
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/federation/followers.js
@@ -0,0 +1,56 @@
+import define from "../../define.js";
+import { Followings } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "federation"
+ ],
+ requireCredential: true,
+ requireAdmin: true,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Following"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ host: {
+ type: "string"
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ }
+ },
+ required: [
+ "host"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = makePaginationQuery(Followings.createQueryBuilder("following"), ps.sinceId, ps.untilId).andWhere("following.followeeHost = :host", {
+ host: ps.host
+ });
+ const followings = await query.take(ps.limit).getMany();
+ return await Followings.packMany(followings, me, {
+ populateFollowee: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/federation/following.js b/packages/backend/built/server/api/endpoints/federation/following.js
new file mode 100644
index 0000000..a2044c8
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/federation/following.js
@@ -0,0 +1,56 @@
+import define from "../../define.js";
+import { Followings } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "federation"
+ ],
+ requireCredential: true,
+ requireAdmin: true,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Following"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ host: {
+ type: "string"
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ }
+ },
+ required: [
+ "host"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = makePaginationQuery(Followings.createQueryBuilder("following"), ps.sinceId, ps.untilId).andWhere("following.followerHost = :host", {
+ host: ps.host
+ });
+ const followings = await query.take(ps.limit).getMany();
+ return await Followings.packMany(followings, me, {
+ populateFollowee: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/federation/instances.js b/packages/backend/built/server/api/endpoints/federation/instances.js
new file mode 100644
index 0000000..925fdf3
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/federation/instances.js
@@ -0,0 +1,201 @@
+import define from "../../define.js";
+import { Instances } from "../../../../models/index.js";
+import { fetchMeta } from "../../../../misc/fetch-meta.js";
+import { sqlLikeEscape } from "../../../../misc/sql-like-escape.js";
+export const meta = {
+ tags: [
+ "federation"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "FederationInstance"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ host: {
+ type: "string",
+ nullable: true,
+ description: "Omit or use `null` to not filter by host."
+ },
+ blocked: {
+ type: "boolean",
+ nullable: true
+ },
+ notResponding: {
+ type: "boolean",
+ nullable: true
+ },
+ suspended: {
+ type: "boolean",
+ nullable: true
+ },
+ federating: {
+ type: "boolean",
+ nullable: true
+ },
+ silenced: {
+ type: "boolean",
+ nullable: true
+ },
+ subscribing: {
+ type: "boolean",
+ nullable: true
+ },
+ publishing: {
+ type: "boolean",
+ nullable: true
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 30
+ },
+ offset: {
+ type: "integer",
+ default: 0
+ },
+ sort: {
+ type: "string"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ if (!me) {
+ ps.blocked = false;
+ ps.suspended = false;
+ ps.silenced = false;
+ }
+ const query = Instances.createQueryBuilder("instance");
+ switch(ps.sort){
+ case "+pubSub":
+ query.orderBy("instance.followingCount", "DESC").orderBy("instance.followersCount", "DESC");
+ break;
+ case "-pubSub":
+ query.orderBy("instance.followingCount", "ASC").orderBy("instance.followersCount", "ASC");
+ break;
+ case "+notes":
+ query.orderBy("instance.notesCount", "DESC");
+ break;
+ case "-notes":
+ query.orderBy("instance.notesCount", "ASC");
+ break;
+ case "+users":
+ query.orderBy("instance.usersCount", "DESC");
+ break;
+ case "-users":
+ query.orderBy("instance.usersCount", "ASC");
+ break;
+ case "+following":
+ query.orderBy("instance.followingCount", "DESC");
+ break;
+ case "-following":
+ query.orderBy("instance.followingCount", "ASC");
+ break;
+ case "+followers":
+ query.orderBy("instance.followersCount", "DESC");
+ break;
+ case "-followers":
+ query.orderBy("instance.followersCount", "ASC");
+ break;
+ case "+caughtAt":
+ query.orderBy("instance.caughtAt", "DESC");
+ break;
+ case "-caughtAt":
+ query.orderBy("instance.caughtAt", "ASC");
+ break;
+ case "+lastCommunicatedAt":
+ query.orderBy("instance.lastCommunicatedAt", "DESC");
+ break;
+ case "-lastCommunicatedAt":
+ query.orderBy("instance.lastCommunicatedAt", "ASC");
+ break;
+ default:
+ query.orderBy("instance.id", "DESC");
+ break;
+ }
+ if (typeof ps.blocked === "boolean") {
+ const meta = await fetchMeta(true);
+ if (ps.blocked) {
+ if (meta.blockedHosts.length === 0) {
+ return [];
+ }
+ query.andWhere("instance.host IN (:...blocks)", {
+ blocks: meta.blockedHosts
+ });
+ } else if (meta.blockedHosts.length > 0) {
+ query.andWhere("instance.host NOT IN (:...blocks)", {
+ blocks: meta.blockedHosts
+ });
+ }
+ }
+ if (typeof ps.silenced === "boolean") {
+ const meta = await fetchMeta(true);
+ if (ps.silenced) {
+ if (meta.silencedHosts.length === 0) {
+ return [];
+ }
+ query.andWhere("instance.host IN (:...silences)", {
+ silences: meta.silencedHosts
+ });
+ } else if (meta.silencedHosts.length > 0) {
+ query.andWhere("instance.host NOT IN (:...silences)", {
+ silences: meta.silencedHosts
+ });
+ }
+ }
+ if (typeof ps.notResponding === "boolean") {
+ if (ps.notResponding) {
+ query.andWhere("instance.isNotResponding = TRUE");
+ } else {
+ query.andWhere("instance.isNotResponding = FALSE");
+ }
+ }
+ if (typeof ps.suspended === "boolean") {
+ if (ps.suspended) {
+ query.andWhere("instance.isSuspended = TRUE");
+ } else {
+ query.andWhere("instance.isSuspended = FALSE");
+ }
+ }
+ if (typeof ps.federating === "boolean") {
+ if (ps.federating) {
+ query.andWhere("((instance.followingCount > 0) OR (instance.followersCount > 0))");
+ } else {
+ query.andWhere("((instance.followingCount = 0) AND (instance.followersCount = 0))");
+ }
+ }
+ if (typeof ps.subscribing === "boolean") {
+ if (ps.subscribing) {
+ query.andWhere("instance.followersCount > 0");
+ } else {
+ query.andWhere("instance.followersCount = 0");
+ }
+ }
+ if (typeof ps.publishing === "boolean") {
+ if (ps.publishing) {
+ query.andWhere("instance.followingCount > 0");
+ } else {
+ query.andWhere("instance.followingCount = 0");
+ }
+ }
+ if (ps.host) {
+ query.andWhere("instance.host like :host", {
+ host: `%${sqlLikeEscape(ps.host.toLowerCase())}%`
+ });
+ }
+ const instances = await query.take(ps.limit).skip(ps.offset).getMany();
+ return await Instances.packMany(instances, me !== null);
+});
diff --git a/packages/backend/built/server/api/endpoints/federation/show-instance.js b/packages/backend/built/server/api/endpoints/federation/show-instance.js
new file mode 100644
index 0000000..73990e3
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/federation/show-instance.js
@@ -0,0 +1,38 @@
+import define from "../../define.js";
+import { Instances } from "../../../../models/index.js";
+import { toPuny } from "../../../../misc/convert-host.js";
+export const meta = {
+ tags: [
+ "federation"
+ ],
+ requireCredential: true,
+ requireCredentialPrivateMode: true,
+ res: {
+ oneOf: [
+ {
+ type: "object",
+ ref: "FederationInstance"
+ },
+ {
+ type: "null"
+ }
+ ]
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ host: {
+ type: "string"
+ }
+ },
+ required: [
+ "host"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const instance = await Instances.findOneBy({
+ host: toPuny(ps.host)
+ });
+ return instance ? await Instances.pack(instance, me !== null) : null;
+});
diff --git a/packages/backend/built/server/api/endpoints/federation/stats.js b/packages/backend/built/server/api/endpoints/federation/stats.js
new file mode 100644
index 0000000..ab1dbf2
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/federation/stats.js
@@ -0,0 +1,64 @@
+import { IsNull, MoreThan, Not } from "typeorm";
+import { Followings, Instances } from "../../../../models/index.js";
+import { awaitAll } from "../../../../prelude/await-all.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "federation"
+ ],
+ requireCredential: false,
+ allowGet: true,
+ cacheSec: 60 * 60
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const [topSubInstances, topPubInstances, allSubCount, allPubCount] = await Promise.all([
+ Instances.find({
+ where: {
+ followersCount: MoreThan(0)
+ },
+ order: {
+ followersCount: "DESC"
+ },
+ take: ps.limit
+ }),
+ Instances.find({
+ where: {
+ followingCount: MoreThan(0)
+ },
+ order: {
+ followingCount: "DESC"
+ },
+ take: ps.limit
+ }),
+ Followings.count({
+ where: {
+ followeeHost: Not(IsNull())
+ }
+ }),
+ Followings.count({
+ where: {
+ followerHost: Not(IsNull())
+ }
+ })
+ ]);
+ const gotSubCount = topSubInstances.map((x)=>x.followersCount).reduce((a, b)=>a + b, 0);
+ const gotPubCount = topPubInstances.map((x)=>x.followingCount).reduce((a, b)=>a + b, 0);
+ return await awaitAll({
+ topSubInstances: Instances.packMany(topSubInstances, me !== null),
+ otherFollowersCount: Math.max(0, allSubCount - gotSubCount),
+ topPubInstances: Instances.packMany(topPubInstances, me !== null),
+ otherFollowingCount: Math.max(0, allPubCount - gotPubCount)
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/federation/update-remote-user.js b/packages/backend/built/server/api/endpoints/federation/update-remote-user.js
new file mode 100644
index 0000000..0bfad8c
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/federation/update-remote-user.js
@@ -0,0 +1,25 @@
+import define from "../../define.js";
+import { getRemoteUser } from "../../common/getters.js";
+import { updatePerson } from "../../../../remote/activitypub/models/person.js";
+export const meta = {
+ tags: [
+ "federation"
+ ],
+ requireCredential: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const user = await getRemoteUser(ps.userId);
+ await updatePerson(user.uri, undefined, undefined, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/federation/users.js b/packages/backend/built/server/api/endpoints/federation/users.js
new file mode 100644
index 0000000..485024c
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/federation/users.js
@@ -0,0 +1,55 @@
+import define from "../../define.js";
+import { Users } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "federation"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserDetailedNotMe"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ host: {
+ type: "string"
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ }
+ },
+ required: [
+ "host"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = makePaginationQuery(Users.createQueryBuilder("user"), ps.sinceId, ps.untilId).andWhere("user.host = :host", {
+ host: ps.host
+ });
+ const users = await query.take(ps.limit).getMany();
+ return await Users.packMany(users, me, {
+ detail: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/fetch-rss.js b/packages/backend/built/server/api/endpoints/fetch-rss.js
new file mode 100644
index 0000000..29b79e9
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/fetch-rss.js
@@ -0,0 +1,37 @@
+import Parser from "rss-parser";
+import { getResponse } from "../../../misc/fetch.js";
+import config from "../../../config/index.js";
+import define from "../define.js";
+const rssParser = new Parser();
+export const meta = {
+ tags: [
+ "meta"
+ ],
+ requireCredential: false,
+ allowGet: true,
+ cacheSec: 60 * 3
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ url: {
+ type: "string"
+ }
+ },
+ required: [
+ "url"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const res = await getResponse({
+ url: ps.url,
+ method: "GET",
+ headers: Object.assign({
+ "User-Agent": config.userAgent,
+ Accept: "application/rss+xml, */*"
+ }),
+ timeout: 5000
+ });
+ const text = await res.text();
+ return rssParser.parseString(text);
+});
diff --git a/packages/backend/built/server/api/endpoints/following/create.js b/packages/backend/built/server/api/endpoints/following/create.js
new file mode 100644
index 0000000..f0ec2c5
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/following/create.js
@@ -0,0 +1,96 @@
+import create from "../../../../services/following/create.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { getUser } from "../../common/getters.js";
+import { Followings, Users } from "../../../../models/index.js";
+import { IdentifiableError } from "../../../../misc/identifiable-error.js";
+import { HOUR } from "../../../../const.js";
+export const meta = {
+ tags: [
+ "following",
+ "users"
+ ],
+ limit: {
+ duration: HOUR,
+ max: 100
+ },
+ requireCredential: true,
+ kind: "write:following",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "fcd2eef9-a9b2-4c4f-8624-038099e90aa5"
+ },
+ followeeIsYourself: {
+ message: "Followee is yourself.",
+ code: "FOLLOWEE_IS_YOURSELF",
+ id: "26fbe7bb-a331-4857-af17-205b426669a9"
+ },
+ alreadyFollowing: {
+ message: "You are already following that user.",
+ code: "ALREADY_FOLLOWING",
+ id: "35387507-38c7-4cb9-9197-300b93783fa0"
+ },
+ blocking: {
+ message: "You are blocking that user.",
+ code: "BLOCKING",
+ id: "4e2206ec-aa4f-4960-b865-6c23ac38e2d9"
+ },
+ blocked: {
+ message: "You are blocked by that user.",
+ code: "BLOCKED",
+ id: "c4ab57cc-4e41-45e9-bfd9-584f61e35ce0"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserLite"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const follower = user;
+ // 自分自身
+ if (user.id === ps.userId) {
+ throw new ApiError(meta.errors.followeeIsYourself);
+ }
+ // Get followee
+ const followee = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ // Check if already following
+ const exist = await Followings.exist({
+ where: {
+ followerId: follower.id,
+ followeeId: followee.id
+ }
+ });
+ if (exist) {
+ throw new ApiError(meta.errors.alreadyFollowing);
+ }
+ try {
+ await create(follower, followee);
+ } catch (e) {
+ if (e instanceof IdentifiableError) {
+ if (e.id === "710e8fb0-b8c3-4922-be49-d5d93d8e6a6e") throw new ApiError(meta.errors.blocking);
+ if (e.id === "3338392a-f764-498d-8855-db939dcf8c48") throw new ApiError(meta.errors.blocked);
+ }
+ throw e;
+ }
+ return await Users.pack(followee.id, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/following/delete.js b/packages/backend/built/server/api/endpoints/following/delete.js
new file mode 100644
index 0000000..5a1b853
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/following/delete.js
@@ -0,0 +1,77 @@
+import deleteFollowing from "../../../../services/following/delete.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { getUser } from "../../common/getters.js";
+import { Followings, Users } from "../../../../models/index.js";
+import { HOUR } from "../../../../const.js";
+export const meta = {
+ tags: [
+ "following",
+ "users"
+ ],
+ limit: {
+ duration: HOUR,
+ max: 100
+ },
+ requireCredential: true,
+ kind: "write:following",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "5b12c78d-2b28-4dca-99d2-f56139b42ff8"
+ },
+ followeeIsYourself: {
+ message: "Followee is yourself.",
+ code: "FOLLOWEE_IS_YOURSELF",
+ id: "d9e400b9-36b0-4808-b1d8-79e707f1296c"
+ },
+ notFollowing: {
+ message: "You are not following that user.",
+ code: "NOT_FOLLOWING",
+ id: "5dbf82f5-c92b-40b1-87d1-6c8c0741fd09"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserLite"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const follower = user;
+ // Check if the followee is yourself
+ if (user.id === ps.userId) {
+ throw new ApiError(meta.errors.followeeIsYourself);
+ }
+ // Get followee
+ const followee = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ // Check not following
+ const exist = await Followings.exist({
+ where: {
+ followerId: follower.id,
+ followeeId: followee.id
+ }
+ });
+ if (!exist) {
+ throw new ApiError(meta.errors.notFollowing);
+ }
+ await deleteFollowing(follower, followee);
+ return await Users.pack(followee.id, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/following/invalidate.js b/packages/backend/built/server/api/endpoints/following/invalidate.js
new file mode 100644
index 0000000..93aca90
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/following/invalidate.js
@@ -0,0 +1,77 @@
+import deleteFollowing from "../../../../services/following/delete.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { getUser } from "../../common/getters.js";
+import { Followings, Users } from "../../../../models/index.js";
+import { HOUR } from "../../../../const.js";
+export const meta = {
+ tags: [
+ "following",
+ "users"
+ ],
+ limit: {
+ duration: HOUR,
+ max: 100
+ },
+ requireCredential: true,
+ kind: "write:following",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "5b12c78d-2b28-4dca-99d2-f56139b42ff8"
+ },
+ followerIsYourself: {
+ message: "Follower is yourself.",
+ code: "FOLLOWER_IS_YOURSELF",
+ id: "07dc03b9-03da-422d-885b-438313707662"
+ },
+ notFollowing: {
+ message: "The other use is not following you.",
+ code: "NOT_FOLLOWING",
+ id: "5dbf82f5-c92b-40b1-87d1-6c8c0741fd09"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserLite"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const followee = user;
+ // Check if the follower is yourself
+ if (user.id === ps.userId) {
+ throw new ApiError(meta.errors.followerIsYourself);
+ }
+ // Get follower
+ const follower = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ // Check not following
+ const exist = await Followings.exist({
+ where: {
+ followerId: follower.id,
+ followeeId: followee.id
+ }
+ });
+ if (!exist) {
+ throw new ApiError(meta.errors.notFollowing);
+ }
+ await deleteFollowing(follower, followee);
+ return await Users.pack(followee.id, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/following/requests/accept.js b/packages/backend/built/server/api/endpoints/following/requests/accept.js
new file mode 100644
index 0000000..3c77b79
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/following/requests/accept.js
@@ -0,0 +1,48 @@
+import acceptFollowRequest from "../../../../../services/following/requests/accept.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { getUser } from "../../../common/getters.js";
+export const meta = {
+ tags: [
+ "following",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "write:following",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "66ce1645-d66c-46bb-8b79-96739af885bd"
+ },
+ noFollowRequest: {
+ message: "No follow request.",
+ code: "NO_FOLLOW_REQUEST",
+ id: "bcde4f8b-0913-4614-8881-614e522fb041"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Fetch follower
+ const follower = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ await acceptFollowRequest(user, follower).catch((e)=>{
+ if (e.id === "8884c2dd-5795-4ac9-b27e-6a01d38190f9") throw new ApiError(meta.errors.noFollowRequest);
+ throw e;
+ });
+ return;
+});
diff --git a/packages/backend/built/server/api/endpoints/following/requests/cancel.js b/packages/backend/built/server/api/endpoints/following/requests/cancel.js
new file mode 100644
index 0000000..37f3c57
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/following/requests/cancel.js
@@ -0,0 +1,60 @@
+import cancelFollowRequest from "../../../../../services/following/requests/cancel.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { getUser } from "../../../common/getters.js";
+import { Users } from "../../../../../models/index.js";
+import { IdentifiableError } from "../../../../../misc/identifiable-error.js";
+export const meta = {
+ tags: [
+ "following",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "write:following",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "4e68c551-fc4c-4e46-bb41-7d4a37bf9dab"
+ },
+ followRequestNotFound: {
+ message: "Follow request not found.",
+ code: "FOLLOW_REQUEST_NOT_FOUND",
+ id: "089b125b-d338-482a-9a09-e2622ac9f8d4"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserLite"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Fetch followee
+ const followee = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ try {
+ await cancelFollowRequest(followee, user);
+ } catch (e) {
+ if (e instanceof IdentifiableError) {
+ if (e.id === "17447091-ce07-46dd-b331-c1fd4f15b1e7") throw new ApiError(meta.errors.followRequestNotFound);
+ }
+ throw e;
+ }
+ return await Users.pack(followee.id, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/following/requests/list.js b/packages/backend/built/server/api/endpoints/following/requests/list.js
new file mode 100644
index 0000000..3ecb3b7
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/following/requests/list.js
@@ -0,0 +1,51 @@
+import define from "../../../define.js";
+import { FollowRequests } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "following",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "read:following",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ follower: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserLite"
+ },
+ followee: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserLite"
+ }
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const reqs = await FollowRequests.findBy({
+ followeeId: user.id
+ });
+ return await Promise.all(reqs.map((req)=>FollowRequests.pack(req)));
+});
diff --git a/packages/backend/built/server/api/endpoints/following/requests/reject.js b/packages/backend/built/server/api/endpoints/following/requests/reject.js
new file mode 100644
index 0000000..8f0060d
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/following/requests/reject.js
@@ -0,0 +1,40 @@
+import { rejectFollowRequest } from "../../../../../services/following/reject.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { getUser } from "../../../common/getters.js";
+export const meta = {
+ tags: [
+ "following",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "write:following",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "abc2ffa6-25b2-4380-ba99-321ff3a94555"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Fetch follower
+ const follower = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ await rejectFollowRequest(user, follower);
+ return;
+});
diff --git a/packages/backend/built/server/api/endpoints/gallery/featured.js b/packages/backend/built/server/api/endpoints/gallery/featured.js
new file mode 100644
index 0000000..2a4962e
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/gallery/featured.js
@@ -0,0 +1,32 @@
+import define from "../../define.js";
+import { GalleryPosts } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "gallery"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "GalleryPost"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = GalleryPosts.createQueryBuilder("post").andWhere("post.createdAt > :date", {
+ date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3)
+ }).andWhere("post.likedCount > 0").orderBy("post.likedCount", "DESC");
+ const posts = await query.take(10).getMany();
+ return await GalleryPosts.packMany(posts, me);
+});
diff --git a/packages/backend/built/server/api/endpoints/gallery/popular.js b/packages/backend/built/server/api/endpoints/gallery/popular.js
new file mode 100644
index 0000000..dfc9611
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/gallery/popular.js
@@ -0,0 +1,30 @@
+import define from "../../define.js";
+import { GalleryPosts } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "gallery"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "GalleryPost"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = GalleryPosts.createQueryBuilder("post").andWhere("post.likedCount > 0").orderBy("post.likedCount", "DESC");
+ const posts = await query.take(10).getMany();
+ return await GalleryPosts.packMany(posts, me);
+});
diff --git a/packages/backend/built/server/api/endpoints/gallery/posts.js b/packages/backend/built/server/api/endpoints/gallery/posts.js
new file mode 100644
index 0000000..c8eb5be
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/gallery/posts.js
@@ -0,0 +1,45 @@
+import define from "../../define.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { GalleryPosts } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "gallery"
+ ],
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "GalleryPost"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = makePaginationQuery(GalleryPosts.createQueryBuilder("post"), ps.sinceId, ps.untilId).innerJoinAndSelect("post.user", "user");
+ const posts = await query.take(ps.limit).getMany();
+ return await GalleryPosts.packMany(posts, me);
+});
diff --git a/packages/backend/built/server/api/endpoints/gallery/posts/create.js b/packages/backend/built/server/api/endpoints/gallery/posts/create.js
new file mode 100644
index 0000000..2e0a55d
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/gallery/posts/create.js
@@ -0,0 +1,74 @@
+import define from "../../../define.js";
+import { DriveFiles, GalleryPosts } from "../../../../../models/index.js";
+import { genId } from "../../../../../misc/gen-id.js";
+import { GalleryPost } from "../../../../../models/entities/gallery-post.js";
+import { HOUR } from "../../../../../const.js";
+export const meta = {
+ tags: [
+ "gallery"
+ ],
+ requireCredential: true,
+ kind: "write:gallery",
+ limit: {
+ duration: HOUR,
+ max: 300
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "GalleryPost"
+ },
+ errors: {}
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ title: {
+ type: "string",
+ minLength: 1
+ },
+ description: {
+ type: "string",
+ nullable: true
+ },
+ fileIds: {
+ type: "array",
+ uniqueItems: true,
+ minItems: 1,
+ maxItems: 32,
+ items: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ isSensitive: {
+ type: "boolean",
+ default: false
+ }
+ },
+ required: [
+ "title",
+ "fileIds"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const files = (await Promise.all(ps.fileIds.map((fileId)=>DriveFiles.findOneBy({
+ id: fileId,
+ userId: user.id
+ })))).filter((file)=>file != null);
+ if (files.length === 0) {
+ throw new Error();
+ }
+ const post = await GalleryPosts.insert(new GalleryPost({
+ id: genId(),
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ title: ps.title,
+ description: ps.description,
+ userId: user.id,
+ isSensitive: ps.isSensitive,
+ fileIds: files.map((file)=>file.id)
+ })).then((x)=>GalleryPosts.findOneByOrFail(x.identifiers[0]));
+ return await GalleryPosts.pack(post, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/gallery/posts/delete.js b/packages/backend/built/server/api/endpoints/gallery/posts/delete.js
new file mode 100644
index 0000000..f77a6f5
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/gallery/posts/delete.js
@@ -0,0 +1,39 @@
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { GalleryPosts } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "gallery"
+ ],
+ requireCredential: true,
+ kind: "write:gallery",
+ errors: {
+ noSuchPost: {
+ message: "No such post.",
+ code: "NO_SUCH_POST",
+ id: "ae52f367-4bd7-4ecd-afc6-5672fff427f5"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ postId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "postId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const post = await GalleryPosts.findOneBy({
+ id: ps.postId,
+ userId: user.id
+ });
+ if (post == null) {
+ throw new ApiError(meta.errors.noSuchPost);
+ }
+ await GalleryPosts.delete(post.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/gallery/posts/like.js b/packages/backend/built/server/api/endpoints/gallery/posts/like.js
new file mode 100644
index 0000000..10f0830
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/gallery/posts/like.js
@@ -0,0 +1,63 @@
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { GalleryPosts, GalleryLikes } from "../../../../../models/index.js";
+import { genId } from "../../../../../misc/gen-id.js";
+export const meta = {
+ tags: [
+ "gallery"
+ ],
+ requireCredential: true,
+ kind: "write:gallery-likes",
+ errors: {
+ noSuchPost: {
+ message: "No such post.",
+ code: "NO_SUCH_POST",
+ id: "56c06af3-1287-442f-9701-c93f7c4a62ff"
+ },
+ alreadyLiked: {
+ message: "The post has already been liked.",
+ code: "ALREADY_LIKED",
+ id: "40e9ed56-a59c-473a-bf3f-f289c54fb5a7"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ postId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "postId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const post = await GalleryPosts.findOneBy({
+ id: ps.postId
+ });
+ if (post == null) {
+ throw new ApiError(meta.errors.noSuchPost);
+ }
+ // if already liked
+ const exist = await GalleryLikes.exist({
+ where: {
+ postId: post.id,
+ userId: user.id
+ }
+ });
+ if (exist) {
+ throw new ApiError(meta.errors.alreadyLiked);
+ }
+ // Create like
+ await GalleryLikes.insert({
+ id: genId(),
+ createdAt: new Date(),
+ postId: post.id,
+ userId: user.id
+ });
+ GalleryPosts.increment({
+ id: post.id
+ }, "likedCount", 1);
+});
diff --git a/packages/backend/built/server/api/endpoints/gallery/posts/show.js b/packages/backend/built/server/api/endpoints/gallery/posts/show.js
new file mode 100644
index 0000000..f9a1a99
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/gallery/posts/show.js
@@ -0,0 +1,44 @@
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { GalleryPosts } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "gallery"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ errors: {
+ noSuchPost: {
+ message: "No such post.",
+ code: "NO_SUCH_POST",
+ id: "1137bf14-c5b0-4604-85bb-5b5371b1cd45"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "GalleryPost"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ postId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "postId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const post = await GalleryPosts.findOneBy({
+ id: ps.postId
+ });
+ if (post == null) {
+ throw new ApiError(meta.errors.noSuchPost);
+ }
+ return await GalleryPosts.pack(post, me);
+});
diff --git a/packages/backend/built/server/api/endpoints/gallery/posts/unlike.js b/packages/backend/built/server/api/endpoints/gallery/posts/unlike.js
new file mode 100644
index 0000000..802b201
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/gallery/posts/unlike.js
@@ -0,0 +1,54 @@
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { GalleryPosts, GalleryLikes } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "gallery"
+ ],
+ requireCredential: true,
+ kind: "write:gallery-likes",
+ errors: {
+ noSuchPost: {
+ message: "No such post.",
+ code: "NO_SUCH_POST",
+ id: "c32e6dd0-b555-4413-925e-b3757d19ed84"
+ },
+ notLiked: {
+ message: "You have not liked that post.",
+ code: "NOT_LIKED",
+ id: "e3e8e06e-be37-41f7-a5b4-87a8250288f0"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ postId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "postId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const post = await GalleryPosts.findOneBy({
+ id: ps.postId
+ });
+ if (post == null) {
+ throw new ApiError(meta.errors.noSuchPost);
+ }
+ const like = await GalleryLikes.findOneBy({
+ postId: post.id,
+ userId: user.id
+ });
+ if (like == null) {
+ throw new ApiError(meta.errors.notLiked);
+ }
+ // Delete like
+ await GalleryLikes.delete(like.id);
+ GalleryPosts.decrement({
+ id: post.id
+ }, "likedCount", 1);
+});
diff --git a/packages/backend/built/server/api/endpoints/gallery/posts/update.js b/packages/backend/built/server/api/endpoints/gallery/posts/update.js
new file mode 100644
index 0000000..794909f
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/gallery/posts/update.js
@@ -0,0 +1,80 @@
+import define from "../../../define.js";
+import { DriveFiles, GalleryPosts } from "../../../../../models/index.js";
+import { HOUR } from "../../../../../const.js";
+export const meta = {
+ tags: [
+ "gallery"
+ ],
+ requireCredential: true,
+ kind: "write:gallery",
+ limit: {
+ duration: HOUR,
+ max: 300
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "GalleryPost"
+ },
+ errors: {}
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ postId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ title: {
+ type: "string",
+ minLength: 1
+ },
+ description: {
+ type: "string",
+ nullable: true
+ },
+ fileIds: {
+ type: "array",
+ uniqueItems: true,
+ minItems: 1,
+ maxItems: 32,
+ items: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ isSensitive: {
+ type: "boolean",
+ default: false
+ }
+ },
+ required: [
+ "postId",
+ "title",
+ "fileIds"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const files = (await Promise.all(ps.fileIds.map((fileId)=>DriveFiles.findOneBy({
+ id: fileId,
+ userId: user.id
+ })))).filter((file)=>file != null);
+ if (files.length === 0) {
+ throw new Error();
+ }
+ await GalleryPosts.update({
+ id: ps.postId,
+ userId: user.id
+ }, {
+ updatedAt: new Date(),
+ title: ps.title,
+ description: ps.description,
+ isSensitive: ps.isSensitive,
+ fileIds: files.map((file)=>file.id)
+ });
+ const post = await GalleryPosts.findOneByOrFail({
+ id: ps.postId
+ });
+ return await GalleryPosts.pack(post, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/get-online-users-count.js b/packages/backend/built/server/api/endpoints/get-online-users-count.js
new file mode 100644
index 0000000..64be583
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/get-online-users-count.js
@@ -0,0 +1,24 @@
+import { MoreThan } from "typeorm";
+import { USER_ONLINE_THRESHOLD } from "../../../const.js";
+import { Users } from "../../../models/index.js";
+import define from "../define.js";
+export const meta = {
+ tags: [
+ "meta"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async ()=>{
+ const count = await Users.countBy({
+ lastActiveDate: MoreThan(new Date(Date.now() - USER_ONLINE_THRESHOLD))
+ });
+ return {
+ count
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/get-sounds.js b/packages/backend/built/server/api/endpoints/get-sounds.js
new file mode 100644
index 0000000..7f8b8b1
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/get-sounds.js
@@ -0,0 +1,29 @@
+import { readdir } from "fs/promises";
+import define from "../define.js";
+export const meta = {
+ tags: [
+ "meta"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: false
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async ()=>{
+ const music_files = [
+ null
+ ];
+ const directory = (await readdir("./assets/sounds", {
+ withFileTypes: true
+ })).filter((potentialFolder)=>potentialFolder.isDirectory());
+ for await (const folder of directory){
+ const files = (await readdir(`./assets/sounds/${folder.name}`)).filter((potentialSong)=>potentialSong.endsWith(".mp3"));
+ for await (const file of files){
+ music_files.push(`${folder.name}/${file.replace(".mp3", "")}`);
+ }
+ }
+ return music_files;
+});
diff --git a/packages/backend/built/server/api/endpoints/hashtags/list.js b/packages/backend/built/server/api/endpoints/hashtags/list.js
new file mode 100644
index 0000000..6b03b9f
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/hashtags/list.js
@@ -0,0 +1,118 @@
+import define from "../../define.js";
+import { Hashtags } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "hashtags"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Hashtag"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ attachedToUserOnly: {
+ type: "boolean",
+ default: false
+ },
+ attachedToLocalUserOnly: {
+ type: "boolean",
+ default: false
+ },
+ attachedToRemoteUserOnly: {
+ type: "boolean",
+ default: false
+ },
+ sort: {
+ type: "string",
+ enum: [
+ "+mentionedUsers",
+ "-mentionedUsers",
+ "+mentionedLocalUsers",
+ "-mentionedLocalUsers",
+ "+mentionedRemoteUsers",
+ "-mentionedRemoteUsers",
+ "+attachedUsers",
+ "-attachedUsers",
+ "+attachedLocalUsers",
+ "-attachedLocalUsers",
+ "+attachedRemoteUsers",
+ "-attachedRemoteUsers"
+ ]
+ }
+ },
+ required: [
+ "sort"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = Hashtags.createQueryBuilder("tag");
+ if (ps.attachedToUserOnly) query.andWhere("tag.attachedUsersCount != 0");
+ if (ps.attachedToLocalUserOnly) query.andWhere("tag.attachedLocalUsersCount != 0");
+ if (ps.attachedToRemoteUserOnly) query.andWhere("tag.attachedRemoteUsersCount != 0");
+ switch(ps.sort){
+ case "+mentionedUsers":
+ query.orderBy("tag.mentionedUsersCount", "DESC");
+ break;
+ case "-mentionedUsers":
+ query.orderBy("tag.mentionedUsersCount", "ASC");
+ break;
+ case "+mentionedLocalUsers":
+ query.orderBy("tag.mentionedLocalUsersCount", "DESC");
+ break;
+ case "-mentionedLocalUsers":
+ query.orderBy("tag.mentionedLocalUsersCount", "ASC");
+ break;
+ case "+mentionedRemoteUsers":
+ query.orderBy("tag.mentionedRemoteUsersCount", "DESC");
+ break;
+ case "-mentionedRemoteUsers":
+ query.orderBy("tag.mentionedRemoteUsersCount", "ASC");
+ break;
+ case "+attachedUsers":
+ query.orderBy("tag.attachedUsersCount", "DESC");
+ break;
+ case "-attachedUsers":
+ query.orderBy("tag.attachedUsersCount", "ASC");
+ break;
+ case "+attachedLocalUsers":
+ query.orderBy("tag.attachedLocalUsersCount", "DESC");
+ break;
+ case "-attachedLocalUsers":
+ query.orderBy("tag.attachedLocalUsersCount", "ASC");
+ break;
+ case "+attachedRemoteUsers":
+ query.orderBy("tag.attachedRemoteUsersCount", "DESC");
+ break;
+ case "-attachedRemoteUsers":
+ query.orderBy("tag.attachedRemoteUsersCount", "ASC");
+ break;
+ }
+ query.select([
+ "tag.name",
+ "tag.mentionedUsersCount",
+ "tag.mentionedLocalUsersCount",
+ "tag.mentionedRemoteUsersCount",
+ "tag.attachedUsersCount",
+ "tag.attachedLocalUsersCount",
+ "tag.attachedRemoteUsersCount"
+ ]);
+ const tags = await query.take(ps.limit).getMany();
+ return Hashtags.packMany(tags);
+});
diff --git a/packages/backend/built/server/api/endpoints/hashtags/search.js b/packages/backend/built/server/api/endpoints/hashtags/search.js
new file mode 100644
index 0000000..dc8d3e8
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/hashtags/search.js
@@ -0,0 +1,47 @@
+import define from "../../define.js";
+import { Hashtags } from "../../../../models/index.js";
+import { sqlLikeEscape } from "../../../../misc/sql-like-escape.js";
+export const meta = {
+ tags: [
+ "hashtags"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ query: {
+ type: "string"
+ },
+ offset: {
+ type: "integer",
+ default: 0
+ }
+ },
+ required: [
+ "query"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const hashtags = await Hashtags.createQueryBuilder("tag").where("tag.name like :q", {
+ q: `${sqlLikeEscape(ps.query.toLowerCase())}%`
+ }).orderBy("tag.count", "DESC").groupBy("tag.id").take(ps.limit).skip(ps.offset).getMany();
+ return hashtags.map((tag)=>tag.name);
+});
diff --git a/packages/backend/built/server/api/endpoints/hashtags/show.js b/packages/backend/built/server/api/endpoints/hashtags/show.js
new file mode 100644
index 0000000..dd76cd2
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/hashtags/show.js
@@ -0,0 +1,44 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { Hashtags } from "../../../../models/index.js";
+import { normalizeForSearch } from "../../../../misc/normalize-for-search.js";
+export const meta = {
+ tags: [
+ "hashtags"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Hashtag"
+ },
+ errors: {
+ noSuchHashtag: {
+ message: "No such hashtag.",
+ code: "NO_SUCH_HASHTAG",
+ id: "110ee688-193e-4a3a-9ecf-c167b2e6981e"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ tag: {
+ type: "string"
+ }
+ },
+ required: [
+ "tag"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const hashtag = await Hashtags.findOneBy({
+ name: normalizeForSearch(ps.tag)
+ });
+ if (hashtag == null) {
+ throw new ApiError(meta.errors.noSuchHashtag);
+ }
+ return await Hashtags.pack(hashtag);
+});
diff --git a/packages/backend/built/server/api/endpoints/hashtags/trend.js b/packages/backend/built/server/api/endpoints/hashtags/trend.js
new file mode 100644
index 0000000..147782f
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/hashtags/trend.js
@@ -0,0 +1,124 @@
+import { Brackets } from "typeorm";
+import define from "../../define.js";
+import { fetchMeta } from "../../../../misc/fetch-meta.js";
+import { Notes } from "../../../../models/index.js";
+import { safeForSql } from "../../../../misc/safe-for-sql.js";
+import { normalizeForSearch } from "../../../../misc/normalize-for-search.js";
+/*
+トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要
+ユニーク投稿数とはそのハッシュタグと投稿ユーザーのペアのカウントで、例えば同じユーザーが複数回同じハッシュタグを投稿してもそのハッシュタグのユニーク投稿数は1とカウントされる
+
+..が理想だけどPostgreSQLでどうするのか分からないので単に「直近Aの内に投稿されたユニーク投稿数が多いハッシュタグ」で妥協する
+*/ const rangeA = 1000 * 60 * 60; // 60分
+//const rangeB = 1000 * 60 * 120; // 2時間
+//const coefficient = 1.25; // 「n倍」の部分
+//const requiredUsers = 3; // 最低何人がそのタグを投稿している必要があるか
+const max = 5;
+export const meta = {
+ tags: [
+ "hashtags"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ tag: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ chart: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "number",
+ optional: false,
+ nullable: false
+ }
+ },
+ usersCount: {
+ type: "number",
+ optional: false,
+ nullable: false
+ }
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async ()=>{
+ const instance = await fetchMeta(true);
+ const hiddenTags = instance.hiddenTags.map((t)=>normalizeForSearch(t));
+ const now = new Date(); // 5分単位で丸めた現在日時
+ now.setMinutes(Math.round(now.getMinutes() / 5) * 5, 0, 0);
+ const tagNotes = await Notes.createQueryBuilder("note").where("note.createdAt > :date", {
+ date: new Date(now.getTime() - rangeA)
+ }).andWhere(new Brackets((qb)=>{
+ qb.where(`note.visibility = 'public'`).orWhere(`note.visibility = 'home'`);
+ })).andWhere(`note.tags != '{}'`).select([
+ "note.tags",
+ "note.userId"
+ ]).cache(60000) // 1 min
+ .getMany();
+ if (tagNotes.length === 0) {
+ return [];
+ }
+ const tags = [];
+ for (const note of tagNotes){
+ for (const tag of note.tags){
+ if (hiddenTags.includes(tag)) continue;
+ const x = tags.find((x)=>x.name === tag);
+ if (x) {
+ if (!x.users.includes(note.userId)) {
+ x.users.push(note.userId);
+ }
+ } else {
+ tags.push({
+ name: tag,
+ users: [
+ note.userId
+ ]
+ });
+ }
+ }
+ }
+ // タグを人気順に並べ替え
+ const hots = tags.sort((a, b)=>b.users.length - a.users.length).map((tag)=>tag.name).slice(0, max);
+ //#region 2(または3)で話題と判定されたタグそれぞれについて過去の投稿数グラフを取得する
+ const countPromises = [];
+ const range = 20;
+ // 10分
+ const interval = 1000 * 60 * 10;
+ for(let i = 0; i < range; i++){
+ countPromises.push(Promise.all(hots.map((tag)=>Notes.createQueryBuilder("note").select("count(distinct note.userId)").where(`'{"${safeForSql(tag) ? tag : "aichan_kawaii"}"}' <@ note.tags`).andWhere("note.createdAt < :lt", {
+ lt: new Date(now.getTime() - interval * i)
+ }).andWhere("note.createdAt > :gt", {
+ gt: new Date(now.getTime() - interval * (i + 1))
+ }).cache(60000) // 1 min
+ .getRawOne().then((x)=>parseInt(x.count, 10)))));
+ }
+ const countsLog = await Promise.all(countPromises);
+ //#endregion
+ const totalCounts = await Promise.all(hots.map((tag)=>Notes.createQueryBuilder("note").select("count(distinct note.userId)").where(`'{"${safeForSql(tag) ? tag : "aichan_kawaii"}"}' <@ note.tags`).andWhere("note.createdAt > :gt", {
+ gt: new Date(now.getTime() - rangeA)
+ }).cache(60000 * 60) // 60 min
+ .getRawOne().then((x)=>parseInt(x.count, 10))));
+ const stats = hots.map((tag, i)=>({
+ tag,
+ chart: countsLog.map((counts)=>counts[i]),
+ usersCount: totalCounts[i]
+ }));
+ return stats;
+});
diff --git a/packages/backend/built/server/api/endpoints/hashtags/users.js b/packages/backend/built/server/api/endpoints/hashtags/users.js
new file mode 100644
index 0000000..99e7d21
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/hashtags/users.js
@@ -0,0 +1,108 @@
+import define from "../../define.js";
+import { Users } from "../../../../models/index.js";
+import { normalizeForSearch } from "../../../../misc/normalize-for-search.js";
+export const meta = {
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ tags: [
+ "hashtags",
+ "users"
+ ],
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserDetailed"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ tag: {
+ type: "string"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sort: {
+ type: "string",
+ enum: [
+ "+follower",
+ "-follower",
+ "+createdAt",
+ "-createdAt",
+ "+updatedAt",
+ "-updatedAt"
+ ]
+ },
+ state: {
+ type: "string",
+ enum: [
+ "all",
+ "alive"
+ ],
+ default: "all"
+ },
+ origin: {
+ type: "string",
+ enum: [
+ "combined",
+ "local",
+ "remote"
+ ],
+ default: "local"
+ }
+ },
+ required: [
+ "tag",
+ "sort"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = Users.createQueryBuilder("user").where(":tag = ANY(user.tags)", {
+ tag: normalizeForSearch(ps.tag)
+ });
+ const recent = new Date(Date.now() - 1000 * 60 * 60 * 24 * 5);
+ if (ps.state === "alive") {
+ query.andWhere("user.updatedAt > :date", {
+ date: recent
+ });
+ }
+ if (ps.origin === "local") {
+ query.andWhere("user.host IS NULL");
+ } else if (ps.origin === "remote") {
+ query.andWhere("user.host IS NOT NULL");
+ }
+ switch(ps.sort){
+ case "+follower":
+ query.orderBy("user.followersCount", "DESC");
+ break;
+ case "-follower":
+ query.orderBy("user.followersCount", "ASC");
+ break;
+ case "+createdAt":
+ query.orderBy("user.createdAt", "DESC");
+ break;
+ case "-createdAt":
+ query.orderBy("user.createdAt", "ASC");
+ break;
+ case "+updatedAt":
+ query.orderBy("user.updatedAt", "DESC");
+ break;
+ case "-updatedAt":
+ query.orderBy("user.updatedAt", "ASC");
+ break;
+ }
+ const users = await query.take(ps.limit).getMany();
+ return await Users.packMany(users, me, {
+ detail: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/i.js b/packages/backend/built/server/api/endpoints/i.js
new file mode 100644
index 0000000..3aceb61
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i.js
@@ -0,0 +1,27 @@
+import { Users } from "../../../models/index.js";
+import define from "../define.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "MeDetailed"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, user, token)=>{
+ const isSecure = token == null;
+ // ここで渡ってきている user はキャッシュされていて古い可能性もあるので id だけ渡す
+ return await Users.pack(user.id, user, {
+ detail: true,
+ includeSecrets: isSecure
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/i/2fa/done.js b/packages/backend/built/server/api/endpoints/i/2fa/done.js
new file mode 100644
index 0000000..bef9956
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/2fa/done.js
@@ -0,0 +1,46 @@
+import { publishMainStream } from "../../../../../services/stream.js";
+import * as OTPAuth from "otpauth";
+import define from "../../../define.js";
+import { Users, UserProfiles } from "../../../../../models/index.js";
+export const meta = {
+ requireCredential: true,
+ secure: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ token: {
+ type: "string"
+ }
+ },
+ required: [
+ "token"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const token = ps.token.replace(/\s/g, "");
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ if (profile.twoFactorTempSecret == null) {
+ throw new Error("二段階認証の設定が開始されていません");
+ }
+ const delta = OTPAuth.TOTP.validate({
+ secret: OTPAuth.Secret.fromBase32(profile.twoFactorTempSecret),
+ digits: 6,
+ token,
+ window: 1
+ });
+ if (delta === null) {
+ throw new Error("not verified");
+ }
+ await UserProfiles.update(user.id, {
+ twoFactorSecret: profile.twoFactorTempSecret,
+ twoFactorEnabled: true
+ });
+ const iObj = await Users.pack(user.id, user, {
+ detail: true,
+ includeSecrets: true
+ });
+ publishMainStream(user.id, "meUpdated", iObj);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/2fa/key-done.js b/packages/backend/built/server/api/endpoints/i/2fa/key-done.js
new file mode 100644
index 0000000..9d9bd81
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/2fa/key-done.js
@@ -0,0 +1,125 @@
+import * as cbor from "cbor";
+import define from "../../../define.js";
+import { UserProfiles, UserSecurityKeys, AttestationChallenges, Users } from "../../../../../models/index.js";
+import config from "../../../../../config/index.js";
+import { procedures, hash } from "../../../2fa.js";
+import { publishMainStream } from "../../../../../services/stream.js";
+import { comparePassword } from "../../../../../misc/password.js";
+const rpIdHashReal = hash(Buffer.from(config.hostname, "utf-8"));
+export const meta = {
+ requireCredential: true,
+ secure: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ clientDataJSON: {
+ type: "string"
+ },
+ attestationObject: {
+ type: "string"
+ },
+ password: {
+ type: "string"
+ },
+ challengeId: {
+ type: "string"
+ },
+ name: {
+ type: "string",
+ minLength: 1,
+ maxLength: 30
+ }
+ },
+ required: [
+ "clientDataJSON",
+ "attestationObject",
+ "password",
+ "challengeId",
+ "name"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ // Compare password
+ const same = await comparePassword(ps.password, profile.password);
+ if (!same) {
+ throw new Error("incorrect password");
+ }
+ if (!profile.twoFactorEnabled) {
+ throw new Error("2fa not enabled");
+ }
+ const clientData = JSON.parse(ps.clientDataJSON);
+ if (clientData.type !== "webauthn.create") {
+ throw new Error("not a creation attestation");
+ }
+ if (clientData.origin !== `${config.scheme}://${config.host}`) {
+ throw new Error("origin mismatch");
+ }
+ const clientDataJSONHash = hash(Buffer.from(ps.clientDataJSON, "utf-8"));
+ const attestation = await cbor.decodeFirst(ps.attestationObject);
+ const rpIdHash = attestation.authData.slice(0, 32);
+ if (!rpIdHashReal.equals(rpIdHash)) {
+ throw new Error("rpIdHash mismatch");
+ }
+ const flags = attestation.authData[32];
+ if (!(flags & 1)) {
+ throw new Error("user not present");
+ }
+ const authData = Buffer.from(attestation.authData);
+ const credentialIdLength = authData.readUInt16BE(53);
+ const credentialId = authData.slice(55, 55 + credentialIdLength);
+ const publicKeyData = authData.slice(55 + credentialIdLength);
+ const publicKey = await cbor.decodeFirst(publicKeyData);
+ if (publicKey.get(3) !== -7) {
+ throw new Error("alg mismatch");
+ }
+ if (!procedures[attestation.fmt]) {
+ throw new Error("unsupported fmt");
+ }
+ const verificationData = procedures[attestation.fmt].verify({
+ attStmt: attestation.attStmt,
+ authenticatorData: authData,
+ clientDataHash: clientDataJSONHash,
+ credentialId,
+ publicKey,
+ rpIdHash
+ });
+ if (!verificationData.valid) throw new Error("signature invalid");
+ const attestationChallenge = await AttestationChallenges.findOneBy({
+ userId: user.id,
+ id: ps.challengeId,
+ registrationChallenge: true,
+ challenge: hash(clientData.challenge).toString("hex")
+ });
+ if (!attestationChallenge) {
+ throw new Error("non-existent challenge");
+ }
+ await AttestationChallenges.delete({
+ userId: user.id,
+ id: ps.challengeId
+ });
+ // Expired challenge (> 5min old)
+ if (new Date().getTime() - attestationChallenge.createdAt.getTime() >= 5 * 60 * 1000) {
+ throw new Error("expired challenge");
+ }
+ const credentialIdString = credentialId.toString("hex");
+ await UserSecurityKeys.insert({
+ userId: user.id,
+ id: credentialIdString,
+ lastUsed: new Date(),
+ name: ps.name,
+ publicKey: verificationData.publicKey.toString("hex")
+ });
+ // Publish meUpdated event
+ publishMainStream(user.id, "meUpdated", await Users.pack(user.id, user, {
+ detail: true,
+ includeSecrets: true
+ }));
+ return {
+ id: credentialIdString,
+ name: ps.name
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/i/2fa/password-less.js b/packages/backend/built/server/api/endpoints/i/2fa/password-less.js
new file mode 100644
index 0000000..c02c106
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/2fa/password-less.js
@@ -0,0 +1,55 @@
+import define from "../../../define.js";
+import { Users, UserProfiles, UserSecurityKeys } from "../../../../../models/index.js";
+import { publishMainStream } from "../../../../../services/stream.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ requireCredential: true,
+ secure: true,
+ errors: {
+ noKey: {
+ message: "No security key.",
+ code: "NO_SECURITY_KEY",
+ id: "f9c54d7f-d4c2-4d3c-9a8g-a70daac86512"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ value: {
+ type: "boolean"
+ }
+ },
+ required: [
+ "value"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ if (ps.value === true) {
+ // セキュリティキーがなければパスワードレスを有効にはできない
+ const keyCount = await UserSecurityKeys.count({
+ where: {
+ userId: user.id
+ },
+ select: {
+ id: true,
+ name: true,
+ lastUsed: true
+ }
+ });
+ if (keyCount === 0) {
+ await UserProfiles.update(user.id, {
+ usePasswordLessLogin: false
+ });
+ throw new ApiError(meta.errors.noKey);
+ }
+ }
+ await UserProfiles.update(user.id, {
+ usePasswordLessLogin: ps.value
+ });
+ const iObj = await Users.pack(user.id, user, {
+ detail: true,
+ includeSecrets: true
+ });
+ publishMainStream(user.id, "meUpdated", iObj);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/2fa/register-key.js b/packages/backend/built/server/api/endpoints/i/2fa/register-key.js
new file mode 100644
index 0000000..58f6809
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/2fa/register-key.js
@@ -0,0 +1,51 @@
+import define from "../../../define.js";
+import { UserProfiles, AttestationChallenges } from "../../../../../models/index.js";
+import { promisify } from "node:util";
+import * as crypto from "node:crypto";
+import { genId } from "../../../../../misc/gen-id.js";
+import { hash } from "../../../2fa.js";
+import { comparePassword } from "../../../../../misc/password.js";
+const randomBytes = promisify(crypto.randomBytes);
+export const meta = {
+ requireCredential: true,
+ secure: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ password: {
+ type: "string"
+ }
+ },
+ required: [
+ "password"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ // Compare password
+ const same = await comparePassword(ps.password, profile.password);
+ if (!same) {
+ throw new Error("incorrect password");
+ }
+ // if (!profile.twoFactorEnabled) {
+ // throw new Error("2fa not enabled");
+ // }
+ // 32 byte challenge
+ const entropy = await randomBytes(32);
+ const challenge = entropy.toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
+ const challengeId = genId();
+ await AttestationChallenges.insert({
+ userId: user.id,
+ id: challengeId,
+ challenge: hash(Buffer.from(challenge, "utf-8")).toString("hex"),
+ createdAt: new Date(),
+ registrationChallenge: true
+ });
+ return {
+ challengeId,
+ challenge
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/i/2fa/register.js b/packages/backend/built/server/api/endpoints/i/2fa/register.js
new file mode 100644
index 0000000..02dac31
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/2fa/register.js
@@ -0,0 +1,52 @@
+import * as OTPAuth from "otpauth";
+import * as QRCode from "qrcode";
+import config from "../../../../../config/index.js";
+import { UserProfiles } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { comparePassword } from "../../../../../misc/password.js";
+export const meta = {
+ requireCredential: true,
+ secure: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ password: {
+ type: "string"
+ }
+ },
+ required: [
+ "password"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ // Compare password
+ const same = await comparePassword(ps.password, profile.password);
+ if (!same) {
+ throw new Error("incorrect password");
+ }
+ // Generate user's secret key
+ const secret = new OTPAuth.Secret();
+ await UserProfiles.update(user.id, {
+ twoFactorTempSecret: secret.base32
+ });
+ // Get the data URL of the authenticator URL
+ const totp = new OTPAuth.TOTP({
+ secret,
+ digits: 6,
+ label: user.username,
+ issuer: config.host
+ });
+ const url = totp.toString();
+ const qr = await QRCode.toDataURL(url);
+ return {
+ qr,
+ url,
+ secret: secret.base32,
+ label: user.username,
+ issuer: config.host
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/i/2fa/remove-key.js b/packages/backend/built/server/api/endpoints/i/2fa/remove-key.js
new file mode 100644
index 0000000..4c4d0c0
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/2fa/remove-key.js
@@ -0,0 +1,60 @@
+import { comparePassword } from "../../../../../misc/password.js";
+import define from "../../../define.js";
+import { UserProfiles, UserSecurityKeys, Users } from "../../../../../models/index.js";
+import { publishMainStream } from "../../../../../services/stream.js";
+export const meta = {
+ requireCredential: true,
+ secure: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ password: {
+ type: "string"
+ },
+ credentialId: {
+ type: "string"
+ }
+ },
+ required: [
+ "password",
+ "credentialId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ // Compare password
+ const same = await comparePassword(ps.password, profile.password);
+ if (!same) {
+ throw new Error("incorrect password");
+ }
+ // Make sure we only delete the user's own creds
+ await UserSecurityKeys.delete({
+ userId: user.id,
+ id: ps.credentialId
+ });
+ // 使われているキーがなくなったらパスワードレスログインをやめる
+ const keyCount = await UserSecurityKeys.count({
+ where: {
+ userId: user.id
+ },
+ select: {
+ id: true,
+ name: true,
+ lastUsed: true
+ }
+ });
+ if (keyCount === 0) {
+ await UserProfiles.update(me.id, {
+ usePasswordLessLogin: false
+ });
+ }
+ // Publish meUpdated event
+ publishMainStream(user.id, "meUpdated", await Users.pack(user.id, user, {
+ detail: true,
+ includeSecrets: true
+ }));
+ return {};
+});
diff --git a/packages/backend/built/server/api/endpoints/i/2fa/unregister.js b/packages/backend/built/server/api/endpoints/i/2fa/unregister.js
new file mode 100644
index 0000000..ff14f7c
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/2fa/unregister.js
@@ -0,0 +1,39 @@
+import { publishMainStream } from "../../../../../services/stream.js";
+import define from "../../../define.js";
+import { Users, UserProfiles } from "../../../../../models/index.js";
+import { comparePassword } from "../../../../../misc/password.js";
+export const meta = {
+ requireCredential: true,
+ secure: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ password: {
+ type: "string"
+ }
+ },
+ required: [
+ "password"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ // Compare password
+ const same = await comparePassword(ps.password, profile.password);
+ if (!same) {
+ throw new Error("incorrect password");
+ }
+ await UserProfiles.update(user.id, {
+ twoFactorSecret: null,
+ twoFactorEnabled: false,
+ usePasswordLessLogin: false
+ });
+ const iObj = await Users.pack(user.id, user, {
+ detail: true,
+ includeSecrets: true
+ });
+ publishMainStream(user.id, "meUpdated", iObj);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/2fa/update-key.js b/packages/backend/built/server/api/endpoints/i/2fa/update-key.js
new file mode 100644
index 0000000..4a92251
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/2fa/update-key.js
@@ -0,0 +1,56 @@
+import { publishMainStream } from "../../../../../services/stream.js";
+import define from "../../../define.js";
+import { Users, UserSecurityKeys } from "../../../../../models/index.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ requireCredential: true,
+ secure: true,
+ errors: {
+ noSuchKey: {
+ message: "No such key.",
+ code: "NO_SUCH_KEY",
+ id: "f9c5467f-d492-4d3c-9a8g-a70dacc86512"
+ },
+ accessDenied: {
+ message: "You do not have edit privilege of the channel.",
+ code: "ACCESS_DENIED",
+ id: "1fb7cb09-d46a-4fff-b8df-057708cce513"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ name: {
+ type: "string",
+ minLength: 1,
+ maxLength: 30
+ },
+ credentialId: {
+ type: "string"
+ }
+ },
+ required: [
+ "name",
+ "credentialId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const key = await UserSecurityKeys.findOneBy({
+ id: ps.credentialId
+ });
+ if (key == null) {
+ throw new ApiError(meta.errors.noSuchKey);
+ }
+ if (key.userId !== user.id) {
+ throw new ApiError(meta.errors.accessDenied);
+ }
+ await UserSecurityKeys.update(key.id, {
+ name: ps.name
+ });
+ const iObj = await Users.pack(user.id, user, {
+ detail: true,
+ includeSecrets: true
+ });
+ publishMainStream(user.id, "meUpdated", iObj);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/apps.js b/packages/backend/built/server/api/endpoints/i/apps.js
new file mode 100644
index 0000000..abfc248
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/apps.js
@@ -0,0 +1,51 @@
+import define from "../../define.js";
+import { AccessTokens } from "../../../../models/index.js";
+export const meta = {
+ requireCredential: true,
+ secure: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ sort: {
+ type: "string",
+ enum: [
+ "+createdAt",
+ "-createdAt",
+ "+lastUsedAt",
+ "-lastUsedAt"
+ ]
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = AccessTokens.createQueryBuilder("token").where("token.userId = :userId", {
+ userId: user.id
+ });
+ switch(ps.sort){
+ case "+createdAt":
+ query.orderBy("token.createdAt", "DESC");
+ break;
+ case "-createdAt":
+ query.orderBy("token.createdAt", "ASC");
+ break;
+ case "+lastUsedAt":
+ query.orderBy("token.lastUsedAt", "DESC");
+ break;
+ case "-lastUsedAt":
+ query.orderBy("token.lastUsedAt", "ASC");
+ break;
+ default:
+ query.orderBy("token.id", "ASC");
+ break;
+ }
+ const tokens = await query.getMany();
+ return await Promise.all(tokens.map((token)=>({
+ id: token.id,
+ name: token.name,
+ createdAt: token.createdAt,
+ lastUsedAt: token.lastUsedAt,
+ permission: token.permission
+ })));
+});
diff --git a/packages/backend/built/server/api/endpoints/i/authorized-apps.js b/packages/backend/built/server/api/endpoints/i/authorized-apps.js
new file mode 100644
index 0000000..406099f
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/authorized-apps.js
@@ -0,0 +1,46 @@
+import define from "../../define.js";
+import { AccessTokens, Apps } from "../../../../models/index.js";
+export const meta = {
+ requireCredential: true,
+ secure: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ offset: {
+ type: "integer",
+ default: 0
+ },
+ sort: {
+ type: "string",
+ enum: [
+ "desc",
+ "asc"
+ ],
+ default: "desc"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Get tokens
+ const tokens = await AccessTokens.find({
+ where: {
+ userId: user.id
+ },
+ take: ps.limit,
+ skip: ps.offset,
+ order: {
+ id: ps.sort === "asc" ? 1 : -1
+ }
+ });
+ return await Promise.all(tokens.map((token)=>Apps.pack(token.appId, user, {
+ detail: true
+ })));
+});
diff --git a/packages/backend/built/server/api/endpoints/i/change-password.js b/packages/backend/built/server/api/endpoints/i/change-password.js
new file mode 100644
index 0000000..84d6b70
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/change-password.js
@@ -0,0 +1,38 @@
+import define from "../../define.js";
+import { UserProfiles } from "../../../../models/index.js";
+import { hashPassword, comparePassword } from "../../../../misc/password.js";
+export const meta = {
+ requireCredential: true,
+ secure: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ currentPassword: {
+ type: "string"
+ },
+ newPassword: {
+ type: "string",
+ minLength: 1
+ }
+ },
+ required: [
+ "currentPassword",
+ "newPassword"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ // Compare password
+ const same = await comparePassword(ps.currentPassword, profile.password);
+ if (!same) {
+ throw new Error("incorrect password");
+ }
+ // Generate hash of password
+ const hash = await hashPassword(ps.newPassword);
+ await UserProfiles.update(user.id, {
+ password: hash
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/i/delete-account.js b/packages/backend/built/server/api/endpoints/i/delete-account.js
new file mode 100644
index 0000000..bc3a3b8
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/delete-account.js
@@ -0,0 +1,36 @@
+import { UserProfiles, Users } from "../../../../models/index.js";
+import { deleteAccount } from "../../../../services/delete-account.js";
+import define from "../../define.js";
+import { comparePassword } from "../../../../misc/password.js";
+export const meta = {
+ requireCredential: true,
+ secure: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ password: {
+ type: "string"
+ }
+ },
+ required: [
+ "password"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ const userDetailed = await Users.findOneByOrFail({
+ id: user.id
+ });
+ if (userDetailed.isDeleted) {
+ return;
+ }
+ // Compare password
+ const same = await comparePassword(ps.password, profile.password);
+ if (!same) {
+ throw new Error("incorrect password");
+ }
+ await deleteAccount(user);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/export-blocking.js b/packages/backend/built/server/api/endpoints/i/export-blocking.js
new file mode 100644
index 0000000..6c311f9
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/export-blocking.js
@@ -0,0 +1,19 @@
+import define from "../../define.js";
+import { createExportBlockingJob } from "../../../../queue/index.js";
+import { HOUR } from "../../../../const.js";
+export const meta = {
+ secure: true,
+ requireCredential: true,
+ limit: {
+ duration: HOUR,
+ max: 1
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ createExportBlockingJob(user);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/export-following.js b/packages/backend/built/server/api/endpoints/i/export-following.js
new file mode 100644
index 0000000..988933e
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/export-following.js
@@ -0,0 +1,28 @@
+import define from "../../define.js";
+import { createExportFollowingJob } from "../../../../queue/index.js";
+import { HOUR } from "../../../../const.js";
+export const meta = {
+ secure: true,
+ requireCredential: true,
+ limit: {
+ duration: HOUR,
+ max: 1
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ excludeMuting: {
+ type: "boolean",
+ default: false
+ },
+ excludeInactive: {
+ type: "boolean",
+ default: false
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ createExportFollowingJob(user, ps.excludeMuting, ps.excludeInactive);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/export-mute.js b/packages/backend/built/server/api/endpoints/i/export-mute.js
new file mode 100644
index 0000000..4aaaa64
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/export-mute.js
@@ -0,0 +1,19 @@
+import define from "../../define.js";
+import { createExportMuteJob } from "../../../../queue/index.js";
+import { HOUR } from "../../../../const.js";
+export const meta = {
+ secure: true,
+ requireCredential: true,
+ limit: {
+ duration: HOUR,
+ max: 1
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ createExportMuteJob(user);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/export-notes.js b/packages/backend/built/server/api/endpoints/i/export-notes.js
new file mode 100644
index 0000000..a444f36
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/export-notes.js
@@ -0,0 +1,19 @@
+import define from "../../define.js";
+import { createExportNotesJob } from "../../../../queue/index.js";
+import { DAY } from "../../../../const.js";
+export const meta = {
+ secure: true,
+ requireCredential: true,
+ limit: {
+ duration: DAY,
+ max: 1
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ createExportNotesJob(user);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/export-user-lists.js b/packages/backend/built/server/api/endpoints/i/export-user-lists.js
new file mode 100644
index 0000000..1b3fa5d
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/export-user-lists.js
@@ -0,0 +1,19 @@
+import define from "../../define.js";
+import { createExportUserListsJob } from "../../../../queue/index.js";
+import { MINUTE } from "../../../../const.js";
+export const meta = {
+ secure: true,
+ requireCredential: true,
+ limit: {
+ duration: MINUTE,
+ max: 1
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ createExportUserListsJob(user);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/favorites.js b/packages/backend/built/server/api/endpoints/i/favorites.js
new file mode 100644
index 0000000..37401ce
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/favorites.js
@@ -0,0 +1,50 @@
+import define from "../../define.js";
+import { NoteFavorites } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "account",
+ "notes",
+ "favorites"
+ ],
+ requireCredential: true,
+ kind: "read:favorites",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "NoteFavorite"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(NoteFavorites.createQueryBuilder("favorite"), ps.sinceId, ps.untilId).andWhere("favorite.userId = :meId", {
+ meId: user.id
+ }).leftJoinAndSelect("favorite.note", "note");
+ const favorites = await query.take(ps.limit).getMany();
+ return await NoteFavorites.packMany(favorites, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/gallery/likes.js b/packages/backend/built/server/api/endpoints/i/gallery/likes.js
new file mode 100644
index 0000000..620cb2f
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/gallery/likes.js
@@ -0,0 +1,62 @@
+import define from "../../../define.js";
+import { GalleryLikes } from "../../../../../models/index.js";
+import { makePaginationQuery } from "../../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "account",
+ "gallery"
+ ],
+ requireCredential: true,
+ kind: "read:gallery-likes",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ post: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "GalleryPost"
+ }
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(GalleryLikes.createQueryBuilder("like"), ps.sinceId, ps.untilId).andWhere("like.userId = :meId", {
+ meId: user.id
+ }).leftJoinAndSelect("like.post", "post");
+ const likes = await query.take(ps.limit).getMany();
+ return await GalleryLikes.packMany(likes, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/gallery/posts.js b/packages/backend/built/server/api/endpoints/i/gallery/posts.js
new file mode 100644
index 0000000..95803df
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/gallery/posts.js
@@ -0,0 +1,49 @@
+import { GalleryPosts } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { makePaginationQuery } from "../../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "account",
+ "gallery"
+ ],
+ requireCredential: true,
+ kind: "read:gallery",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "GalleryPost"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(GalleryPosts.createQueryBuilder("post"), ps.sinceId, ps.untilId).andWhere("post.userId = :meId", {
+ meId: user.id
+ });
+ const posts = await query.take(ps.limit).getMany();
+ return await GalleryPosts.packMany(posts, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/import-blocking.js b/packages/backend/built/server/api/endpoints/i/import-blocking.js
new file mode 100644
index 0000000..c5f5595
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/import-blocking.js
@@ -0,0 +1,57 @@
+import define from "../../define.js";
+import { createImportBlockingJob } from "../../../../queue/index.js";
+import { ApiError } from "../../error.js";
+import { DriveFiles } from "../../../../models/index.js";
+import { HOUR } from "../../../../const.js";
+export const meta = {
+ secure: true,
+ requireCredential: true,
+ limit: {
+ duration: HOUR,
+ max: 1
+ },
+ errors: {
+ noSuchFile: {
+ message: "No such file.",
+ code: "NO_SUCH_FILE",
+ id: "ebb53e5f-6574-9c0c-0b92-7ca6def56d7e"
+ },
+ unexpectedFileType: {
+ message: "We need csv file.",
+ code: "UNEXPECTED_FILE_TYPE",
+ id: "b6fab7d6-d945-d67c-dfdb-32da1cd12cfe"
+ },
+ tooBigFile: {
+ message: "That file is too big.",
+ code: "TOO_BIG_FILE",
+ id: "b7fbf0b1-aeef-3b21-29ef-fadd4cb72ccf"
+ },
+ emptyFile: {
+ message: "That file is empty.",
+ code: "EMPTY_FILE",
+ id: "6f3a4dcc-f060-a707-4950-806fbdbe60d6"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ fileId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "fileId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const file = await DriveFiles.findOneBy({
+ id: ps.fileId
+ });
+ if (file == null) throw new ApiError(meta.errors.noSuchFile);
+ //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
+ if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile);
+ if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
+ createImportBlockingJob(user, file.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/import-following.js b/packages/backend/built/server/api/endpoints/i/import-following.js
new file mode 100644
index 0000000..f59fed8
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/import-following.js
@@ -0,0 +1,57 @@
+import define from "../../define.js";
+import { createImportFollowingJob } from "../../../../queue/index.js";
+import { ApiError } from "../../error.js";
+import { DriveFiles } from "../../../../models/index.js";
+import { HOUR } from "../../../../const.js";
+export const meta = {
+ secure: true,
+ requireCredential: true,
+ limit: {
+ duratition: HOUR,
+ max: 1
+ },
+ errors: {
+ noSuchFile: {
+ message: "No such file.",
+ code: "NO_SUCH_FILE",
+ id: "b98644cf-a5ac-4277-a502-0b8054a709a3"
+ },
+ unexpectedFileType: {
+ message: "Must be a CSV or JSON file.",
+ code: "UNEXPECTED_FILE_TYPE",
+ id: "660f3599-bce0-4f95-9dde-311fd841c183"
+ },
+ tooBigFile: {
+ message: "That file is too big.",
+ code: "TOO_BIG_FILE",
+ id: "dee9d4ed-ad07-43ed-8b34-b2856398bc60"
+ },
+ emptyFile: {
+ message: "That file is empty.",
+ code: "EMPTY_FILE",
+ id: "31a1b42c-06f7-42ae-8a38-a661c5c9f691"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ fileId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "fileId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const file = await DriveFiles.findOneBy({
+ id: ps.fileId
+ });
+ if (file == null) throw new ApiError(meta.errors.noSuchFile);
+ //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
+ if (file.size > 2_000_000) throw new ApiError(meta.errors.tooBigFile);
+ if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
+ createImportFollowingJob(user, file.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/import-muting.js b/packages/backend/built/server/api/endpoints/i/import-muting.js
new file mode 100644
index 0000000..72e7134
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/import-muting.js
@@ -0,0 +1,57 @@
+import define from "../../define.js";
+import { createImportMutingJob } from "../../../../queue/index.js";
+import { ApiError } from "../../error.js";
+import { DriveFiles } from "../../../../models/index.js";
+import { HOUR } from "../../../../const.js";
+export const meta = {
+ secure: true,
+ requireCredential: true,
+ limit: {
+ duration: HOUR,
+ max: 1
+ },
+ errors: {
+ noSuchFile: {
+ message: "No such file.",
+ code: "NO_SUCH_FILE",
+ id: "e674141e-bd2a-ba85-e616-aefb187c9c2a"
+ },
+ unexpectedFileType: {
+ message: "We need csv file.",
+ code: "UNEXPECTED_FILE_TYPE",
+ id: "568c6e42-c86c-ba09-c004-517f83f9f1a8"
+ },
+ tooBigFile: {
+ message: "That file is too big.",
+ code: "TOO_BIG_FILE",
+ id: "9b4ada6d-d7f7-0472-0713-4f558bd1ec9c"
+ },
+ emptyFile: {
+ message: "That file is empty.",
+ code: "EMPTY_FILE",
+ id: "d2f12af1-e7b4-feac-86a3-519548f2728e"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ fileId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "fileId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const file = await DriveFiles.findOneBy({
+ id: ps.fileId
+ });
+ if (file == null) throw new ApiError(meta.errors.noSuchFile);
+ //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
+ if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile);
+ if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
+ createImportMutingJob(user, file.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/import-posts.js b/packages/backend/built/server/api/endpoints/i/import-posts.js
new file mode 100644
index 0000000..9faab8d
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/import-posts.js
@@ -0,0 +1,46 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { DAY } from "../../../../const.js";
+export const meta = {
+ secure: true,
+ requireCredential: true,
+ limit: {
+ duration: DAY * 30,
+ max: 2
+ },
+ errors: {
+ noSuchFile: {
+ message: "No such file.",
+ code: "NO_SUCH_FILE",
+ id: "e674141e-bd2a-ba85-e616-aefb187c9c2a"
+ },
+ emptyFile: {
+ message: "That file is empty.",
+ code: "EMPTY_FILE",
+ id: "d2f12af1-e7b4-feac-86a3-519548f2728e"
+ },
+ importsDisabled: {
+ message: "Post imports are disabled for security reasons.",
+ code: "IMPORTS_DISABLED",
+ id: " bc9227e4-fb82-11ed-be56-0242ac120002"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ fileId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ signatureCheck: {
+ type: "boolean"
+ }
+ },
+ required: [
+ "fileId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ throw new ApiError(meta.errors.importsDisabled);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/import-user-lists.js b/packages/backend/built/server/api/endpoints/i/import-user-lists.js
new file mode 100644
index 0000000..2e1b13d
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/import-user-lists.js
@@ -0,0 +1,57 @@
+import define from "../../define.js";
+import { createImportUserListsJob } from "../../../../queue/index.js";
+import { ApiError } from "../../error.js";
+import { DriveFiles } from "../../../../models/index.js";
+import { HOUR } from "../../../../const.js";
+export const meta = {
+ secure: true,
+ requireCredential: true,
+ limit: {
+ duration: HOUR,
+ max: 1
+ },
+ errors: {
+ noSuchFile: {
+ message: "No such file.",
+ code: "NO_SUCH_FILE",
+ id: "ea9cc34f-c415-4bc6-a6fe-28ac40357049"
+ },
+ unexpectedFileType: {
+ message: "We need csv file.",
+ code: "UNEXPECTED_FILE_TYPE",
+ id: "a3c9edda-dd9b-4596-be6a-150ef813745c"
+ },
+ tooBigFile: {
+ message: "That file is too big.",
+ code: "TOO_BIG_FILE",
+ id: "ae6e7a22-971b-4b52-b2be-fc0b9b121fe9"
+ },
+ emptyFile: {
+ message: "That file is empty.",
+ code: "EMPTY_FILE",
+ id: "99efe367-ce6e-4d44-93f8-5fae7b040356"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ fileId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "fileId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const file = await DriveFiles.findOneBy({
+ id: ps.fileId
+ });
+ if (file == null) throw new ApiError(meta.errors.noSuchFile);
+ //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
+ if (file.size > 30000) throw new ApiError(meta.errors.tooBigFile);
+ if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
+ createImportUserListsJob(user, file.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/known-as.js b/packages/backend/built/server/api/endpoints/i/known-as.js
new file mode 100644
index 0000000..d87bcfd
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/known-as.js
@@ -0,0 +1,95 @@
+import { Users } from "../../../../models/index.js";
+import { resolveUser } from "../../../../remote/resolve-user.js";
+import acceptAllFollowRequests from "../../../../services/following/requests/accept-all.js";
+import { publishToFollowers } from "../../../../services/i/update.js";
+import { publishMainStream } from "../../../../services/stream.js";
+import { DAY } from "../../../../const.js";
+import { apiLogger } from "../../logger.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { parse } from "../../../../misc/acct.js";
+export const meta = {
+ tags: [
+ "users"
+ ],
+ secure: true,
+ requireCredential: true,
+ limit: {
+ duration: DAY,
+ max: 30
+ },
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "fcd2eef9-a9b2-4c4f-8624-038099e90aa5"
+ },
+ notRemote: {
+ message: "User is not remote. You can only migrate to other instances.",
+ code: "NOT_REMOTE",
+ id: "4362f8dc-731f-4ad8-a694-be2a88922a24"
+ },
+ uriNull: {
+ message: "User ActivityPup URI is null.",
+ 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"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ alsoKnownAs: {
+ type: "array",
+ maxItems: 10,
+ uniqueItems: true,
+ items: {
+ type: "string"
+ }
+ }
+ },
+ required: [
+ "alsoKnownAs"
+ ]
+};
+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);
+ const newAka = new Set();
+ for (const line of ps.alsoKnownAs){
+ if (!line) throw new ApiError(meta.errors.noSuchUser);
+ const { username, host } = parse(line);
+ const aka = await resolveUser(username, host).catch((e)=>{
+ apiLogger.warn(`failed to resolve remote user: ${e}`);
+ throw new ApiError(meta.errors.noSuchUser);
+ });
+ 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
+ };
+ await Users.update(user.id, updates);
+ const iObj = await Users.pack(user.id, user, {
+ detail: true,
+ includeSecrets: true
+ });
+ // Publish meUpdated event
+ publishMainStream(user.id, "meUpdated", iObj);
+ if (user.isLocked === false) {
+ acceptAllFollowRequests(user);
+ }
+ publishToFollowers(user.id);
+ return iObj;
+});
diff --git a/packages/backend/built/server/api/endpoints/i/move.js b/packages/backend/built/server/api/endpoints/i/move.js
new file mode 100644
index 0000000..bb90ec9
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/move.js
@@ -0,0 +1,147 @@
+import { resolveUser } from "../../../../remote/resolve-user.js";
+import { DAY } from "../../../../const.js";
+import DeliverManager from "../../../../remote/activitypub/deliver-manager.js";
+import { renderActivity } from "../../../../remote/activitypub/renderer/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { apiLogger } from "../../logger.js";
+import deleteFollowing from "../../../../services/following/delete.js";
+import create from "../../../../services/following/create.js";
+import { getUser } from "../../common/getters.js";
+import { Followings, Users } 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"
+ ],
+ secure: true,
+ requireCredential: true,
+ limit: {
+ duration: DAY,
+ max: 5
+ },
+ errors: {
+ noSuchMoveTarget: {
+ message: "No such move target.",
+ code: "NO_SUCH_MOVE_TARGET",
+ id: "b5c90186-4ab0-49c8-9bba-a1f76c202ba4"
+ },
+ remoteAccountForbids: {
+ message: "Remote account doesn't have proper 'Known As' alias. Did you remember to set it?",
+ code: "REMOTE_ACCOUNT_FORBIDS",
+ id: "b5c90186-4ab0-49c8-9bba-a1f766282ba4"
+ },
+ notRemote: {
+ message: "User is not remote. You can only migrate to other instances.",
+ code: "NOT_REMOTE",
+ id: "4362f8dc-731f-4ad8-a694-be2a88922a24"
+ },
+ adminForbidden: {
+ message: "Admins cant migrate.",
+ code: "NOT_ADMIN_FORBIDDEN",
+ id: "4362e8dc-731f-4ad8-a694-be2a88922a24"
+ },
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "fcd2eef9-a9b2-4c4f-8624-038099e90aa5"
+ },
+ uriNull: {
+ message: "User ActivityPup URI is null.",
+ code: "URI_NULL",
+ id: "bf326f31-d430-4f97-9933-5d61e4d48a23"
+ },
+ localUriNull: {
+ message: "Local User ActivityPup URI is null.",
+ code: "URI_NULL",
+ id: "95ba11b9-90e8-43a5-ba16-7acc1ab32e71"
+ },
+ alreadyMoved: {
+ message: "Account was already moved to another account.",
+ code: "ALREADY_MOVED",
+ id: "b234a14e-9ebe-4581-8000-074b3c215962"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ moveToAccount: {
+ type: "string"
+ }
+ },
+ required: [
+ "moveToAccount"
+ ]
+};
+function moveActivity(toUrl, fromUrl) {
+ const activity = {
+ id: null,
+ actor: fromUrl,
+ type: "Move",
+ object: fromUrl,
+ target: toUrl
+ };
+ return renderActivity(activity);
+}
+export default define(meta, paramDef, async (ps, user)=>{
+ if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchMoveTarget);
+ if (user.isAdmin) throw new ApiError(meta.errors.adminForbidden);
+ if (user.movedToUri) throw new ApiError(meta.errors.alreadyMoved);
+ const { username, host } = parse(ps.moveToAccount);
+ if (!host) throw new ApiError(meta.errors.notRemote);
+ const moveTo = await resolveUser(username, host).catch((e)=>{
+ apiLogger.warn(`failed to resolve remote user: ${e}`);
+ throw new ApiError(meta.errors.noSuchMoveTarget);
+ });
+ let fromUrl = user.uri;
+ if (!fromUrl) {
+ fromUrl = `${config.url}/users/${user.id}`;
+ }
+ let toUrl = moveTo.uri;
+ if (!toUrl) {
+ throw new ApiError(meta.errors.uriNull);
+ }
+ let allowed = false;
+ moveTo.alsoKnownAs?.forEach((element)=>{
+ if (fromUrl.includes(element)) allowed = true;
+ });
+ if (!(allowed && toUrl && fromUrl)) throw new ApiError(meta.errors.remoteAccountForbids);
+ const updates = {};
+ if (!toUrl) toUrl = "";
+ updates.movedToUri = toUrl;
+ updates.alsoKnownAs = user.alsoKnownAs?.concat(toUrl) ?? [
+ toUrl
+ ];
+ await Users.update(user.id, updates);
+ const iObj = await Users.pack(user.id, user, {
+ detail: true,
+ includeSecrets: true
+ });
+ const moveAct = moveActivity(toUrl, fromUrl);
+ const dm = new DeliverManager(user, moveAct);
+ dm.addFollowersRecipe();
+ dm.execute();
+ // Publish meUpdated event
+ publishMainStream(user.id, "meUpdated", iObj);
+ const followings = await Followings.findBy({
+ followeeId: user.id
+ });
+ followings.forEach(async (following)=>{
+ //if follower is local
+ if (!following.followerHost) {
+ const follower = await getUser(following.followerId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ await deleteFollowing(follower, user);
+ try {
+ await create(follower, moveTo);
+ } catch (e) {
+ /* empty */ }
+ }
+ });
+ return iObj;
+});
diff --git a/packages/backend/built/server/api/endpoints/i/notifications.js b/packages/backend/built/server/api/endpoints/i/notifications.js
new file mode 100644
index 0000000..f24b0f8
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/notifications.js
@@ -0,0 +1,145 @@
+import { Brackets } from "typeorm";
+import { Notifications, Followings, Mutings, Users, UserProfiles } from "../../../../models/index.js";
+import { notificationTypes } from "../../../../types.js";
+import read from "../../../../services/note/read.js";
+import { readNotification } from "../../common/read-notification.js";
+import define from "../../define.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "account",
+ "notifications"
+ ],
+ requireCredential: true,
+ limit: {
+ duration: 60000,
+ max: 15
+ },
+ kind: "read:notifications",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Notification"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ following: {
+ type: "boolean",
+ default: false
+ },
+ unreadOnly: {
+ type: "boolean",
+ default: false
+ },
+ markAsRead: {
+ type: "boolean",
+ default: true
+ },
+ includeTypes: {
+ type: "array",
+ items: {
+ type: "string",
+ enum: notificationTypes
+ }
+ },
+ excludeTypes: {
+ type: "array",
+ items: {
+ type: "string",
+ enum: notificationTypes
+ }
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // includeTypes が空の場合はクエリしない
+ if (ps.includeTypes && ps.includeTypes.length === 0) {
+ return [];
+ }
+ // excludeTypes に全指定されている場合はクエリしない
+ if (notificationTypes.every((type)=>ps.excludeTypes?.includes(type))) {
+ return [];
+ }
+ const followingQuery = Followings.createQueryBuilder("following").select("following.followeeId").where("following.followerId = :followerId", {
+ followerId: user.id
+ });
+ const mutingQuery = Mutings.createQueryBuilder("muting").select("muting.muteeId").where("muting.muterId = :muterId", {
+ muterId: user.id
+ });
+ const mutingInstanceQuery = UserProfiles.createQueryBuilder("user_profile").select("user_profile.mutedInstances").where("user_profile.userId = :muterId", {
+ muterId: user.id
+ });
+ const suspendedQuery = Users.createQueryBuilder("users").select("users.id").where("users.isSuspended = TRUE");
+ const query = makePaginationQuery(Notifications.createQueryBuilder("notification"), ps.sinceId, ps.untilId).andWhere("notification.notifieeId = :meId", {
+ meId: user.id
+ }).leftJoinAndSelect("notification.notifier", "notifier").leftJoinAndSelect("notification.note", "note").leftJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser");
+ // muted users
+ query.andWhere(new Brackets((qb)=>{
+ qb.where(`notification.notifierId NOT IN (${mutingQuery.getQuery()})`).orWhere("notification.notifierId IS NULL");
+ }));
+ query.setParameters(mutingQuery.getParameters());
+ // muted instances
+ query.andWhere(new Brackets((qb)=>{
+ qb.andWhere("notifier.host IS NULL").orWhere(`NOT (( ${mutingInstanceQuery.getQuery()} )::jsonb ? notifier.host)`);
+ }));
+ query.setParameters(mutingInstanceQuery.getParameters());
+ // suspended users
+ query.andWhere(new Brackets((qb)=>{
+ qb.where(`notification.notifierId NOT IN (${suspendedQuery.getQuery()})`).orWhere("notification.notifierId IS NULL");
+ }));
+ if (ps.following) {
+ query.andWhere(`((notification.notifierId IN (${followingQuery.getQuery()})) OR (notification.notifierId = :meId))`, {
+ meId: user.id
+ });
+ query.setParameters(followingQuery.getParameters());
+ }
+ if (ps.includeTypes && ps.includeTypes.length > 0) {
+ query.andWhere("notification.type IN (:...includeTypes)", {
+ includeTypes: ps.includeTypes
+ });
+ } else if (ps.excludeTypes && ps.excludeTypes.length > 0) {
+ query.andWhere("notification.type NOT IN (:...excludeTypes)", {
+ excludeTypes: ps.excludeTypes
+ });
+ }
+ if (ps.unreadOnly) {
+ query.andWhere("notification.isRead = false");
+ }
+ const notifications = await query.take(ps.limit).getMany();
+ // Mark all as read
+ if (notifications.length > 0 && ps.markAsRead) {
+ readNotification(user.id, notifications.map((x)=>x.id));
+ }
+ const notes = notifications.filter((notification)=>[
+ "mention",
+ "reply",
+ "quote"
+ ].includes(notification.type)).map((notification)=>notification.note);
+ if (notes.length > 0) {
+ read(user.id, notes);
+ }
+ return await Notifications.packMany(notifications, user.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/page-likes.js b/packages/backend/built/server/api/endpoints/i/page-likes.js
new file mode 100644
index 0000000..21d7d82
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/page-likes.js
@@ -0,0 +1,60 @@
+import { PageLikes } from "../../../../models/index.js";
+import define from "../../define.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "account",
+ "pages"
+ ],
+ requireCredential: true,
+ kind: "read:page-likes",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ page: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Page"
+ }
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(PageLikes.createQueryBuilder("like"), ps.sinceId, ps.untilId).andWhere("like.userId = :meId", {
+ meId: user.id
+ }).leftJoinAndSelect("like.page", "page");
+ const likes = await query.take(ps.limit).getMany();
+ return PageLikes.packMany(likes, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/pages.js b/packages/backend/built/server/api/endpoints/i/pages.js
new file mode 100644
index 0000000..8de2b95
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/pages.js
@@ -0,0 +1,49 @@
+import { Pages } from "../../../../models/index.js";
+import define from "../../define.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "account",
+ "pages"
+ ],
+ requireCredential: true,
+ kind: "read:pages",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Page"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(Pages.createQueryBuilder("page"), ps.sinceId, ps.untilId).andWhere("page.userId = :meId", {
+ meId: user.id
+ });
+ const pages = await query.take(ps.limit).getMany();
+ return await Pages.packMany(pages);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/pin.js b/packages/backend/built/server/api/endpoints/i/pin.js
new file mode 100644
index 0000000..b521d7a
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/pin.js
@@ -0,0 +1,58 @@
+import { addPinned } from "../../../../services/i/pin.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { Users } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "account",
+ "notes"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "56734f8b-3928-431e-bf80-6ff87df40cb3"
+ },
+ pinLimitExceeded: {
+ message: "You can not pin notes any more.",
+ code: "PIN_LIMIT_EXCEEDED",
+ id: "72dab508-c64d-498f-8740-a8eec1ba385a"
+ },
+ alreadyPinned: {
+ message: "That note has already been pinned.",
+ code: "ALREADY_PINNED",
+ id: "8b18c2b7-68fe-4edb-9892-c0cbaeb6c913"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "MeDetailed"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ await addPinned(user, ps.noteId).catch((e)=>{
+ if (e.id === "70c4e51f-5bea-449c-a030-53bee3cce202") throw new ApiError(meta.errors.noSuchNote);
+ if (e.id === "15a018eb-58e5-4da1-93be-330fcc5e4e1a") throw new ApiError(meta.errors.pinLimitExceeded);
+ if (e.id === "23f0cf4e-59a3-4276-a91d-61a5891c1514") throw new ApiError(meta.errors.alreadyPinned);
+ throw e;
+ });
+ return await Users.pack(user.id, user, {
+ detail: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/i/read-all-messaging-messages.js b/packages/backend/built/server/api/endpoints/i/read-all-messaging-messages.js
new file mode 100644
index 0000000..db17da8
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/read-all-messaging-messages.js
@@ -0,0 +1,38 @@
+import { publishMainStream } from "../../../../services/stream.js";
+import define from "../../define.js";
+import { MessagingMessages, UserGroupJoinings } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "account",
+ "messaging"
+ ],
+ requireCredential: true,
+ kind: "write:account"
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Update documents
+ await MessagingMessages.update({
+ recipientId: user.id,
+ isRead: false
+ }, {
+ isRead: true
+ });
+ const joinings = await UserGroupJoinings.findBy({
+ userId: user.id
+ });
+ await Promise.all(joinings.map((j)=>MessagingMessages.createQueryBuilder().update().set({
+ reads: ()=>`array_append("reads", '${user.id}')`
+ }).where("groupId = :groupId", {
+ groupId: j.userGroupId
+ }).andWhere("userId != :userId", {
+ userId: user.id
+ }).andWhere("NOT (:userId = ANY(reads))", {
+ userId: user.id
+ }).execute()));
+ publishMainStream(user.id, "readAllMessagingMessages");
+});
diff --git a/packages/backend/built/server/api/endpoints/i/read-all-unread-notes.js b/packages/backend/built/server/api/endpoints/i/read-all-unread-notes.js
new file mode 100644
index 0000000..9a0d4ab
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/read-all-unread-notes.js
@@ -0,0 +1,24 @@
+import { publishMainStream } from "../../../../services/stream.js";
+import define from "../../define.js";
+import { NoteUnreads } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ kind: "write:account"
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Remove documents
+ await NoteUnreads.delete({
+ userId: user.id
+ });
+ // 全て既読になったイベントを発行
+ publishMainStream(user.id, "readAllUnreadMentions");
+ publishMainStream(user.id, "readAllUnreadSpecifiedNotes");
+});
diff --git a/packages/backend/built/server/api/endpoints/i/read-announcement.js b/packages/backend/built/server/api/endpoints/i/read-announcement.js
new file mode 100644
index 0000000..caf26f2
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/read-announcement.js
@@ -0,0 +1,62 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { genId } from "../../../../misc/gen-id.js";
+import { AnnouncementReads, Announcements, Users } from "../../../../models/index.js";
+import { publishMainStream } from "../../../../services/stream.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchAnnouncement: {
+ message: "No such announcement.",
+ code: "NO_SUCH_ANNOUNCEMENT",
+ id: "184663db-df88-4bc2-8b52-fb85f0681939"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ announcementId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "announcementId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Check if announcement exists
+ const exist = await Announcements.exist({
+ where: {
+ id: ps.announcementId
+ }
+ });
+ if (!exist) {
+ throw new ApiError(meta.errors.noSuchAnnouncement);
+ }
+ // Check if already read
+ const read = await AnnouncementReads.exist({
+ where: {
+ announcementId: ps.announcementId,
+ userId: user.id
+ }
+ });
+ if (read) {
+ return;
+ }
+ // Create read
+ await AnnouncementReads.insert({
+ id: genId(),
+ createdAt: new Date(),
+ announcementId: ps.announcementId,
+ userId: user.id
+ });
+ if (!await Users.getHasUnreadAnnouncement(user.id)) {
+ publishMainStream(user.id, "readAllAnnouncements");
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/i/regenerate-token.js b/packages/backend/built/server/api/endpoints/i/regenerate-token.js
new file mode 100644
index 0000000..73816d3
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/regenerate-token.js
@@ -0,0 +1,49 @@
+import { publishInternalEvent, publishMainStream, publishUserEvent } from "../../../../services/stream.js";
+import generateUserToken from "../../common/generate-native-user-token.js";
+import define from "../../define.js";
+import { Users, UserProfiles } from "../../../../models/index.js";
+import { comparePassword } from "../../../../misc/password.js";
+export const meta = {
+ requireCredential: true,
+ secure: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ password: {
+ type: "string"
+ }
+ },
+ required: [
+ "password"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const freshUser = await Users.findOneByOrFail({
+ id: user.id
+ });
+ const oldToken = freshUser.token;
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ // Compare password
+ const same = await comparePassword(ps.password, profile.password);
+ if (!same) {
+ throw new Error("incorrect password");
+ }
+ const newToken = generateUserToken();
+ await Users.update(user.id, {
+ token: newToken
+ });
+ // Publish event
+ publishInternalEvent("userTokenRegenerated", {
+ id: user.id,
+ oldToken,
+ newToken
+ });
+ publishMainStream(user.id, "myTokenRegenerated");
+ // Terminate streaming
+ setTimeout(()=>{
+ publishUserEvent(user.id, "terminate", {});
+ }, 5000);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/registry/get-all.js b/packages/backend/built/server/api/endpoints/i/registry/get-all.js
new file mode 100644
index 0000000..21c6c44
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/registry/get-all.js
@@ -0,0 +1,33 @@
+import define from "../../../define.js";
+import { RegistryItems } from "../../../../../models/index.js";
+export const meta = {
+ requireCredential: true,
+ secure: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ scope: {
+ type: "array",
+ default: [],
+ items: {
+ type: "string",
+ pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1)
+ }
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = RegistryItems.createQueryBuilder("item").where("item.domain IS NULL").andWhere("item.userId = :userId", {
+ userId: user.id
+ }).andWhere("item.scope = :scope", {
+ scope: ps.scope
+ });
+ const items = await query.getMany();
+ const res = {};
+ for (const item of items){
+ res[item.key] = item.value;
+ }
+ return res;
+});
diff --git a/packages/backend/built/server/api/endpoints/i/registry/get-detail.js b/packages/backend/built/server/api/endpoints/i/registry/get-detail.js
new file mode 100644
index 0000000..e3a4d6a
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/registry/get-detail.js
@@ -0,0 +1,50 @@
+import define from "../../../define.js";
+import { RegistryItems } from "../../../../../models/index.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ requireCredential: true,
+ secure: true,
+ errors: {
+ noSuchKey: {
+ message: "No such key.",
+ code: "NO_SUCH_KEY",
+ id: "97a1e8e7-c0f7-47d2-957a-92e61256e01a"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ key: {
+ type: "string"
+ },
+ scope: {
+ type: "array",
+ default: [],
+ items: {
+ type: "string",
+ pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1)
+ }
+ }
+ },
+ required: [
+ "key"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = RegistryItems.createQueryBuilder("item").where("item.domain IS NULL").andWhere("item.userId = :userId", {
+ userId: user.id
+ }).andWhere("item.key = :key", {
+ key: ps.key
+ }).andWhere("item.scope = :scope", {
+ scope: ps.scope
+ });
+ const item = await query.getOne();
+ if (item == null) {
+ throw new ApiError(meta.errors.noSuchKey);
+ }
+ return {
+ updatedAt: item.updatedAt,
+ value: item.value
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/i/registry/get.js b/packages/backend/built/server/api/endpoints/i/registry/get.js
new file mode 100644
index 0000000..0c80199
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/registry/get.js
@@ -0,0 +1,47 @@
+import define from "../../../define.js";
+import { RegistryItems } from "../../../../../models/index.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ requireCredential: true,
+ secure: true,
+ errors: {
+ noSuchKey: {
+ message: "No such key.",
+ code: "NO_SUCH_KEY",
+ id: "ac3ed68a-62f0-422b-a7bc-d5e09e8f6a6a"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ key: {
+ type: "string"
+ },
+ scope: {
+ type: "array",
+ default: [],
+ items: {
+ type: "string",
+ pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1)
+ }
+ }
+ },
+ required: [
+ "key"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = RegistryItems.createQueryBuilder("item").where("item.domain IS NULL").andWhere("item.userId = :userId", {
+ userId: user.id
+ }).andWhere("item.key = :key", {
+ key: ps.key
+ }).andWhere("item.scope = :scope", {
+ scope: ps.scope
+ });
+ const item = await query.getOne();
+ if (item == null) {
+ throw new ApiError(meta.errors.noSuchKey);
+ }
+ return item.value;
+});
diff --git a/packages/backend/built/server/api/endpoints/i/registry/keys-with-type.js b/packages/backend/built/server/api/endpoints/i/registry/keys-with-type.js
new file mode 100644
index 0000000..fca89e6
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/registry/keys-with-type.js
@@ -0,0 +1,34 @@
+import define from "../../../define.js";
+import { RegistryItems } from "../../../../../models/index.js";
+export const meta = {
+ requireCredential: true,
+ secure: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ scope: {
+ type: "array",
+ default: [],
+ items: {
+ type: "string",
+ pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1)
+ }
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = RegistryItems.createQueryBuilder("item").where("item.domain IS NULL").andWhere("item.userId = :userId", {
+ userId: user.id
+ }).andWhere("item.scope = :scope", {
+ scope: ps.scope
+ });
+ const items = await query.getMany();
+ const res = {};
+ for (const item of items){
+ const type = typeof item.value;
+ res[item.key] = item.value === null ? "null" : Array.isArray(item.value) ? "array" : type === "number" ? "number" : type === "string" ? "string" : type === "boolean" ? "boolean" : type === "object" ? "object" : null;
+ }
+ return res;
+});
diff --git a/packages/backend/built/server/api/endpoints/i/registry/keys.js b/packages/backend/built/server/api/endpoints/i/registry/keys.js
new file mode 100644
index 0000000..f18a877
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/registry/keys.js
@@ -0,0 +1,29 @@
+import define from "../../../define.js";
+import { RegistryItems } from "../../../../../models/index.js";
+export const meta = {
+ requireCredential: true,
+ secure: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ scope: {
+ type: "array",
+ default: [],
+ items: {
+ type: "string",
+ pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1)
+ }
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = RegistryItems.createQueryBuilder("item").select("item.key").where("item.domain IS NULL").andWhere("item.userId = :userId", {
+ userId: user.id
+ }).andWhere("item.scope = :scope", {
+ scope: ps.scope
+ });
+ const items = await query.getMany();
+ return items.map((x)=>x.key);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/registry/remove.js b/packages/backend/built/server/api/endpoints/i/registry/remove.js
new file mode 100644
index 0000000..064ab78
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/registry/remove.js
@@ -0,0 +1,47 @@
+import define from "../../../define.js";
+import { RegistryItems } from "../../../../../models/index.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ requireCredential: true,
+ secure: true,
+ errors: {
+ noSuchKey: {
+ message: "No such key.",
+ code: "NO_SUCH_KEY",
+ id: "1fac4e8a-a6cd-4e39-a4a5-3a7e11f1b019"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ key: {
+ type: "string"
+ },
+ scope: {
+ type: "array",
+ default: [],
+ items: {
+ type: "string",
+ pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1)
+ }
+ }
+ },
+ required: [
+ "key"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = RegistryItems.createQueryBuilder("item").where("item.domain IS NULL").andWhere("item.userId = :userId", {
+ userId: user.id
+ }).andWhere("item.key = :key", {
+ key: ps.key
+ }).andWhere("item.scope = :scope", {
+ scope: ps.scope
+ });
+ const item = await query.getOne();
+ if (item == null) {
+ throw new ApiError(meta.errors.noSuchKey);
+ }
+ await RegistryItems.remove(item);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/registry/scopes.js b/packages/backend/built/server/api/endpoints/i/registry/scopes.js
new file mode 100644
index 0000000..557b411
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/registry/scopes.js
@@ -0,0 +1,23 @@
+import define from "../../../define.js";
+import { RegistryItems } from "../../../../../models/index.js";
+export const meta = {
+ requireCredential: true,
+ secure: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = RegistryItems.createQueryBuilder("item").select("item.scope").where("item.domain IS NULL").andWhere("item.userId = :userId", {
+ userId: user.id
+ });
+ const items = await query.getMany();
+ const res = [];
+ for (const item of items){
+ if (res.some((scope)=>scope.join(".") === item.scope.join("."))) continue;
+ res.push(item.scope);
+ }
+ return res;
+});
diff --git a/packages/backend/built/server/api/endpoints/i/registry/set.js b/packages/backend/built/server/api/endpoints/i/registry/set.js
new file mode 100644
index 0000000..cdb9558
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/registry/set.js
@@ -0,0 +1,63 @@
+import { publishMainStream } from "../../../../../services/stream.js";
+import define from "../../../define.js";
+import { RegistryItems } from "../../../../../models/index.js";
+import { genId } from "../../../../../misc/gen-id.js";
+export const meta = {
+ requireCredential: true,
+ secure: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ key: {
+ type: "string",
+ minLength: 1
+ },
+ value: {},
+ scope: {
+ type: "array",
+ default: [],
+ items: {
+ type: "string",
+ pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1)
+ }
+ }
+ },
+ required: [
+ "key",
+ "value"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = RegistryItems.createQueryBuilder("item").where("item.domain IS NULL").andWhere("item.userId = :userId", {
+ userId: user.id
+ }).andWhere("item.key = :key", {
+ key: ps.key
+ }).andWhere("item.scope = :scope", {
+ scope: ps.scope
+ });
+ const existingItem = await query.getOne();
+ if (existingItem) {
+ await RegistryItems.update(existingItem.id, {
+ updatedAt: new Date(),
+ value: ps.value
+ });
+ } else {
+ await RegistryItems.insert({
+ id: genId(),
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ userId: user.id,
+ domain: null,
+ scope: ps.scope,
+ key: ps.key,
+ value: ps.value
+ });
+ }
+ // TODO: サードパーティアプリが傍受出来てしまうのでどうにかする
+ publishMainStream(user.id, "registryUpdated", {
+ scope: ps.scope,
+ key: ps.key,
+ value: ps.value
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/i/request-verified-badge.js b/packages/backend/built/server/api/endpoints/i/request-verified-badge.js
new file mode 100644
index 0000000..969f4a1
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/request-verified-badge.js
@@ -0,0 +1,46 @@
+import define from "../../define.js";
+import { VerifiedBadgeRequests, Users } from "../../../../models/index.js";
+import { genId } from "../../../../misc/gen-id.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ kind: "write:account"
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ comment: {
+ type: "string",
+ maxLength: 2048,
+ default: ""
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const me = await Users.findOneByOrFail({
+ id: user.id
+ });
+ if (me.isVerified) {
+ throw new Error("already verified");
+ }
+ const existing = await VerifiedBadgeRequests.findOneBy({
+ userId: user.id,
+ status: "pending"
+ });
+ if (existing != null) {
+ return await VerifiedBadgeRequests.pack(existing);
+ }
+ const request = await VerifiedBadgeRequests.insert({
+ id: genId(),
+ createdAt: new Date(),
+ resolvedAt: null,
+ userId: user.id,
+ resolverId: null,
+ status: "pending",
+ comment: ps.comment ?? ""
+ }).then((x)=>VerifiedBadgeRequests.findOneByOrFail(x.identifiers[0]));
+ return await VerifiedBadgeRequests.pack(request);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/revoke-token.js b/packages/backend/built/server/api/endpoints/i/revoke-token.js
new file mode 100644
index 0000000..9392bf5
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/revoke-token.js
@@ -0,0 +1,34 @@
+import define from "../../define.js";
+import { AccessTokens } from "../../../../models/index.js";
+import { publishUserEvent } from "../../../../services/stream.js";
+export const meta = {
+ requireCredential: true,
+ secure: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ tokenId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "tokenId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const exist = await AccessTokens.exist({
+ where: {
+ id: ps.tokenId
+ }
+ });
+ if (exist) {
+ await AccessTokens.delete({
+ id: ps.tokenId,
+ userId: user.id
+ });
+ // Terminate streaming
+ publishUserEvent(user.id, "terminate");
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/i/signin-history.js b/packages/backend/built/server/api/endpoints/i/signin-history.js
new file mode 100644
index 0000000..ba98513
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/signin-history.js
@@ -0,0 +1,34 @@
+import define from "../../define.js";
+import { Signins } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ requireCredential: true,
+ secure: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(Signins.createQueryBuilder("signin"), ps.sinceId, ps.untilId).andWhere("signin.userId = :meId", {
+ meId: user.id
+ });
+ const history = await query.take(ps.limit).getMany();
+ return await Promise.all(history.map((record)=>Signins.pack(record)));
+});
diff --git a/packages/backend/built/server/api/endpoints/i/unpin.js b/packages/backend/built/server/api/endpoints/i/unpin.js
new file mode 100644
index 0000000..a0d6d22
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/unpin.js
@@ -0,0 +1,46 @@
+import { removePinned } from "../../../../services/i/pin.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { Users } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "account",
+ "notes"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "454170ce-9d63-4a43-9da1-ea10afe81e21"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "MeDetailed"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ await removePinned(user, ps.noteId).catch((e)=>{
+ if (e.id === "b302d4cf-c050-400a-bbb3-be208681f40c") throw new ApiError(meta.errors.noSuchNote);
+ throw e;
+ });
+ return await Users.pack(user.id, user, {
+ detail: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/i/update-email.js b/packages/backend/built/server/api/endpoints/i/update-email.js
new file mode 100644
index 0000000..869a7d9
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/update-email.js
@@ -0,0 +1,81 @@
+import { publishMainStream } from "../../../../services/stream.js";
+import define from "../../define.js";
+import rndstr from "rndstr";
+import config from "../../../../config/index.js";
+import { Users, UserProfiles } from "../../../../models/index.js";
+import { sendEmail } from "../../../../services/send-email.js";
+import { ApiError } from "../../error.js";
+import { validateEmailForAccount } from "../../../../services/validate-email-for-account.js";
+import { HOUR } from "../../../../const.js";
+import { comparePassword } from "../../../../misc/password.js";
+export const meta = {
+ requireCredential: true,
+ secure: true,
+ limit: {
+ duration: HOUR,
+ max: 3
+ },
+ errors: {
+ incorrectPassword: {
+ message: "Incorrect password.",
+ code: "INCORRECT_PASSWORD",
+ id: "e54c1d7e-e7d6-4103-86b6-0a95069b4ad3"
+ },
+ unavailable: {
+ message: "Unavailable email address.",
+ code: "UNAVAILABLE",
+ id: "a2defefb-f220-8849-0af6-17f816099323"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ password: {
+ type: "string"
+ },
+ email: {
+ type: "string",
+ nullable: true
+ }
+ },
+ required: [
+ "password"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ // Compare password
+ const same = await comparePassword(ps.password, profile.password);
+ if (!same) {
+ throw new ApiError(meta.errors.incorrectPassword);
+ }
+ if (ps.email != null) {
+ const available = await validateEmailForAccount(ps.email);
+ if (!available) {
+ throw new ApiError(meta.errors.unavailable);
+ }
+ }
+ await UserProfiles.update(user.id, {
+ email: ps.email,
+ emailVerified: false,
+ emailVerifyCode: null
+ });
+ const iObj = await Users.pack(user.id, user, {
+ detail: true,
+ includeSecrets: true
+ });
+ // Publish meUpdated event
+ publishMainStream(user.id, "meUpdated", iObj);
+ if (ps.email != null) {
+ const code = rndstr("a-z0-9", 16);
+ await UserProfiles.update(user.id, {
+ emailVerifyCode: code
+ });
+ const link = `${config.url}/verify-email/${code}`;
+ sendEmail(ps.email, "Email verification", `To verify email, please click this link:
${link}`, `To verify email, please click this link: ${link}`);
+ }
+ return iObj;
+});
diff --git a/packages/backend/built/server/api/endpoints/i/update.js b/packages/backend/built/server/api/endpoints/i/update.js
new file mode 100644
index 0000000..72efb0a
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/update.js
@@ -0,0 +1,349 @@
+import RE2 from "re2";
+import { updateUserProfileData } from "../../../../services/i/update.js";
+import { Users, DriveFiles, UserProfiles, Pages } from "../../../../models/index.js";
+import { notificationTypes } from "../../../../types.js";
+import { langmap } from "../../../../misc/langmap.js";
+import { verifyLink } from "../../../../services/fetch-rel-me.js";
+import { ApiError } from "../../error.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchAvatar: {
+ message: "No such avatar file.",
+ code: "NO_SUCH_AVATAR",
+ id: "539f3a45-f215-4f81-a9a8-31293640207f"
+ },
+ noSuchBanner: {
+ message: "No such banner file.",
+ code: "NO_SUCH_BANNER",
+ id: "0d8f5629-f210-41c2-9433-735831a58595"
+ },
+ avatarNotAnImage: {
+ message: "The file specified as an avatar is not an image.",
+ code: "AVATAR_NOT_AN_IMAGE",
+ id: "f419f9f8-2f4d-46b1-9fb4-49d3a2fd7191"
+ },
+ bannerNotAnImage: {
+ message: "The file specified as a banner is not an image.",
+ code: "BANNER_NOT_AN_IMAGE",
+ id: "75aedb19-2afd-4e6d-87fc-67941256fa60"
+ },
+ noSuchPage: {
+ message: "No such page.",
+ code: "NO_SUCH_PAGE",
+ id: "8e01b590-7eb9-431b-a239-860e086c408e"
+ },
+ invalidRegexp: {
+ message: "Invalid Regular Expression.",
+ code: "INVALID_REGEXP",
+ id: "0d786918-10df-41cd-8f33-8dec7d9a89a5"
+ },
+ invalidFieldName: {
+ message: "Invalid field name.",
+ code: "INVALID_FIELD_NAME",
+ id: "8f81972e-8b53-4d30-b0d2-efb026dda673"
+ },
+ invalidFieldValue: {
+ message: "Invalid field value.",
+ code: "INVALID_FIELD_VALUE",
+ id: "aede7444-244b-11ee-be56-0242ac120002"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "MeDetailed"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ name: {
+ ...Users.nameSchema,
+ nullable: true
+ },
+ description: {
+ ...Users.descriptionSchema,
+ nullable: true
+ },
+ location: {
+ ...Users.locationSchema,
+ nullable: true
+ },
+ birthday: {
+ ...Users.birthdaySchema,
+ nullable: true
+ },
+ lang: {
+ type: "string",
+ enum: [
+ null,
+ ...Object.keys(langmap)
+ ],
+ nullable: true
+ },
+ avatarId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ bannerId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ fields: {
+ type: "array",
+ minItems: 0,
+ maxItems: 16,
+ items: {
+ type: "object",
+ properties: {
+ name: {
+ type: "string"
+ },
+ value: {
+ type: "string"
+ }
+ },
+ required: [
+ "name",
+ "value"
+ ]
+ }
+ },
+ isLocked: {
+ type: "boolean"
+ },
+ isExplorable: {
+ type: "boolean"
+ },
+ hideOnlineStatus: {
+ type: "boolean"
+ },
+ publicReactions: {
+ type: "boolean"
+ },
+ allowCalls: {
+ type: "boolean"
+ },
+ symbolFileId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ carefulBot: {
+ type: "boolean"
+ },
+ autoAcceptFollowed: {
+ type: "boolean"
+ },
+ noCrawle: {
+ type: "boolean"
+ },
+ preventAiLearning: {
+ type: "boolean"
+ },
+ isBot: {
+ type: "boolean"
+ },
+ isCat: {
+ type: "boolean"
+ },
+ speakAsCat: {
+ type: "boolean"
+ },
+ minorBadges: {
+ type: "array",
+ uniqueItems: true,
+ maxItems: 1,
+ items: {
+ type: "string",
+ enum: [
+ "K",
+ "T",
+ "E"
+ ]
+ }
+ },
+ injectFeaturedNote: {
+ type: "boolean"
+ },
+ receiveAnnouncementEmail: {
+ type: "boolean"
+ },
+ alwaysMarkNsfw: {
+ type: "boolean"
+ },
+ ffVisibility: {
+ type: "string",
+ enum: [
+ "public",
+ "followers",
+ "private"
+ ]
+ },
+ pinnedPageId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ mutedWords: {
+ type: "array"
+ },
+ mutedInstances: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ },
+ mutingNotificationTypes: {
+ type: "array",
+ items: {
+ type: "string",
+ enum: notificationTypes
+ }
+ },
+ emailNotificationTypes: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ },
+ pronouns: {
+ type: "object",
+ nullable: true
+ },
+ canBite: {
+ type: "string",
+ enum: [
+ "anyone",
+ "followers",
+ "nobody"
+ ],
+ nullable: true
+ }
+ }
+};
+export default define(meta, paramDef, async (ps, _user, token)=>{
+ const user = await Users.findOneByOrFail({
+ id: _user.id
+ });
+ const isSecure = token == null;
+ const updates = {};
+ const profileUpdates = {};
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ if (ps.name !== undefined) updates.name = ps.name;
+ if (ps.description !== undefined) profileUpdates.description = ps.description;
+ if (ps.lang !== undefined) profileUpdates.lang = ps.lang;
+ if (ps.location !== undefined) profileUpdates.location = ps.location;
+ if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
+ if (ps.ffVisibility !== undefined) profileUpdates.ffVisibility = ps.ffVisibility;
+ if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId;
+ if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId;
+ if (ps.mutedWords !== undefined) {
+ // validate regular expression syntax
+ ps.mutedWords.filter((x)=>!Array.isArray(x)).forEach((x)=>{
+ const regexp = x.match(/^\/(.+)\/(.*)$/);
+ if (!regexp) throw new ApiError(meta.errors.invalidRegexp);
+ try {
+ new RE2(regexp[1], regexp[2]);
+ } catch (err) {
+ throw new ApiError(meta.errors.invalidRegexp);
+ }
+ });
+ profileUpdates.mutedWords = ps.mutedWords;
+ profileUpdates.enableWordMute = ps.mutedWords.length > 0;
+ }
+ if (ps.mutedInstances !== undefined) profileUpdates.mutedInstances = ps.mutedInstances;
+ if (ps.mutingNotificationTypes !== undefined) profileUpdates.mutingNotificationTypes = ps.mutingNotificationTypes;
+ if (typeof ps.isLocked === "boolean") updates.isLocked = ps.isLocked;
+ if (typeof ps.isExplorable === "boolean") updates.isExplorable = ps.isExplorable;
+ if (typeof ps.hideOnlineStatus === "boolean") updates.hideOnlineStatus = ps.hideOnlineStatus;
+ if (typeof ps.publicReactions === "boolean") profileUpdates.publicReactions = ps.publicReactions;
+ if (typeof ps.allowCalls === "boolean") profileUpdates.allowCalls = ps.allowCalls;
+ if (ps.symbolFileId !== undefined) profileUpdates.symbolFileId = ps.symbolFileId;
+ if (typeof ps.isBot === "boolean") updates.isBot = ps.isBot;
+ if (ps.minorBadges !== undefined) updates.minorBadges = ps.minorBadges.slice(0, 1);
+ if (typeof ps.carefulBot === "boolean") profileUpdates.carefulBot = ps.carefulBot;
+ if (typeof ps.autoAcceptFollowed === "boolean") profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
+ if (typeof ps.noCrawle === "boolean") profileUpdates.noCrawle = ps.noCrawle;
+ if (typeof ps.preventAiLearning === "boolean") profileUpdates.preventAiLearning = ps.preventAiLearning;
+ if (typeof ps.isCat === "boolean") updates.isCat = ps.isCat;
+ if (typeof ps.speakAsCat === "boolean") updates.speakAsCat = ps.speakAsCat;
+ if (typeof ps.injectFeaturedNote === "boolean") profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
+ if (typeof ps.receiveAnnouncementEmail === "boolean") profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
+ if (typeof ps.alwaysMarkNsfw === "boolean") profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
+ if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes;
+ if (typeof ps.pronouns === "object") {
+ if (ps.pronouns === null) ps.pronouns = {};
+ for (const key of Object.keys(ps.pronouns)){
+ if (key.length !== 2 || typeof ps.pronouns[key] !== "string" || ps.pronouns[key].length === 0) {
+ delete ps.pronouns[key];
+ }
+ }
+ profileUpdates.pronouns = ps.pronouns;
+ }
+ const avatar = ps.avatarId ? await DriveFiles.findOneBy({
+ id: ps.avatarId
+ }) : null;
+ const banner = ps.bannerId ? await DriveFiles.findOneBy({
+ id: ps.bannerId
+ }) : null;
+ if (ps.avatarId) {
+ if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar);
+ if (!avatar.type.startsWith("image/")) throw new ApiError(meta.errors.avatarNotAnImage);
+ updates.avatarUrl = DriveFiles.getDatabasePrefetchUrl(avatar, true);
+ updates.avatarBlurhash = avatar.blurhash;
+ }
+ if (ps.bannerId) {
+ if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner);
+ if (!banner.type.startsWith("image/")) throw new ApiError(meta.errors.bannerNotAnImage);
+ updates.bannerUrl = DriveFiles.getDatabasePrefetchUrl(banner, false);
+ updates.bannerBlurhash = banner.blurhash;
+ }
+ if (ps.pinnedPageId) {
+ const page = await Pages.findOneBy({
+ id: ps.pinnedPageId
+ });
+ if (page == null || page.userId !== user.id) throw new ApiError(meta.errors.noSuchPage);
+ profileUpdates.pinnedPageId = page.id;
+ } else if (ps.pinnedPageId === null) {
+ profileUpdates.pinnedPageId = null;
+ }
+ if (ps.fields) {
+ for (const field of ps.fields){
+ if (!field || field.name === "" || field.value === "") {
+ continue;
+ }
+ if (typeof field.name !== "string" || field.name === "") {
+ throw new ApiError(meta.errors.invalidFieldName);
+ }
+ if (typeof field.value !== "string" || field.value === "") {
+ throw new ApiError(meta.errors.invalidFieldValue);
+ }
+ if (field.value.startsWith("http")) {
+ field.verified = await verifyLink(field.value, user.username);
+ }
+ }
+ profileUpdates.fields = ps.fields.filter((x)=>Object.keys(x).length !== 0).map((x)=>{
+ return {
+ name: x.name,
+ value: x.value,
+ verified: x.verified
+ };
+ });
+ }
+ if (ps.canBite) {
+ updates.canBite = ps.canBite;
+ }
+ return updateUserProfileData(user, profile, updates, profileUpdates, isSecure);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/user-emojis/create.js b/packages/backend/built/server/api/endpoints/i/user-emojis/create.js
new file mode 100644
index 0000000..69397d7
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/user-emojis/create.js
@@ -0,0 +1,96 @@
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { DriveFiles, UserEmojis } from "../../../../../models/index.js";
+import { genId } from "../../../../../misc/gen-id.js";
+import { getEmojiSize } from "../../../../../misc/emoji-meta.js";
+import { clearUserEmojiCache } from "../../../../../misc/populate-emojis.js";
+function normalizeMimeType(type) {
+ const mime = type?.split(";")[0]?.trim().toLowerCase();
+ return mime && mime.length <= 64 ? mime : null;
+}
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchFile: {
+ message: "No such file.",
+ code: "NO_SUCH_FILE",
+ id: "7be656a7-cb71-41a8-9904-e8b6481e61dd"
+ },
+ notImage: {
+ message: "The file is not an image.",
+ code: "NOT_IMAGE",
+ id: "64cfbb24-2628-4217-873d-09e22957cf49"
+ },
+ alreadyExists: {
+ message: "User emoji already exists.",
+ code: "ALREADY_EXISTS",
+ id: "259b6fdb-c603-4244-81cb-7218b967ab62"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ name: {
+ type: "string",
+ pattern: "^[a-z0-9_]{1,64}$"
+ },
+ fileId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ glyph: {
+ type: "boolean",
+ default: false
+ }
+ },
+ required: [
+ "name",
+ "fileId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const file = await DriveFiles.findOneBy({
+ id: ps.fileId,
+ userId: me.id
+ });
+ if (!file) throw new ApiError(meta.errors.noSuchFile);
+ if (!file.type.startsWith("image/")) throw new ApiError(meta.errors.notImage);
+ const exists = await UserEmojis.findOneBy({
+ name: ps.name,
+ userId: me.id
+ });
+ if (exists) throw new ApiError(meta.errors.alreadyExists);
+ const size = await getEmojiSize(file.url).catch(()=>({
+ width: null,
+ height: null
+ }));
+ const type = normalizeMimeType(file.webpublicType ?? file.type);
+ const emoji = await UserEmojis.insert({
+ id: genId(),
+ createdAt: new Date(),
+ name: ps.name,
+ userId: me.id,
+ userGroupId: null,
+ originalUrl: file.url,
+ publicUrl: file.webpublicUrl ?? file.url,
+ type,
+ glyph: ps.glyph,
+ width: size.width || null,
+ height: size.height || null
+ }).then((x)=>UserEmojis.findOneByOrFail(x.identifiers[0]));
+ await clearUserEmojiCache(emoji.name, me.username, me.host);
+ return {
+ id: emoji.id,
+ name: emoji.name,
+ url: emoji.publicUrl || emoji.originalUrl,
+ glyph: emoji.glyph,
+ glyphUrl: emoji.glyph ? emoji.originalUrl : null,
+ width: emoji.width,
+ height: emoji.height
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/i/user-emojis/delete.js b/packages/backend/built/server/api/endpoints/i/user-emojis/delete.js
new file mode 100644
index 0000000..88ff397
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/user-emojis/delete.js
@@ -0,0 +1,39 @@
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { UserEmojis } from "../../../../../models/index.js";
+import { clearUserEmojiCache } from "../../../../../misc/populate-emojis.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchEmoji: {
+ message: "No such user emoji.",
+ code: "NO_SUCH_USER_EMOJI",
+ id: "a44ed453-7bec-4ae0-940e-2eb3e80e7b62"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "id"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const emoji = await UserEmojis.findOneBy({
+ id: ps.id,
+ userId: me.id
+ });
+ if (!emoji) throw new ApiError(meta.errors.noSuchEmoji);
+ await UserEmojis.delete(emoji.id);
+ await clearUserEmojiCache(emoji.name, me.username, me.host);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/user-emojis/list.js b/packages/backend/built/server/api/endpoints/i/user-emojis/list.js
new file mode 100644
index 0000000..1a96fe5
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/user-emojis/list.js
@@ -0,0 +1,33 @@
+import define from "../../../define.js";
+import { UserEmojis } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ kind: "read:account"
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (_ps, me)=>{
+ const emojis = await UserEmojis.find({
+ where: {
+ userId: me.id
+ },
+ order: {
+ createdAt: "DESC"
+ }
+ });
+ return emojis.map((emoji)=>({
+ id: emoji.id,
+ name: emoji.name,
+ url: emoji.publicUrl || emoji.originalUrl,
+ glyph: emoji.glyph,
+ glyphUrl: emoji.glyph ? emoji.originalUrl : null,
+ width: emoji.width,
+ height: emoji.height
+ }));
+});
diff --git a/packages/backend/built/server/api/endpoints/i/user-group-invites.js b/packages/backend/built/server/api/endpoints/i/user-group-invites.js
new file mode 100644
index 0000000..1628bb9
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/user-group-invites.js
@@ -0,0 +1,62 @@
+import define from "../../define.js";
+import { UserGroupInvitations } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "account",
+ "groups"
+ ],
+ requireCredential: true,
+ kind: "read:user-groups",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ group: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserGroup"
+ }
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(UserGroupInvitations.createQueryBuilder("invitation"), ps.sinceId, ps.untilId).andWhere("invitation.userId = :meId", {
+ meId: user.id
+ }).leftJoinAndSelect("invitation.userGroup", "user_group");
+ const invitations = await query.take(ps.limit).getMany();
+ return await UserGroupInvitations.packMany(invitations);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/webhooks/create.js b/packages/backend/built/server/api/endpoints/i/webhooks/create.js
new file mode 100644
index 0000000..70fd17a
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/webhooks/create.js
@@ -0,0 +1,58 @@
+import define from "../../../define.js";
+import { genId } from "../../../../../misc/gen-id.js";
+import { Webhooks } from "../../../../../models/index.js";
+import { publishInternalEvent } from "../../../../../services/stream.js";
+import { webhookEventTypes } from "../../../../../models/entities/webhook.js";
+export const meta = {
+ tags: [
+ "webhooks"
+ ],
+ requireCredential: true,
+ kind: "write:account"
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ name: {
+ type: "string",
+ minLength: 1,
+ maxLength: 100
+ },
+ url: {
+ type: "string",
+ minLength: 1,
+ maxLength: 1024
+ },
+ secret: {
+ type: "string",
+ minLength: 1,
+ maxLength: 1024
+ },
+ on: {
+ type: "array",
+ items: {
+ type: "string",
+ enum: webhookEventTypes
+ }
+ }
+ },
+ required: [
+ "name",
+ "url",
+ "secret",
+ "on"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const webhook = await Webhooks.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ name: ps.name,
+ url: ps.url,
+ secret: ps.secret,
+ on: ps.on
+ }).then((x)=>Webhooks.findOneByOrFail(x.identifiers[0]));
+ publishInternalEvent("webhookCreated", webhook);
+ return webhook;
+});
diff --git a/packages/backend/built/server/api/endpoints/i/webhooks/delete.js b/packages/backend/built/server/api/endpoints/i/webhooks/delete.js
new file mode 100644
index 0000000..bb43425
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/webhooks/delete.js
@@ -0,0 +1,41 @@
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { Webhooks } from "../../../../../models/index.js";
+import { publishInternalEvent } from "../../../../../services/stream.js";
+export const meta = {
+ tags: [
+ "webhooks"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchWebhook: {
+ message: "No such webhook.",
+ code: "NO_SUCH_WEBHOOK",
+ id: "bae73e5a-5522-4965-ae19-3a8688e71d82"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ webhookId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "webhookId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const webhook = await Webhooks.findOneBy({
+ id: ps.webhookId,
+ userId: user.id
+ });
+ if (webhook == null) {
+ throw new ApiError(meta.errors.noSuchWebhook);
+ }
+ await Webhooks.delete(webhook.id);
+ publishInternalEvent("webhookDeleted", webhook);
+});
diff --git a/packages/backend/built/server/api/endpoints/i/webhooks/list.js b/packages/backend/built/server/api/endpoints/i/webhooks/list.js
new file mode 100644
index 0000000..44bc942
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/webhooks/list.js
@@ -0,0 +1,21 @@
+import define from "../../../define.js";
+import { Webhooks } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "webhooks",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "read:account"
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const webhooks = await Webhooks.findBy({
+ userId: me.id
+ });
+ return webhooks;
+});
diff --git a/packages/backend/built/server/api/endpoints/i/webhooks/show.js b/packages/backend/built/server/api/endpoints/i/webhooks/show.js
new file mode 100644
index 0000000..19832f3
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/webhooks/show.js
@@ -0,0 +1,39 @@
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { Webhooks } from "../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "webhooks"
+ ],
+ requireCredential: true,
+ kind: "read:account",
+ errors: {
+ noSuchWebhook: {
+ message: "No such webhook.",
+ code: "NO_SUCH_WEBHOOK",
+ id: "50f614d9-3047-4f7e-90d8-ad6b2d5fb098"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ webhookId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "webhookId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const webhook = await Webhooks.findOneBy({
+ id: ps.webhookId,
+ userId: user.id
+ });
+ if (webhook == null) {
+ throw new ApiError(meta.errors.noSuchWebhook);
+ }
+ return webhook;
+});
diff --git a/packages/backend/built/server/api/endpoints/i/webhooks/update.js b/packages/backend/built/server/api/endpoints/i/webhooks/update.js
new file mode 100644
index 0000000..f53753d
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/i/webhooks/update.js
@@ -0,0 +1,78 @@
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { Webhooks } from "../../../../../models/index.js";
+import { publishInternalEvent } from "../../../../../services/stream.js";
+import { webhookEventTypes } from "../../../../../models/entities/webhook.js";
+export const meta = {
+ tags: [
+ "webhooks"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchWebhook: {
+ message: "No such webhook.",
+ code: "NO_SUCH_WEBHOOK",
+ id: "fb0fea69-da18-45b1-828d-bd4fd1612518"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ webhookId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ name: {
+ type: "string",
+ minLength: 1,
+ maxLength: 100
+ },
+ url: {
+ type: "string",
+ minLength: 1,
+ maxLength: 1024
+ },
+ secret: {
+ type: "string",
+ minLength: 1,
+ maxLength: 1024
+ },
+ on: {
+ type: "array",
+ items: {
+ type: "string",
+ enum: webhookEventTypes
+ }
+ },
+ active: {
+ type: "boolean"
+ }
+ },
+ required: [
+ "webhookId",
+ "name",
+ "url",
+ "secret",
+ "on",
+ "active"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const webhook = await Webhooks.findOneBy({
+ id: ps.webhookId,
+ userId: user.id
+ });
+ if (webhook == null) {
+ throw new ApiError(meta.errors.noSuchWebhook);
+ }
+ await Webhooks.update(webhook.id, {
+ name: ps.name,
+ url: ps.url,
+ secret: ps.secret,
+ on: ps.on,
+ active: ps.active
+ });
+ publishInternalEvent("webhookUpdated", webhook);
+});
diff --git a/packages/backend/built/server/api/endpoints/latest-version.js b/packages/backend/built/server/api/endpoints/latest-version.js
new file mode 100644
index 0000000..f714c10
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/latest-version.js
@@ -0,0 +1,30 @@
+import define from "../define.js";
+import config from "../../../config/index.js";
+export const meta = {
+ tags: [
+ "meta"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+const LATEST_VERSION_TIMEOUT = 3000;
+export default define(meta, paramDef, async ()=>{
+ let tag_name = null;
+ try {
+ const response = await fetch(`https://iceshrimp.dev/api/v1/repos/iceshrimp/iceshrimp/releases?draft=false&pre-release=${config.version.includes("-pre")}&page=1&limit=1`, {
+ signal: AbortSignal.timeout(LATEST_VERSION_TIMEOUT)
+ });
+ if (response.ok) {
+ const data = await response.json();
+ tag_name = Array.isArray(data) ? data[0]?.tag_name ?? null : null;
+ }
+ } catch {}
+ return {
+ tag_name
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/memoriet/create.js b/packages/backend/built/server/api/endpoints/memoriet/create.js
new file mode 100644
index 0000000..3f30aa4
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/memoriet/create.js
@@ -0,0 +1,255 @@
+import { Notes, Memoriets } from "../../../../models/index.js";
+import { MAX_NOTE_TEXT_LENGTH } from "../../../../const.js";
+import { noteVisibilities } from "../../../../types.js";
+import { ApiError } from "../../error.js";
+import define from "../../define.js";
+import { HOUR } from "../../../../const.js";
+import { createNoteFromApiData } from "../../../../services/note/create-from-api.js";
+import { genId } from "../../../../misc/gen-id.js";
+export const meta = {
+ tags: [
+ "memoriet"
+ ],
+ requireCredential: true,
+ limit: {
+ duration: HOUR,
+ max: 300
+ },
+ kind: "write:notes",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ memoriet: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ expiresAt: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ textLayers: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false
+ }
+ },
+ note: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ }
+ }
+ }
+ },
+ errors: {
+ cannotExpireToPast: {
+ message: "Expiration time must be in the future.",
+ code: "CANNOT_EXPIRE_TO_PAST",
+ id: "4b86af67-c80f-46ef-a166-440c4e3cf83a"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ visibility: {
+ type: "string",
+ enum: noteVisibilities,
+ default: "home"
+ },
+ visibleUserIds: {
+ type: "array",
+ uniqueItems: true,
+ items: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ text: {
+ type: "string",
+ maxLength: MAX_NOTE_TEXT_LENGTH,
+ nullable: true
+ },
+ cw: {
+ type: "string",
+ nullable: true,
+ maxLength: 100
+ },
+ fileIds: {
+ type: "array",
+ uniqueItems: true,
+ minItems: 1,
+ maxItems: 16,
+ items: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ textLayers: {
+ type: "array",
+ maxItems: 32,
+ default: [],
+ items: {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ maxLength: 64
+ },
+ text: {
+ type: "string",
+ minLength: 1,
+ maxLength: 200
+ },
+ color: {
+ type: "string",
+ maxLength: 32
+ },
+ backgroundColor: {
+ type: "string",
+ maxLength: 32,
+ nullable: true
+ },
+ backgroundWidth: {
+ type: "number",
+ minimum: 0,
+ maximum: 100,
+ nullable: true
+ },
+ backgroundHeight: {
+ type: "number",
+ minimum: 0,
+ maximum: 100,
+ nullable: true
+ },
+ fontSize: {
+ type: "number",
+ minimum: 8,
+ maximum: 96
+ },
+ x: {
+ type: "number",
+ minimum: 0,
+ maximum: 100
+ },
+ y: {
+ type: "number",
+ minimum: 0,
+ maximum: 100
+ },
+ rotate: {
+ type: "number",
+ minimum: -180,
+ maximum: 180
+ }
+ },
+ required: [
+ "text"
+ ],
+ additionalProperties: false
+ }
+ },
+ expiresAt: {
+ type: "integer",
+ nullable: true
+ }
+ },
+ anyOf: [
+ {
+ properties: {
+ text: {
+ type: "string",
+ minLength: 1,
+ maxLength: MAX_NOTE_TEXT_LENGTH,
+ nullable: false
+ }
+ },
+ required: [
+ "text"
+ ]
+ },
+ {
+ required: [
+ "fileIds"
+ ]
+ }
+ ]
+};
+const colorPattern = /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
+function clamp(value, min, max, fallback) {
+ const num = typeof value === "number" && Number.isFinite(value) ? value : fallback;
+ return Math.min(max, Math.max(min, num));
+}
+function normalizeColor(value, fallback) {
+ return typeof value === "string" && colorPattern.test(value) ? value : fallback;
+}
+function normalizeTextLayers(layers) {
+ if (!Array.isArray(layers)) return [];
+ return layers.slice(0, 32).flatMap((layer, index)=>{
+ if (layer == null || typeof layer !== "object") return [];
+ const input = layer;
+ const text = typeof input.text === "string" ? input.text.trim().slice(0, 200) : "";
+ if (text.length === 0) return [];
+ const backgroundColor = input.backgroundColor == null || input.backgroundColor === "transparent" ? null : normalizeColor(input.backgroundColor, "#000000cc");
+ return [
+ {
+ id: typeof input.id === "string" && input.id.length > 0 ? input.id.slice(0, 64) : genId(),
+ text,
+ color: normalizeColor(input.color, "#ffffff"),
+ backgroundColor,
+ backgroundWidth: input.backgroundWidth == null ? null : clamp(input.backgroundWidth, 0, 100, 0),
+ backgroundHeight: input.backgroundHeight == null ? null : clamp(input.backgroundHeight, 0, 100, 0),
+ fontSize: clamp(input.fontSize, 8, 96, 28),
+ x: clamp(input.x, 0, 100, 50),
+ y: clamp(input.y, 0, 100, 50 + index * 6),
+ rotate: clamp(input.rotate, -180, 180, 0)
+ }
+ ];
+ });
+}
+export default define(meta, paramDef, async (ps, user)=>{
+ const expiresAt = typeof ps.expiresAt === "number" ? new Date(ps.expiresAt) : null;
+ if (expiresAt != null && expiresAt.getTime() <= Date.now()) {
+ throw new ApiError(meta.errors.cannotExpireToPast);
+ }
+ const textLayers = normalizeTextLayers(ps.textLayers);
+ const note = await createNoteFromApiData(user, {
+ text: ps.text ? `${ps.text.trim()}\n#Memoriet` : "#Memoriet",
+ cw: ps.cw,
+ fileIds: ps.fileIds,
+ visibility: ps.visibility,
+ visibleUserIds: ps.visibleUserIds,
+ localOnly: true
+ }, new Date());
+ const memoriet = await Memoriets.save({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ noteId: note.id,
+ expiresAt,
+ textLayers
+ });
+ return {
+ memoriet: {
+ id: memoriet.id,
+ expiresAt: memoriet.expiresAt?.toISOString() ?? null,
+ textLayers: memoriet.textLayers,
+ note: await Notes.pack(note, user)
+ }
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/memoriet/deleted-list.js b/packages/backend/built/server/api/endpoints/memoriet/deleted-list.js
new file mode 100644
index 0000000..7462704
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/memoriet/deleted-list.js
@@ -0,0 +1,119 @@
+import { DriveFiles, MemorietArchives } from "../../../../models/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "memoriet"
+ ],
+ requireCredential: true,
+ kind: "read:account",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ createdAt: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ deletedAt: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ text: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ cw: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ fileIds: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "string"
+ }
+ },
+ files: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ ref: "DriveFile"
+ }
+ },
+ textLayers: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false
+ }
+ },
+ visibility: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 50,
+ default: 20
+ },
+ offset: {
+ type: "integer",
+ minimum: 0,
+ default: 0
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const archives = await MemorietArchives.find({
+ where: {
+ userId: user.id
+ },
+ order: {
+ deletedAt: "DESC"
+ },
+ skip: ps.offset,
+ take: ps.limit
+ });
+ const files = await Promise.all(archives.map((archive)=>DriveFiles.packMany(archive.fileIds)));
+ return archives.map((archive, index)=>({
+ id: archive.id,
+ createdAt: archive.createdAt.toISOString(),
+ deletedAt: archive.deletedAt.toISOString(),
+ text: archive.text,
+ cw: archive.cw,
+ fileIds: archive.fileIds,
+ files: files[index],
+ textLayers: archive.textLayers,
+ visibility: archive.visibility
+ }));
+});
diff --git a/packages/backend/built/server/api/endpoints/memoriet/list.js b/packages/backend/built/server/api/endpoints/memoriet/list.js
new file mode 100644
index 0000000..49a239e
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/memoriet/list.js
@@ -0,0 +1,124 @@
+import { Brackets } from "typeorm";
+import { Notes, MemorietViews } from "../../../../models/index.js";
+import { genId } from "../../../../misc/gen-id.js";
+import define from "../../define.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+export const meta = {
+ tags: [
+ "memoriet"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ expiresAt: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ textLayers: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false
+ }
+ },
+ viewerCount: {
+ type: "number",
+ optional: false,
+ nullable: true
+ },
+ note: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 50,
+ default: 20
+ },
+ offset: {
+ type: "integer",
+ minimum: 0,
+ default: 0
+ },
+ userId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = Notes.createQueryBuilder("note").innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser").innerJoin("memoriet", "memoriet", "memoriet.noteId = note.id").addSelect("memoriet.id", "memoriet_id").addSelect("memoriet.userId", "memoriet_userId").addSelect("memoriet.createdAt", "memoriet_createdAt").addSelect("memoriet.expiresAt", "memoriet_expiresAt").addSelect("memoriet.textLayers", "memoriet_textLayers").andWhere(new Brackets((qb)=>{
+ qb.where("memoriet.expiresAt IS NULL").orWhere("memoriet.expiresAt > :now", {
+ now: new Date()
+ });
+ }));
+ if (ps.userId) query.andWhere("note.userId = :userId", {
+ userId: ps.userId
+ });
+ generateVisibilityQuery(query, user);
+ if (user) generateMutedUserQuery(query, user);
+ if (user) generateBlockedUserQuery(query, user);
+ const { entities, raw } = await query.orderBy("memoriet.createdAt", "DESC").skip(ps.offset).take(ps.limit).getRawAndEntities();
+ if (user) {
+ const now = new Date();
+ for (const row of raw){
+ if (row.memoriet_userId === user.id) continue;
+ await MemorietViews.query(`INSERT INTO "memoriet_view" ("id", "createdAt", "viewedAt", "memorietId", "viewerId") VALUES ($1, $2, $3, $4, $5) ON CONFLICT ("memorietId", "viewerId") DO UPDATE SET "viewedAt" = EXCLUDED."viewedAt"`, [
+ genId(),
+ now,
+ now,
+ row.memoriet_id,
+ user.id
+ ]);
+ }
+ }
+ const memorietIds = raw.map((row)=>row.memoriet_id);
+ const viewerCounts = new Map();
+ if (user && memorietIds.length > 0) {
+ const counts = await MemorietViews.createQueryBuilder("view").select("view.memorietId", "memorietId").addSelect("COUNT(*)", "count").where("view.memorietId IN (:...memorietIds)", {
+ memorietIds
+ }).groupBy("view.memorietId").getRawMany();
+ for (const count of counts){
+ viewerCounts.set(count.memorietId, Number(count.count));
+ }
+ }
+ const packed = await Notes.packMany(entities, user);
+ return packed.map((note, index)=>({
+ id: raw[index].memoriet_id,
+ expiresAt: raw[index].memoriet_expiresAt?.toISOString?.() ?? raw[index].memoriet_expiresAt ?? null,
+ textLayers: raw[index].memoriet_textLayers ?? [],
+ viewerCount: user && raw[index].memoriet_userId === user.id ? viewerCounts.get(raw[index].memoriet_id) ?? 0 : null,
+ note
+ }));
+});
diff --git a/packages/backend/built/server/api/endpoints/memoriet/repost.js b/packages/backend/built/server/api/endpoints/memoriet/repost.js
new file mode 100644
index 0000000..d096c63
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/memoriet/repost.js
@@ -0,0 +1,125 @@
+import { MemorietArchives, Memoriets, Notes } from "../../../../models/index.js";
+import { noteVisibilities } from "../../../../types.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { HOUR } from "../../../../const.js";
+import { createNoteFromApiData } from "../../../../services/note/create-from-api.js";
+import { genId } from "../../../../misc/gen-id.js";
+export const meta = {
+ tags: [
+ "memoriet"
+ ],
+ requireCredential: true,
+ limit: {
+ duration: HOUR,
+ max: 300
+ },
+ kind: "write:notes",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ memoriet: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ expiresAt: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ textLayers: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false
+ }
+ },
+ note: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ }
+ }
+ }
+ },
+ errors: {
+ noSuchArchive: {
+ message: "No such Memoriet archive.",
+ code: "NO_SUCH_MEMORIET_ARCHIVE",
+ id: "53e0b18f-4f1d-49c0-ad79-ad2944cbe139",
+ httpStatusCode: 404
+ },
+ cannotExpireToPast: {
+ message: "Expiration time must be in the future.",
+ code: "CANNOT_EXPIRE_TO_PAST",
+ id: "12ff555a-fb0c-40ed-b253-1aa5d6ed2461"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ archiveId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ visibility: {
+ type: "string",
+ enum: noteVisibilities
+ },
+ expiresAt: {
+ type: "integer",
+ nullable: true
+ }
+ },
+ required: [
+ "archiveId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const archive = await MemorietArchives.findOneBy({
+ id: ps.archiveId,
+ userId: user.id
+ });
+ if (!archive) throw new ApiError(meta.errors.noSuchArchive);
+ const expiresAt = typeof ps.expiresAt === "number" ? new Date(ps.expiresAt) : null;
+ if (expiresAt != null && expiresAt.getTime() <= Date.now()) {
+ throw new ApiError(meta.errors.cannotExpireToPast);
+ }
+ const note = await createNoteFromApiData(user, {
+ text: archive.text ? `${archive.text.trim()}\n#Memoriet` : "#Memoriet",
+ cw: archive.cw,
+ fileIds: archive.fileIds,
+ visibility: ps.visibility ?? archive.visibility,
+ localOnly: true
+ }, new Date());
+ const memoriet = await Memoriets.save({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ noteId: note.id,
+ expiresAt,
+ textLayers: archive.textLayers
+ });
+ return {
+ memoriet: {
+ id: memoriet.id,
+ expiresAt: memoriet.expiresAt?.toISOString() ?? null,
+ textLayers: memoriet.textLayers,
+ note: await Notes.pack(note, user)
+ }
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/memoriet/viewers.js b/packages/backend/built/server/api/endpoints/memoriet/viewers.js
new file mode 100644
index 0000000..7db97d0
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/memoriet/viewers.js
@@ -0,0 +1,94 @@
+import { Memoriets, MemorietViews, Users } from "../../../../models/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+export const meta = {
+ tags: [
+ "memoriet"
+ ],
+ requireCredential: true,
+ kind: "read:account",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ viewedAt: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ user: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "User"
+ }
+ }
+ }
+ },
+ errors: {
+ noSuchMemoriet: {
+ message: "No such Memoriet.",
+ code: "NO_SUCH_MEMORIET",
+ id: "1470626e-d318-4e01-9294-fbfa1260e6c4",
+ httpStatusCode: 404
+ },
+ accessDenied: {
+ message: "You cannot see viewers of this Memoriet.",
+ code: "ACCESS_DENIED",
+ id: "d705b05b-23c0-4b93-a5bb-2c303c66db2b",
+ httpStatusCode: 403
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ memorietId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 50
+ },
+ offset: {
+ type: "integer",
+ minimum: 0,
+ default: 0
+ }
+ },
+ required: [
+ "memorietId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const memoriet = await Memoriets.findOneBy({
+ id: ps.memorietId
+ });
+ if (!memoriet) throw new ApiError(meta.errors.noSuchMemoriet);
+ if (memoriet.userId !== user.id) throw new ApiError(meta.errors.accessDenied);
+ const views = await MemorietViews.find({
+ where: {
+ memorietId: memoriet.id
+ },
+ order: {
+ viewedAt: "DESC"
+ },
+ skip: ps.offset,
+ take: ps.limit
+ });
+ const users = await Users.packMany(views.map((view)=>view.viewerId), user, {
+ detail: false
+ });
+ return views.map((view, index)=>({
+ viewedAt: view.viewedAt.toISOString(),
+ user: users[index]
+ }));
+});
diff --git a/packages/backend/built/server/api/endpoints/messaging/history.js b/packages/backend/built/server/api/endpoints/messaging/history.js
new file mode 100644
index 0000000..7c19ec5
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/messaging/history.js
@@ -0,0 +1,95 @@
+import { Brackets } from "typeorm";
+import { MessagingMessages, Mutings, UserGroupJoinings } from "../../../../models/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "messaging"
+ ],
+ requireCredential: true,
+ kind: "read:messaging",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "MessagingMessage"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ group: {
+ type: "boolean",
+ default: false
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const mute = await Mutings.findBy({
+ muterId: user.id
+ });
+ const groups = ps.group ? await UserGroupJoinings.findBy({
+ userId: user.id
+ }).then((xs)=>xs.map((x)=>x.userGroupId)) : [];
+ if (ps.group && groups.length === 0) {
+ return [];
+ }
+ const history = [];
+ for(let i = 0; i < ps.limit; i++){
+ const found = ps.group ? history.map((m)=>m.groupId) : history.map((m)=>m.userId === user.id ? m.recipientId : m.userId);
+ const query = MessagingMessages.createQueryBuilder("message").orderBy("message.createdAt", "DESC");
+ if (ps.group) {
+ query.where("message.groupId IN (:...groups)", {
+ groups: groups
+ });
+ if (found.length > 0) {
+ query.andWhere("message.groupId NOT IN (:...found)", {
+ found: found
+ });
+ }
+ } else {
+ query.where(new Brackets((qb)=>{
+ qb.where("message.userId = :userId", {
+ userId: user.id
+ }).orWhere("message.recipientId = :userId", {
+ userId: user.id
+ });
+ }));
+ query.andWhere("message.groupId IS NULL");
+ if (found.length > 0) {
+ query.andWhere("message.userId NOT IN (:...found)", {
+ found: found
+ });
+ query.andWhere("message.recipientId NOT IN (:...found)", {
+ found: found
+ });
+ }
+ if (mute.length > 0) {
+ query.andWhere("message.userId NOT IN (:...mute)", {
+ mute: mute.map((m)=>m.muteeId)
+ });
+ query.andWhere("message.recipientId NOT IN (:...mute)", {
+ mute: mute.map((m)=>m.muteeId)
+ });
+ }
+ }
+ const message = await query.getOne();
+ if (message) {
+ history.push(message);
+ } else {
+ break;
+ }
+ }
+ return await Promise.all(history.map((h)=>MessagingMessages.pack(h.id, user)));
+});
diff --git a/packages/backend/built/server/api/endpoints/messaging/messages.js b/packages/backend/built/server/api/endpoints/messaging/messages.js
new file mode 100644
index 0000000..0d04104
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/messaging/messages.js
@@ -0,0 +1,144 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { getUser } from "../../common/getters.js";
+import { MessagingMessages, UserGroups, UserGroupJoinings, Users } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { Brackets } from "typeorm";
+import { readUserMessagingMessage, readGroupMessagingMessage, deliverReadActivity } from "../../common/read-messaging-message.js";
+export const meta = {
+ tags: [
+ "messaging"
+ ],
+ requireCredential: true,
+ kind: "read:messaging",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "MessagingMessage"
+ }
+ },
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "11795c64-40ea-4198-b06e-3c873ed9039d"
+ },
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "c4d9f88c-9270-4632-b032-6ed8cee36f7f"
+ },
+ groupAccessDenied: {
+ message: "You can not read messages of groups that you have not joined.",
+ code: "GROUP_ACCESS_DENIED",
+ id: "a053a8dd-a491-4718-8f87-50775aad9284"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ markAsRead: {
+ type: "boolean",
+ default: true
+ }
+ },
+ anyOf: [
+ {
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+ },
+ {
+ properties: {
+ groupId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "groupId"
+ ]
+ }
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ if (ps.userId != null) {
+ // Fetch recipient (user)
+ const recipient = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ const query = makePaginationQuery(MessagingMessages.createQueryBuilder("message"), ps.sinceId, ps.untilId).andWhere(new Brackets((qb)=>{
+ qb.where(new Brackets((qb)=>{
+ qb.where("message.userId = :meId").andWhere("message.recipientId = :recipientId");
+ })).orWhere(new Brackets((qb)=>{
+ qb.where("message.userId = :recipientId").andWhere("message.recipientId = :meId");
+ }));
+ })).setParameter("meId", user.id).setParameter("recipientId", recipient.id);
+ const messages = await query.take(ps.limit).getMany();
+ // Mark all as read
+ if (ps.markAsRead) {
+ readUserMessagingMessage(user.id, recipient.id, messages.filter((m)=>m.recipientId === user.id).map((x)=>x.id));
+ // リモートユーザーとのメッセージだったら既読配信
+ if (Users.isLocalUser(user) && Users.isRemoteUser(recipient)) {
+ deliverReadActivity(user, recipient, messages);
+ }
+ }
+ return await Promise.all(messages.map((message)=>MessagingMessages.pack(message, user, {
+ populateRecipient: false
+ })));
+ } else if (ps.groupId != null) {
+ // Fetch recipient (group)
+ const recipientGroup = await UserGroups.findOneBy({
+ id: ps.groupId
+ });
+ if (recipientGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
+ // check joined
+ const joining = await UserGroupJoinings.findOneBy({
+ userId: user.id,
+ userGroupId: recipientGroup.id
+ });
+ if (joining == null) {
+ throw new ApiError(meta.errors.groupAccessDenied);
+ }
+ const query = makePaginationQuery(MessagingMessages.createQueryBuilder("message"), ps.sinceId, ps.untilId).andWhere("message.groupId = :groupId", {
+ groupId: recipientGroup.id
+ });
+ const messages = await query.take(ps.limit).getMany();
+ // Mark all as read
+ if (ps.markAsRead) {
+ readGroupMessagingMessage(user.id, recipientGroup.id, messages.map((x)=>x.id));
+ }
+ return await Promise.all(messages.map((message)=>MessagingMessages.pack(message, user, {
+ populateGroup: false
+ })));
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/messaging/messages/create.js b/packages/backend/built/server/api/endpoints/messaging/messages/create.js
new file mode 100644
index 0000000..44ba981
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/messaging/messages/create.js
@@ -0,0 +1,147 @@
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { getUser } from "../../../common/getters.js";
+import { DriveFiles, UserGroups, UserGroupJoinings, Blockings } from "../../../../../models/index.js";
+import { createMessage } from "../../../../../services/messages/create.js";
+export const meta = {
+ tags: [
+ "messaging"
+ ],
+ requireCredential: true,
+ kind: "write:messaging",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "MessagingMessage"
+ },
+ errors: {
+ recipientIsYourself: {
+ message: "You can not send a message to yourself.",
+ code: "RECIPIENT_IS_YOURSELF",
+ id: "17e2ba79-e22a-4cbc-bf91-d327643f4a7e"
+ },
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "11795c64-40ea-4198-b06e-3c873ed9039d"
+ },
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "c94e2a5d-06aa-4914-8fa6-6a42e73d6537"
+ },
+ groupAccessDenied: {
+ message: "You can not send messages to groups that you have not joined.",
+ code: "GROUP_ACCESS_DENIED",
+ id: "d96b3cca-5ad1-438b-ad8b-02f931308fbd"
+ },
+ noSuchFile: {
+ message: "No such file.",
+ code: "NO_SUCH_FILE",
+ id: "4372b8e2-185d-4146-8749-2f68864a3e5f"
+ },
+ contentRequired: {
+ message: "Content required. You need to set text or fileId.",
+ code: "CONTENT_REQUIRED",
+ id: "25587321-b0e6-449c-9239-f8925092942c"
+ },
+ youHaveBeenBlocked: {
+ message: "You cannot send a message because you have been blocked by this user.",
+ code: "YOU_HAVE_BEEN_BLOCKED",
+ id: "c15a5199-7422-4968-941a-2a462c478f7d"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ text: {
+ type: "string",
+ nullable: true,
+ maxLength: 3000
+ },
+ fileId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ anyOf: [
+ {
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+ },
+ {
+ properties: {
+ groupId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "groupId"
+ ]
+ }
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ let recipientUser;
+ let recipientGroup;
+ if (ps.userId != null) {
+ // Myself
+ if (ps.userId === user.id) {
+ throw new ApiError(meta.errors.recipientIsYourself);
+ }
+ // Fetch recipient (user)
+ recipientUser = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ // Check blocking
+ const block = await Blockings.findOneBy({
+ blockerId: recipientUser.id,
+ blockeeId: user.id
+ });
+ if (block) {
+ throw new ApiError(meta.errors.youHaveBeenBlocked);
+ }
+ } else if (ps.groupId != null) {
+ // Fetch recipient (group)
+ recipientGroup = await UserGroups.findOneBy({
+ id: ps.groupId
+ });
+ if (recipientGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
+ // check joined
+ const joining = await UserGroupJoinings.findOneBy({
+ userId: user.id,
+ userGroupId: recipientGroup.id
+ });
+ if (joining == null) {
+ throw new ApiError(meta.errors.groupAccessDenied);
+ }
+ }
+ let file = null;
+ if (ps.fileId != null) {
+ file = await DriveFiles.findOneBy({
+ id: ps.fileId,
+ userId: user.id
+ });
+ if (file == null) {
+ throw new ApiError(meta.errors.noSuchFile);
+ }
+ }
+ // テキストが無いかつ添付ファイルも無かったらエラー
+ if ((ps.text == null || ps.text.trim() === "") && file == null) {
+ throw new ApiError(meta.errors.contentRequired);
+ }
+ return await createMessage(user, recipientUser, recipientGroup, ps.text, file);
+});
diff --git a/packages/backend/built/server/api/endpoints/messaging/messages/delete.js b/packages/backend/built/server/api/endpoints/messaging/messages/delete.js
new file mode 100644
index 0000000..3764542
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/messaging/messages/delete.js
@@ -0,0 +1,46 @@
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { MessagingMessages } from "../../../../../models/index.js";
+import { deleteMessage } from "../../../../../services/messages/delete.js";
+import { SECOND, HOUR } from "../../../../../const.js";
+export const meta = {
+ tags: [
+ "messaging"
+ ],
+ requireCredential: true,
+ kind: "write:messaging",
+ limit: {
+ duration: HOUR,
+ max: 300,
+ minInterval: SECOND
+ },
+ errors: {
+ noSuchMessage: {
+ message: "No such message.",
+ code: "NO_SUCH_MESSAGE",
+ id: "54b5b326-7925-42cf-8019-130fda8b56af"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ messageId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "messageId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const message = await MessagingMessages.findOneBy({
+ id: ps.messageId,
+ userId: user.id
+ });
+ if (message == null) {
+ throw new ApiError(meta.errors.noSuchMessage);
+ }
+ await deleteMessage(message);
+});
diff --git a/packages/backend/built/server/api/endpoints/messaging/messages/read.js b/packages/backend/built/server/api/endpoints/messaging/messages/read.js
new file mode 100644
index 0000000..65d4d13
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/messaging/messages/read.js
@@ -0,0 +1,53 @@
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { MessagingMessages } from "../../../../../models/index.js";
+import { readUserMessagingMessage, readGroupMessagingMessage } from "../../../common/read-messaging-message.js";
+export const meta = {
+ tags: [
+ "messaging"
+ ],
+ requireCredential: true,
+ kind: "write:messaging",
+ errors: {
+ noSuchMessage: {
+ message: "No such message.",
+ code: "NO_SUCH_MESSAGE",
+ id: "86d56a2f-a9c3-4afb-b13c-3e9bfef9aa14"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ messageId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "messageId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const message = await MessagingMessages.findOneBy({
+ id: ps.messageId
+ });
+ if (message == null) {
+ throw new ApiError(meta.errors.noSuchMessage);
+ }
+ if (message.recipientId) {
+ await readUserMessagingMessage(user.id, message.userId, [
+ message.id
+ ]).catch((e)=>{
+ if (e.id === "e140a4bf-49ce-4fb6-b67c-b78dadf6b52f") throw new ApiError(meta.errors.noSuchMessage);
+ throw e;
+ });
+ } else if (message.groupId) {
+ await readGroupMessagingMessage(user.id, message.groupId, [
+ message.id
+ ]).catch((e)=>{
+ if (e.id === "930a270c-714a-46b2-b776-ad27276dc569") throw new ApiError(meta.errors.noSuchMessage);
+ throw e;
+ });
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/meta.js b/packages/backend/built/server/api/endpoints/meta.js
new file mode 100644
index 0000000..1c71e13
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/meta.js
@@ -0,0 +1,480 @@
+import JSON5 from "json5";
+import { IsNull } from "typeorm";
+import config from "../../../config/index.js";
+import { fetchMeta } from "../../../misc/fetch-meta.js";
+import { Emojis, Users } from "../../../models/index.js";
+import { MAX_NOTE_TEXT_LENGTH, MAX_CAPTION_TEXT_LENGTH } from "../../../const.js";
+import define from "../define.js";
+export const meta = {
+ tags: [
+ "meta"
+ ],
+ requireCredential: false,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ maintainerName: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ maintainerEmail: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ version: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ example: config.version
+ },
+ name: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ uri: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "url",
+ example: "https://iceshrimp.example.com"
+ },
+ domain: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "domain",
+ example: "example.com"
+ },
+ description: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ langs: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ },
+ tosUrl: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ repositoryUrl: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ default: "https://iceshrimp.dev/iceshrimp/iceshrimp"
+ },
+ feedbackUrl: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ default: "https://iceshrimp.dev/iceshrimp/iceshrimp/issues"
+ },
+ defaultDarkTheme: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ defaultLightTheme: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ disableRegistration: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ disableLocalTimeline: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ disableRecommendedTimeline: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ disableGlobalTimeline: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ driveCapacityPerLocalUserMb: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ driveCapacityPerRemoteUserMb: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ lua4frozenDatabaseCapacityMb: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ cacheRemoteFiles: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ emailRequiredForSignup: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ enableHcaptcha: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ hcaptchaSiteKey: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ enableRecaptcha: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ recaptchaSiteKey: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ swPublickey: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ mascotImageUrl: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ default: "/twemoji/1f440.svg"
+ },
+ bannerUrl: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ errorImageUrl: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ default: "/twemoji/1f480.svg"
+ },
+ iconUrl: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ maxNoteTextLength: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ maxCaptionTextLength: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ searchEngine: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ emojis: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ aliases: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ },
+ category: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ host: {
+ type: "string",
+ optional: false,
+ nullable: true,
+ description: "The local host is represented with `null`."
+ },
+ url: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "url"
+ }
+ }
+ }
+ },
+ requireSetup: {
+ type: "boolean",
+ optional: false,
+ nullable: false,
+ example: false
+ },
+ enableEmail: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ enableGithubIntegration: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ enableDiscordIntegration: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ translatorAvailable: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ images: {
+ type: 'object',
+ optional: false,
+ nullable: false,
+ properties: {
+ info: {
+ type: 'string'
+ },
+ notFound: {
+ type: 'string'
+ },
+ error: {
+ type: 'string'
+ }
+ }
+ },
+ features: {
+ type: "object",
+ optional: true,
+ nullable: false,
+ properties: {
+ registration: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ localTimeLine: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ recommendedTimeLine: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ globalTimeLine: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ hcaptcha: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ recaptcha: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ objectStorage: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ github: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ discord: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ serviceWorker: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ miauth: {
+ type: "boolean",
+ optional: true,
+ nullable: false,
+ default: true
+ }
+ }
+ },
+ secureMode: {
+ type: "boolean",
+ optional: true,
+ nullable: false,
+ default: false
+ },
+ privateMode: {
+ type: "boolean",
+ optional: true,
+ nullable: false,
+ default: false
+ },
+ defaultReaction: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ default: "⭐"
+ },
+ donationLink: {
+ type: "string",
+ optional: true,
+ nullable: true
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ detail: {
+ type: "boolean",
+ default: true
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const instance = await fetchMeta(true);
+ const emojis = await Emojis.find({
+ where: {
+ host: IsNull()
+ },
+ order: {
+ category: "ASC",
+ name: "ASC"
+ },
+ cache: {
+ id: "meta_emojis",
+ milliseconds: 3600000
+ }
+ });
+ const response = {
+ maintainerName: instance.maintainerName,
+ maintainerEmail: instance.maintainerEmail,
+ version: config.version,
+ name: instance.name,
+ uri: config.url,
+ domain: config.domain,
+ description: instance.description,
+ langs: instance.langs,
+ tosUrl: instance.ToSUrl,
+ repositoryUrl: instance.repositoryUrl,
+ feedbackUrl: instance.feedbackUrl,
+ secureMode: instance.secureMode,
+ privateMode: instance.privateMode,
+ disableRegistration: instance.disableRegistration,
+ disableLocalTimeline: instance.disableLocalTimeline,
+ disableRecommendedTimeline: instance.disableRecommendedTimeline,
+ disableGlobalTimeline: instance.disableGlobalTimeline,
+ driveCapacityPerLocalUserMb: instance.localDriveCapacityMb,
+ driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb,
+ lua4frozenDatabaseCapacityMb: instance.lua4frozenDatabaseCapacityMb,
+ emailRequiredForSignup: instance.emailRequiredForSignup,
+ enableHcaptcha: instance.enableHcaptcha,
+ hcaptchaSiteKey: instance.hcaptchaSiteKey,
+ enableRecaptcha: instance.enableRecaptcha,
+ recaptchaSiteKey: instance.recaptchaSiteKey,
+ swPublickey: instance.swPublicKey,
+ themeColor: instance.themeColor,
+ mascotImageUrl: instance.mascotImageUrl,
+ bannerUrl: instance.bannerUrl,
+ errorImageUrl: instance.errorImageUrl,
+ iconUrl: instance.iconUrl,
+ backgroundImageUrl: instance.backgroundImageUrl,
+ logoImageUrl: instance.logoImageUrl,
+ maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
+ maxCaptionTextLength: MAX_CAPTION_TEXT_LENGTH,
+ searchEngine: config.searchEngine,
+ emojis: instance.privateMode && !me ? [] : await Emojis.packMany(emojis),
+ // クライアントの手間を減らすためあらかじめJSONに変換しておく
+ defaultLightTheme: instance.defaultLightTheme ? JSON.stringify(JSON5.parse(instance.defaultLightTheme)) : null,
+ defaultDarkTheme: instance.defaultDarkTheme ? JSON.stringify(JSON5.parse(instance.defaultDarkTheme)) : null,
+ images: config.images,
+ enableEmail: instance.enableEmail,
+ enableGithubIntegration: instance.enableGithubIntegration,
+ enableDiscordIntegration: instance.enableDiscordIntegration,
+ translatorAvailable: instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null,
+ defaultReaction: instance.defaultReaction,
+ donationLink: instance.donationLink,
+ ...ps.detail ? {
+ pinnedPages: instance.privateMode && !me ? [] : instance.pinnedPages,
+ pinnedClipId: instance.privateMode && !me ? [] : instance.pinnedClipId,
+ cacheRemoteFiles: instance.cacheRemoteFiles,
+ requireSetup: await Users.countBy({
+ host: IsNull(),
+ isAdmin: true
+ }) === 0
+ } : {}
+ };
+ if (ps.detail) {
+ response.features = {
+ registration: !instance.disableRegistration,
+ localTimeLine: !instance.disableLocalTimeline,
+ recommendedTimeline: !instance.disableRecommendedTimeline,
+ globalTimeLine: !instance.disableGlobalTimeline,
+ emailRequiredForSignup: instance.emailRequiredForSignup,
+ hcaptcha: instance.enableHcaptcha,
+ recaptcha: instance.enableRecaptcha,
+ objectStorage: instance.useObjectStorage,
+ github: instance.enableGithubIntegration,
+ discord: instance.enableDiscordIntegration,
+ serviceWorker: true,
+ postEditing: true,
+ postImports: instance.experimentalFeatures?.postImports || false,
+ miauth: true
+ };
+ }
+ return response;
+});
diff --git a/packages/backend/built/server/api/endpoints/miauth/gen-token.js b/packages/backend/built/server/api/endpoints/miauth/gen-token.js
new file mode 100644
index 0000000..fceff03
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/miauth/gen-token.js
@@ -0,0 +1,77 @@
+import define from "../../define.js";
+import { AccessTokens } from "../../../../models/index.js";
+import { genId } from "../../../../misc/gen-id.js";
+import { secureRndstr } from "../../../../misc/secure-rndstr.js";
+export const meta = {
+ tags: [
+ "auth"
+ ],
+ requireCredential: true,
+ secure: true,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ token: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ session: {
+ type: "string",
+ nullable: true
+ },
+ name: {
+ type: "string",
+ nullable: true
+ },
+ description: {
+ type: "string",
+ nullable: true
+ },
+ iconUrl: {
+ type: "string",
+ nullable: true
+ },
+ permission: {
+ type: "array",
+ uniqueItems: true,
+ items: {
+ type: "string"
+ }
+ }
+ },
+ required: [
+ "session",
+ "permission"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Generate access token
+ const accessToken = secureRndstr(32);
+ const now = new Date();
+ // Insert access token doc
+ await AccessTokens.insert({
+ id: genId(),
+ createdAt: now,
+ lastUsedAt: now,
+ session: ps.session,
+ userId: user.id,
+ token: accessToken,
+ hash: accessToken,
+ name: ps.name,
+ description: ps.description,
+ iconUrl: ps.iconUrl,
+ permission: ps.permission
+ });
+ return {
+ token: accessToken
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/mute/create.js b/packages/backend/built/server/api/endpoints/mute/create.js
new file mode 100644
index 0000000..1441797
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/mute/create.js
@@ -0,0 +1,106 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { getUser } from "../../common/getters.js";
+import { genId } from "../../../../misc/gen-id.js";
+import { Mutings, NoteWatchings } from "../../../../models/index.js";
+import { publishUserEvent } from "../../../../services/stream.js";
+import { getGroupActor } from "../../common/get-group-actor.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ kind: "write:mutes",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "6fef56f3-e765-4957-88e5-c6f65329b8a5"
+ },
+ muteeIsYourself: {
+ message: "Mutee is yourself.",
+ code: "MUTEE_IS_YOURSELF",
+ id: "a4619cb2-5f23-484b-9301-94c903074e10"
+ },
+ alreadyMuting: {
+ message: "You are already muting that user.",
+ code: "ALREADY_MUTING",
+ id: "7e7359cb-160c-4956-b08f-4d1c653cd007"
+ },
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "83dd6d0c-c979-49d8-95d4-0ae273ab21af"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ expiresAt: {
+ type: "integer",
+ nullable: true,
+ description: "A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute."
+ },
+ groupId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const muter = user;
+ const group = await getGroupActor(ps.groupId, user);
+ if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
+ // 自分自身
+ if (group == null && user.id === ps.userId) {
+ throw new ApiError(meta.errors.muteeIsYourself);
+ }
+ // Get mutee
+ const mutee = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ // Check if already muting
+ const exist = await Mutings.exist({
+ where: {
+ muteeId: mutee.id,
+ ...group ? {
+ groupId: group.id
+ } : {
+ muterId: muter.id,
+ groupId: null
+ }
+ }
+ });
+ if (exist) {
+ throw new ApiError(meta.errors.alreadyMuting);
+ }
+ if (ps.expiresAt && ps.expiresAt <= Date.now()) {
+ return;
+ }
+ // Create mute
+ await Mutings.insert({
+ id: genId(),
+ createdAt: new Date(),
+ expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null,
+ muterId: muter.id,
+ muteeId: mutee.id,
+ groupId: group?.id ?? null
+ });
+ if (group == null) publishUserEvent(user.id, "mute", mutee);
+ if (group == null) {
+ NoteWatchings.delete({
+ userId: muter.id,
+ noteUserId: mutee.id
+ });
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/mute/delete.js b/packages/backend/built/server/api/endpoints/mute/delete.js
new file mode 100644
index 0000000..8acaa31
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/mute/delete.js
@@ -0,0 +1,84 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { getUser } from "../../common/getters.js";
+import { Mutings } from "../../../../models/index.js";
+import { publishUserEvent } from "../../../../services/stream.js";
+import { getGroupActor } from "../../common/get-group-actor.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ kind: "write:mutes",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "b851d00b-8ab1-4a56-8b1b-e24187cb48ef"
+ },
+ muteeIsYourself: {
+ message: "Mutee is yourself.",
+ code: "MUTEE_IS_YOURSELF",
+ id: "f428b029-6b39-4d48-a1d2-cc1ae6dd5cf9"
+ },
+ notMuting: {
+ message: "You are not muting that user.",
+ code: "NOT_MUTING",
+ id: "5467d020-daa9-4553-81e1-135c0c35a96d"
+ },
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "124512ed-89b5-4ccf-b044-cd9d0c7f70c9"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ groupId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const muter = user;
+ const group = await getGroupActor(ps.groupId, user);
+ if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
+ // Check if the mutee is yourself
+ if (group == null && user.id === ps.userId) {
+ throw new ApiError(meta.errors.muteeIsYourself);
+ }
+ // Get mutee
+ const mutee = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ // Check not muting
+ const muting = await Mutings.findOneBy({
+ muteeId: mutee.id,
+ ...group ? {
+ groupId: group.id
+ } : {
+ muterId: muter.id,
+ groupId: null
+ }
+ });
+ if (muting == null) {
+ throw new ApiError(meta.errors.notMuting);
+ }
+ // Delete mute
+ await Mutings.delete({
+ id: muting.id
+ });
+ if (group == null) publishUserEvent(user.id, "unmute", mutee);
+});
diff --git a/packages/backend/built/server/api/endpoints/mute/list.js b/packages/backend/built/server/api/endpoints/mute/list.js
new file mode 100644
index 0000000..f718e3f
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/mute/list.js
@@ -0,0 +1,72 @@
+import define from "../../define.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { Mutings } from "../../../../models/index.js";
+import { ApiError } from "../../error.js";
+import { getGroupActor } from "../../common/get-group-actor.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ kind: "read:mutes",
+ errors: {
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "d31b97bc-b1f9-4d7f-9402-52161e9d416f"
+ }
+ },
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Muting"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 30
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ groupId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const group = await getGroupActor(ps.groupId, me);
+ if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
+ const query = makePaginationQuery(Mutings.createQueryBuilder("muting"), ps.sinceId, ps.untilId);
+ if (group) {
+ query.andWhere("muting.groupId = :groupId", {
+ groupId: group.id
+ });
+ } else {
+ query.andWhere("muting.muterId = :meId", {
+ meId: me.id
+ });
+ query.andWhere("muting.groupId IS NULL");
+ }
+ const mutings = await query.take(ps.limit).getMany();
+ return await Mutings.packMany(mutings, me);
+});
diff --git a/packages/backend/built/server/api/endpoints/my/apps.js b/packages/backend/built/server/api/endpoints/my/apps.js
new file mode 100644
index 0000000..c4968be
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/my/apps.js
@@ -0,0 +1,49 @@
+import define from "../../define.js";
+import { Apps } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "account",
+ "app"
+ ],
+ requireCredential: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "App"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ offset: {
+ type: "integer",
+ default: 0
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = {
+ userId: user.id
+ };
+ const apps = await Apps.find({
+ where: query,
+ take: ps.limit,
+ skip: ps.offset
+ });
+ return await Promise.all(apps.map((app)=>Apps.pack(app, user, {
+ detail: true
+ })));
+});
diff --git a/packages/backend/built/server/api/endpoints/notes.js b/packages/backend/built/server/api/endpoints/notes.js
new file mode 100644
index 0000000..8c3a83f
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes.js
@@ -0,0 +1,80 @@
+import { Notes } from "../../../models/index.js";
+import define from "../define.js";
+import { makePaginationQuery } from "../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ local: {
+ type: "boolean",
+ default: false
+ },
+ reply: {
+ type: "boolean"
+ },
+ renote: {
+ type: "boolean"
+ },
+ withFiles: {
+ type: "boolean"
+ },
+ poll: {
+ type: "boolean"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps)=>{
+ const query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId).andWhere("note.visibility = 'public'").andWhere("note.localOnly = FALSE").innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser");
+ if (ps.local) {
+ query.andWhere("note.userHost IS NULL");
+ }
+ if (ps.reply !== undefined) {
+ query.andWhere(ps.reply ? "note.replyId IS NOT NULL" : "note.replyId IS NULL");
+ }
+ if (ps.renote !== undefined) {
+ query.andWhere(ps.renote ? "note.renoteId IS NOT NULL" : "note.renoteId IS NULL");
+ }
+ if (ps.withFiles !== undefined) {
+ query.andWhere(ps.withFiles ? "note.fileIds != '{}'" : "note.fileIds = '{}'");
+ }
+ if (ps.poll !== undefined) {
+ query.andWhere(ps.poll ? "note.hasPoll = TRUE" : "note.hasPoll = FALSE");
+ }
+ // TODO
+ //if (bot != undefined) {
+ // query.isBot = bot;
+ //}
+ const notes = await query.take(ps.limit).getMany();
+ return await Notes.packMany(notes);
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/children.js b/packages/backend/built/server/api/endpoints/notes/children.js
new file mode 100644
index 0000000..d6876ec
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/children.js
@@ -0,0 +1,73 @@
+import { Notes } from "../../../../models/index.js";
+import define from "../../define.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ description: "Get threaded/chained replies to a note",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ depth: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 12
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId).andWhere("note.id IN (SELECT id FROM note_replies(:noteId, :depth, :limit))", {
+ noteId: ps.noteId,
+ depth: ps.depth,
+ limit: ps.limit
+ }).innerJoinAndSelect("note.user", "user");
+ generateVisibilityQuery(query, user);
+ if (user) {
+ generateMutedUserQuery(query, user);
+ generateBlockedUserQuery(query, user);
+ }
+ const notes = await query.getMany();
+ return await Notes.packMany(notes, user, {
+ detail: false
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/clips.js b/packages/backend/built/server/api/endpoints/notes/clips.js
new file mode 100644
index 0000000..faa174a
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/clips.js
@@ -0,0 +1,59 @@
+import { In } from "typeorm";
+import { ClipNotes, Clips } from "../../../../models/index.js";
+import define from "../../define.js";
+import { getNote } from "../../common/getters.js";
+import { ApiError } from "../../error.js";
+export const meta = {
+ tags: [
+ "clips",
+ "notes"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Clip"
+ }
+ },
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "47db1a1c-b0af-458d-8fb4-986e4efafe1e"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const note = await getNote(ps.noteId, me, {
+ allowAdservice: true
+ }).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ const clipNotes = await ClipNotes.findBy({
+ noteId: note.id
+ });
+ const clips = await Clips.findBy({
+ id: In(clipNotes.map((x)=>x.clipId)),
+ isPublic: true
+ });
+ return await Promise.all(clips.map((x)=>Clips.pack(x)));
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/conversation.js b/packages/backend/built/server/api/endpoints/notes/conversation.js
new file mode 100644
index 0000000..d9cac89
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/conversation.js
@@ -0,0 +1,82 @@
+import { Notes } from "../../../../models/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { getNote } from "../../common/getters.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ description: "Get conversation of a note thread/chain by a reply",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ },
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "e1035875-9551-45ec-afa8-1ded1fcb53c8"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id",
+ description: "Should be a reply"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ offset: {
+ type: "integer",
+ default: 0
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const note = await getNote(ps.noteId, user).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ const conversation = [];
+ let i = 0;
+ async function get(id) {
+ i++;
+ const p = await getNote(id, user).catch((e)=>{
+ if (e.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") return null;
+ throw e;
+ });
+ if (p == null) return;
+ if (i > ps.offset) {
+ conversation.push(p);
+ }
+ if (conversation.length === ps.limit) {
+ return;
+ }
+ if (p.replyId) {
+ await get(p.replyId);
+ }
+ }
+ if (note.replyId) {
+ await get(note.replyId);
+ }
+ return await Notes.packMany(conversation, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/create.js b/packages/backend/built/server/api/endpoints/notes/create.js
new file mode 100644
index 0000000..57ced7b
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/create.js
@@ -0,0 +1,294 @@
+import { Notes } from "../../../../models/index.js";
+import { MAX_NOTE_TEXT_LENGTH } from "../../../../const.js";
+import { noteVisibilities } from "../../../../types.js";
+import { ApiError } from "../../error.js";
+import define from "../../define.js";
+import { HOUR } from "../../../../const.js";
+import { createNoteFromApiData } from "../../../../services/note/create-from-api.js";
+import { scheduleNote } from "../../../../services/note/scheduled.js";
+import { enqueueScheduledNote } from "../../../../queue/queues/system/scheduled-note.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: true,
+ limit: {
+ duration: HOUR,
+ max: 300
+ },
+ kind: "write:notes",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ createdNote: {
+ type: "object",
+ optional: true,
+ nullable: false,
+ ref: "Note"
+ },
+ scheduledNote: {
+ type: "object",
+ optional: true,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ scheduledAt: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ }
+ }
+ }
+ },
+ errors: {
+ noSuchRenoteTarget: {
+ message: "No such renote target.",
+ code: "NO_SUCH_RENOTE_TARGET",
+ id: "b5c90186-4ab0-49c8-9bba-a1f76c282ba4"
+ },
+ cannotReRenote: {
+ message: "You can not Renote a pure Renote.",
+ code: "CANNOT_RENOTE_TO_A_PURE_RENOTE",
+ id: "fd4cc33e-2a37-48dd-99cc-9b806eb2031a"
+ },
+ noSuchReplyTarget: {
+ message: "No such reply target.",
+ code: "NO_SUCH_REPLY_TARGET",
+ id: "749ee0f6-d3da-459a-bf02-282e2da4292c"
+ },
+ cannotReplyToPureRenote: {
+ message: "You can not reply to a pure Renote.",
+ code: "CANNOT_REPLY_TO_A_PURE_RENOTE",
+ id: "3ac74a84-8fd5-4bb0-870f-01804f82ce15"
+ },
+ cannotCreateAlreadyExpiredPoll: {
+ message: "Poll is already expired.",
+ code: "CANNOT_CREATE_ALREADY_EXPIRED_POLL",
+ id: "04da457d-b083-4055-9082-955525eda5a5"
+ },
+ noSuchChannel: {
+ message: "No such channel.",
+ code: "NO_SUCH_CHANNEL",
+ id: "b1653923-5453-4edc-b786-7c4f39bb0bbb"
+ },
+ youHaveBeenBlocked: {
+ message: "You have been blocked by this user.",
+ code: "YOU_HAVE_BEEN_BLOCKED",
+ id: "b390d7e1-8a5e-46ed-b625-06271cafd3d3"
+ },
+ accountLocked: {
+ message: "You migrated. Your account is now locked.",
+ code: "ACCOUNT_LOCKED",
+ id: "d390d7e1-8a5e-46ed-b625-06271cafd3d3"
+ },
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "7a42ce9c-bdbd-4dcc-8a5d-daf2f520bfaa"
+ },
+ cannotScheduleToPast: {
+ message: "Scheduled time must be in the future.",
+ code: "CANNOT_SCHEDULE_TO_PAST",
+ id: "181f385d-1f59-42c8-b3a2-b52253a92057"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ visibility: {
+ type: "string",
+ enum: noteVisibilities,
+ default: "public"
+ },
+ visibleUserIds: {
+ type: "array",
+ uniqueItems: true,
+ items: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ text: {
+ type: "string",
+ maxLength: MAX_NOTE_TEXT_LENGTH,
+ nullable: true
+ },
+ cw: {
+ type: "string",
+ nullable: true,
+ maxLength: 100
+ },
+ localOnly: {
+ type: "boolean",
+ default: false
+ },
+ noExtractMentions: {
+ type: "boolean",
+ default: false
+ },
+ noExtractHashtags: {
+ type: "boolean",
+ default: false
+ },
+ noExtractEmojis: {
+ type: "boolean",
+ default: false
+ },
+ fileIds: {
+ type: "array",
+ uniqueItems: true,
+ minItems: 1,
+ maxItems: 16,
+ items: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ mediaIds: {
+ deprecated: true,
+ description: "Use `fileIds` instead. If both are specified, this property is discarded.",
+ type: "array",
+ uniqueItems: true,
+ minItems: 1,
+ maxItems: 16,
+ items: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ replyId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ renoteId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ channelId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ groupId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ scheduledAt: {
+ type: "integer",
+ nullable: true
+ },
+ poll: {
+ type: "object",
+ nullable: true,
+ properties: {
+ choices: {
+ type: "array",
+ uniqueItems: true,
+ minItems: 2,
+ maxItems: 10,
+ items: {
+ type: "string",
+ minLength: 1,
+ maxLength: 50
+ }
+ },
+ multiple: {
+ type: "boolean",
+ default: false
+ },
+ expiresAt: {
+ type: "integer",
+ nullable: true
+ },
+ expiredAfter: {
+ type: "integer",
+ nullable: true,
+ minimum: 1
+ }
+ },
+ required: [
+ "choices"
+ ]
+ }
+ },
+ anyOf: [
+ {
+ // (re)note with text, files and poll are optional
+ properties: {
+ text: {
+ type: "string",
+ minLength: 1,
+ maxLength: MAX_NOTE_TEXT_LENGTH,
+ nullable: false
+ }
+ },
+ required: [
+ "text"
+ ]
+ },
+ {
+ // (re)note with files, text and poll are optional
+ required: [
+ "fileIds"
+ ]
+ },
+ {
+ // (re)note with files, text and poll are optional
+ required: [
+ "mediaIds"
+ ]
+ },
+ {
+ // (re)note with poll, text and files are optional
+ properties: {
+ poll: {
+ type: "object",
+ nullable: false
+ }
+ },
+ required: [
+ "poll"
+ ]
+ },
+ {
+ // pure renote
+ required: [
+ "renoteId"
+ ]
+ }
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ if (user.movedToUri != null) throw new ApiError(meta.errors.accountLocked);
+ if (typeof ps.scheduledAt === "number") {
+ const scheduledAt = new Date(ps.scheduledAt);
+ if (scheduledAt.getTime() <= Date.now()) {
+ throw new ApiError(meta.errors.cannotScheduleToPast);
+ }
+ const scheduledNote = await scheduleNote(user, ps, scheduledAt);
+ await enqueueScheduledNote(scheduledNote).catch((err)=>{
+ console.error("Failed to enqueue scheduled note", err);
+ });
+ return {
+ scheduledNote: {
+ id: scheduledNote.id,
+ scheduledAt: scheduledNote.scheduledAt.toISOString()
+ }
+ };
+ }
+ const note = await createNoteFromApiData(user, ps, new Date());
+ return {
+ createdNote: await Notes.pack(note, user)
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/delete.js b/packages/backend/built/server/api/endpoints/notes/delete.js
new file mode 100644
index 0000000..9d73db5
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/delete.js
@@ -0,0 +1,68 @@
+import deleteNote from "../../../../services/note/delete.js";
+import { Users } from "../../../../models/index.js";
+import define from "../../define.js";
+import { getNote } from "../../common/getters.js";
+import { ApiError } from "../../error.js";
+import { SECOND, HOUR } from "../../../../const.js";
+import { getGroupActor } from "../../common/get-group-actor.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: true,
+ kind: "write:notes",
+ limit: {
+ duration: HOUR,
+ max: 300,
+ minInterval: SECOND
+ },
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "490be23f-8c1f-4796-819f-94cb4f9d1630"
+ },
+ accessDenied: {
+ message: "Access denied.",
+ code: "ACCESS_DENIED",
+ id: "fe8d7103-0ea8-4ec3-814d-f8b401dc69e9"
+ },
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "4dc5f42e-ae8b-4b9c-88df-4f3bc0d87a9a"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ groupId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const note = await getNote(ps.noteId, user).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ const group = await getGroupActor(ps.groupId, user);
+ if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
+ if (!(user.isAdmin || user.isModerator) && (group == null ? note.userId !== user.id : note.groupId !== group.id)) {
+ throw new ApiError(meta.errors.accessDenied);
+ }
+ // この操作を行うのが投稿者とは限らない(例えばモデレーター)ため
+ await deleteNote(await Users.findOneByOrFail({
+ id: note.userId
+ }), note);
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/edit.js b/packages/backend/built/server/api/endpoints/notes/edit.js
new file mode 100644
index 0000000..525cdd1
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/edit.js
@@ -0,0 +1,209 @@
+import { Users, DriveFiles, Notes } from "../../../../models/index.js";
+import { MAX_NOTE_TEXT_LENGTH } from "../../../../const.js";
+import { ApiError } from "../../error.js";
+import define from "../../define.js";
+import { HOUR } from "../../../../const.js";
+import editNote from "../../../../services/note/edit.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: true,
+ limit: {
+ duration: HOUR,
+ max: 300
+ },
+ kind: "write:notes",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ },
+ errors: {
+ cannotCreateAlreadyExpiredPoll: {
+ message: "Poll is already expired.",
+ code: "CANNOT_CREATE_ALREADY_EXPIRED_POLL",
+ id: "04da457d-b083-4055-9082-955525eda5a5"
+ },
+ accountLocked: {
+ message: "You migrated. Your account is now locked.",
+ code: "ACCOUNT_LOCKED",
+ id: "d390d7e1-8a5e-46ed-b625-06271cafd3d3"
+ },
+ needsEditId: {
+ message: "You need to specify `editId`.",
+ code: "NEEDS_EDIT_ID",
+ id: "d697edc8-8c73-4de8-bded-35fd198b79e5"
+ },
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "eef6c173-3010-4a23-8674-7c4fcaeba719"
+ },
+ youAreNotTheAuthor: {
+ message: "You are not the author of this note.",
+ code: "YOU_ARE_NOT_THE_AUTHOR",
+ id: "c6e61685-411d-43d0-b90a-a448d2539001"
+ },
+ notLocalUser: {
+ message: "You are not a local user.",
+ code: "NOT_LOCAL_USER",
+ id: "b907f407-2aa0-4283-800b-a2c56290b822"
+ },
+ cannotAddPollToPost: {
+ message: "You cannot add a poll to a post.",
+ code: "CANNOT_ADD_POLL",
+ id: "e6a00055-21c8-4d69-a4d3-c26731cf6f18"
+ },
+ cannotRemovePollFromPost: {
+ message: "You cannot remove a poll from a post.",
+ code: "CANNOT_REMOVE_POLL",
+ id: "7c4889f1-cb50-4a36-a740-9d04f097479b"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ editId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ text: {
+ type: "string",
+ maxLength: MAX_NOTE_TEXT_LENGTH,
+ nullable: true
+ },
+ cw: {
+ type: "string",
+ nullable: true,
+ maxLength: 250
+ },
+ fileIds: {
+ type: "array",
+ uniqueItems: true,
+ minItems: 1,
+ maxItems: 16,
+ items: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ poll: {
+ type: "object",
+ nullable: true,
+ properties: {
+ choices: {
+ type: "array",
+ uniqueItems: true,
+ minItems: 2,
+ maxItems: 10,
+ items: {
+ type: "string",
+ minLength: 1,
+ maxLength: 50
+ }
+ },
+ multiple: {
+ type: "boolean",
+ default: false
+ },
+ expiresAt: {
+ type: "integer",
+ nullable: true
+ },
+ expiredAfter: {
+ type: "integer",
+ nullable: true,
+ minimum: 1
+ }
+ },
+ required: [
+ "choices"
+ ]
+ }
+ },
+ anyOf: [
+ {
+ // note with text, files and poll are optional
+ properties: {
+ text: {
+ type: "string",
+ minLength: 1,
+ maxLength: MAX_NOTE_TEXT_LENGTH,
+ nullable: false
+ }
+ },
+ required: [
+ "text"
+ ]
+ },
+ {
+ // note with files, text and poll are optional
+ required: [
+ "fileIds"
+ ]
+ },
+ {
+ // note with poll, text and files are optional
+ properties: {
+ poll: {
+ type: "object",
+ nullable: false
+ }
+ },
+ required: [
+ "poll"
+ ]
+ }
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ if (user.movedToUri != null) throw new ApiError(meta.errors.accountLocked);
+ if (!Users.isLocalUser(user)) {
+ throw new ApiError(meta.errors.notLocalUser);
+ }
+ if (!ps.editId) {
+ throw new ApiError(meta.errors.needsEditId);
+ }
+ let note = await Notes.findOneBy({
+ id: ps.editId
+ });
+ if (!note) {
+ throw new ApiError(meta.errors.noSuchNote);
+ }
+ if (note.userId !== user.id) {
+ throw new ApiError(meta.errors.youAreNotTheAuthor);
+ }
+ if (ps.poll?.expiresAt && new Date(ps.poll.expiresAt).getTime() < new Date().getTime()) {
+ throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll);
+ }
+ if (!note.hasPoll && ps.poll) {
+ throw new ApiError(meta.errors.cannotAddPollToPost);
+ }
+ if (note.hasPoll && !ps.poll) {
+ throw new ApiError(meta.errors.cannotRemovePollFromPost);
+ }
+ let files = [];
+ const fileIds = ps.fileIds ?? null;
+ if (fileIds != null) {
+ files = await DriveFiles.createQueryBuilder("file").where("file.userId = :userId AND file.id IN (:...fileIds)", {
+ userId: user.id,
+ fileIds
+ }).orderBy('array_position(ARRAY[:...fileIds], "id"::text)').setParameters({
+ fileIds
+ }).getMany();
+ }
+ note = await editNote(user, note, {
+ text: ps.text,
+ cw: ps.cw,
+ poll: ps.poll ? {
+ choices: ps.poll.choices,
+ multiple: ps.poll.multiple,
+ expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null
+ } : null,
+ files: files
+ });
+ return Notes.pack(note, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/favorites/create.js b/packages/backend/built/server/api/endpoints/notes/favorites/create.js
new file mode 100644
index 0000000..da4335a
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/favorites/create.js
@@ -0,0 +1,80 @@
+import { NoteFavorites } from "../../../../../models/index.js";
+import { genId } from "../../../../../misc/gen-id.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { getNote } from "../../../common/getters.js";
+import { getGroupActor } from "../../../common/get-group-actor.js";
+export const meta = {
+ tags: [
+ "notes",
+ "favorites"
+ ],
+ requireCredential: true,
+ kind: "write:favorites",
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "6dd26674-e060-4816-909a-45ba3f4da458"
+ },
+ alreadyFavorited: {
+ message: "The note has already been marked as a favorite.",
+ code: "ALREADY_FAVORITED",
+ id: "a402c12b-34dd-41d2-97d8-4d2ffd96a1a6"
+ },
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "02578569-44a6-4761-a112-d60dfc8764b3"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ groupId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Get favoritee
+ const note = await getNote(ps.noteId, user).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ const group = await getGroupActor(ps.groupId, user);
+ if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
+ // if already favorited
+ const exist = await NoteFavorites.exist({
+ where: {
+ noteId: note.id,
+ ...group ? {
+ groupId: group.id
+ } : {
+ userId: user.id,
+ groupId: null
+ }
+ }
+ });
+ if (exist) {
+ throw new ApiError(meta.errors.alreadyFavorited);
+ }
+ // Create favorite
+ await NoteFavorites.insert({
+ id: genId(),
+ createdAt: new Date(),
+ noteId: note.id,
+ userId: user.id,
+ groupId: group?.id ?? null
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/favorites/delete.js b/packages/backend/built/server/api/endpoints/notes/favorites/delete.js
new file mode 100644
index 0000000..38ee218
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/favorites/delete.js
@@ -0,0 +1,71 @@
+import { NoteFavorites } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { getNote } from "../../../common/getters.js";
+import { getGroupActor } from "../../../common/get-group-actor.js";
+export const meta = {
+ tags: [
+ "notes",
+ "favorites"
+ ],
+ requireCredential: true,
+ kind: "write:favorites",
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "80848a2c-398f-4343-baa9-df1d57696c56"
+ },
+ notFavorited: {
+ message: "You have not marked that note a favorite.",
+ code: "NOT_FAVORITED",
+ id: "b625fc69-635e-45e9-86f4-dbefbef35af5"
+ },
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "76417d51-a8c7-47d5-b7e2-28d89f0d438e"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ groupId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Get favoritee
+ const note = await getNote(ps.noteId, user).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ const group = await getGroupActor(ps.groupId, user);
+ if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
+ // if already favorited
+ const favorite = await NoteFavorites.findOneBy({
+ noteId: note.id,
+ ...group ? {
+ groupId: group.id
+ } : {
+ userId: user.id,
+ groupId: null
+ }
+ });
+ if (favorite == null) {
+ throw new ApiError(meta.errors.notFavorited);
+ }
+ // Delete favorite
+ await NoteFavorites.delete(favorite.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/featured.js b/packages/backend/built/server/api/endpoints/notes/featured.js
new file mode 100644
index 0000000..d5352bd
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/featured.js
@@ -0,0 +1,74 @@
+import { Notes } from "../../../../models/index.js";
+import define from "../../define.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ offset: {
+ type: "integer",
+ default: 0
+ },
+ origin: {
+ type: "string",
+ enum: [
+ "combined",
+ "local",
+ "remote"
+ ],
+ default: "local"
+ },
+ days: {
+ type: "integer",
+ minimum: 1,
+ maximum: 365,
+ default: 3
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const max = 30;
+ const day = 1000 * 60 * 60 * 24 * ps.days;
+ const query = Notes.createQueryBuilder("note").addSelect("note.score").andWhere("note.score > 0").andWhere("note.createdAt > :date", {
+ date: new Date(Date.now() - day)
+ }).andWhere("note.visibility = 'public'").innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser");
+ switch(ps.origin){
+ case "local":
+ query.andWhere("note.userHost IS NULL");
+ break;
+ case "remote":
+ query.andWhere("note.userHost IS NOT NULL");
+ break;
+ }
+ if (user) generateMutedUserQuery(query, user);
+ if (user) generateBlockedUserQuery(query, user);
+ let notes = await query.orderBy("note.score", "DESC").take(max).getMany();
+ notes.sort((a, b)=>new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
+ notes = notes.slice(ps.offset, ps.offset + ps.limit);
+ return await Notes.packMany(notes, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/global-timeline.js b/packages/backend/built/server/api/endpoints/notes/global-timeline.js
new file mode 100644
index 0000000..b21182b
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/global-timeline.js
@@ -0,0 +1,111 @@
+import { fetchMeta } from "../../../../misc/fetch-meta.js";
+import { Notes } from "../../../../models/index.js";
+import { activeUsersChart } from "../../../../services/chart/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { generateRepliesQuery } from "../../common/generate-replies-query.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+import { generateMutedUserRenotesQueryForNotes } from "../../common/generated-muted-renote-query.js";
+import { generateExcludeMemorietQuery } from "../../common/generate-exclude-memoriet-query.js";
+import { injectPromo } from "../../common/inject-promo.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ },
+ errors: {
+ gtlDisabled: {
+ message: "Global timeline has been disabled.",
+ code: "GTL_DISABLED",
+ id: "0332fc13-6ab2-4427-ae80-a9fadffd1a6b"
+ },
+ queryError: {
+ message: "Please follow more users.",
+ code: "QUERY_ERROR",
+ id: "620763f4-f621-4533-ab33-0577a1a3c343"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ withFiles: {
+ type: "boolean",
+ default: false,
+ description: "Only show notes that have attached files."
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ sinceDate: {
+ type: "integer"
+ },
+ untilDate: {
+ type: "integer"
+ },
+ withReplies: {
+ type: "boolean",
+ default: false,
+ description: "Show replies in the timeline"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const m = await fetchMeta();
+ if (m.disableGlobalTimeline) {
+ if (user == null || !(user.isAdmin || user.isModerator)) {
+ throw new ApiError(meta.errors.gtlDisabled);
+ }
+ }
+ //#region Construct query
+ const query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate).andWhere("note.visibility = 'public'").andWhere("note.channelId IS NULL").andWhere(`NOT ('adservice' = ANY(note.tags))`).innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser");
+ generateRepliesQuery(query, ps.withReplies, user);
+ if (user) {
+ generateMutedUserQuery(query, user);
+ generateBlockedUserQuery(query, user);
+ generateMutedUserRenotesQueryForNotes(query, user);
+ }
+ generateExcludeMemorietQuery(query);
+ if (ps.withFiles) {
+ query.andWhere("note.fileIds != '{}'");
+ }
+ query.andWhere("note.visibility != 'hidden'");
+ //#endregion
+ process.nextTick(()=>{
+ if (user) {
+ activeUsersChart.read(user);
+ }
+ });
+ try {
+ const notes = await query.take(ps.limit).getMany();
+ await injectPromo(notes, user);
+ return await Notes.packMany(notes, user);
+ } catch (error) {
+ throw new ApiError(meta.errors.queryError);
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/hybrid-timeline.js b/packages/backend/built/server/api/endpoints/notes/hybrid-timeline.js
new file mode 100644
index 0000000..7d4e1ca
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/hybrid-timeline.js
@@ -0,0 +1,162 @@
+import { Brackets } from "typeorm";
+import { fetchMeta } from "../../../../misc/fetch-meta.js";
+import { Followings, Notes } from "../../../../models/index.js";
+import { activeUsersChart } from "../../../../services/chart/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { generateRepliesQuery } from "../../common/generate-replies-query.js";
+import { generateChannelQuery } from "../../common/generate-channel-query.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+import { generateMutedUserRenotesQueryForNotes } from "../../common/generated-muted-renote-query.js";
+import { generateListQuery } from "../../common/generate-list-query.js";
+import { generateExcludeMemorietQuery } from "../../common/generate-exclude-memoriet-query.js";
+import { injectPromo } from "../../common/inject-promo.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ },
+ errors: {
+ stlDisabled: {
+ message: "Hybrid timeline has been disabled.",
+ code: "STL_DISABLED",
+ id: "620763f4-f621-4533-ab33-0577a1a3c342"
+ },
+ queryError: {
+ message: "Please follow more users.",
+ code: "QUERY_ERROR",
+ id: "620763f4-f621-4533-ab33-0577a1a3c343"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ sinceDate: {
+ type: "integer"
+ },
+ untilDate: {
+ type: "integer"
+ },
+ includeMyRenotes: {
+ type: "boolean",
+ default: true
+ },
+ includeRenotedMyNotes: {
+ type: "boolean",
+ default: true
+ },
+ includeLocalRenotes: {
+ type: "boolean",
+ default: true
+ },
+ withFiles: {
+ type: "boolean",
+ default: false,
+ description: "Only show notes that have attached files."
+ },
+ withReplies: {
+ type: "boolean",
+ default: false,
+ description: "Show replies in the timeline"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const m = await fetchMeta();
+ if (m.disableLocalTimeline && !user.isAdmin && !user.isModerator) {
+ throw new ApiError(meta.errors.stlDisabled);
+ }
+ //#region Construct query
+ const followingQuery = Followings.createQueryBuilder("following").select("following.followeeId").where("following.followerId = :followerId", {
+ followerId: user.id
+ });
+ const query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate).andWhere(new Brackets((qb)=>{
+ qb.where(`((note.userId IN (${followingQuery.getQuery()})) OR (note.userId = :meId))`, {
+ meId: user.id
+ }).orWhere("(note.visibility = 'public') AND (note.userHost IS NULL)");
+ })).innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser").setParameters(followingQuery.getParameters());
+ generateListQuery(query, user);
+ generateChannelQuery(query, user);
+ generateRepliesQuery(query, ps.withReplies, user);
+ generateVisibilityQuery(query, user);
+ generateMutedUserQuery(query, user);
+ generateBlockedUserQuery(query, user);
+ generateMutedUserRenotesQueryForNotes(query, user);
+ generateExcludeMemorietQuery(query);
+ if (ps.includeMyRenotes === false) {
+ query.andWhere(new Brackets((qb)=>{
+ qb.orWhere("note.userId != :meId", {
+ meId: user.id
+ });
+ qb.orWhere("note.renoteId IS NULL");
+ qb.orWhere("note.text IS NOT NULL");
+ qb.orWhere("note.fileIds != '{}'");
+ qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+ }));
+ }
+ if (ps.includeRenotedMyNotes === false) {
+ query.andWhere(new Brackets((qb)=>{
+ qb.orWhere("note.renoteUserId != :meId", {
+ meId: user.id
+ });
+ qb.orWhere("note.renoteId IS NULL");
+ qb.orWhere("note.text IS NOT NULL");
+ qb.orWhere("note.fileIds != '{}'");
+ qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+ }));
+ }
+ if (ps.includeLocalRenotes === false) {
+ query.andWhere(new Brackets((qb)=>{
+ qb.orWhere("note.renoteUserHost IS NOT NULL");
+ qb.orWhere("note.renoteId IS NULL");
+ qb.orWhere("note.text IS NOT NULL");
+ qb.orWhere("note.fileIds != '{}'");
+ qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+ }));
+ }
+ if (ps.withFiles) {
+ query.andWhere("note.fileIds != '{}'");
+ }
+ query.andWhere("note.visibility != 'hidden'");
+ //#endregion
+ process.nextTick(()=>{
+ activeUsersChart.read(user);
+ });
+ try {
+ const notes = await query.take(ps.limit).getMany();
+ await injectPromo(notes, user);
+ return await Notes.packMany(notes, user);
+ } catch (error) {
+ throw new ApiError(meta.errors.queryError);
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/increment-service-view.js b/packages/backend/built/server/api/endpoints/notes/increment-service-view.js
new file mode 100644
index 0000000..247d9dd
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/increment-service-view.js
@@ -0,0 +1,75 @@
+import { Notes } from "../../../../models/index.js";
+import { MINUTE } from "../../../../const.js";
+import { getIpHash } from "../../../../misc/get-ip-hash.js";
+import define from "../../define.js";
+import { getNote } from "../../common/getters.js";
+import { ApiError } from "../../error.js";
+import { limiter } from "../../limiter.js";
+const serviceTags = {
+ video: "videoservice",
+ audio: "audioservice",
+ lua4frozen: "lua4frozen",
+ karaoke: "karaokeservice"
+};
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "438ff599-8b8b-45d1-a72d-7e94d3476bd6",
+ httpStatusCode: 404
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ service: {
+ type: "string",
+ enum: [
+ "video",
+ "audio",
+ "lua4frozen",
+ "karaoke"
+ ]
+ }
+ },
+ required: [
+ "noteId",
+ "service"
+ ]
+};
+export default define(meta, paramDef, async (ps, user, token, file, cleanup, ip)=>{
+ const note = await getNote(ps.noteId, user, {
+ allowAdservice: true
+ }).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ if (!note.tags.includes(serviceTags[ps.service])) {
+ throw new ApiError(meta.errors.noSuchNote);
+ }
+ const actor = user ? user.id : getIpHash(ip ?? "");
+ const counted = await limiter({
+ key: `service-view:${ps.service}:${note.id}`,
+ duration: MINUTE,
+ max: 1
+ }, actor).then(()=>true, ()=>false);
+ if (counted) {
+ await Notes.increment({
+ id: note.id
+ }, "viewCount", 1);
+ }
+ return {
+ counted
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/karaoke-service-search.js b/packages/backend/built/server/api/endpoints/notes/karaoke-service-search.js
new file mode 100644
index 0000000..41e4021
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/karaoke-service-search.js
@@ -0,0 +1,122 @@
+import { Brackets } from "typeorm";
+import { Notes } from "../../../../models/index.js";
+import define from "../../define.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+function hasTag(note, tag) {
+ return note.tags?.some((x)=>x.toLowerCase() === tag.toLowerCase()) ?? false;
+}
+function searchableText(file) {
+ return `${file.name} ${file.comment ?? ""}`.toLowerCase();
+}
+function isMidiFile(file) {
+ const text = searchableText(file);
+ const type = file.type.toLowerCase();
+ return text.endsWith(".mid") || text.endsWith(".midi") || type === "audio/midi" || type === "audio/mid" || type === "audio/x-midi" || type === "audio/x-mid" || type === "application/midi" || type === "application/x-midi";
+}
+export function classifyKaraokeFile(file) {
+ const text = searchableText(file);
+ if (file.type.startsWith("video/")) return "video";
+ if (isMidiFile(file)) return "pitch";
+ if (text.includes("score") || text.includes("scoring") || text.includes("採点")) return "scoring";
+ if (text.includes("pitch") || text.includes("tempo") || text.includes("melody") || text.includes("part") || text.includes("音程") || text.includes("テンポ") || text.includes("パート")) return "pitch";
+ if (text.includes("lyrics") || text.includes("lyric") || text.endsWith(".lrc") || text.includes("歌詞")) return "lyrics";
+ if (file.type.startsWith("audio/")) return "accompaniment";
+ if (file.type.startsWith("text/") && (text.includes("info") || text.includes("metadata") || text.includes("song") || text.includes("楽曲情報"))) return "info";
+ return null;
+}
+function isKaraokeServiceNote(note) {
+ if (![
+ "public",
+ "home",
+ "followers"
+ ].includes(note.visibility)) return false;
+ if (!hasTag(note, "KaraokeService")) return false;
+ const counts = {
+ accompaniment: 0,
+ lyrics: 0,
+ pitch: 0,
+ scoring: 0,
+ video: 0,
+ info: 0
+ };
+ const fallbackDataFiles = [];
+ for (const file of note.files ?? []){
+ const role = classifyKaraokeFile(file);
+ if (role == null) {
+ fallbackDataFiles.push(file);
+ continue;
+ }
+ counts[role]++;
+ }
+ for (const role of [
+ "lyrics",
+ "pitch",
+ "scoring"
+ ]){
+ if (counts[role] === 0 && fallbackDataFiles.length > 0) {
+ fallbackDataFiles.shift();
+ counts[role]++;
+ }
+ }
+ return counts.accompaniment === 1 && counts.lyrics === 1 && counts.pitch === 1 && counts.scoring === 1 && counts.video <= 1 && counts.info <= 1 && fallbackDataFiles.length === 0;
+}
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ query: {
+ type: "string",
+ default: ""
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 50,
+ default: 20
+ },
+ offset: {
+ type: "integer",
+ minimum: 0,
+ default: 0
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const queryText = ps.query.trim();
+ const query = Notes.createQueryBuilder("note").innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser").where(`'{"karaokeservice"}' <@ note.tags`).andWhere("note.fileIds != '{}'");
+ generateVisibilityQuery(query, user);
+ if (user) generateMutedUserQuery(query, user);
+ if (user) generateBlockedUserQuery(query, user);
+ if (queryText.length > 0) {
+ query.andWhere(new Brackets((qb)=>{
+ qb.where("note.text ILIKE :query", {
+ query: `%${queryText}%`
+ }).orWhere("user.usernameLower ILIKE :query", {
+ query: `%${queryText.toLowerCase()}%`
+ });
+ }));
+ }
+ const notes = await query.orderBy("note.createdAt", "DESC").skip(ps.offset).take(ps.limit * 4).getMany();
+ const packed = await Notes.packMany(notes, user);
+ return packed.filter((note)=>isKaraokeServiceNote(note)).slice(0, ps.limit);
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/local-timeline.js b/packages/backend/built/server/api/endpoints/notes/local-timeline.js
new file mode 100644
index 0000000..001017f
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/local-timeline.js
@@ -0,0 +1,136 @@
+import { Brackets } from "typeorm";
+import { fetchMeta } from "../../../../misc/fetch-meta.js";
+import { Notes } from "../../../../models/index.js";
+import { activeUsersChart } from "../../../../services/chart/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { generateRepliesQuery } from "../../common/generate-replies-query.js";
+import { generateChannelQuery } from "../../common/generate-channel-query.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+import { generateMutedUserRenotesQueryForNotes } from "../../common/generated-muted-renote-query.js";
+import { generateExcludeMemorietQuery } from "../../common/generate-exclude-memoriet-query.js";
+import { injectPromo } from "../../common/inject-promo.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ },
+ errors: {
+ ltlDisabled: {
+ message: "Local timeline has been disabled.",
+ code: "LTL_DISABLED",
+ id: "45a6eb02-7695-4393-b023-dd3be9aaaefd"
+ },
+ queryError: {
+ message: "Please follow more users.",
+ code: "QUERY_ERROR",
+ id: "620763f4-f621-4533-ab33-0577a1a3c343"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ withFiles: {
+ type: "boolean",
+ default: false,
+ description: "Only show notes that have attached files."
+ },
+ fileType: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ },
+ excludeNsfw: {
+ type: "boolean",
+ default: false
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ sinceDate: {
+ type: "integer"
+ },
+ untilDate: {
+ type: "integer"
+ },
+ withReplies: {
+ type: "boolean",
+ default: false,
+ description: "Show replies in the timeline"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const m = await fetchMeta();
+ if (m.disableLocalTimeline) {
+ if (user == null || !(user.isAdmin || user.isModerator)) {
+ throw new ApiError(meta.errors.ltlDisabled);
+ }
+ }
+ //#region Construct query
+ const query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate).andWhere("note.visibility = 'public'").andWhere("note.userHost IS NULL").andWhere(`NOT ('adservice' = ANY(note.tags))`).innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser");
+ generateChannelQuery(query, user);
+ generateRepliesQuery(query, ps.withReplies, user);
+ if (user) generateMutedUserQuery(query, user);
+ if (user) generateBlockedUserQuery(query, user);
+ if (user) generateMutedUserRenotesQueryForNotes(query, user);
+ generateExcludeMemorietQuery(query);
+ if (ps.withFiles) {
+ query.andWhere("note.fileIds != '{}'");
+ }
+ if (ps.fileType != null) {
+ query.andWhere("note.fileIds != '{}'");
+ query.andWhere(new Brackets((qb)=>{
+ for (const type of ps.fileType){
+ const i = ps.fileType.indexOf(type);
+ qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, {
+ [`type${i}`]: type
+ });
+ }
+ }));
+ if (ps.excludeNsfw) {
+ query.andWhere("note.cw IS NULL");
+ query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)');
+ }
+ }
+ //#endregion
+ process.nextTick(()=>{
+ if (user) {
+ activeUsersChart.read(user);
+ }
+ });
+ try {
+ const notes = await query.take(ps.limit).getMany();
+ await injectPromo(notes, user);
+ return await Notes.packMany(notes, user);
+ } catch (error) {
+ throw new ApiError(meta.errors.queryError);
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/lua4frozen-search.js b/packages/backend/built/server/api/endpoints/notes/lua4frozen-search.js
new file mode 100644
index 0000000..c604c5e
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/lua4frozen-search.js
@@ -0,0 +1,66 @@
+import { Brackets, In } from "typeorm";
+import { Notes } from "../../../../models/index.js";
+import define from "../../define.js";
+function hasLuaFile(note) {
+ return note.files?.some((file)=>file.name.toLowerCase().endsWith(".lua")) ?? false;
+}
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ query: {
+ type: "string",
+ default: ""
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 50,
+ default: 20
+ },
+ offset: {
+ type: "integer",
+ minimum: 0,
+ default: 0
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const queryText = ps.query.trim();
+ const query = Notes.createQueryBuilder("note").innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser").where({
+ visibility: In([
+ "public",
+ "home"
+ ])
+ }).andWhere("'{\"lua4frozen\"}' <@ note.tags").andWhere("note.fileIds != '{}'");
+ if (queryText.length > 0) {
+ query.andWhere(new Brackets((qb)=>{
+ qb.where("note.text ILIKE :query", {
+ query: `%${queryText}%`
+ }).orWhere("user.usernameLower ILIKE :query", {
+ query: `%${queryText.toLowerCase()}%`
+ });
+ }));
+ }
+ const notes = await query.orderBy("note.createdAt", "DESC").skip(ps.offset).take(ps.limit * 3).getMany();
+ const packed = await Notes.packMany(notes, user);
+ return packed.filter((note)=>hasLuaFile(note)).slice(0, ps.limit);
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/media-service-search.js b/packages/backend/built/server/api/endpoints/notes/media-service-search.js
new file mode 100644
index 0000000..cf7d59d
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/media-service-search.js
@@ -0,0 +1,93 @@
+import { Brackets } from "typeorm";
+import { Notes } from "../../../../models/index.js";
+import define from "../../define.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+function hasTag(note, tag) {
+ return note.tags?.some((x)=>x.toLowerCase() === tag.toLowerCase()) ?? false;
+}
+function mediaServiceMatches(note, kind) {
+ const serviceTag = kind === "video" ? "VideoService" : kind === "audio" ? "AudioService" : "ImageService";
+ return [
+ "public",
+ "home",
+ "followers"
+ ].includes(note.visibility) && hasTag(note, serviceTag) && hasValidMediaFiles(note.files ?? [], kind);
+}
+function hasValidMediaFiles(files, kind) {
+ const mediaFiles = files.filter((file)=>file.type.startsWith(`${kind}/`));
+ if (kind === "image") {
+ return mediaFiles.length >= 1 && files.every((file)=>file.type.startsWith("image/"));
+ }
+ return mediaFiles.length >= 1 && files.every((file)=>file.type.startsWith(`${kind}/`) || file.type.startsWith("image/"));
+}
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ kind: {
+ type: "string",
+ enum: [
+ "video",
+ "audio",
+ "image"
+ ]
+ },
+ query: {
+ type: "string",
+ default: ""
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 50,
+ default: 20
+ },
+ offset: {
+ type: "integer",
+ minimum: 0,
+ default: 0
+ }
+ },
+ required: [
+ "kind"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const serviceTag = ps.kind === "video" ? "videoservice" : ps.kind === "audio" ? "audioservice" : "imageservice";
+ const queryText = ps.query.trim();
+ const query = Notes.createQueryBuilder("note").innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser").where(`'{"${serviceTag}"}' <@ note.tags`).andWhere("note.fileIds != '{}'");
+ generateVisibilityQuery(query, user);
+ if (user) generateMutedUserQuery(query, user);
+ if (user) generateBlockedUserQuery(query, user);
+ if (queryText.length > 0) {
+ query.andWhere(new Brackets((qb)=>{
+ qb.where("note.text ILIKE :query", {
+ query: `%${queryText}%`
+ }).orWhere("user.usernameLower ILIKE :query", {
+ query: `%${queryText.toLowerCase()}%`
+ });
+ }));
+ }
+ const notes = await query.orderBy("note.createdAt", "DESC").skip(ps.offset).take(ps.limit * 3).getMany();
+ const packed = await Notes.packMany(notes, user);
+ return packed.filter((note)=>mediaServiceMatches(note, ps.kind)).slice(0, ps.limit);
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/mentions.js b/packages/backend/built/server/api/endpoints/notes/mentions.js
new file mode 100644
index 0000000..f013e79
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/mentions.js
@@ -0,0 +1,80 @@
+import { Brackets } from "typeorm";
+import read from "../../../../services/note/read.js";
+import { Notes, Followings } from "../../../../models/index.js";
+import define from "../../define.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+import { generateMutedNoteThreadQuery } from "../../common/generate-muted-note-thread-query.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ following: {
+ type: "boolean",
+ default: false
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ visibility: {
+ type: "string"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const followingQuery = Followings.createQueryBuilder("following").select("following.followeeId").where("following.followerId = :followerId", {
+ followerId: user.id
+ });
+ const query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId).andWhere(new Brackets((qb)=>{
+ qb.where(`'{"${user.id}"}' <@ note.mentions`).orWhere(`'{"${user.id}"}' <@ note.visibleUserIds`);
+ })).innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser");
+ generateVisibilityQuery(query, user);
+ generateMutedUserQuery(query, user);
+ generateMutedNoteThreadQuery(query, user);
+ generateBlockedUserQuery(query, user);
+ if (ps.visibility) {
+ query.andWhere("note.visibility = :visibility", {
+ visibility: ps.visibility
+ });
+ }
+ if (ps.following) {
+ query.andWhere(`((note.userId IN (${followingQuery.getQuery()})) OR (note.userId = :meId))`, {
+ meId: user.id
+ });
+ query.setParameters(followingQuery.getParameters());
+ }
+ const notes = await query.take(ps.limit).getMany();
+ const packed = await Notes.packMany(notes, user);
+ read(user.id, packed);
+ return packed;
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/polls/recommendation.js b/packages/backend/built/server/api/endpoints/notes/polls/recommendation.js
new file mode 100644
index 0000000..12d5cc8
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/polls/recommendation.js
@@ -0,0 +1,72 @@
+import { Brackets, In } from "typeorm";
+import { Polls, Mutings, Notes, PollVotes } from "../../../../../models/index.js";
+import define from "../../../define.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ offset: {
+ type: "integer",
+ default: 0
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = Polls.createQueryBuilder("poll").where("poll.userHost IS NULL").andWhere("poll.userId != :meId", {
+ meId: user.id
+ }).andWhere("poll.noteVisibility = 'public'").andWhere(new Brackets((qb)=>{
+ qb.where("poll.expiresAt IS NULL").orWhere("poll.expiresAt > :now", {
+ now: new Date()
+ });
+ }));
+ //#region exclude arleady voted polls
+ const votedQuery = PollVotes.createQueryBuilder("vote").select("vote.noteId").where("vote.userId = :meId", {
+ meId: user.id
+ });
+ query.andWhere(`poll.noteId NOT IN (${votedQuery.getQuery()})`);
+ query.setParameters(votedQuery.getParameters());
+ //#endregion
+ //#region mute
+ const mutingQuery = Mutings.createQueryBuilder("muting").select("muting.muteeId").where("muting.muterId = :muterId", {
+ muterId: user.id
+ });
+ query.andWhere(`poll.userId NOT IN (${mutingQuery.getQuery()})`);
+ query.setParameters(mutingQuery.getParameters());
+ //#endregion
+ const polls = await query.orderBy("poll.noteId", "DESC").take(ps.limit).skip(ps.offset).getMany();
+ if (polls.length === 0) return [];
+ const notes = await Notes.find({
+ where: {
+ id: In(polls.map((poll)=>poll.noteId))
+ },
+ order: {
+ createdAt: "DESC"
+ }
+ });
+ return await Notes.packMany(notes, user, {
+ detail: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/polls/vote.js b/packages/backend/built/server/api/endpoints/notes/polls/vote.js
new file mode 100644
index 0000000..aa8671b
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/polls/vote.js
@@ -0,0 +1,162 @@
+import { Not } from "typeorm";
+import { publishNoteStream } from "../../../../../services/stream.js";
+import { createNotification } from "../../../../../services/create-notification.js";
+import { deliver } from "../../../../../queue/index.js";
+import { renderActivity } from "../../../../../remote/activitypub/renderer/index.js";
+import renderVote from "../../../../../remote/activitypub/renderer/vote.js";
+import { PollVotes, NoteWatchings, Users, Polls, Blockings } from "../../../../../models/index.js";
+import { genId } from "../../../../../misc/gen-id.js";
+import { getNote } from "../../../common/getters.js";
+import { ApiError } from "../../../error.js";
+import define from "../../../define.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: true,
+ kind: "write:votes",
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "ecafbd2e-c283-4d6d-aecb-1a0a33b75396"
+ },
+ noPoll: {
+ message: "The note does not attach a poll.",
+ code: "NO_POLL",
+ id: "5f979967-52d9-4314-a911-1c673727f92f"
+ },
+ invalidChoice: {
+ message: "Choice ID is invalid.",
+ code: "INVALID_CHOICE",
+ id: "e0cc9a04-f2e8-41e4-a5f1-4127293260cc"
+ },
+ alreadyVoted: {
+ message: "You have already voted.",
+ code: "ALREADY_VOTED",
+ id: "0963fc77-efac-419b-9424-b391608dc6d8"
+ },
+ alreadyExpired: {
+ message: "The poll is already expired.",
+ code: "ALREADY_EXPIRED",
+ id: "1022a357-b085-4054-9083-8f8de358337e"
+ },
+ youHaveBeenBlocked: {
+ message: "You cannot vote this poll because you have been blocked by this user.",
+ code: "YOU_HAVE_BEEN_BLOCKED",
+ id: "85a5377e-b1e9-4617-b0b9-5bea73331e49"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ choice: {
+ type: "integer"
+ }
+ },
+ required: [
+ "noteId",
+ "choice"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const createdAt = new Date();
+ // Get votee
+ const note = await getNote(ps.noteId, user).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ if (!note.hasPoll) {
+ throw new ApiError(meta.errors.noPoll);
+ }
+ // Check blocking
+ if (note.userId !== user.id) {
+ const blocked = await Blockings.exist({
+ where: [
+ {
+ blockerId: note.userId,
+ blockeeId: user.id,
+ groupId: null
+ },
+ ...note.groupId ? [
+ {
+ groupId: note.groupId,
+ blockeeId: user.id
+ }
+ ] : []
+ ]
+ });
+ if (blocked) {
+ throw new ApiError(meta.errors.youHaveBeenBlocked);
+ }
+ }
+ const poll = await Polls.findOneByOrFail({
+ noteId: note.id
+ });
+ if (poll.expiresAt && poll.expiresAt < createdAt) {
+ throw new ApiError(meta.errors.alreadyExpired);
+ }
+ if (poll.choices[ps.choice] == null) {
+ throw new ApiError(meta.errors.invalidChoice);
+ }
+ // if already voted
+ const exist = await PollVotes.findBy({
+ noteId: note.id,
+ userId: user.id
+ });
+ if (exist.length) {
+ if (poll.multiple) {
+ if (exist.some((x)=>x.choice === ps.choice)) {
+ throw new ApiError(meta.errors.alreadyVoted);
+ }
+ } else {
+ throw new ApiError(meta.errors.alreadyVoted);
+ }
+ }
+ // Create vote
+ const vote = await PollVotes.insert({
+ id: genId(),
+ createdAt,
+ noteId: note.id,
+ userId: user.id,
+ choice: ps.choice
+ }).then((x)=>PollVotes.findOneByOrFail(x.identifiers[0]));
+ // Increment votes count
+ const index = ps.choice + 1; // In SQL, array index is 1 based
+ await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE "noteId" = '${poll.noteId}'`);
+ publishNoteStream(note.id, "pollVoted", {
+ choice: ps.choice,
+ userId: user.id
+ });
+ // Notify
+ createNotification(note.userId, "pollVote", {
+ notifierId: user.id,
+ noteId: note.id,
+ choice: ps.choice
+ });
+ // Fetch watchers
+ NoteWatchings.findBy({
+ noteId: note.id,
+ userId: Not(user.id)
+ }).then((watchers)=>{
+ for (const watcher of watchers){
+ createNotification(watcher.userId, "pollVote", {
+ notifierId: user.id,
+ noteId: note.id,
+ choice: ps.choice
+ });
+ }
+ });
+ // リモート投票の場合リプライ送信
+ if (note.userHost != null) {
+ const pollOwner = await Users.findOneByOrFail({
+ id: note.userId
+ });
+ deliver(user, renderActivity(await renderVote(user, vote, note, poll, pollOwner)), pollOwner.inbox);
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/reactions.js b/packages/backend/built/server/api/endpoints/notes/reactions.js
new file mode 100644
index 0000000..eabbd81
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/reactions.js
@@ -0,0 +1,98 @@
+import { NoteReactions } from "../../../../models/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { getNote } from "../../common/getters.js";
+export const meta = {
+ tags: [
+ "notes",
+ "reactions"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ allowGet: true,
+ cacheSec: 60,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "NoteReaction"
+ }
+ },
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "263fff3d-d0e1-4af4-bea7-8408059b451a"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ type: {
+ type: "string",
+ nullable: true
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ offset: {
+ type: "integer",
+ default: 0
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // check note visibility
+ const note = await getNote(ps.noteId, user).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ const query = {
+ noteId: ps.noteId
+ };
+ if (ps.type) {
+ // ローカルリアクションはホスト名が . とされているが
+ // DB 上ではそうではないので、必要に応じて変換
+ const suffix = "@.:";
+ const type = ps.type.endsWith(suffix) ? `${ps.type.slice(0, ps.type.length - suffix.length)}:` : ps.type;
+ query.reaction = type;
+ }
+ const reactions = await NoteReactions.find({
+ where: query,
+ take: ps.limit,
+ skip: ps.offset,
+ order: {
+ id: -1
+ },
+ relations: [
+ "user",
+ "user.avatar",
+ "user.banner",
+ "note"
+ ]
+ });
+ return await NoteReactions.packMany(reactions, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/reactions/create.js b/packages/backend/built/server/api/endpoints/notes/reactions/create.js
new file mode 100644
index 0000000..c2689e7
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/reactions/create.js
@@ -0,0 +1,79 @@
+import createReaction from "../../../../../services/note/reaction/create.js";
+import define from "../../../define.js";
+import { getNote } from "../../../common/getters.js";
+import { ApiError } from "../../../error.js";
+import { getGroupActor } from "../../../common/get-group-actor.js";
+export const meta = {
+ tags: [
+ "reactions",
+ "notes"
+ ],
+ requireCredential: true,
+ kind: "write:reactions",
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "033d0620-5bfe-4027-965d-980b0c85a3ea"
+ },
+ alreadyReacted: {
+ message: "You are already reacting to that note.",
+ code: "ALREADY_REACTED",
+ id: "71efcf98-86d6-4e2b-b2ad-9d032369366b"
+ },
+ youHaveBeenBlocked: {
+ message: "You cannot react this note because you have been blocked by this user.",
+ code: "YOU_HAVE_BEEN_BLOCKED",
+ id: "20ef5475-9f38-4e4c-bd33-de6d979498ec"
+ },
+ accountLocked: {
+ message: "You migrated. Your account is now locked.",
+ code: "ACCOUNT_LOCKED",
+ id: "d390d7e1-8a5e-46ed-b625-06271cafd3d3"
+ },
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "8ad1dbe0-6498-45ac-9243-5864d80ed5c1"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ reaction: {
+ type: "string"
+ },
+ groupId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: [
+ "noteId",
+ "reaction"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ if (user.movedToUri != null) throw new ApiError(meta.errors.accountLocked);
+ const note = await getNote(ps.noteId, user).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ const group = await getGroupActor(ps.groupId, user);
+ if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
+ await createReaction({
+ ...user,
+ groupId: group?.id ?? null
+ }, note, ps.reaction).catch((e)=>{
+ if (e.id === "51c42bb4-931a-456b-bff7-e5a8a70dd298") throw new ApiError(meta.errors.alreadyReacted);
+ if (e.id === "e70412a4-7197-4726-8e74-f3e0deb92aa7") throw new ApiError(meta.errors.youHaveBeenBlocked);
+ throw e;
+ });
+ return;
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/reactions/delete.js b/packages/backend/built/server/api/endpoints/notes/reactions/delete.js
new file mode 100644
index 0000000..c7b8ccd
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/reactions/delete.js
@@ -0,0 +1,68 @@
+import deleteReaction from "../../../../../services/note/reaction/delete.js";
+import define from "../../../define.js";
+import { getNote } from "../../../common/getters.js";
+import { ApiError } from "../../../error.js";
+import { SECOND, HOUR } from "../../../../../const.js";
+import { getGroupActor } from "../../../common/get-group-actor.js";
+export const meta = {
+ tags: [
+ "reactions",
+ "notes"
+ ],
+ requireCredential: true,
+ kind: "write:reactions",
+ limit: {
+ duration: HOUR,
+ max: 60,
+ minInterval: 3 * SECOND
+ },
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "764d9fce-f9f2-4a0e-92b1-6ceac9a7ad37"
+ },
+ notReacted: {
+ message: "You are not reacting to that note.",
+ code: "NOT_REACTED",
+ id: "92f4426d-4196-4125-aa5b-02943e2ec8fc"
+ },
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "04a4f3d8-773e-43e4-a645-71da6bb03a6f"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ groupId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const note = await getNote(ps.noteId, user).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ const group = await getGroupActor(ps.groupId, user);
+ if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
+ await deleteReaction({
+ ...user,
+ groupId: group?.id ?? null
+ }, note).catch((e)=>{
+ if (e.id === "60527ec9-b4cb-4a88-a6bd-32d3ad26817d") throw new ApiError(meta.errors.notReacted);
+ throw e;
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/recommended-timeline.js b/packages/backend/built/server/api/endpoints/notes/recommended-timeline.js
new file mode 100644
index 0000000..19e58cd
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/recommended-timeline.js
@@ -0,0 +1,138 @@
+import { Brackets } from "typeorm";
+import { fetchMeta } from "../../../../misc/fetch-meta.js";
+import { Notes } from "../../../../models/index.js";
+import { activeUsersChart } from "../../../../services/chart/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { generateRepliesQuery } from "../../common/generate-replies-query.js";
+import { generateChannelQuery } from "../../common/generate-channel-query.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+import { generateMutedUserRenotesQueryForNotes } from "../../common/generated-muted-renote-query.js";
+import { generateExcludeMemorietQuery } from "../../common/generate-exclude-memoriet-query.js";
+import { injectPromo } from "../../common/inject-promo.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ },
+ errors: {
+ rtlDisabled: {
+ message: "Recommended timeline has been disabled.",
+ code: "RTL_DISABLED",
+ id: "45a6eb02-7695-4393-b023-dd3be9aaaefe"
+ },
+ queryError: {
+ message: "Please follow more users.",
+ code: "QUERY_ERROR",
+ id: "620763f4-f621-4533-ab33-0577a1a3c343"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ withFiles: {
+ type: "boolean",
+ default: false,
+ description: "Only show notes that have attached files."
+ },
+ fileType: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ },
+ excludeNsfw: {
+ type: "boolean",
+ default: false
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ sinceDate: {
+ type: "integer"
+ },
+ untilDate: {
+ type: "integer"
+ },
+ withReplies: {
+ type: "boolean",
+ default: false,
+ description: "Show replies in the timeline"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const m = await fetchMeta();
+ if (m.disableRecommendedTimeline) {
+ if (user == null || !(user.isAdmin || user.isModerator)) {
+ throw new ApiError(meta.errors.rtlDisabled);
+ }
+ }
+ //#region Construct query
+ const query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate).andWhere(`note.userHost IN (:...instances)`, {
+ instances: m.recommendedInstances
+ }).andWhere("note.visibility = 'public'").andWhere(`NOT ('adservice' = ANY(note.tags))`).innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser");
+ generateChannelQuery(query, user);
+ generateRepliesQuery(query, ps.withReplies, user);
+ if (user) generateMutedUserQuery(query, user);
+ if (user) generateBlockedUserQuery(query, user);
+ if (user) generateMutedUserRenotesQueryForNotes(query, user);
+ generateExcludeMemorietQuery(query);
+ if (ps.withFiles) {
+ query.andWhere("note.fileIds != '{}'");
+ }
+ if (ps.fileType != null) {
+ query.andWhere("note.fileIds != '{}'");
+ query.andWhere(new Brackets((qb)=>{
+ for (const type of ps.fileType){
+ const i = ps.fileType.indexOf(type);
+ qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, {
+ [`type${i}`]: type
+ });
+ }
+ }));
+ if (ps.excludeNsfw) {
+ query.andWhere("note.cw IS NULL");
+ query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)');
+ }
+ }
+ //#endregion
+ process.nextTick(()=>{
+ if (user) {
+ activeUsersChart.read(user);
+ }
+ });
+ try {
+ const notes = await query.take(ps.limit).getMany();
+ await injectPromo(notes, user);
+ return await Notes.packMany(notes, user);
+ } catch (error) {
+ throw new ApiError(meta.errors.queryError);
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/renotes.js b/packages/backend/built/server/api/endpoints/notes/renotes.js
new file mode 100644
index 0000000..aa3b2d4
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/renotes.js
@@ -0,0 +1,83 @@
+import { Notes } from "../../../../models/index.js";
+import define from "../../define.js";
+import { getNote } from "../../common/getters.js";
+import { ApiError } from "../../error.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ },
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "12908022-2e21-46cd-ba6a-3edaf6093f46"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const note = await getNote(ps.noteId, user).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ let query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId).andWhere("note.renoteId = :renoteId", {
+ renoteId: note.id
+ }).innerJoinAndSelect("note.user", "user");
+ if (ps.userId) {
+ query.andWhere("user.id = :userId", {
+ userId: ps.userId
+ });
+ }
+ query.leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser");
+ generateVisibilityQuery(query, user);
+ if (user) generateMutedUserQuery(query, user);
+ if (user) generateBlockedUserQuery(query, user);
+ const notes = await query.take(ps.limit).getMany();
+ return await Notes.packMany(notes, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/replies.js b/packages/backend/built/server/api/endpoints/notes/replies.js
new file mode 100644
index 0000000..7f2d5cd
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/replies.js
@@ -0,0 +1,60 @@
+import { Notes } from "../../../../models/index.js";
+import define from "../../define.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId).andWhere("note.replyId = :replyId", {
+ replyId: ps.noteId
+ }).innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser");
+ generateVisibilityQuery(query, user);
+ if (user) generateMutedUserQuery(query, user);
+ if (user) generateBlockedUserQuery(query, user);
+ const notes = await query.take(ps.limit).getMany();
+ return await Notes.packMany(notes, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/search-by-tag.js b/packages/backend/built/server/api/endpoints/notes/search-by-tag.js
new file mode 100644
index 0000000..26d56b3
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/search-by-tag.js
@@ -0,0 +1,154 @@
+import { Brackets } from "typeorm";
+import { Notes } from "../../../../models/index.js";
+import { safeForSql } from "../../../../misc/safe-for-sql.js";
+import { normalizeForSearch } from "../../../../misc/normalize-for-search.js";
+import define from "../../define.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+import { injectPromo } from "../../common/inject-promo.js";
+export const meta = {
+ tags: [
+ "notes",
+ "hashtags"
+ ],
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ reply: {
+ type: "boolean",
+ nullable: true,
+ default: null
+ },
+ renote: {
+ type: "boolean",
+ nullable: true,
+ default: null
+ },
+ withFiles: {
+ type: "boolean",
+ default: false,
+ description: "Only show notes that have attached files."
+ },
+ poll: {
+ type: "boolean",
+ nullable: true,
+ default: null
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ }
+ },
+ anyOf: [
+ {
+ properties: {
+ tag: {
+ type: "string",
+ minLength: 1
+ }
+ },
+ required: [
+ "tag"
+ ]
+ },
+ {
+ properties: {
+ query: {
+ type: "array",
+ description: "The outer arrays are chained with OR, the inner arrays are chained with AND.",
+ items: {
+ type: "array",
+ items: {
+ type: "string",
+ minLength: 1
+ },
+ minItems: 1
+ },
+ minItems: 1
+ }
+ },
+ required: [
+ "query"
+ ]
+ }
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId).innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser");
+ generateVisibilityQuery(query, me);
+ if (me) generateMutedUserQuery(query, me);
+ if (me) generateBlockedUserQuery(query, me);
+ try {
+ if (ps.tag) {
+ if (!safeForSql(normalizeForSearch(ps.tag))) throw "Injection";
+ query.andWhere(`'{"${normalizeForSearch(ps.tag)}"}' <@ note.tags`);
+ } else {
+ query.andWhere(new Brackets((qb)=>{
+ for (const tags of ps.query){
+ qb.orWhere(new Brackets((qb)=>{
+ for (const tag of tags){
+ if (!safeForSql(normalizeForSearch(tag))) throw "Injection";
+ qb.andWhere(`'{"${normalizeForSearch(tag)}"}' <@ note.tags`);
+ }
+ }));
+ }
+ }));
+ }
+ } catch (e) {
+ if (e.message === "Injection") return [];
+ throw e;
+ }
+ if (ps.reply != null) {
+ if (ps.reply) {
+ query.andWhere("note.replyId IS NOT NULL");
+ } else {
+ query.andWhere("note.replyId IS NULL");
+ }
+ }
+ if (ps.renote != null) {
+ if (ps.renote) {
+ query.andWhere("note.renoteId IS NOT NULL");
+ } else {
+ query.andWhere("note.renoteId IS NULL");
+ }
+ }
+ if (ps.withFiles) {
+ query.andWhere("note.fileIds != '{}'");
+ }
+ if (ps.poll != null) {
+ if (ps.poll) {
+ query.andWhere("note.hasPoll = TRUE");
+ } else {
+ query.andWhere("note.hasPoll = FALSE");
+ }
+ }
+ const notes = await query.take(ps.limit).getMany();
+ const preferredTag = ps.tag ?? ps.query?.flat()[0] ?? null;
+ await injectPromo(notes, me, preferredTag);
+ return await Notes.packMany(notes, me);
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/search.js b/packages/backend/built/server/api/endpoints/notes/search.js
new file mode 100644
index 0000000..4af4655
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/search.js
@@ -0,0 +1,91 @@
+import { Notes } from "../../../../models/index.js";
+import define from "../../define.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+import { generateFtsQuery } from "../../common/generate-fts-query.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: true,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ },
+ errors: {}
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ query: {
+ type: "string"
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ offset: {
+ type: "integer",
+ default: 0
+ },
+ host: {
+ type: "string",
+ nullable: true,
+ description: "The local host is represented with `null`."
+ },
+ userId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true,
+ default: null
+ },
+ channelId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true,
+ default: null
+ }
+ },
+ required: [
+ "query"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId);
+ if (ps.userId) {
+ query.andWhere("note.userId = :userId", {
+ userId: ps.userId
+ });
+ } else if (ps.channelId) {
+ query.andWhere("note.channelId = :channelId", {
+ channelId: ps.channelId
+ });
+ }
+ query.innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser");
+ generateFtsQuery(query, ps.query);
+ generateVisibilityQuery(query, me);
+ generateMutedUserQuery(query, me);
+ generateBlockedUserQuery(query, me);
+ query.setParameter("meId", me.id);
+ return await query.take(ps.limit).getMany().then((notes)=>Notes.packMany(notes, me));
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/show.js b/packages/backend/built/server/api/endpoints/notes/show.js
new file mode 100644
index 0000000..c1012b4
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/show.js
@@ -0,0 +1,65 @@
+import { Notes } from "../../../../models/index.js";
+import define from "../../define.js";
+import { getNote } from "../../common/getters.js";
+import { ApiError } from "../../error.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ },
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "24fcbfc6-2e37-42b6-8388-c29b3861a08d",
+ httpStatusCode: 404
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const note = await getNote(ps.noteId, user, {
+ allowAdservice: true
+ }).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ const serviceTags = [
+ "videoservice",
+ "audioservice",
+ "lua4frozen",
+ "karaokeservice"
+ ];
+ if (!serviceTags.some((tag)=>note.tags.includes(tag))) {
+ await Notes.increment({
+ id: note.id
+ }, "viewCount", 1);
+ note.viewCount += 1;
+ }
+ return await Notes.pack(note, user, {
+ // FIXME: packing with detail may throw an error if the reply or renote is not visible (#8774)
+ detail: true,
+ allowAdservice: true
+ }).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/state.js b/packages/backend/built/server/api/endpoints/notes/state.js
new file mode 100644
index 0000000..4fd41be
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/state.js
@@ -0,0 +1,74 @@
+import { NoteFavorites, NoteThreadMutings, NoteWatchings } from "../../../../models/index.js";
+import { getNote } from "../../common/getters.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: true,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ isFavorited: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ isWatching: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ isMutedThread: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const note = await getNote(ps.noteId, user);
+ const [favorite, watching, threadMuting] = await Promise.all([
+ NoteFavorites.count({
+ where: {
+ userId: user.id,
+ noteId: note.id
+ },
+ take: 1
+ }),
+ NoteWatchings.count({
+ where: {
+ userId: user.id,
+ noteId: note.id
+ },
+ take: 1
+ }),
+ NoteThreadMutings.count({
+ where: {
+ userId: user.id,
+ threadId: note.threadId || note.id
+ },
+ take: 1
+ })
+ ]);
+ return {
+ isFavorited: favorite !== 0,
+ isWatching: watching !== 0,
+ isMutedThread: threadMuting !== 0
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/thread-muting/create.js b/packages/backend/built/server/api/endpoints/notes/thread-muting/create.js
new file mode 100644
index 0000000..9168fa2
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/thread-muting/create.js
@@ -0,0 +1,55 @@
+import { Notes, NoteThreadMutings } from "../../../../../models/index.js";
+import { genId } from "../../../../../misc/gen-id.js";
+import readNote from "../../../../../services/note/read.js";
+import define from "../../../define.js";
+import { getNote } from "../../../common/getters.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "5ff67ada-ed3b-2e71-8e87-a1a421e177d2"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const note = await getNote(ps.noteId, user).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ const mutedNotes = await Notes.find({
+ where: [
+ {
+ id: note.threadId || note.id
+ },
+ {
+ threadId: note.threadId || note.id
+ }
+ ]
+ });
+ await readNote(user.id, mutedNotes);
+ await NoteThreadMutings.insert({
+ id: genId(),
+ createdAt: new Date(),
+ threadId: note.threadId || note.id,
+ userId: user.id
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/thread-muting/delete.js b/packages/backend/built/server/api/endpoints/notes/thread-muting/delete.js
new file mode 100644
index 0000000..a4ed135
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/thread-muting/delete.js
@@ -0,0 +1,40 @@
+import { NoteThreadMutings } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { getNote } from "../../../common/getters.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "bddd57ac-ceb3-b29d-4334-86ea5fae481a"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const note = await getNote(ps.noteId, user).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ await NoteThreadMutings.delete({
+ threadId: note.threadId || note.id,
+ userId: user.id
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/timeline.js b/packages/backend/built/server/api/endpoints/notes/timeline.js
new file mode 100644
index 0000000..f33f452
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/timeline.js
@@ -0,0 +1,147 @@
+import { Brackets } from "typeorm";
+import { Notes } from "../../../../models/index.js";
+import { activeUsersChart } from "../../../../services/chart/index.js";
+import define from "../../define.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { generateRepliesQuery } from "../../common/generate-replies-query.js";
+import { generateChannelQuery } from "../../common/generate-channel-query.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+import { generateMutedUserRenotesQueryForNotes } from "../../common/generated-muted-renote-query.js";
+import { ApiError } from "../../error.js";
+import { generateListQuery } from "../../common/generate-list-query.js";
+import { generateFollowingQuery } from "../../common/generate-following-query.js";
+import { generateExcludeMemorietQuery } from "../../common/generate-exclude-memoriet-query.js";
+import { injectPromo } from "../../common/inject-promo.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ },
+ errors: {
+ queryError: {
+ message: "Please follow more users.",
+ code: "QUERY_ERROR",
+ id: "620763f4-f621-4533-ab33-0577a1a3c343"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ sinceDate: {
+ type: "integer"
+ },
+ untilDate: {
+ type: "integer"
+ },
+ includeMyRenotes: {
+ type: "boolean",
+ default: true
+ },
+ includeRenotedMyNotes: {
+ type: "boolean",
+ default: true
+ },
+ includeLocalRenotes: {
+ type: "boolean",
+ default: true
+ },
+ withFiles: {
+ type: "boolean",
+ default: false,
+ description: "Only show notes that have attached files."
+ },
+ withReplies: {
+ type: "boolean",
+ default: false,
+ description: "Show replies in the timeline"
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ //#region Construct query
+ const query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate).innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser");
+ await generateFollowingQuery(query, user);
+ generateListQuery(query, user);
+ generateChannelQuery(query, user);
+ generateRepliesQuery(query, ps.withReplies, user);
+ generateVisibilityQuery(query, user);
+ generateMutedUserQuery(query, user);
+ generateBlockedUserQuery(query, user);
+ generateMutedUserRenotesQueryForNotes(query, user);
+ generateExcludeMemorietQuery(query);
+ if (ps.includeMyRenotes === false) {
+ query.andWhere(new Brackets((qb)=>{
+ qb.orWhere("note.userId != :meId", {
+ meId: user.id
+ });
+ qb.orWhere("note.renoteId IS NULL");
+ qb.orWhere("note.text IS NOT NULL");
+ qb.orWhere("note.fileIds != '{}'");
+ qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+ }));
+ }
+ if (ps.includeRenotedMyNotes === false) {
+ query.andWhere(new Brackets((qb)=>{
+ qb.orWhere("note.renoteUserId != :meId", {
+ meId: user.id
+ });
+ qb.orWhere("note.renoteId IS NULL");
+ qb.orWhere("note.text IS NOT NULL");
+ qb.orWhere("note.fileIds != '{}'");
+ qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+ }));
+ }
+ if (ps.includeLocalRenotes === false) {
+ query.andWhere(new Brackets((qb)=>{
+ qb.orWhere("note.renoteUserHost IS NOT NULL");
+ qb.orWhere("note.renoteId IS NULL");
+ qb.orWhere("note.text IS NOT NULL");
+ qb.orWhere("note.fileIds != '{}'");
+ qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+ }));
+ }
+ if (ps.withFiles) {
+ query.andWhere("note.fileIds != '{}'");
+ }
+ query.andWhere("note.visibility != 'hidden'");
+ //#endregion
+ process.nextTick(()=>{
+ activeUsersChart.read(user);
+ });
+ try {
+ const notes = await query.take(ps.limit).getMany();
+ await injectPromo(notes, user);
+ return await Notes.packMany(notes, user);
+ } catch (error) {
+ throw new ApiError(meta.errors.queryError);
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/translate.js b/packages/backend/built/server/api/endpoints/notes/translate.js
new file mode 100644
index 0000000..5e0ab36
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/translate.js
@@ -0,0 +1,111 @@
+import { URLSearchParams } from "node:url";
+import fetch from "node-fetch";
+import config from "../../../../config/index.js";
+import { getAgentByUrl } from "../../../../misc/fetch.js";
+import { fetchMeta } from "../../../../misc/fetch-meta.js";
+import { ApiError } from "../../error.js";
+import { getNote } from "../../common/getters.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false
+ },
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "bea9b03f-36e0-49c5-a4db-627a029f8971"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ targetLang: {
+ type: "string"
+ }
+ },
+ required: [
+ "noteId",
+ "targetLang"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const note = await getNote(ps.noteId, user).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ if (note.text == null) {
+ return 204;
+ }
+ const instance = await fetchMeta();
+ if (instance.deeplAuthKey == null && instance.libreTranslateApiUrl == null) {
+ return 204; // TODO: 良い感じのエラー返す
+ }
+ let targetLang = ps.targetLang;
+ if (targetLang.includes("-")) targetLang = targetLang.split("-")[0];
+ if (instance.libreTranslateApiUrl != null) {
+ const jsonBody = {
+ q: note.text,
+ source: "auto",
+ target: targetLang,
+ format: "text",
+ api_key: instance.libreTranslateApiKey ?? ""
+ };
+ const url = new URL(instance.libreTranslateApiUrl);
+ if (url.pathname.endsWith("/")) {
+ url.pathname = url.pathname.slice(0, -1);
+ }
+ if (!url.pathname.endsWith("/translate")) {
+ url.pathname += "/translate";
+ }
+ const res = await fetch(url.toString(), {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify(jsonBody),
+ agent: getAgentByUrl,
+ size: 10 * 1024 * 1024
+ });
+ const json = await res.json();
+ return {
+ sourceLang: json.detectedLanguage?.language,
+ text: json.translatedText
+ };
+ }
+ const params = new URLSearchParams();
+ params.append("auth_key", instance.deeplAuthKey ?? "");
+ params.append("text", note.text);
+ params.append("target_lang", targetLang);
+ const endpoint = instance.deeplIsPro ? "https://api.deepl.com/v2/translate" : "https://api-free.deepl.com/v2/translate";
+ const res = await fetch(endpoint, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ "User-Agent": config.userAgent,
+ Accept: "application/json, */*"
+ },
+ body: params,
+ size: 10 * 1024 * 1024,
+ // TODO
+ //timeout: 10000,
+ agent: getAgentByUrl
+ });
+ const json = await res.json();
+ return {
+ sourceLang: json.translations[0].detected_source_language,
+ text: json.translations[0].text
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/unrenote.js b/packages/backend/built/server/api/endpoints/notes/unrenote.js
new file mode 100644
index 0000000..061cb2c
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/unrenote.js
@@ -0,0 +1,66 @@
+import deleteNote from "../../../../services/note/delete.js";
+import { Notes, Users } from "../../../../models/index.js";
+import define from "../../define.js";
+import { getNote } from "../../common/getters.js";
+import { ApiError } from "../../error.js";
+import { SECOND, HOUR } from "../../../../const.js";
+import { getGroupActor } from "../../common/get-group-actor.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: true,
+ kind: "write:notes",
+ limit: {
+ duration: HOUR,
+ max: 300,
+ minInterval: SECOND
+ },
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "efd4a259-2442-496b-8dd7-b255aa1a160f"
+ },
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "1e4d6ad6-bbc5-478a-9f4b-8a0f578c1e72"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ groupId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const note = await getNote(ps.noteId, user).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ const group = await getGroupActor(ps.groupId, user);
+ if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
+ const renotes = await Notes.findBy({
+ userId: user.id,
+ renoteId: note.id,
+ groupId: group?.id ?? null
+ });
+ for (const note of renotes){
+ deleteNote(await Users.findOneByOrFail({
+ id: user.id
+ }), note);
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/user-list-timeline.js b/packages/backend/built/server/api/endpoints/notes/user-list-timeline.js
new file mode 100644
index 0000000..991b509
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/user-list-timeline.js
@@ -0,0 +1,148 @@
+import { Brackets } from "typeorm";
+import { UserLists, UserListJoinings, Notes } from "../../../../models/index.js";
+import { activeUsersChart } from "../../../../services/chart/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+import { generateExcludeMemorietQuery } from "../../common/generate-exclude-memoriet-query.js";
+export const meta = {
+ tags: [
+ "notes",
+ "lists"
+ ],
+ requireCredential: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ },
+ errors: {
+ noSuchList: {
+ message: "No such list.",
+ code: "NO_SUCH_LIST",
+ id: "8fb1fbd5-e476-4c37-9fb0-43d55b63a2ff"
+ },
+ queryError: {
+ message: "Please follow more users.",
+ code: "QUERY_ERROR",
+ id: "620763f4-f621-4533-ab33-0577a1a3c343"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ listId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ sinceDate: {
+ type: "integer"
+ },
+ untilDate: {
+ type: "integer"
+ },
+ includeMyRenotes: {
+ type: "boolean",
+ default: true
+ },
+ includeRenotedMyNotes: {
+ type: "boolean",
+ default: true
+ },
+ includeLocalRenotes: {
+ type: "boolean",
+ default: true
+ },
+ withFiles: {
+ type: "boolean",
+ default: false,
+ description: "Only show notes that have attached files."
+ }
+ },
+ required: [
+ "listId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const list = await UserLists.findOneBy({
+ id: ps.listId,
+ userId: user.id
+ });
+ if (list == null) {
+ throw new ApiError(meta.errors.noSuchList);
+ }
+ //#region Construct query
+ const query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId).innerJoin(UserListJoinings.metadata.targetName, "userListJoining", "userListJoining.userId = note.userId").innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser").andWhere("userListJoining.userListId = :userListId", {
+ userListId: list.id
+ });
+ generateVisibilityQuery(query, user);
+ generateExcludeMemorietQuery(query);
+ if (ps.includeMyRenotes === false) {
+ query.andWhere(new Brackets((qb)=>{
+ qb.orWhere("note.userId != :meId", {
+ meId: user.id
+ });
+ qb.orWhere("note.renoteId IS NULL");
+ qb.orWhere("note.text IS NOT NULL");
+ qb.orWhere("note.fileIds != '{}'");
+ qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+ }));
+ }
+ if (ps.includeRenotedMyNotes === false) {
+ query.andWhere(new Brackets((qb)=>{
+ qb.orWhere("note.renoteUserId != :meId", {
+ meId: user.id
+ });
+ qb.orWhere("note.renoteId IS NULL");
+ qb.orWhere("note.text IS NOT NULL");
+ qb.orWhere("note.fileIds != '{}'");
+ qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+ }));
+ }
+ if (ps.includeLocalRenotes === false) {
+ query.andWhere(new Brackets((qb)=>{
+ qb.orWhere("note.renoteUserHost IS NOT NULL");
+ qb.orWhere("note.renoteId IS NULL");
+ qb.orWhere("note.text IS NOT NULL");
+ qb.orWhere("note.fileIds != '{}'");
+ qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+ }));
+ }
+ if (ps.withFiles) {
+ query.andWhere("note.fileIds != '{}'");
+ }
+ //#endregion
+ process.nextTick(()=>{
+ if (user) {
+ activeUsersChart.read(user);
+ }
+ });
+ try {
+ const notes = await query.take(ps.limit).getMany();
+ return await Notes.packMany(notes, user);
+ } catch (error) {
+ throw new ApiError(meta.errors.queryError);
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/watching/create.js b/packages/backend/built/server/api/endpoints/notes/watching/create.js
new file mode 100644
index 0000000..77176ba
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/watching/create.js
@@ -0,0 +1,37 @@
+import watch from "../../../../../services/note/watch.js";
+import define from "../../../define.js";
+import { getNote } from "../../../common/getters.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "ea0e37a6-90a3-4f58-ba6b-c328ca206fc7"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const note = await getNote(ps.noteId, user).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ await watch(user.id, note);
+});
diff --git a/packages/backend/built/server/api/endpoints/notes/watching/delete.js b/packages/backend/built/server/api/endpoints/notes/watching/delete.js
new file mode 100644
index 0000000..89081d4
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notes/watching/delete.js
@@ -0,0 +1,37 @@
+import unwatch from "../../../../../services/note/unwatch.js";
+import define from "../../../define.js";
+import { getNote } from "../../../common/getters.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "09b3695c-f72c-4731-a428-7cff825fc82e"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const note = await getNote(ps.noteId, user).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+ await unwatch(user.id, note);
+});
diff --git a/packages/backend/built/server/api/endpoints/notifications/create.js b/packages/backend/built/server/api/endpoints/notifications/create.js
new file mode 100644
index 0000000..5dc1244
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notifications/create.js
@@ -0,0 +1,37 @@
+import { createNotification } from "../../../../services/create-notification.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "notifications"
+ ],
+ requireCredential: true,
+ kind: "write:notifications",
+ errors: {}
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ body: {
+ type: "string"
+ },
+ header: {
+ type: "string",
+ nullable: true
+ },
+ icon: {
+ type: "string",
+ nullable: true
+ }
+ },
+ required: [
+ "body"
+ ]
+};
+export default define(meta, paramDef, async (ps, user, token)=>{
+ createNotification(user.id, "app", {
+ appAccessTokenId: token ? token.id : null,
+ customBody: ps.body,
+ customHeader: ps.header,
+ customIcon: ps.icon
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/notifications/mark-all-as-read.js b/packages/backend/built/server/api/endpoints/notifications/mark-all-as-read.js
new file mode 100644
index 0000000..f3c2aa3
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notifications/mark-all-as-read.js
@@ -0,0 +1,29 @@
+import { publishMainStream } from "../../../../services/stream.js";
+import { pushNotification } from "../../../../services/push-notification.js";
+import { Notifications } from "../../../../models/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "notifications",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "write:notifications"
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Update documents
+ await Notifications.update({
+ notifieeId: user.id,
+ isRead: false
+ }, {
+ isRead: true
+ });
+ // 全ての通知を読みましたよというイベントを発行
+ publishMainStream(user.id, "readAllNotifications");
+ pushNotification(user.id, "readAllNotifications", undefined);
+});
diff --git a/packages/backend/built/server/api/endpoints/notifications/read.js b/packages/backend/built/server/api/endpoints/notifications/read.js
new file mode 100644
index 0000000..cf5d5f8
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/notifications/read.js
@@ -0,0 +1,56 @@
+import define from "../../define.js";
+import { readNotification } from "../../common/read-notification.js";
+export const meta = {
+ tags: [
+ "notifications",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "write:notifications",
+ description: "Mark a notification as read.",
+ errors: {
+ noSuchNotification: {
+ message: "No such notification.",
+ code: "NO_SUCH_NOTIFICATION",
+ id: "efa929d5-05b5-47d1-beec-e6a4dbed011e"
+ }
+ }
+};
+export const paramDef = {
+ oneOf: [
+ {
+ type: "object",
+ properties: {
+ notificationId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "notificationId"
+ ]
+ },
+ {
+ type: "object",
+ properties: {
+ notificationIds: {
+ type: "array",
+ items: {
+ type: "string",
+ format: "misskey:id"
+ },
+ maxItems: 100
+ }
+ },
+ required: [
+ "notificationIds"
+ ]
+ }
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ if ("notificationId" in ps) return readNotification(user.id, [
+ ps.notificationId
+ ]);
+ return readNotification(user.id, ps.notificationIds);
+});
diff --git a/packages/backend/built/server/api/endpoints/page-push.js b/packages/backend/built/server/api/endpoints/page-push.js
new file mode 100644
index 0000000..451a244
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/page-push.js
@@ -0,0 +1,51 @@
+import { publishMainStream } from "../../../services/stream.js";
+import { Users, Pages } from "../../../models/index.js";
+import define from "../define.js";
+import { ApiError } from "../error.js";
+export const meta = {
+ requireCredential: true,
+ secure: true,
+ errors: {
+ noSuchPage: {
+ message: "No such page.",
+ code: "NO_SUCH_PAGE",
+ id: "4a13ad31-6729-46b4-b9af-e86b265c2e74"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ pageId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ event: {
+ type: "string"
+ },
+ var: {}
+ },
+ required: [
+ "pageId",
+ "event"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const page = await Pages.findOneBy({
+ id: ps.pageId
+ });
+ if (page == null) {
+ throw new ApiError(meta.errors.noSuchPage);
+ }
+ publishMainStream(page.userId, "pageEvent", {
+ pageId: ps.pageId,
+ event: ps.event,
+ var: ps.var,
+ userId: user.id,
+ user: await Users.pack(user.id, {
+ id: page.userId
+ }, {
+ detail: true
+ })
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/pages/create.js b/packages/backend/built/server/api/endpoints/pages/create.js
new file mode 100644
index 0000000..260033b
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/pages/create.js
@@ -0,0 +1,139 @@
+import { Pages, DriveFiles } from "../../../../models/index.js";
+import { genId } from "../../../../misc/gen-id.js";
+import { Page } from "../../../../models/entities/page.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { HOUR } from "../../../../const.js";
+export const meta = {
+ tags: [
+ "pages"
+ ],
+ requireCredential: true,
+ kind: "write:pages",
+ limit: {
+ duration: HOUR,
+ max: 300
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Page"
+ },
+ errors: {
+ noSuchFile: {
+ message: "No such file.",
+ code: "NO_SUCH_FILE",
+ id: "b7b97489-0f66-4b12-a5ff-b21bd63f6e1c"
+ },
+ nameAlreadyExists: {
+ message: "Specified name already exists.",
+ code: "NAME_ALREADY_EXISTS",
+ id: "4650348e-301c-499a-83c9-6aa988c66bc1"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ title: {
+ type: "string"
+ },
+ name: {
+ type: "string",
+ minLength: 1
+ },
+ summary: {
+ type: "string",
+ nullable: true
+ },
+ content: {
+ type: "array",
+ items: {
+ type: "object",
+ additionalProperties: true
+ }
+ },
+ variables: {
+ type: "array",
+ items: {
+ type: "object",
+ additionalProperties: true
+ }
+ },
+ script: {
+ type: "string"
+ },
+ eyeCatchingImageId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ font: {
+ type: "string",
+ enum: [
+ "serif",
+ "sans-serif"
+ ],
+ default: "sans-serif"
+ },
+ alignCenter: {
+ type: "boolean",
+ default: false
+ },
+ isPublic: {
+ type: "boolean",
+ default: true
+ },
+ hideTitleWhenPinned: {
+ type: "boolean",
+ default: false
+ }
+ },
+ required: [
+ "title",
+ "name",
+ "content",
+ "variables",
+ "script"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ let eyeCatchingImage = null;
+ if (ps.eyeCatchingImageId != null) {
+ eyeCatchingImage = await DriveFiles.findOneBy({
+ id: ps.eyeCatchingImageId,
+ userId: user.id
+ });
+ if (eyeCatchingImage == null) {
+ throw new ApiError(meta.errors.noSuchFile);
+ }
+ }
+ await Pages.findBy({
+ userId: user.id,
+ name: ps.name
+ }).then((result)=>{
+ if (result.length > 0) {
+ throw new ApiError(meta.errors.nameAlreadyExists);
+ }
+ });
+ const page = await Pages.insert(new Page({
+ id: genId(),
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ title: ps.title,
+ name: ps.name,
+ summary: ps.summary,
+ content: ps.content,
+ variables: ps.variables,
+ script: ps.script,
+ eyeCatchingImageId: eyeCatchingImage ? eyeCatchingImage.id : null,
+ userId: user.id,
+ visibility: "public",
+ alignCenter: ps.alignCenter,
+ hideTitleWhenPinned: ps.hideTitleWhenPinned,
+ font: ps.font,
+ isPublic: ps.isPublic
+ })).then((x)=>Pages.findOneByOrFail(x.identifiers[0]));
+ return await Pages.pack(page);
+});
diff --git a/packages/backend/built/server/api/endpoints/pages/delete.js b/packages/backend/built/server/api/endpoints/pages/delete.js
new file mode 100644
index 0000000..e574e2c
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/pages/delete.js
@@ -0,0 +1,46 @@
+import { Pages } from "../../../../models/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+export const meta = {
+ tags: [
+ "pages"
+ ],
+ requireCredential: true,
+ kind: "write:pages",
+ errors: {
+ noSuchPage: {
+ message: "No such page.",
+ code: "NO_SUCH_PAGE",
+ id: "eb0c6e1d-d519-4764-9486-52a7e1c6392a"
+ },
+ accessDenied: {
+ message: "Access denied.",
+ code: "ACCESS_DENIED",
+ id: "8b741b3e-2c22-44b3-a15f-29949aa1601e"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ pageId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "pageId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const page = await Pages.findOneBy({
+ id: ps.pageId
+ });
+ if (page == null) {
+ throw new ApiError(meta.errors.noSuchPage);
+ }
+ if (page.userId !== user.id) {
+ throw new ApiError(meta.errors.accessDenied);
+ }
+ await Pages.delete(page.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/pages/featured.js b/packages/backend/built/server/api/endpoints/pages/featured.js
new file mode 100644
index 0000000..d0e2d72
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/pages/featured.js
@@ -0,0 +1,30 @@
+import { Pages } from "../../../../models/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "pages"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Page"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = Pages.createQueryBuilder("page").where("page.visibility = 'public'").andWhere("page.likedCount > 0").orderBy("page.likedCount", "DESC");
+ const pages = await query.take(10).getMany();
+ return await Pages.packMany(pages, me);
+});
diff --git a/packages/backend/built/server/api/endpoints/pages/like.js b/packages/backend/built/server/api/endpoints/pages/like.js
new file mode 100644
index 0000000..4c4b46f
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/pages/like.js
@@ -0,0 +1,63 @@
+import { Pages, PageLikes } from "../../../../models/index.js";
+import { genId } from "../../../../misc/gen-id.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+export const meta = {
+ tags: [
+ "pages"
+ ],
+ requireCredential: true,
+ kind: "write:page-likes",
+ errors: {
+ noSuchPage: {
+ message: "No such page.",
+ code: "NO_SUCH_PAGE",
+ id: "cc98a8a2-0dc3-4123-b198-62c71df18ed3"
+ },
+ alreadyLiked: {
+ message: "The page has already been liked.",
+ code: "ALREADY_LIKED",
+ id: "cc98a8a2-0dc3-4123-b198-62c71df18ed3"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ pageId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "pageId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const page = await Pages.findOneBy({
+ id: ps.pageId
+ });
+ if (page == null) {
+ throw new ApiError(meta.errors.noSuchPage);
+ }
+ // if already liked
+ const exist = await PageLikes.exist({
+ where: {
+ pageId: page.id,
+ userId: user.id
+ }
+ });
+ if (exist) {
+ throw new ApiError(meta.errors.alreadyLiked);
+ }
+ // Create like
+ await PageLikes.insert({
+ id: genId(),
+ createdAt: new Date(),
+ pageId: page.id,
+ userId: user.id
+ });
+ Pages.increment({
+ id: page.id
+ }, "likedCount", 1);
+});
diff --git a/packages/backend/built/server/api/endpoints/pages/show.js b/packages/backend/built/server/api/endpoints/pages/show.js
new file mode 100644
index 0000000..07528d8
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/pages/show.js
@@ -0,0 +1,80 @@
+import { IsNull } from "typeorm";
+import { Pages, Users } from "../../../../models/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+export const meta = {
+ tags: [
+ "pages"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Page"
+ },
+ errors: {
+ noSuchPage: {
+ message: "No such page.",
+ code: "NO_SUCH_PAGE",
+ id: "222120c0-3ead-4528-811b-b96f233388d7"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ anyOf: [
+ {
+ properties: {
+ pageId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "pageId"
+ ]
+ },
+ {
+ properties: {
+ name: {
+ type: "string"
+ },
+ username: {
+ type: "string"
+ }
+ },
+ required: [
+ "name",
+ "username"
+ ]
+ }
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ let page = null;
+ if (ps.pageId) {
+ page = await Pages.findOneBy({
+ id: ps.pageId
+ });
+ } else if (ps.name && ps.username) {
+ const author = await Users.findOneBy({
+ host: IsNull(),
+ usernameLower: ps.username.toLowerCase()
+ });
+ if (author) {
+ page = await Pages.findOneBy({
+ name: ps.name,
+ userId: author.id
+ });
+ }
+ }
+ if (page == null) {
+ throw new ApiError(meta.errors.noSuchPage);
+ }
+ if (!page.isPublic && (user == null || page.userId !== user.id)) {
+ throw new ApiError(meta.errors.noSuchPage);
+ }
+ return await Pages.pack(page, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/pages/unlike.js b/packages/backend/built/server/api/endpoints/pages/unlike.js
new file mode 100644
index 0000000..2498056
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/pages/unlike.js
@@ -0,0 +1,54 @@
+import { Pages, PageLikes } from "../../../../models/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+export const meta = {
+ tags: [
+ "pages"
+ ],
+ requireCredential: true,
+ kind: "write:page-likes",
+ errors: {
+ noSuchPage: {
+ message: "No such page.",
+ code: "NO_SUCH_PAGE",
+ id: "a0d41e20-1993-40bd-890e-f6e560ae648e"
+ },
+ notLiked: {
+ message: "You have not liked that page.",
+ code: "NOT_LIKED",
+ id: "f5e586b0-ce93-4050-b0e3-7f31af5259ee"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ pageId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "pageId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const page = await Pages.findOneBy({
+ id: ps.pageId
+ });
+ if (page == null) {
+ throw new ApiError(meta.errors.noSuchPage);
+ }
+ const like = await PageLikes.findOneBy({
+ pageId: page.id,
+ userId: user.id
+ });
+ if (like == null) {
+ throw new ApiError(meta.errors.notLiked);
+ }
+ // Delete like
+ await PageLikes.delete(like.id);
+ Pages.decrement({
+ id: page.id
+ }, "likedCount", 1);
+});
diff --git a/packages/backend/built/server/api/endpoints/pages/update.js b/packages/backend/built/server/api/endpoints/pages/update.js
new file mode 100644
index 0000000..1b17b57
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/pages/update.js
@@ -0,0 +1,148 @@
+import { Not } from "typeorm";
+import { Pages, DriveFiles } from "../../../../models/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { HOUR } from "../../../../const.js";
+export const meta = {
+ tags: [
+ "pages"
+ ],
+ requireCredential: true,
+ kind: "write:pages",
+ limit: {
+ duration: HOUR,
+ max: 300
+ },
+ errors: {
+ noSuchPage: {
+ message: "No such page.",
+ code: "NO_SUCH_PAGE",
+ id: "21149b9e-3616-4778-9592-c4ce89f5a864"
+ },
+ accessDenied: {
+ message: "Access denied.",
+ code: "ACCESS_DENIED",
+ id: "3c15cd52-3b4b-4274-967d-6456fc4f792b"
+ },
+ noSuchFile: {
+ message: "No such file.",
+ code: "NO_SUCH_FILE",
+ id: "cfc23c7c-3887-490e-af30-0ed576703c82"
+ },
+ nameAlreadyExists: {
+ message: "Specified name already exists.",
+ code: "NAME_ALREADY_EXISTS",
+ id: "2298a392-d4a1-44c5-9ebb-ac1aeaa5a9ab"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ pageId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ title: {
+ type: "string"
+ },
+ name: {
+ type: "string",
+ minLength: 1
+ },
+ summary: {
+ type: "string",
+ nullable: true
+ },
+ content: {
+ type: "array",
+ items: {
+ type: "object",
+ additionalProperties: true
+ }
+ },
+ variables: {
+ type: "array",
+ items: {
+ type: "object",
+ additionalProperties: true
+ }
+ },
+ script: {
+ type: "string"
+ },
+ eyeCatchingImageId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ font: {
+ type: "string",
+ enum: [
+ "serif",
+ "sans-serif"
+ ]
+ },
+ alignCenter: {
+ type: "boolean"
+ },
+ hideTitleWhenPinned: {
+ type: "boolean"
+ },
+ isPublic: {
+ type: "boolean"
+ }
+ },
+ required: [
+ "pageId",
+ "title",
+ "name",
+ "content",
+ "variables",
+ "script"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const page = await Pages.findOneBy({
+ id: ps.pageId
+ });
+ if (page == null) {
+ throw new ApiError(meta.errors.noSuchPage);
+ }
+ if (page.userId !== user.id) {
+ throw new ApiError(meta.errors.accessDenied);
+ }
+ let eyeCatchingImage = null;
+ if (ps.eyeCatchingImageId != null) {
+ eyeCatchingImage = await DriveFiles.findOneBy({
+ id: ps.eyeCatchingImageId,
+ userId: user.id
+ });
+ if (eyeCatchingImage == null) {
+ throw new ApiError(meta.errors.noSuchFile);
+ }
+ }
+ await Pages.findBy({
+ id: Not(ps.pageId),
+ userId: user.id,
+ name: ps.name
+ }).then((result)=>{
+ if (result.length > 0) {
+ throw new ApiError(meta.errors.nameAlreadyExists);
+ }
+ });
+ await Pages.update(page.id, {
+ updatedAt: new Date(),
+ title: ps.title,
+ name: ps.name === undefined ? page.name : ps.name,
+ summary: ps.name === undefined ? page.summary : ps.summary,
+ content: ps.content,
+ variables: ps.variables,
+ script: ps.script,
+ isPublic: ps.isPublic,
+ alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter,
+ hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned,
+ font: ps.font === undefined ? page.font : ps.font,
+ eyeCatchingImageId: ps.eyeCatchingImageId === null ? null : ps.eyeCatchingImageId === undefined ? page.eyeCatchingImageId : eyeCatchingImage.id
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/ping.js b/packages/backend/built/server/api/endpoints/ping.js
new file mode 100644
index 0000000..d4c51aa
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/ping.js
@@ -0,0 +1,29 @@
+import define from "../define.js";
+export const meta = {
+ requireCredential: false,
+ tags: [
+ "meta"
+ ],
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ pong: {
+ type: "number",
+ optional: false,
+ nullable: false
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async ()=>{
+ return {
+ pong: Date.now()
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/pinned-users.js b/packages/backend/built/server/api/endpoints/pinned-users.js
new file mode 100644
index 0000000..d848774
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/pinned-users.js
@@ -0,0 +1,38 @@
+import { IsNull } from "typeorm";
+import { Users } from "../../../models/index.js";
+import { fetchMeta } from "../../../misc/fetch-meta.js";
+import * as Acct from "../../../misc/acct.js";
+import define from "../define.js";
+export const meta = {
+ tags: [
+ "users"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: false,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserDetailed"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const meta = await fetchMeta();
+ const users = await Promise.all(meta.pinnedUsers.map((acct)=>Acct.parse(acct)).map((acct)=>Users.findOneBy({
+ usernameLower: acct.username.toLowerCase(),
+ host: acct.host ?? IsNull()
+ })));
+ return await Users.packMany(users.filter((x)=>x !== undefined), me, {
+ detail: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/promo/read.js b/packages/backend/built/server/api/endpoints/promo/read.js
new file mode 100644
index 0000000..5e3bbcc
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/promo/read.js
@@ -0,0 +1,47 @@
+import { PromoNotes } from "../../../../models/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { getNote } from "../../common/getters.js";
+import { readPromo } from "../../common/read-promo.js";
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: true,
+ errors: {
+ noSuchNote: {
+ message: "No such note.",
+ code: "NO_SUCH_NOTE",
+ id: "d785b897-fcd3-4fe9-8fc3-b85c26e6c932"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ noteId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "noteId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const [note, promo] = await Promise.all([
+ getNote(ps.noteId, user, {
+ allowAdservice: true
+ }).catch((err)=>{
+ if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ }),
+ PromoNotes.findOneBy({
+ noteId: ps.noteId
+ })
+ ]);
+ if (note == null || promo == null || promo.expiresAt.getTime() <= Date.now() || promo.remainingCredits <= 0) {
+ throw new ApiError(meta.errors.noSuchNote);
+ }
+ await readPromo(note, promo, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/promo/show.js b/packages/backend/built/server/api/endpoints/promo/show.js
new file mode 100644
index 0000000..50431dc
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/promo/show.js
@@ -0,0 +1,115 @@
+import rndstr from "rndstr";
+import { Notes, PromoNotes, PromoReads, Users } from "../../../../models/index.js";
+import define from "../../define.js";
+import { shouldHideEUsersFor } from "../../common/generate-minor-badge-visibility-query.js";
+import { readPromo } from "../../common/read-promo.js";
+const serviceTags = {
+ video: "videoservice",
+ audio: "audioservice",
+ image: "imageservice",
+ karaoke: "karaokeservice",
+ lua4frozen: "lua4frozen"
+};
+const systemTags = new Set([
+ "adservice",
+ "videoservice",
+ "audioservice",
+ "imageservice",
+ "karaokeservice",
+ "lua4frozen"
+]);
+function isExplicitAd(note) {
+ return note.tags.includes("explicit") || note.user?.minorBadges?.includes("E") === true;
+}
+function canShowExplicit(note, user, hideExplicit) {
+ if (!isExplicitAd(note)) return true;
+ if (hideExplicit) return false;
+ return !(user && shouldHideEUsersFor(user));
+}
+function hasPollOr(note, fn) {
+ return note.hasPoll || fn();
+}
+function hasFileType(note, prefix) {
+ return note.attachedFileTypes.some((type)=>type.startsWith(prefix));
+}
+function matchesService(note, service) {
+ if (service == null) return true;
+ if (!note.tags.includes(serviceTags[service])) return false;
+ switch(service){
+ case "video":
+ return hasPollOr(note, ()=>hasFileType(note, "video/"));
+ case "audio":
+ return hasPollOr(note, ()=>hasFileType(note, "audio/"));
+ case "image":
+ return hasPollOr(note, ()=>hasFileType(note, "image/"));
+ case "karaoke":
+ return hasFileType(note, "video/") || hasFileType(note, "audio/") && hasFileType(note, "image/");
+ case "lua4frozen":
+ return hasPollOr(note, ()=>hasFileType(note, "image/"));
+ }
+}
+export const meta = {
+ tags: [
+ "notes"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ service: {
+ type: "string",
+ enum: [
+ "video",
+ "audio",
+ "image",
+ "karaoke",
+ "lua4frozen"
+ ],
+ nullable: true
+ },
+ tag: {
+ type: "string",
+ nullable: true
+ },
+ hideExplicit: {
+ type: "boolean",
+ default: false
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const readDay = new Date().toISOString().slice(0, 10);
+ const reads = user ? await PromoReads.findBy({
+ userId: user.id,
+ readDay
+ }) : [];
+ const readNoteIds = new Set(reads.map((read)=>read.noteId));
+ const promos = (await PromoNotes.find()).filter((promo)=>promo.expiresAt.getTime() > Date.now() && promo.remainingCredits > 0 && !readNoteIds.has(promo.noteId));
+ const candidates = [];
+ for (const promo of promos){
+ const note = await Notes.findOneBy({
+ id: promo.noteId
+ });
+ if (!note?.tags.includes("adservice")) continue;
+ note.user = await Users.findOneByOrFail({
+ id: note.userId
+ });
+ if (!canShowExplicit(note, user, ps.hideExplicit)) continue;
+ if (!matchesService(note, ps.service ?? null)) continue;
+ candidates.push(note);
+ }
+ if (candidates.length === 0) return null;
+ const requestedTag = ps.tag?.trim().toLowerCase().replace(/^#/, "");
+ const priority = requestedTag && !systemTags.has(requestedTag) ? candidates.filter((note)=>note.tags.includes(requestedTag)) : [];
+ const pool = priority.length > 0 ? priority : candidates;
+ const note = pool[Math.floor(Math.random() * pool.length)];
+ const promo = promos.find((promo)=>promo.noteId === note.id);
+ note._prId_ = rndstr("a-z0-9", 8);
+ if (user && promo) {
+ await readPromo(note, promo, user);
+ }
+ return Notes.pack(note, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/recommended-instances.js b/packages/backend/built/server/api/endpoints/recommended-instances.js
new file mode 100644
index 0000000..0eb0f61
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/recommended-instances.js
@@ -0,0 +1,30 @@
+// import { IsNull } from 'typeorm';
+import { fetchMeta } from "../../../misc/fetch-meta.js";
+import define from "../define.js";
+export const meta = {
+ tags: [
+ "meta"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "string",
+ optional: false,
+ nullable: false
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async ()=>{
+ const meta = await fetchMeta();
+ const instances = await Promise.all(meta.recommendedInstances.map((x)=>x));
+ return instances;
+});
diff --git a/packages/backend/built/server/api/endpoints/release.js b/packages/backend/built/server/api/endpoints/release.js
new file mode 100644
index 0000000..153989a
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/release.js
@@ -0,0 +1,21 @@
+import define from "../define.js";
+export const meta = {
+ tags: [
+ "meta"
+ ],
+ description: "Get release notes from Codeberg",
+ requireCredential: false,
+ requireCredentialPrivateMode: false
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async ()=>{
+ let release;
+ await fetch("https://iceshrimp.dev/iceshrimp/iceshrimp/raw/branch/dev/release.json").then((response)=>response.json()).then((data)=>{
+ release = data;
+ });
+ return release;
+});
diff --git a/packages/backend/built/server/api/endpoints/renote-mute/create.js b/packages/backend/built/server/api/endpoints/renote-mute/create.js
new file mode 100644
index 0000000..bfebb67
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/renote-mute/create.js
@@ -0,0 +1,63 @@
+import { genId } from "../../../../misc/gen-id.js";
+import { RenoteMutings } from "../../../../models/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { getUser } from "../../common/getters.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ kind: "write:mutes",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "6fef56f3-e765-4957-88e5-c6f65329b8a5"
+ },
+ alreadyMuting: {
+ message: "You are already muting that user.",
+ code: "ALREADY_MUTING",
+ id: "7e7359cb-160c-4956-b08f-4d1c653cd007"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+// eslint-disable-next-line import/no-default-export
+export default define(meta, paramDef, async (ps, user)=>{
+ const muter = user;
+ // Get mutee
+ const mutee = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ // Check if already muting
+ const exist = await RenoteMutings.exist({
+ where: {
+ muterId: muter.id,
+ muteeId: mutee.id
+ }
+ });
+ if (exist) {
+ throw new ApiError(meta.errors.alreadyMuting);
+ }
+ // Create mute
+ await RenoteMutings.insert({
+ id: genId(),
+ createdAt: new Date(),
+ muterId: muter.id,
+ muteeId: mutee.id
+ });
+// publishUserEvent(user.id, "mute", mutee);
+});
diff --git a/packages/backend/built/server/api/endpoints/renote-mute/delete.js b/packages/backend/built/server/api/endpoints/renote-mute/delete.js
new file mode 100644
index 0000000..2b1c2f5
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/renote-mute/delete.js
@@ -0,0 +1,57 @@
+import { RenoteMutings } from "../../../../models/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { getUser } from "../../common/getters.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ kind: "write:mutes",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "b851d00b-8ab1-4a56-8b1b-e24187cb48ef"
+ },
+ notMuting: {
+ message: "You are not muting that user.",
+ code: "NOT_MUTING",
+ id: "5467d020-daa9-4553-81e1-135c0c35a96d"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+// eslint-disable-next-line import/no-default-export
+export default define(meta, paramDef, async (ps, user)=>{
+ const muter = user;
+ // Get mutee
+ const mutee = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ // Check not muting
+ const muting = await RenoteMutings.findOneBy({
+ muterId: muter.id,
+ muteeId: mutee.id
+ });
+ if (muting == null) {
+ throw new ApiError(meta.errors.notMuting);
+ }
+ // Delete mute
+ await RenoteMutings.delete({
+ id: muting.id
+ });
+// publishUserEvent(user.id, "unmute", mutee);
+});
diff --git a/packages/backend/built/server/api/endpoints/renote-mute/list.js b/packages/backend/built/server/api/endpoints/renote-mute/list.js
new file mode 100644
index 0000000..d4907d2
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/renote-mute/list.js
@@ -0,0 +1,49 @@
+import { RenoteMutings } from "../../../../models/index.js";
+import define from "../../define.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ kind: "read:mutes",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "RenoteMuting"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 30
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: []
+};
+// eslint-disable-next-line import/no-default-export
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = makePaginationQuery(RenoteMutings.createQueryBuilder("muting"), ps.sinceId, ps.untilId).andWhere("muting.muterId = :meId", {
+ meId: me.id
+ });
+ const mutings = await query.take(ps.limit).getMany();
+ return await RenoteMutings.packMany(mutings, me);
+});
diff --git a/packages/backend/built/server/api/endpoints/request-reset-password.js b/packages/backend/built/server/api/endpoints/request-reset-password.js
new file mode 100644
index 0000000..5e35e2d
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/request-reset-password.js
@@ -0,0 +1,65 @@
+import rndstr from "rndstr";
+import { IsNull } from "typeorm";
+import config from "../../../config/index.js";
+import { Users, UserProfiles, PasswordResetRequests } from "../../../models/index.js";
+import { sendEmail } from "../../../services/send-email.js";
+import { genId } from "../../../misc/gen-id.js";
+import define from "../define.js";
+import { HOUR } from "../../../const.js";
+export const meta = {
+ tags: [
+ "reset password"
+ ],
+ requireCredential: false,
+ description: "Request a users password to be reset.",
+ limit: {
+ duration: HOUR,
+ max: 3
+ },
+ errors: {}
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ username: {
+ type: "string"
+ },
+ email: {
+ type: "string"
+ }
+ },
+ required: [
+ "username",
+ "email"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const user = await Users.findOneBy({
+ usernameLower: ps.username.toLowerCase(),
+ host: IsNull()
+ });
+ // 合致するユーザーが登録されていなかったら無視
+ if (user == null) {
+ return;
+ }
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ // 合致するメアドが登録されていなかったら無視
+ if (profile.email !== ps.email) {
+ return;
+ }
+ // メアドが認証されていなかったら無視
+ if (!profile.emailVerified) {
+ return;
+ }
+ const token = rndstr("a-z0-9", 64);
+ await PasswordResetRequests.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: profile.userId,
+ token
+ });
+ const link = `${config.url}/reset-password/${token}`;
+ sendEmail(ps.email, "Password reset requested", `To reset password, please click this link:
${link}`, `To reset password, please click this link: ${link}`);
+});
diff --git a/packages/backend/built/server/api/endpoints/reset-db.js b/packages/backend/built/server/api/endpoints/reset-db.js
new file mode 100644
index 0000000..73990b3
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/reset-db.js
@@ -0,0 +1,20 @@
+import { resetDb } from "../../../db/postgre.js";
+import define from "../define.js";
+export const meta = {
+ tags: [
+ "non-productive"
+ ],
+ requireCredential: false,
+ description: "Only available when running with NODE_ENV=testing. Reset the database and flush Redis.",
+ errors: {}
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ if (process.env.NODE_ENV !== "test") throw new Error("NODE_ENV is not a test");
+ await resetDb();
+ await new Promise((resolve)=>setTimeout(resolve, 1000));
+});
diff --git a/packages/backend/built/server/api/endpoints/reset-password.js b/packages/backend/built/server/api/endpoints/reset-password.js
new file mode 100644
index 0000000..2af12bc
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/reset-password.js
@@ -0,0 +1,41 @@
+import { UserProfiles, PasswordResetRequests } from "../../../models/index.js";
+import define from "../define.js";
+import { hashPassword } from "../../../misc/password.js";
+export const meta = {
+ tags: [
+ "reset password"
+ ],
+ requireCredential: false,
+ description: "Complete the password reset that was previously requested.",
+ errors: {}
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ token: {
+ type: "string"
+ },
+ password: {
+ type: "string"
+ }
+ },
+ required: [
+ "token",
+ "password"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const req = await PasswordResetRequests.findOneByOrFail({
+ token: ps.token
+ });
+ // 発行してから30分以上経過していたら無効
+ if (Date.now() - req.createdAt.getTime() > 1000 * 60 * 30) {
+ throw new Error(); // TODO
+ }
+ // Generate hash of password
+ const hash = await hashPassword(ps.password);
+ await UserProfiles.update(req.userId, {
+ password: hash
+ });
+ PasswordResetRequests.delete(req.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/reversi/cancel-match.js b/packages/backend/built/server/api/endpoints/reversi/cancel-match.js
new file mode 100644
index 0000000..ceffc70
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/reversi/cancel-match.js
@@ -0,0 +1,23 @@
+import define from "../../define.js";
+import { cancelMatch } from "../../../../services/reversi/index.js";
+export const meta = {
+ tags: [
+ "reversi"
+ ],
+ requireCredential: true,
+ kind: "write:account"
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ await cancelMatch(user, ps.userId);
+});
diff --git a/packages/backend/built/server/api/endpoints/reversi/games.js b/packages/backend/built/server/api/endpoints/reversi/games.js
new file mode 100644
index 0000000..f5f2a08
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/reversi/games.js
@@ -0,0 +1,58 @@
+import { Brackets } from "typeorm";
+import define from "../../define.js";
+import { ReversiGames } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "reversi"
+ ],
+ requireCredential: false,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ my: {
+ type: "boolean",
+ default: false
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(ReversiGames.createQueryBuilder("game"), ps.sinceId, ps.untilId).innerJoinAndSelect("game.user1", "user1").innerJoinAndSelect("game.user2", "user2");
+ if (ps.my && user) {
+ query.andWhere(new Brackets((qb)=>{
+ qb.where("game.user1Id = :userId", {
+ userId: user.id
+ }).orWhere("game.user2Id = :userId", {
+ userId: user.id
+ });
+ }));
+ } else {
+ query.andWhere("game.isStarted = TRUE");
+ }
+ const games = await query.take(ps.limit).getMany();
+ return await Promise.all(games.map((game)=>ReversiGames.packLite(game)));
+});
diff --git a/packages/backend/built/server/api/endpoints/reversi/invitations.js b/packages/backend/built/server/api/endpoints/reversi/invitations.js
new file mode 100644
index 0000000..25176cb
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/reversi/invitations.js
@@ -0,0 +1,29 @@
+import define from "../../define.js";
+import { Users } from "../../../../models/index.js";
+import { getInvitations } from "../../../../services/reversi/index.js";
+export const meta = {
+ tags: [
+ "reversi"
+ ],
+ requireCredential: true,
+ kind: "read:account",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (_ps, user)=>{
+ const ids = await getInvitations(user);
+ return await Users.packMany(ids, user, {
+ detail: false
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/reversi/match.js b/packages/backend/built/server/api/endpoints/reversi/match.js
new file mode 100644
index 0000000..7d1c5e3
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/reversi/match.js
@@ -0,0 +1,60 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { Users, ReversiGames } from "../../../../models/index.js";
+import { matchAnyUser, matchSpecificUser } from "../../../../services/reversi/index.js";
+export const meta = {
+ tags: [
+ "reversi"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "0b4f0559-b484-4e31-9581-3f73cee89b28"
+ },
+ isYourself: {
+ message: "Target user is yourself.",
+ code: "TARGET_IS_YOURSELF",
+ id: "96fd7bd6-d2bc-426c-a865-d055dcd2828e"
+ }
+ },
+ res: {
+ type: "object",
+ optional: true,
+ nullable: false
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ noIrregularRules: {
+ type: "boolean",
+ default: false
+ },
+ multiple: {
+ type: "boolean",
+ default: false
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ if (ps.userId === user.id) throw new ApiError(meta.errors.isYourself);
+ const target = ps.userId ? await Users.findOneBy({
+ id: ps.userId
+ }).then((u)=>{
+ if (!u) throw new ApiError(meta.errors.noSuchUser);
+ return u;
+ }) : null;
+ const game = target ? await matchSpecificUser(user, target, ps.multiple) : await matchAnyUser(user, {
+ noIrregularRules: ps.noIrregularRules
+ }, ps.multiple);
+ return game ? await ReversiGames.packDetail(game) : undefined;
+});
diff --git a/packages/backend/built/server/api/endpoints/reversi/show-game.js b/packages/backend/built/server/api/endpoints/reversi/show-game.js
new file mode 100644
index 0000000..c60e8a5
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/reversi/show-game.js
@@ -0,0 +1,46 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { ReversiGames } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "reversi"
+ ],
+ requireCredential: false,
+ errors: {
+ noSuchGame: {
+ message: "No such game.",
+ code: "NO_SUCH_GAME",
+ id: "f13a03db-fae1-46c9-87f3-43c8165419e1"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ gameId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "gameId"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const game = await ReversiGames.findOne({
+ where: {
+ id: ps.gameId
+ },
+ relations: {
+ user1: true,
+ user2: true
+ }
+ });
+ if (!game) throw new ApiError(meta.errors.noSuchGame);
+ return await ReversiGames.packDetail(game);
+});
diff --git a/packages/backend/built/server/api/endpoints/reversi/surrender.js b/packages/backend/built/server/api/endpoints/reversi/surrender.js
new file mode 100644
index 0000000..bf7e96d
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/reversi/surrender.js
@@ -0,0 +1,24 @@
+import define from "../../define.js";
+import { surrender } from "../../../../services/reversi/index.js";
+export const meta = {
+ tags: [
+ "reversi"
+ ],
+ requireCredential: true,
+ kind: "write:account"
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ gameId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "gameId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ await surrender(ps.gameId, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/reversi/verify.js b/packages/backend/built/server/api/endpoints/reversi/verify.js
new file mode 100644
index 0000000..54a9604
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/reversi/verify.js
@@ -0,0 +1,41 @@
+import define from "../../define.js";
+import { ReversiGames } from "../../../../models/index.js";
+import { checkCrc } from "../../../../services/reversi/index.js";
+export const meta = {
+ tags: [
+ "reversi"
+ ],
+ requireCredential: false,
+ res: {
+ type: "object",
+ optional: true,
+ nullable: false
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ gameId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ crc32: {
+ anyOf: [
+ {
+ type: "string"
+ },
+ {
+ type: "number"
+ }
+ ]
+ }
+ },
+ required: [
+ "gameId",
+ "crc32"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const game = await checkCrc(ps.gameId, ps.crc32);
+ return game ? await ReversiGames.packDetail(game) : undefined;
+});
diff --git a/packages/backend/built/server/api/endpoints/server-info.js b/packages/backend/built/server/api/endpoints/server-info.js
new file mode 100644
index 0000000..232e6e3
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/server-info.js
@@ -0,0 +1,61 @@
+import * as os from "node:os";
+import si from "systeminformation";
+import define from "../define.js";
+import { fetchMeta } from "../../../misc/fetch-meta.js";
+export const meta = {
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ allowGet: true,
+ cacheSec: 30,
+ tags: [
+ "meta"
+ ]
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async ()=>{
+ const memStats = await si.mem();
+ const fsStats = await si.fsSize();
+ let fsIndex = 0;
+ // Get the first index of fs sizes that are actualy used.
+ for (const [i, stat] of fsStats.entries()){
+ if (stat.rw === true && stat.used > 0) {
+ fsIndex = i;
+ break;
+ }
+ }
+ const instanceMeta = await fetchMeta();
+ if (!instanceMeta.enableServerMachineStats) {
+ return {
+ machine: "Not specified",
+ cpu: {
+ model: "Not specified",
+ cores: 0
+ },
+ mem: {
+ total: 0
+ },
+ fs: {
+ total: 0,
+ used: 0
+ }
+ };
+ }
+ return {
+ machine: os.hostname(),
+ cpu: {
+ model: os.cpus()[0].model,
+ cores: os.cpus().length
+ },
+ mem: {
+ total: memStats.total
+ },
+ fs: {
+ total: fsStats[fsIndex].size,
+ used: fsStats[fsIndex].used
+ }
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/shogi/cancel-match.js b/packages/backend/built/server/api/endpoints/shogi/cancel-match.js
new file mode 100644
index 0000000..d4b824d
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/shogi/cancel-match.js
@@ -0,0 +1,23 @@
+import define from "../../define.js";
+import { cancelMatch } from "../../../../services/shogi/index.js";
+export const meta = {
+ tags: [
+ "shogi"
+ ],
+ requireCredential: true,
+ kind: "write:account"
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ await cancelMatch(user, ps.userId);
+});
diff --git a/packages/backend/built/server/api/endpoints/shogi/games.js b/packages/backend/built/server/api/endpoints/shogi/games.js
new file mode 100644
index 0000000..4501187
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/shogi/games.js
@@ -0,0 +1,58 @@
+import { Brackets } from "typeorm";
+import define from "../../define.js";
+import { ShogiGames } from "../../../../models/index.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "shogi"
+ ],
+ requireCredential: false,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ my: {
+ type: "boolean",
+ default: false
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(ShogiGames.createQueryBuilder("game"), ps.sinceId, ps.untilId).innerJoinAndSelect("game.user1", "user1").innerJoinAndSelect("game.user2", "user2");
+ if (ps.my && user) {
+ query.andWhere(new Brackets((qb)=>{
+ qb.where("game.user1Id = :userId", {
+ userId: user.id
+ }).orWhere("game.user2Id = :userId", {
+ userId: user.id
+ });
+ }));
+ } else {
+ query.andWhere("game.isStarted = TRUE");
+ }
+ const games = await query.take(ps.limit).getMany();
+ return await Promise.all(games.map((game)=>ShogiGames.packLite(game)));
+});
diff --git a/packages/backend/built/server/api/endpoints/shogi/invitations.js b/packages/backend/built/server/api/endpoints/shogi/invitations.js
new file mode 100644
index 0000000..1f7ecc2
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/shogi/invitations.js
@@ -0,0 +1,29 @@
+import define from "../../define.js";
+import { Users } from "../../../../models/index.js";
+import { getInvitations } from "../../../../services/shogi/index.js";
+export const meta = {
+ tags: [
+ "shogi"
+ ],
+ requireCredential: true,
+ kind: "read:account",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (_ps, user)=>{
+ const ids = await getInvitations(user);
+ return await Users.packMany(ids, user, {
+ detail: false
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/shogi/match.js b/packages/backend/built/server/api/endpoints/shogi/match.js
new file mode 100644
index 0000000..5e999c1
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/shogi/match.js
@@ -0,0 +1,54 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { ShogiGames, Users } from "../../../../models/index.js";
+import { matchAnyUser, matchSpecificUser } from "../../../../services/shogi/index.js";
+export const meta = {
+ tags: [
+ "shogi"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "6dc7bf6e-d472-42ec-95c7-43d8f91ecf97"
+ },
+ isYourself: {
+ message: "Target user is yourself.",
+ code: "TARGET_IS_YOURSELF",
+ id: "d7cb9670-245e-4fc4-90d0-11cc1796f647"
+ }
+ },
+ res: {
+ type: "object",
+ optional: true,
+ nullable: false
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ multiple: {
+ type: "boolean",
+ default: false
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ if (ps.userId === user.id) throw new ApiError(meta.errors.isYourself);
+ const target = ps.userId ? await Users.findOneBy({
+ id: ps.userId
+ }).then((u)=>{
+ if (!u) throw new ApiError(meta.errors.noSuchUser);
+ return u;
+ }) : null;
+ const game = target ? await matchSpecificUser(user, target, ps.multiple) : await matchAnyUser(user, ps.multiple);
+ return game ? await ShogiGames.packDetail(game) : undefined;
+});
diff --git a/packages/backend/built/server/api/endpoints/shogi/show-game.js b/packages/backend/built/server/api/endpoints/shogi/show-game.js
new file mode 100644
index 0000000..ee4f6c7
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/shogi/show-game.js
@@ -0,0 +1,46 @@
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { ShogiGames } from "../../../../models/index.js";
+export const meta = {
+ tags: [
+ "shogi"
+ ],
+ requireCredential: false,
+ errors: {
+ noSuchGame: {
+ message: "No such game.",
+ code: "NO_SUCH_GAME",
+ id: "f732066e-d92e-4d80-940f-b596d512f2ef"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ gameId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "gameId"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const game = await ShogiGames.findOne({
+ where: {
+ id: ps.gameId
+ },
+ relations: {
+ user1: true,
+ user2: true
+ }
+ });
+ if (!game) throw new ApiError(meta.errors.noSuchGame);
+ return await ShogiGames.packDetail(game);
+});
diff --git a/packages/backend/built/server/api/endpoints/shogi/surrender.js b/packages/backend/built/server/api/endpoints/shogi/surrender.js
new file mode 100644
index 0000000..630360a
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/shogi/surrender.js
@@ -0,0 +1,24 @@
+import define from "../../define.js";
+import { surrender } from "../../../../services/shogi/index.js";
+export const meta = {
+ tags: [
+ "shogi"
+ ],
+ requireCredential: true,
+ kind: "write:account"
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ gameId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "gameId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ await surrender(ps.gameId, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/stats.js b/packages/backend/built/server/api/endpoints/stats.js
new file mode 100644
index 0000000..999cf5f
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/stats.js
@@ -0,0 +1,90 @@
+import { Instances, NoteReactions } from "../../../models/index.js";
+import define from "../define.js";
+import { driveChart, notesChart, usersChart } from "../../../services/chart/index.js";
+export const meta = {
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ tags: [
+ "meta"
+ ],
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ notesCount: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ originalNotesCount: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ usersCount: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ originalUsersCount: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ instances: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ driveUsageLocal: {
+ type: "number",
+ optional: false,
+ nullable: false
+ },
+ driveUsageRemote: {
+ type: "number",
+ optional: false,
+ nullable: false
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async ()=>{
+ const notesChartData = await notesChart.getChart("hour", 1, null);
+ const notesCount = notesChartData.local.total[0] + notesChartData.remote.total[0];
+ const originalNotesCount = notesChartData.local.total[0];
+ const usersChartData = await usersChart.getChart("hour", 1, null);
+ const usersCount = usersChartData.local.total[0] + usersChartData.remote.total[0];
+ const originalUsersCount = usersChartData.local.total[0];
+ const driveChartData = await driveChart.getChart("hour", 1, null);
+ //TODO: fixme currently returns 0
+ const driveUsageLocal = driveChartData.local.incSize[0];
+ const driveUsageRemote = driveChartData.remote.incSize[0];
+ const [reactionsCount, //originalReactionsCount,
+ instances] = await Promise.all([
+ NoteReactions.count({
+ cache: 3600000
+ }),
+ //NoteReactions.count({ where: { userHost: IsNull() }, cache: 3600000 }),
+ Instances.count({
+ cache: 3600000
+ })
+ ]);
+ return {
+ notesCount,
+ originalNotesCount,
+ usersCount,
+ originalUsersCount,
+ reactionsCount,
+ //originalReactionsCount,
+ instances,
+ driveUsageLocal,
+ driveUsageRemote
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/sw/register.js b/packages/backend/built/server/api/endpoints/sw/register.js
new file mode 100644
index 0000000..8d292c6
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/sw/register.js
@@ -0,0 +1,105 @@
+import { fetchMeta } from "../../../../misc/fetch-meta.js";
+import { genId } from "../../../../misc/gen-id.js";
+import { SwSubscriptions } from "../../../../models/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ description: "Register to receive push notifications.",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ state: {
+ type: "string",
+ optional: true,
+ nullable: false,
+ enum: [
+ "already-subscribed",
+ "subscribed"
+ ]
+ },
+ key: {
+ type: "string",
+ optional: false,
+ nullable: true
+ },
+ userId: {
+ type: "string",
+ optional: true,
+ nullable: false
+ },
+ endpoint: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ sendReadMessage: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ endpoint: {
+ type: "string"
+ },
+ auth: {
+ type: "string"
+ },
+ publickey: {
+ type: "string"
+ },
+ sendReadMessage: {
+ type: "boolean",
+ default: false
+ }
+ },
+ required: [
+ "endpoint",
+ "auth",
+ "publickey"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const subscription = await SwSubscriptions.findOneBy({
+ userId: me.id,
+ endpoint: ps.endpoint,
+ auth: ps.auth,
+ publickey: ps.publickey
+ });
+ const instance = await fetchMeta(true);
+ // if already subscribed
+ if (subscription != null) {
+ return {
+ state: "already-subscribed",
+ key: instance.swPublicKey,
+ userId: me.id,
+ endpoint: subscription.endpoint,
+ sendReadMessage: subscription.sendReadMessage
+ };
+ }
+ await SwSubscriptions.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: me.id,
+ endpoint: ps.endpoint,
+ auth: ps.auth,
+ publickey: ps.publickey,
+ sendReadMessage: ps.sendReadMessage
+ });
+ return {
+ state: "subscribed",
+ key: instance.swPublicKey,
+ userId: me.id,
+ endpoint: ps.endpoint,
+ sendReadMessage: ps.sendReadMessage
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/sw/show-registration.js b/packages/backend/built/server/api/endpoints/sw/show-registration.js
new file mode 100644
index 0000000..2b640a6
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/sw/show-registration.js
@@ -0,0 +1,57 @@
+import { SwSubscriptions } from "../../../../models/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ description: "Check push notification registration exists.",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: true,
+ properties: {
+ userId: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ endpoint: {
+ type: "string",
+ optional: false,
+ nullable: false
+ },
+ sendReadMessage: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ endpoint: {
+ type: "string"
+ }
+ },
+ required: [
+ "endpoint"
+ ]
+};
+// eslint-disable-next-line import/no-default-export
+export default define(meta, paramDef, async (ps, me)=>{
+ const subscription = await SwSubscriptions.findOneBy({
+ userId: me.id,
+ endpoint: ps.endpoint
+ });
+ if (subscription != null) {
+ return {
+ userId: subscription.userId,
+ endpoint: subscription.endpoint,
+ sendReadMessage: subscription.sendReadMessage
+ };
+ }
+ return null;
+});
diff --git a/packages/backend/built/server/api/endpoints/sw/unregister.js b/packages/backend/built/server/api/endpoints/sw/unregister.js
new file mode 100644
index 0000000..425f93f
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/sw/unregister.js
@@ -0,0 +1,28 @@
+import { SwSubscriptions } from "../../../../models/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: false,
+ description: "Unregister from receiving push notifications."
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ endpoint: {
+ type: "string"
+ }
+ },
+ required: [
+ "endpoint"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ await SwSubscriptions.delete({
+ ...me ? {
+ userId: me.id
+ } : {},
+ endpoint: ps.endpoint
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/sw/update-registration.js b/packages/backend/built/server/api/endpoints/sw/update-registration.js
new file mode 100644
index 0000000..937dc82
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/sw/update-registration.js
@@ -0,0 +1,43 @@
+import { SwSubscriptions } from "../../../../models/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "account"
+ ],
+ requireCredential: true,
+ description: "Unregister from receiving push notifications."
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ endpoint: {
+ type: "string"
+ },
+ sendReadMessage: {
+ type: "boolean"
+ }
+ },
+ required: [
+ "endpoint"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const swSubscription = await SwSubscriptions.findOneBy({
+ userId: me.id,
+ endpoint: ps.endpoint
+ });
+ if (swSubscription === null) {
+ throw new Error("No such registration");
+ }
+ if (ps.sendReadMessage !== undefined) {
+ swSubscription.sendReadMessage = ps.sendReadMessage;
+ }
+ await SwSubscriptions.update(swSubscription.id, {
+ sendReadMessage: swSubscription.sendReadMessage
+ });
+ return {
+ userId: swSubscription.userId,
+ endpoint: swSubscription.endpoint,
+ sendReadMessage: swSubscription.sendReadMessage
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/test.js b/packages/backend/built/server/api/endpoints/test.js
new file mode 100644
index 0000000..2dea274
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/test.js
@@ -0,0 +1,38 @@
+import define from "../define.js";
+export const meta = {
+ tags: [
+ "non-productive"
+ ],
+ description: "Endpoint for testing input validation.",
+ requireCredential: false
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ required: {
+ type: "boolean"
+ },
+ string: {
+ type: "string"
+ },
+ default: {
+ type: "string",
+ default: "hello"
+ },
+ nullableDefault: {
+ type: "string",
+ nullable: true,
+ default: "hello"
+ },
+ id: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "required"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ return ps;
+});
diff --git a/packages/backend/built/server/api/endpoints/username/available.js b/packages/backend/built/server/api/endpoints/username/available.js
new file mode 100644
index 0000000..979cede
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/username/available.js
@@ -0,0 +1,45 @@
+import { IsNull } from "typeorm";
+import { Users, UsedUsernames } from "../../../../models/index.js";
+import config from "../../../../config/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "users"
+ ],
+ requireCredential: false,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ available: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ username: Users.localUsernameSchema
+ },
+ required: [
+ "username"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ // Get exist
+ const exist = await Users.countBy({
+ host: IsNull(),
+ usernameLower: ps.username.toLowerCase()
+ });
+ const exist2 = await UsedUsernames.countBy({
+ username: ps.username.toLowerCase()
+ });
+ const reserved = config.reservedUsernames?.includes(ps.username.toLowerCase());
+ return {
+ available: exist === 0 && exist2 === 0 && !reserved
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/users.js b/packages/backend/built/server/api/endpoints/users.js
new file mode 100644
index 0000000..5efc860
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users.js
@@ -0,0 +1,141 @@
+import { Users } from "../../../models/index.js";
+import define from "../define.js";
+import { generateMutedUserQueryForUsers } from "../common/generate-muted-user-query.js";
+import { generateBlockQueryForUsers } from "../common/generate-block-query.js";
+import { generateMinorBadgeUserVisibilityQuery } from "../common/generate-minor-badge-visibility-query.js";
+export const meta = {
+ tags: [
+ "users"
+ ],
+ requireCredential: true,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserDetailed"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ offset: {
+ type: "integer",
+ default: 0
+ },
+ sort: {
+ type: "string",
+ enum: [
+ "+follower",
+ "-follower",
+ "+createdAt",
+ "-createdAt",
+ "+updatedAt",
+ "-updatedAt"
+ ]
+ },
+ state: {
+ type: "string",
+ enum: [
+ "all",
+ "admin",
+ "moderator",
+ "adminOrModerator",
+ "alive"
+ ],
+ default: "all"
+ },
+ origin: {
+ type: "string",
+ enum: [
+ "combined",
+ "local",
+ "remote"
+ ],
+ default: "local"
+ },
+ hostname: {
+ type: "string",
+ nullable: true,
+ default: null,
+ description: "The local host is represented with `null`."
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = Users.createQueryBuilder("user");
+ query.where("user.isExplorable = TRUE");
+ switch(ps.state){
+ case "admin":
+ query.andWhere("user.isAdmin = TRUE");
+ break;
+ case "moderator":
+ query.andWhere("user.isModerator = TRUE");
+ break;
+ case "adminOrModerator":
+ query.andWhere("user.isAdmin = TRUE OR user.isModerator = TRUE");
+ break;
+ case "alive":
+ query.andWhere("user.updatedAt > :date", {
+ date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5)
+ });
+ break;
+ }
+ switch(ps.origin){
+ case "local":
+ query.andWhere("user.host IS NULL");
+ break;
+ case "remote":
+ query.andWhere("user.host IS NOT NULL");
+ break;
+ }
+ if (ps.hostname) {
+ query.andWhere("user.host = :hostname", {
+ hostname: ps.hostname.toLowerCase()
+ });
+ }
+ switch(ps.sort){
+ case "+follower":
+ query.orderBy("user.followersCount", "DESC");
+ break;
+ case "-follower":
+ query.orderBy("user.followersCount", "ASC");
+ break;
+ case "+createdAt":
+ query.orderBy("user.createdAt", "DESC");
+ break;
+ case "-createdAt":
+ query.orderBy("user.createdAt", "ASC");
+ break;
+ case "+updatedAt":
+ query.andWhere("user.updatedAt IS NOT NULL").orderBy("user.updatedAt", "DESC");
+ break;
+ case "-updatedAt":
+ query.andWhere("user.updatedAt IS NOT NULL").orderBy("user.updatedAt", "ASC");
+ break;
+ default:
+ query.orderBy("user.id", "ASC");
+ break;
+ }
+ if (me) generateMutedUserQueryForUsers(query, me);
+ if (me) generateBlockQueryForUsers(query, me);
+ generateMinorBadgeUserVisibilityQuery(query, me);
+ query.take(ps.limit);
+ query.skip(ps.offset);
+ const users = await query.getMany();
+ return await Users.packMany(users, me, {
+ detail: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/users/clips.js b/packages/backend/built/server/api/endpoints/users/clips.js
new file mode 100644
index 0000000..91cb3cf
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/clips.js
@@ -0,0 +1,55 @@
+import { Clips } from "../../../../models/index.js";
+import define from "../../define.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "users",
+ "clips"
+ ],
+ requireCredentialPrivateMode: true,
+ description: "Show all clips this user owns.",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Clip"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(Clips.createQueryBuilder("clip"), ps.sinceId, ps.untilId).andWhere("clip.userId = :userId", {
+ userId: ps.userId
+ }).andWhere("clip.isPublic = true");
+ const clips = await query.take(ps.limit).getMany();
+ return await Clips.packMany(clips);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/followers.js b/packages/backend/built/server/api/endpoints/users/followers.js
new file mode 100644
index 0000000..895378a
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/followers.js
@@ -0,0 +1,130 @@
+import { IsNull } from "typeorm";
+import { Users, Followings, UserProfiles } from "../../../../models/index.js";
+import { toPunyNullable } from "../../../../misc/convert-host.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "users"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ description: "Show everyone that follows this user.",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Following"
+ }
+ },
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "27fa5435-88ab-43de-9360-387de88727cd"
+ },
+ forbidden: {
+ message: "Forbidden.",
+ code: "FORBIDDEN",
+ id: "3c6a84db-d619-26af-ca14-06232a21df8a"
+ },
+ nullFollowers: {
+ message: "No followers found.",
+ code: "NULL_FOLLOWERS",
+ id: "174a6507-a6c2-4925-8e5d-92fd08aedc9e"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ }
+ },
+ anyOf: [
+ {
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+ },
+ {
+ properties: {
+ username: {
+ type: "string"
+ },
+ host: {
+ type: "string",
+ nullable: true,
+ description: "The local host is represented with `null`."
+ }
+ },
+ required: [
+ "username",
+ "host"
+ ]
+ }
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const user = await Users.findOneBy(ps.userId != null ? {
+ id: ps.userId
+ } : {
+ usernameLower: ps.username.toLowerCase(),
+ host: toPunyNullable(ps.host) ?? IsNull()
+ });
+ if (user == null) {
+ throw new ApiError(meta.errors.noSuchUser);
+ }
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ if (profile.ffVisibility === "private") {
+ if (me == null || me.id !== user.id) {
+ throw new ApiError(meta.errors.forbidden);
+ }
+ } else if (profile.ffVisibility === "followers") {
+ if (me == null) {
+ throw new ApiError(meta.errors.forbidden);
+ } else if (me.id !== user.id) {
+ const isFollowed = await Followings.exist({
+ where: {
+ followeeId: user.id,
+ followerId: me.id
+ }
+ });
+ if (!isFollowed) {
+ throw new ApiError(meta.errors.nullFollowers);
+ }
+ }
+ }
+ const query = makePaginationQuery(Followings.createQueryBuilder("following"), ps.sinceId, ps.untilId).andWhere("following.followeeId = :userId", {
+ userId: user.id
+ }).innerJoinAndSelect("following.follower", "follower");
+ const followings = await query.take(ps.limit).getMany();
+ return await Followings.packMany(followings, me, {
+ populateFollower: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/users/following.js b/packages/backend/built/server/api/endpoints/users/following.js
new file mode 100644
index 0000000..e5182f0
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/following.js
@@ -0,0 +1,130 @@
+import { IsNull } from "typeorm";
+import { Users, Followings, UserProfiles } from "../../../../models/index.js";
+import { toPunyNullable } from "../../../../misc/convert-host.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "users"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ description: "Show everyone that this user is following.",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Following"
+ }
+ },
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "63e4aba4-4156-4e53-be25-c9559e42d71b"
+ },
+ forbidden: {
+ message: "Forbidden.",
+ code: "FORBIDDEN",
+ id: "f6cdb0df-c19f-ec5c-7dbb-0ba84a1f92ba"
+ },
+ cannot_find: {
+ message: "Cannot find the following.",
+ code: "CANNOT_FIND",
+ id: "7a55f0d7-8e06-4a7e-9c77-ee7d59b25a82"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ }
+ },
+ anyOf: [
+ {
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+ },
+ {
+ properties: {
+ username: {
+ type: "string"
+ },
+ host: {
+ type: "string",
+ nullable: true,
+ description: "The local host is represented with `null`."
+ }
+ },
+ required: [
+ "username",
+ "host"
+ ]
+ }
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const user = await Users.findOneBy(ps.userId != null ? {
+ id: ps.userId
+ } : {
+ usernameLower: ps.username.toLowerCase(),
+ host: toPunyNullable(ps.host) ?? IsNull()
+ });
+ if (user == null) {
+ throw new ApiError(meta.errors.noSuchUser);
+ }
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ if (profile.ffVisibility === "private") {
+ if (me == null || me.id !== user.id) {
+ throw new ApiError(meta.errors.forbidden);
+ }
+ } else if (profile.ffVisibility === "followers") {
+ if (me == null) {
+ throw new ApiError(meta.errors.forbidden);
+ } else if (me.id !== user.id) {
+ const isFollowing = await Followings.exist({
+ where: {
+ followeeId: user.id,
+ followerId: me.id
+ }
+ });
+ if (!isFollowing) {
+ throw new ApiError(meta.errors.cannot_find);
+ }
+ }
+ }
+ const query = makePaginationQuery(Followings.createQueryBuilder("following"), ps.sinceId, ps.untilId).andWhere("following.followerId = :userId", {
+ userId: user.id
+ }).innerJoinAndSelect("following.followee", "followee");
+ const followings = await query.take(ps.limit).getMany();
+ return await Followings.packMany(followings, me, {
+ populateFollowee: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/users/gallery/posts.js b/packages/backend/built/server/api/endpoints/users/gallery/posts.js
new file mode 100644
index 0000000..d15a705
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/gallery/posts.js
@@ -0,0 +1,55 @@
+import define from "../../../define.js";
+import { GalleryPosts } from "../../../../../models/index.js";
+import { makePaginationQuery } from "../../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "users",
+ "gallery"
+ ],
+ requireCredentialPrivateMode: true,
+ description: "Show all gallery posts by the given user.",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "GalleryPost"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(GalleryPosts.createQueryBuilder("post"), ps.sinceId, ps.untilId).andWhere("post.userId = :userId", {
+ userId: ps.userId
+ });
+ const posts = await query.take(ps.limit).getMany();
+ return await GalleryPosts.packMany(posts, user);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/get-frequently-replied-users.js b/packages/backend/built/server/api/endpoints/users/get-frequently-replied-users.js
new file mode 100644
index 0000000..38d875a
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/get-frequently-replied-users.js
@@ -0,0 +1,119 @@
+import { Not, In, IsNull } from "typeorm";
+import { maximum } from "../../../../prelude/array.js";
+import { Notes, Users } from "../../../../models/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { getUser } from "../../common/getters.js";
+export const meta = {
+ tags: [
+ "users"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ description: "Get a list of other users that the specified user frequently replies to.",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ user: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserDetailed"
+ },
+ weight: {
+ type: "number",
+ optional: false,
+ nullable: false
+ }
+ }
+ }
+ },
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "e6965129-7b2a-40a4-bae2-cd84cd434822"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ // Lookup user
+ const user = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ // Fetch recent notes
+ const recentNotes = await Notes.find({
+ where: {
+ userId: user.id,
+ replyId: Not(IsNull())
+ },
+ order: {
+ id: -1
+ },
+ take: 1000,
+ select: [
+ "replyId"
+ ]
+ });
+ // 投稿が少なかったら中断
+ if (recentNotes.length === 0) {
+ return [];
+ }
+ // TODO ミュートを考慮
+ const replyTargetNotes = await Notes.find({
+ where: {
+ id: In(recentNotes.map((p)=>p.replyId))
+ },
+ select: [
+ "userId"
+ ]
+ });
+ const repliedUsers = {};
+ // Extract replies from recent notes
+ for (const userId of replyTargetNotes.map((x)=>x.userId.toString())){
+ if (repliedUsers[userId]) {
+ repliedUsers[userId]++;
+ } else {
+ repliedUsers[userId] = 1;
+ }
+ }
+ // Calc peak
+ const peak = maximum(Object.values(repliedUsers));
+ // Sort replies by frequency
+ const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b)=>repliedUsers[b] - repliedUsers[a]);
+ // Extract top replied users
+ const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit);
+ // Make replies object (includes weights)
+ const repliesObj = await Promise.all(topRepliedUsers.map(async (user)=>({
+ user: await Users.pack(user, me, {
+ detail: true
+ }),
+ weight: repliedUsers[user] / peak
+ })));
+ return repliesObj;
+});
diff --git a/packages/backend/built/server/api/endpoints/users/groups/create.js b/packages/backend/built/server/api/endpoints/users/groups/create.js
new file mode 100644
index 0000000..979a41c
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/groups/create.js
@@ -0,0 +1,46 @@
+import { UserGroups, UserGroupJoinings } from "../../../../../models/index.js";
+import { genId } from "../../../../../misc/gen-id.js";
+import define from "../../../define.js";
+export const meta = {
+ tags: [
+ "groups"
+ ],
+ requireCredential: true,
+ kind: "write:user-groups",
+ description: "Create a new group.",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserGroup"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ name: {
+ type: "string",
+ minLength: 1,
+ maxLength: 100
+ }
+ },
+ required: [
+ "name"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const userGroup = await UserGroups.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ name: ps.name
+ }).then((x)=>UserGroups.findOneByOrFail(x.identifiers[0]));
+ // Push the owner
+ await UserGroupJoinings.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ userGroupId: userGroup.id
+ });
+ return await UserGroups.pack(userGroup);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/groups/delete.js b/packages/backend/built/server/api/endpoints/users/groups/delete.js
new file mode 100644
index 0000000..27633ae
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/groups/delete.js
@@ -0,0 +1,40 @@
+import { UserGroups } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ tags: [
+ "groups"
+ ],
+ requireCredential: true,
+ kind: "write:user-groups",
+ description: "Delete an existing group.",
+ errors: {
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "63dbd64c-cd77-413f-8e08-61781e210b38"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ groupId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "groupId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const userGroup = await UserGroups.findOneBy({
+ id: ps.groupId,
+ userId: user.id
+ });
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
+ await UserGroups.delete(userGroup.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/groups/emojis/create.js b/packages/backend/built/server/api/endpoints/users/groups/emojis/create.js
new file mode 100644
index 0000000..b9706d2
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/groups/emojis/create.js
@@ -0,0 +1,104 @@
+import define from "../../../../define.js";
+import { ApiError } from "../../../../error.js";
+import { DriveFiles, UserEmojis, UserGroups } from "../../../../../../models/index.js";
+import { genId } from "../../../../../../misc/gen-id.js";
+import { getEmojiSize } from "../../../../../../misc/emoji-meta.js";
+import { clearGroupEmojiCache } from "../../../../../../misc/populate-emojis.js";
+function normalizeMimeType(type) {
+ const mime = type?.split(";")[0]?.trim().toLowerCase();
+ return mime && mime.length <= 64 ? mime : null;
+}
+export const meta = {
+ tags: [
+ "groups"
+ ],
+ requireCredential: true,
+ kind: "write:user-groups",
+ errors: {
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "ab752eba-3f77-4746-b805-1457719f648d"
+ },
+ noSuchFile: {
+ message: "No such file.",
+ code: "NO_SUCH_FILE",
+ id: "be2812bf-55c0-4fd9-8d1b-813578fc81f9"
+ },
+ alreadyExists: {
+ message: "Group emoji already exists.",
+ code: "ALREADY_EXISTS",
+ id: "7957d717-e009-47c8-b32d-37a13afed4db"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ groupId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ name: {
+ type: "string",
+ pattern: "^[a-z0-9_]{1,64}$"
+ },
+ fileId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ glyph: {
+ type: "boolean",
+ default: false
+ }
+ },
+ required: [
+ "groupId",
+ "name",
+ "fileId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const group = await UserGroups.findOneBy({
+ id: ps.groupId,
+ userId: me.id
+ });
+ if (!group) throw new ApiError(meta.errors.noSuchGroup);
+ const file = await DriveFiles.findOneBy({
+ id: ps.fileId,
+ userId: me.id
+ });
+ if (!file || !file.type.startsWith("image/")) throw new ApiError(meta.errors.noSuchFile);
+ const exists = await UserEmojis.findOneBy({
+ name: ps.name,
+ userGroupId: group.id
+ });
+ if (exists) throw new ApiError(meta.errors.alreadyExists);
+ const size = await getEmojiSize(file.url).catch(()=>({
+ width: null,
+ height: null
+ }));
+ const emoji = await UserEmojis.insert({
+ id: genId(),
+ createdAt: new Date(),
+ name: ps.name,
+ userId: null,
+ userGroupId: group.id,
+ originalUrl: file.url,
+ publicUrl: file.webpublicUrl ?? file.url,
+ type: normalizeMimeType(file.webpublicType ?? file.type),
+ glyph: ps.glyph,
+ width: size.width || null,
+ height: size.height || null
+ }).then((x)=>UserEmojis.findOneByOrFail(x.identifiers[0]));
+ await clearGroupEmojiCache(emoji.name, group.username);
+ return {
+ id: emoji.id,
+ name: emoji.name,
+ url: emoji.publicUrl || emoji.originalUrl,
+ glyph: emoji.glyph,
+ glyphUrl: emoji.glyph ? emoji.originalUrl : null,
+ width: emoji.width,
+ height: emoji.height
+ };
+});
diff --git a/packages/backend/built/server/api/endpoints/users/groups/emojis/delete.js b/packages/backend/built/server/api/endpoints/users/groups/emojis/delete.js
new file mode 100644
index 0000000..6c500c3
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/groups/emojis/delete.js
@@ -0,0 +1,43 @@
+import define from "../../../../define.js";
+import { ApiError } from "../../../../error.js";
+import { UserEmojis, UserGroups } from "../../../../../../models/index.js";
+import { clearGroupEmojiCache } from "../../../../../../misc/populate-emojis.js";
+export const meta = {
+ tags: [
+ "groups"
+ ],
+ requireCredential: true,
+ kind: "write:user-groups",
+ errors: {
+ noSuchEmoji: {
+ message: "No such group emoji.",
+ code: "NO_SUCH_GROUP_EMOJI",
+ id: "e22d8bd7-d9c5-4470-8ef0-9a9095985b3f"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "id"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const emoji = await UserEmojis.findOneBy({
+ id: ps.id
+ });
+ if (!emoji?.userGroupId) throw new ApiError(meta.errors.noSuchEmoji);
+ const group = await UserGroups.findOneBy({
+ id: emoji.userGroupId,
+ userId: me.id
+ });
+ if (!group) throw new ApiError(meta.errors.noSuchEmoji);
+ await UserEmojis.delete(emoji.id);
+ await clearGroupEmojiCache(emoji.name, group.username);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/groups/emojis/list.js b/packages/backend/built/server/api/endpoints/users/groups/emojis/list.js
new file mode 100644
index 0000000..07e9502
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/groups/emojis/list.js
@@ -0,0 +1,57 @@
+import define from "../../../../define.js";
+import { ApiError } from "../../../../error.js";
+import { UserEmojis, UserGroupJoinings, UserGroups } from "../../../../../../models/index.js";
+export const meta = {
+ tags: [
+ "groups"
+ ],
+ requireCredential: true,
+ kind: "read:user-groups",
+ errors: {
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "e9f78109-710a-44fd-8ab2-51582f65dd97"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ groupId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "groupId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const group = await UserGroups.findOneBy({
+ id: ps.groupId
+ });
+ if (!group) throw new ApiError(meta.errors.noSuchGroup);
+ const member = await UserGroupJoinings.findOneBy({
+ userGroupId: group.id,
+ userId: me.id
+ });
+ if (group.userId !== me.id && !member) throw new ApiError(meta.errors.noSuchGroup);
+ const emojis = await UserEmojis.find({
+ where: {
+ userGroupId: group.id
+ },
+ order: {
+ createdAt: "DESC"
+ }
+ });
+ return emojis.map((emoji)=>({
+ id: emoji.id,
+ name: emoji.name,
+ url: emoji.publicUrl || emoji.originalUrl,
+ glyph: emoji.glyph,
+ glyphUrl: emoji.glyph ? emoji.originalUrl : null,
+ width: emoji.width,
+ height: emoji.height
+ }));
+});
diff --git a/packages/backend/built/server/api/endpoints/users/groups/invitations/accept.js b/packages/backend/built/server/api/endpoints/users/groups/invitations/accept.js
new file mode 100644
index 0000000..fd88270
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/groups/invitations/accept.js
@@ -0,0 +1,52 @@
+import { UserGroupJoinings, UserGroupInvitations } from "../../../../../../models/index.js";
+import { genId } from "../../../../../../misc/gen-id.js";
+import { ApiError } from "../../../../error.js";
+import define from "../../../../define.js";
+export const meta = {
+ tags: [
+ "groups",
+ "users"
+ ],
+ requireCredential: true,
+ kind: "write:user-groups",
+ description: "Join a group the authenticated user has been invited to.",
+ errors: {
+ noSuchInvitation: {
+ message: "No such invitation.",
+ code: "NO_SUCH_INVITATION",
+ id: "98c11eca-c890-4f42-9806-c8c8303ebb5e"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ invitationId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "invitationId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Fetch the invitation
+ const invitation = await UserGroupInvitations.findOneBy({
+ id: ps.invitationId
+ });
+ if (invitation == null) {
+ throw new ApiError(meta.errors.noSuchInvitation);
+ }
+ if (invitation.userId !== user.id) {
+ throw new ApiError(meta.errors.noSuchInvitation);
+ }
+ // Push the user
+ await UserGroupJoinings.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ userGroupId: invitation.userGroupId
+ });
+ UserGroupInvitations.delete(invitation.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/groups/invitations/reject.js b/packages/backend/built/server/api/endpoints/users/groups/invitations/reject.js
new file mode 100644
index 0000000..b619612
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/groups/invitations/reject.js
@@ -0,0 +1,44 @@
+import { UserGroupInvitations } from "../../../../../../models/index.js";
+import define from "../../../../define.js";
+import { ApiError } from "../../../../error.js";
+export const meta = {
+ tags: [
+ "groups",
+ "users"
+ ],
+ requireCredential: true,
+ kind: "write:user-groups",
+ description: "Delete an existing group invitation for the authenticated user without joining the group.",
+ errors: {
+ noSuchInvitation: {
+ message: "No such invitation.",
+ code: "NO_SUCH_INVITATION",
+ id: "ad7471d4-2cd9-44b4-ac68-e7136b4ce656"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ invitationId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "invitationId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Fetch the invitation
+ const invitation = await UserGroupInvitations.findOneBy({
+ id: ps.invitationId
+ });
+ if (invitation == null) {
+ throw new ApiError(meta.errors.noSuchInvitation);
+ }
+ if (invitation.userId !== user.id) {
+ throw new ApiError(meta.errors.noSuchInvitation);
+ }
+ await UserGroupInvitations.delete(invitation.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/groups/invite.js b/packages/backend/built/server/api/endpoints/users/groups/invite.js
new file mode 100644
index 0000000..e78b1a6
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/groups/invite.js
@@ -0,0 +1,94 @@
+import { UserGroups, UserGroupJoinings, UserGroupInvitations } from "../../../../../models/index.js";
+import { genId } from "../../../../../misc/gen-id.js";
+import { createNotification } from "../../../../../services/create-notification.js";
+import { getUser } from "../../../common/getters.js";
+import { ApiError } from "../../../error.js";
+import define from "../../../define.js";
+export const meta = {
+ tags: [
+ "groups",
+ "users"
+ ],
+ requireCredential: true,
+ kind: "write:user-groups",
+ description: "Invite a user to an existing group.",
+ errors: {
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "583f8bc0-8eee-4b78-9299-1e14fc91e409"
+ },
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "da52de61-002c-475b-90e1-ba64f9cf13a8"
+ },
+ alreadyAdded: {
+ message: "That user has already been added to that group.",
+ code: "ALREADY_ADDED",
+ id: "7e35c6a0-39b2-4488-aea6-6ee20bd5da2c"
+ },
+ alreadyInvited: {
+ message: "That user has already been invited to that group.",
+ code: "ALREADY_INVITED",
+ id: "ee0f58b4-b529-4d13-b761-b9a3e69f97e6"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ groupId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "groupId",
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ // Fetch the group
+ const userGroup = await UserGroups.findOneBy({
+ id: ps.groupId,
+ userId: me.id
+ });
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
+ // Fetch the user
+ const user = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ const joining = await UserGroupJoinings.findOneBy({
+ userGroupId: userGroup.id,
+ userId: user.id
+ });
+ if (joining) {
+ throw new ApiError(meta.errors.alreadyAdded);
+ }
+ const existInvitation = await UserGroupInvitations.findOneBy({
+ userGroupId: userGroup.id,
+ userId: user.id
+ });
+ if (existInvitation) {
+ throw new ApiError(meta.errors.alreadyInvited);
+ }
+ const invitation = await UserGroupInvitations.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ userGroupId: userGroup.id
+ }).then((x)=>UserGroupInvitations.findOneByOrFail(x.identifiers[0]));
+ // 通知を作成
+ createNotification(user.id, "groupInvited", {
+ notifierId: me.id,
+ userGroupInvitationId: invitation.id
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/users/groups/joined.js b/packages/backend/built/server/api/endpoints/users/groups/joined.js
new file mode 100644
index 0000000..09c862c
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/groups/joined.js
@@ -0,0 +1,40 @@
+import { Not, In } from "typeorm";
+import { UserGroups, UserGroupJoinings } from "../../../../../models/index.js";
+import define from "../../../define.js";
+export const meta = {
+ tags: [
+ "groups",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "read:user-groups",
+ description: "List the groups that the authenticated user is a member of.",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserGroup"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const ownedGroups = await UserGroups.findBy({
+ userId: me.id
+ });
+ const joinings = await UserGroupJoinings.findBy({
+ userId: me.id,
+ ...ownedGroups.length > 0 ? {
+ userGroupId: Not(In(ownedGroups.map((x)=>x.id)))
+ } : {}
+ });
+ return await Promise.all(joinings.map((x)=>UserGroups.pack(x.userGroupId)));
+});
diff --git a/packages/backend/built/server/api/endpoints/users/groups/leave.js b/packages/backend/built/server/api/endpoints/users/groups/leave.js
new file mode 100644
index 0000000..77ff39b
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/groups/leave.js
@@ -0,0 +1,52 @@
+import { UserGroups, UserGroupJoinings } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ tags: [
+ "groups",
+ "users"
+ ],
+ requireCredential: true,
+ kind: "write:user-groups",
+ description: "Leave a group. The owner of a group can not leave. They must transfer ownership or delete the group instead.",
+ errors: {
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "62780270-1f67-5dc0-daca-3eb510612e31"
+ },
+ youAreOwner: {
+ message: "Your are the owner.",
+ code: "YOU_ARE_OWNER",
+ id: "b6d6e0c2-ef8a-9bb8-653d-79f4a3107c69"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ groupId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "groupId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ // Fetch the group
+ const userGroup = await UserGroups.findOneBy({
+ id: ps.groupId
+ });
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
+ if (me.id === userGroup.userId) {
+ throw new ApiError(meta.errors.youAreOwner);
+ }
+ await UserGroupJoinings.delete({
+ userGroupId: userGroup.id,
+ userId: me.id
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/users/groups/owned.js b/packages/backend/built/server/api/endpoints/users/groups/owned.js
new file mode 100644
index 0000000..855e20c
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/groups/owned.js
@@ -0,0 +1,33 @@
+import { UserGroups } from "../../../../../models/index.js";
+import define from "../../../define.js";
+export const meta = {
+ tags: [
+ "groups",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "read:user-groups",
+ description: "List the groups that the authenticated user is the owner of.",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserGroup"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const userGroups = await UserGroups.findBy({
+ userId: me.id
+ });
+ return await Promise.all(userGroups.map((x)=>UserGroups.pack(x)));
+});
diff --git a/packages/backend/built/server/api/endpoints/users/groups/pull.js b/packages/backend/built/server/api/endpoints/users/groups/pull.js
new file mode 100644
index 0000000..36d8d87
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/groups/pull.js
@@ -0,0 +1,70 @@
+import { UserGroups, UserGroupJoinings } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { getUser } from "../../../common/getters.js";
+export const meta = {
+ tags: [
+ "groups",
+ "users"
+ ],
+ requireCredential: true,
+ kind: "write:user-groups",
+ description: "Removes a specified user from a group. The owner can not be removed.",
+ errors: {
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "4662487c-05b1-4b78-86e5-fd46998aba74"
+ },
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "0b5cc374-3681-41da-861e-8bc1146f7a55"
+ },
+ isOwner: {
+ message: "The user is the owner.",
+ code: "IS_OWNER",
+ id: "1546eed5-4414-4dea-81c1-b0aec4f6d2af"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ groupId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "groupId",
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ // Fetch the group
+ const userGroup = await UserGroups.findOneBy({
+ id: ps.groupId,
+ userId: me.id
+ });
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
+ // Fetch the user
+ const user = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ if (user.id === userGroup.userId) {
+ throw new ApiError(meta.errors.isOwner);
+ }
+ // Pull the user
+ await UserGroupJoinings.delete({
+ userGroupId: userGroup.id,
+ userId: user.id
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/users/groups/show-by-username.js b/packages/backend/built/server/api/endpoints/users/groups/show-by-username.js
new file mode 100644
index 0000000..8b331a9
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/groups/show-by-username.js
@@ -0,0 +1,44 @@
+import { UserGroups } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ tags: [
+ "groups"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserGroup"
+ },
+ errors: {
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "e2eb1dcc-d778-4a5f-b3c1-f7a0c7c3f9ad"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ username: {
+ type: "string",
+ minLength: 1,
+ maxLength: 64
+ }
+ },
+ required: [
+ "username"
+ ]
+};
+export default define(meta, paramDef, async (ps)=>{
+ const group = await UserGroups.findOneBy({
+ username: ps.username.toLowerCase(),
+ isPrivate: false
+ });
+ if (!group) throw new ApiError(meta.errors.noSuchGroup);
+ return UserGroups.pack(group);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/groups/show.js b/packages/backend/built/server/api/endpoints/users/groups/show.js
new file mode 100644
index 0000000..5b9d0b6
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/groups/show.js
@@ -0,0 +1,54 @@
+import { UserGroups, UserGroupJoinings } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ tags: [
+ "groups",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "read:user-groups",
+ description: "Show the properties of a group.",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserGroup"
+ },
+ errors: {
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "ea04751e-9b7e-487b-a509-330fb6bd6b9b"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ groupId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "groupId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ // Fetch the group
+ const userGroup = await UserGroups.findOneBy({
+ id: ps.groupId
+ });
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
+ const joining = await UserGroupJoinings.findOneBy({
+ userId: me.id,
+ userGroupId: userGroup.id
+ });
+ if (joining == null && userGroup.userId !== me.id) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
+ return await UserGroups.pack(userGroup);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/groups/transfer.js b/packages/backend/built/server/api/endpoints/users/groups/transfer.js
new file mode 100644
index 0000000..dd7dfa8
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/groups/transfer.js
@@ -0,0 +1,79 @@
+import { UserGroups, UserGroupJoinings } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { getUser } from "../../../common/getters.js";
+export const meta = {
+ tags: [
+ "groups",
+ "users"
+ ],
+ requireCredential: true,
+ kind: "write:user-groups",
+ description: "Transfer ownership of a group from the authenticated user to another user.",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserGroup"
+ },
+ errors: {
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "8e31d36b-2f88-4ccd-a438-e2d78a9162db"
+ },
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "711f7ebb-bbb9-4dfa-b540-b27809fed5e9"
+ },
+ noSuchGroupMember: {
+ message: "No such group member.",
+ code: "NO_SUCH_GROUP_MEMBER",
+ id: "d31bebee-196d-42c2-9a3e-9474d4be6cc4"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ groupId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "groupId",
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ // Fetch the group
+ const userGroup = await UserGroups.findOneBy({
+ id: ps.groupId,
+ userId: me.id
+ });
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
+ // Fetch the user
+ const user = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ const joining = await UserGroupJoinings.findOneBy({
+ userGroupId: userGroup.id,
+ userId: user.id
+ });
+ if (joining == null) {
+ throw new ApiError(meta.errors.noSuchGroupMember);
+ }
+ await UserGroups.update(userGroup.id, {
+ userId: ps.userId
+ });
+ return await UserGroups.pack(userGroup.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/groups/update.js b/packages/backend/built/server/api/endpoints/users/groups/update.js
new file mode 100644
index 0000000..fb14f9a
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/groups/update.js
@@ -0,0 +1,102 @@
+import { DriveFiles, UserGroups } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ tags: [
+ "groups"
+ ],
+ requireCredential: true,
+ kind: "write:user-groups",
+ description: "Update the properties of a group.",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserGroup"
+ },
+ errors: {
+ noSuchGroup: {
+ message: "No such group.",
+ code: "NO_SUCH_GROUP",
+ id: "9081cda3-7a9e-4fac-a6ce-908d70f282f6"
+ },
+ noSuchFile: {
+ message: "No such file.",
+ code: "NO_SUCH_FILE",
+ id: "45861e5e-75d4-4353-b8a9-a72fd2b4c62f"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ groupId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ name: {
+ type: "string",
+ minLength: 1,
+ maxLength: 100
+ },
+ username: {
+ type: "string",
+ pattern: "^[a-zA-Z0-9_]{1,64}$",
+ nullable: true
+ },
+ allowCalls: {
+ type: "boolean"
+ },
+ iconFileId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ },
+ symbolFileId: {
+ type: "string",
+ format: "misskey:id",
+ nullable: true
+ }
+ },
+ required: [
+ "groupId",
+ "name"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ // Fetch the group
+ const userGroup = await UserGroups.findOneBy({
+ id: ps.groupId,
+ userId: me.id
+ });
+ if (userGroup == null) {
+ throw new ApiError(meta.errors.noSuchGroup);
+ }
+ const updates = {
+ name: ps.name
+ };
+ if (ps.username !== undefined) updates.username = ps.username?.toLowerCase() ?? null;
+ if (typeof ps.allowCalls === "boolean") updates.allowCalls = ps.allowCalls;
+ if (ps.iconFileId !== undefined) {
+ if (ps.iconFileId) {
+ const file = await DriveFiles.findOneBy({
+ id: ps.iconFileId,
+ userId: me.id
+ });
+ if (!file) throw new ApiError(meta.errors.noSuchFile);
+ }
+ updates.iconFileId = ps.iconFileId;
+ }
+ if (ps.symbolFileId !== undefined) {
+ if (ps.symbolFileId) {
+ const file = await DriveFiles.findOneBy({
+ id: ps.symbolFileId,
+ userId: me.id
+ });
+ if (!file) throw new ApiError(meta.errors.noSuchFile);
+ }
+ updates.symbolFileId = ps.symbolFileId;
+ }
+ await UserGroups.update(userGroup.id, updates);
+ return await UserGroups.pack(userGroup.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/lists/create.js b/packages/backend/built/server/api/endpoints/users/lists/create.js
new file mode 100644
index 0000000..5038dcb
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/lists/create.js
@@ -0,0 +1,39 @@
+import { UserLists } from "../../../../../models/index.js";
+import { genId } from "../../../../../misc/gen-id.js";
+import define from "../../../define.js";
+export const meta = {
+ tags: [
+ "lists"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ description: "Create a new list of users.",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserList"
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ name: {
+ type: "string",
+ minLength: 1,
+ maxLength: 100
+ }
+ },
+ required: [
+ "name"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const userList = await UserLists.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ name: ps.name
+ }).then((x)=>UserLists.findOneByOrFail(x.identifiers[0]));
+ return await UserLists.pack(userList);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/lists/delete-all.js b/packages/backend/built/server/api/endpoints/users/lists/delete-all.js
new file mode 100644
index 0000000..6c2b287
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/lists/delete-all.js
@@ -0,0 +1,34 @@
+import { UserLists } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ tags: [
+ "lists"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ description: "Delete all lists of users.",
+ errors: {
+ noSuchList: {
+ message: "No such list.",
+ code: "NO_SUCH_LIST",
+ id: "78436795-db79-42f5-b1e2-55ea2cf19166"
+ }
+ }
+};
+export const paramDef = {
+ type: "object"
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ while(await UserLists.findOneBy({
+ userId: user.id
+ }) != null){
+ const userList = await UserLists.findOneBy({
+ userId: user.id
+ });
+ if (userList == null) {
+ throw new ApiError(meta.errors.noSuchList);
+ }
+ await UserLists.delete(userList.id);
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/users/lists/delete.js b/packages/backend/built/server/api/endpoints/users/lists/delete.js
new file mode 100644
index 0000000..ea7e85c
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/lists/delete.js
@@ -0,0 +1,40 @@
+import { UserLists } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ tags: [
+ "lists"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ description: "Delete an existing list of users.",
+ errors: {
+ noSuchList: {
+ message: "No such list.",
+ code: "NO_SUCH_LIST",
+ id: "78436795-db79-42f5-b1e2-55ea2cf19166"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ listId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "listId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const userList = await UserLists.findOneBy({
+ id: ps.listId,
+ userId: user.id
+ });
+ if (userList == null) {
+ throw new ApiError(meta.errors.noSuchList);
+ }
+ await UserLists.delete(userList.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/lists/list.js b/packages/backend/built/server/api/endpoints/users/lists/list.js
new file mode 100644
index 0000000..3defe25
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/lists/list.js
@@ -0,0 +1,33 @@
+import { UserLists } from "../../../../../models/index.js";
+import define from "../../../define.js";
+export const meta = {
+ tags: [
+ "lists",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "read:account",
+ description: "Show all lists that the authenticated user has created.",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserList"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {},
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const userLists = await UserLists.findBy({
+ userId: me.id
+ });
+ return await Promise.all(userLists.map((x)=>UserLists.pack(x)));
+});
diff --git a/packages/backend/built/server/api/endpoints/users/lists/pull.js b/packages/backend/built/server/api/endpoints/users/lists/pull.js
new file mode 100644
index 0000000..4177ace
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/lists/pull.js
@@ -0,0 +1,60 @@
+import { UserLists } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { getUser } from "../../../common/getters.js";
+import { pullUserFromUserList } from "../../../../../services/user-list/pull.js";
+export const meta = {
+ tags: [
+ "lists",
+ "users"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ description: "Remove a user from a list.",
+ errors: {
+ noSuchList: {
+ message: "No such list.",
+ code: "NO_SUCH_LIST",
+ id: "7f44670e-ab16-43b8-b4c1-ccd2ee89cc02"
+ },
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "588e7f72-c744-4a61-b180-d354e912bda2"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ listId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "listId",
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ // Fetch the list
+ const userList = await UserLists.findOneBy({
+ id: ps.listId,
+ userId: me.id
+ });
+ if (userList == null) {
+ throw new ApiError(meta.errors.noSuchList);
+ }
+ // Fetch the user
+ const user = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ // Pull the user
+ await pullUserFromUserList(user, userList);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/lists/push.js b/packages/backend/built/server/api/endpoints/users/lists/push.js
new file mode 100644
index 0000000..3dcbc9c
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/lists/push.js
@@ -0,0 +1,105 @@
+import { pushUserToUserList } from "../../../../../services/user-list/push.js";
+import { UserLists, UserListJoinings, Blockings, Followings } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { getUser } from "../../../common/getters.js";
+export const meta = {
+ tags: [
+ "lists",
+ "users"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ description: "Add a user to an existing list.",
+ errors: {
+ noSuchList: {
+ message: "No such list.",
+ code: "NO_SUCH_LIST",
+ id: "2214501d-ac96-4049-b717-91e42272a711"
+ },
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "a89abd3d-f0bc-4cce-beb1-2f446f4f1e6a"
+ },
+ alreadyAdded: {
+ message: "That user has already been added to that list.",
+ code: "ALREADY_ADDED",
+ id: "1de7c884-1595-49e9-857e-61f12f4d4fc5"
+ },
+ youHaveBeenBlocked: {
+ message: "You cannot push this user because you have been blocked by this user.",
+ code: "YOU_HAVE_BEEN_BLOCKED",
+ id: "990232c5-3f9d-4d83-9f3f-ef27b6332a4b"
+ },
+ notFollowing: {
+ message: "You cannot push this user because you are not following this user.",
+ code: "NOT_FOLLOWING",
+ id: "0a2e4d73-fe61-41fb-822c-d365ec81ba2a"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ listId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "listId",
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ // Fetch the list
+ const userList = await UserLists.findOneBy({
+ id: ps.listId,
+ userId: me.id
+ });
+ if (!userList) {
+ throw new ApiError(meta.errors.noSuchList);
+ }
+ // Fetch the user
+ const user = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ // Check blocking and following status
+ if (user.id !== me.id) {
+ const isBlocked = await Blockings.exist({
+ where: {
+ blockerId: user.id,
+ blockeeId: me.id
+ }
+ });
+ const isFollowed = await Followings.exist({
+ where: {
+ followerId: me.id,
+ followeeId: user.id
+ }
+ });
+ if (isBlocked) {
+ throw new ApiError(meta.errors.youHaveBeenBlocked);
+ }
+ if (!isFollowed) {
+ throw new ApiError(meta.errors.notFollowing);
+ }
+ }
+ const exist = await UserListJoinings.exist({
+ where: {
+ userListId: ps.listId,
+ userId: user.id
+ }
+ });
+ if (exist) {
+ throw new ApiError(meta.errors.alreadyAdded);
+ }
+ // Push the user
+ await pushUserToUserList(user, userList);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/lists/show.js b/packages/backend/built/server/api/endpoints/users/lists/show.js
new file mode 100644
index 0000000..556b66e
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/lists/show.js
@@ -0,0 +1,48 @@
+import { UserLists } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+export const meta = {
+ tags: [
+ "lists",
+ "account"
+ ],
+ requireCredential: true,
+ kind: "read:account",
+ description: "Show the properties of a list.",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserList"
+ },
+ errors: {
+ noSuchList: {
+ message: "No such list.",
+ code: "NO_SUCH_LIST",
+ id: "7bc05c21-1d7a-41ae-88f1-66820f4dc686"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ listId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "listId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ // Fetch the list
+ const userList = await UserLists.findOneBy({
+ id: ps.listId,
+ userId: me.id
+ });
+ if (!userList) {
+ throw new ApiError(meta.errors.noSuchList);
+ }
+ return await UserLists.pack(userList);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/lists/update.js b/packages/backend/built/server/api/endpoints/users/lists/update.js
new file mode 100644
index 0000000..db5a1f0
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/lists/update.js
@@ -0,0 +1,71 @@
+import { UserListJoinings, UserLists } from "../../../../../models/index.js";
+import define from "../../../define.js";
+import { ApiError } from "../../../error.js";
+import { publishUserEvent } from "../../../../../services/stream.js";
+export const meta = {
+ tags: [
+ "lists"
+ ],
+ requireCredential: true,
+ kind: "write:account",
+ description: "Update the properties of a list.",
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserList"
+ },
+ errors: {
+ noSuchList: {
+ message: "No such list.",
+ code: "NO_SUCH_LIST",
+ id: "796666fe-3dff-4d39-becb-8a5932c1d5b7"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ listId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ name: {
+ type: "string",
+ minLength: 1,
+ maxLength: 100
+ },
+ hideFromHomeTl: {
+ type: "boolean",
+ nullable: true
+ }
+ },
+ required: [
+ "listId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ // Fetch the list
+ const userList = await UserLists.findOneBy({
+ id: ps.listId,
+ userId: user.id
+ });
+ if (userList == null) {
+ throw new ApiError(meta.errors.noSuchList);
+ }
+ const partial = {
+ name: ps.name ?? undefined,
+ hideFromHomeTl: ps.hideFromHomeTl ?? undefined
+ };
+ if (Object.keys(partial).length > 0) await UserLists.update(userList.id, partial);
+ if (ps.hideFromHomeTl != null) {
+ UserListJoinings.findBy({
+ userListId: ps.listId
+ }).then((members)=>{
+ for (const member of members){
+ publishUserEvent(userList.userId, ps.hideFromHomeTl ? "userHidden" : "userUnhidden", member.userId);
+ }
+ });
+ }
+ return await UserLists.pack(userList.id);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/notes.js b/packages/backend/built/server/api/endpoints/users/notes.js
new file mode 100644
index 0000000..62cbb5d
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/notes.js
@@ -0,0 +1,140 @@
+import { Brackets } from "typeorm";
+import { Notes } from "../../../../models/index.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+import { getUser } from "../../common/getters.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+export const meta = {
+ tags: [
+ "users",
+ "notes"
+ ],
+ requireCredentialPrivateMode: true,
+ description: "Show all notes that this user created.",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Note"
+ }
+ },
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "27e494ba-2ac2-48e8-893b-10d4d8c2387b"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ includeReplies: {
+ type: "boolean",
+ default: true
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ sinceDate: {
+ type: "integer"
+ },
+ untilDate: {
+ type: "integer"
+ },
+ includeMyRenotes: {
+ type: "boolean",
+ default: true
+ },
+ withFiles: {
+ type: "boolean",
+ default: false
+ },
+ fileType: {
+ type: "array",
+ items: {
+ type: "string"
+ }
+ },
+ excludeNsfw: {
+ type: "boolean",
+ default: false
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ // Lookup user
+ const user = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ //#region Construct query
+ const query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate).andWhere("note.userId = :userId", {
+ userId: user.id
+ }).innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser");
+ generateVisibilityQuery(query, me);
+ if (me) {
+ generateMutedUserQuery(query, me, user);
+ generateBlockedUserQuery(query, me);
+ }
+ if (ps.withFiles) {
+ query.andWhere("note.fileIds != '{}'");
+ }
+ if (ps.fileType != null) {
+ query.andWhere("note.fileIds != '{}'");
+ query.andWhere(new Brackets((qb)=>{
+ for (const type of ps.fileType){
+ const i = ps.fileType.indexOf(type);
+ qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, {
+ [`type${i}`]: type
+ });
+ }
+ }));
+ if (ps.excludeNsfw) {
+ query.andWhere("note.cw IS NULL");
+ query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)');
+ }
+ }
+ if (!ps.includeReplies) {
+ query.andWhere("note.replyId IS NULL");
+ }
+ if (ps.includeMyRenotes === false) {
+ query.andWhere(new Brackets((qb)=>{
+ qb.orWhere("note.userId != :userId", {
+ userId: user.id
+ });
+ qb.orWhere("note.renoteId IS NULL");
+ qb.orWhere("note.text IS NOT NULL");
+ qb.orWhere("note.fileIds != '{}'");
+ qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+ }));
+ }
+ //#endregion
+ const timeline = await query.take(ps.limit).getMany();
+ return await Notes.packMany(timeline, me);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/pages.js b/packages/backend/built/server/api/endpoints/users/pages.js
new file mode 100644
index 0000000..0fb608c
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/pages.js
@@ -0,0 +1,55 @@
+import { Pages } from "../../../../models/index.js";
+import define from "../../define.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+export const meta = {
+ tags: [
+ "users",
+ "pages"
+ ],
+ requireCredentialPrivateMode: true,
+ description: "Show all pages this user created.",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "Page"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, user)=>{
+ const query = makePaginationQuery(Pages.createQueryBuilder("page"), ps.sinceId, ps.untilId).andWhere("page.userId = :userId", {
+ userId: ps.userId
+ }).andWhere("page.visibility = 'public'").andWhere("page.isPublic = true");
+ const pages = await query.take(ps.limit).getMany();
+ return await Pages.packMany(pages);
+});
diff --git a/packages/backend/built/server/api/endpoints/users/reactions.js b/packages/backend/built/server/api/endpoints/users/reactions.js
new file mode 100644
index 0000000..e4247cf
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/reactions.js
@@ -0,0 +1,80 @@
+import { NoteReactions, UserProfiles } from "../../../../models/index.js";
+import define from "../../define.js";
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+import { ApiError } from "../../error.js";
+export const meta = {
+ tags: [
+ "users",
+ "reactions"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ description: "Show all reactions this user made.",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "NoteReaction"
+ }
+ },
+ errors: {
+ reactionsNotPublic: {
+ message: "Reactions of the user is not public.",
+ code: "REACTIONS_NOT_PUBLIC",
+ id: "673a7dd2-6924-1093-e0c0-e68456ceae5c"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ sinceId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ untilId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ sinceDate: {
+ type: "integer"
+ },
+ untilDate: {
+ type: "integer"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: ps.userId
+ });
+ if (me.id !== ps.userId && !profile.publicReactions) {
+ throw new ApiError(meta.errors.reactionsNotPublic);
+ }
+ const query = makePaginationQuery(NoteReactions.createQueryBuilder("reaction"), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate).andWhere("reaction.userId = :userId", {
+ userId: ps.userId
+ }).leftJoinAndSelect("reaction.note", "note");
+ generateVisibilityQuery(query, me);
+ const reactions = await query.take(ps.limit).getMany();
+ return await NoteReactions.packMany(reactions, me, {
+ withNote: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/users/recommendation.js b/packages/backend/built/server/api/endpoints/users/recommendation.js
new file mode 100644
index 0000000..04bc037
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/recommendation.js
@@ -0,0 +1,59 @@
+import { Users, Followings } from "../../../../models/index.js";
+import define from "../../define.js";
+import { generateMutedUserQueryForUsers } from "../../common/generate-muted-user-query.js";
+import { generateBlockedUserQuery, generateBlockQueryForUsers } from "../../common/generate-block-query.js";
+import { DAY } from "../../../../const.js";
+export const meta = {
+ tags: [
+ "users"
+ ],
+ requireCredential: true,
+ kind: "read:account",
+ description: "Show users that the authenticated user might be interested to follow.",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "UserDetailed"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ offset: {
+ type: "integer",
+ default: 0
+ }
+ },
+ required: []
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const query = Users.createQueryBuilder("user").where("user.isLocked = FALSE").andWhere("user.isExplorable = TRUE").andWhere("user.host IS NULL").andWhere("user.updatedAt >= :date", {
+ date: new Date(Date.now() - 7 * DAY)
+ }).andWhere("user.id != :meId", {
+ meId: me.id
+ }).orderBy("user.followersCount", "DESC");
+ generateMutedUserQueryForUsers(query, me);
+ generateBlockQueryForUsers(query, me);
+ generateBlockedUserQuery(query, me);
+ const followingQuery = Followings.createQueryBuilder("following").select("following.followeeId").where("following.followerId = :followerId", {
+ followerId: me.id
+ });
+ query.andWhere(`user.id NOT IN (${followingQuery.getQuery()})`);
+ query.setParameters(followingQuery.getParameters());
+ const users = await query.take(ps.limit).skip(ps.offset).getMany();
+ return await Users.packMany(users, me, {
+ detail: true
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/users/relation.js b/packages/backend/built/server/api/endpoints/users/relation.js
new file mode 100644
index 0000000..2669981
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/relation.js
@@ -0,0 +1,152 @@
+import { Users } from "../../../../models/index.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "users"
+ ],
+ requireCredential: true,
+ description: "Show the different kinds of relations between the authenticated user and the specified user(s).",
+ res: {
+ optional: false,
+ nullable: false,
+ oneOf: [
+ {
+ type: "object",
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ isFollowing: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ hasPendingFollowRequestFromYou: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ hasPendingFollowRequestToYou: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ isFollowed: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ isBlocking: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ isBlocked: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ isMuted: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ isRenoteMuted: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ }
+ }
+ },
+ {
+ type: "array",
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ id: {
+ type: "string",
+ optional: false,
+ nullable: false,
+ format: "id"
+ },
+ isFollowing: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ hasPendingFollowRequestFromYou: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ hasPendingFollowRequestToYou: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ isFollowed: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ isBlocking: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ isBlocked: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ isMuted: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ },
+ isRenoteMuted: {
+ type: "boolean",
+ optional: false,
+ nullable: false
+ }
+ }
+ }
+ }
+ ]
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ anyOf: [
+ {
+ type: "string",
+ format: "misskey:id"
+ },
+ {
+ type: "array",
+ items: {
+ type: "string",
+ format: "misskey:id"
+ }
+ }
+ ]
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const ids = Array.isArray(ps.userId) ? ps.userId : [
+ ps.userId
+ ];
+ const relations = await Promise.all(ids.map((id)=>Users.getRelation(me.id, id)));
+ return Array.isArray(ps.userId) ? relations : relations[0];
+});
diff --git a/packages/backend/built/server/api/endpoints/users/report-abuse.js b/packages/backend/built/server/api/endpoints/users/report-abuse.js
new file mode 100644
index 0000000..7b966b3
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/report-abuse.js
@@ -0,0 +1,98 @@
+import * as sanitizeHtml from "sanitize-html";
+import { publishAdminStream } from "../../../../services/stream.js";
+import { AbuseUserReports, Users } from "../../../../models/index.js";
+import { genId } from "../../../../misc/gen-id.js";
+import { sendEmail } from "../../../../services/send-email.js";
+import { fetchMeta } from "../../../../misc/fetch-meta.js";
+import { getUser } from "../../common/getters.js";
+import { ApiError } from "../../error.js";
+import define from "../../define.js";
+export const meta = {
+ tags: [
+ "users"
+ ],
+ requireCredential: true,
+ description: "File a report.",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "1acefcb5-0959-43fd-9685-b48305736cb5"
+ },
+ cannotReportYourself: {
+ message: "Cannot report yourself.",
+ code: "CANNOT_REPORT_YOURSELF",
+ id: "1e13149e-b1e8-43cf-902e-c01dbfcb202f"
+ },
+ cannotReportAdmin: {
+ message: "Cannot report the admin.",
+ code: "CANNOT_REPORT_THE_ADMIN",
+ id: "35e166f5-05fb-4f87-a2d5-adb42676d48f"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ },
+ comment: {
+ type: "string",
+ minLength: 1,
+ maxLength: 2048
+ }
+ },
+ required: [
+ "userId",
+ "comment"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ // Lookup user
+ const user = await getUser(ps.userId).catch((e)=>{
+ if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
+ throw e;
+ });
+ if (user.id === me.id) {
+ throw new ApiError(meta.errors.cannotReportYourself);
+ }
+ if (user.isAdmin) {
+ throw new ApiError(meta.errors.cannotReportAdmin);
+ }
+ const report = await AbuseUserReports.insert({
+ id: genId(),
+ createdAt: new Date(),
+ targetUserId: user.id,
+ targetUserHost: user.host,
+ reporterId: me.id,
+ reporterHost: null,
+ comment: ps.comment
+ }).then((x)=>AbuseUserReports.findOneByOrFail(x.identifiers[0]));
+ // Publish event to moderators
+ setImmediate(async ()=>{
+ const moderators = await Users.find({
+ where: [
+ {
+ isAdmin: true
+ },
+ {
+ isModerator: true
+ }
+ ]
+ });
+ for (const moderator of moderators){
+ publishAdminStream(moderator.id, "newAbuseUserReport", {
+ id: report.id,
+ targetUserId: report.targetUserId,
+ reporterId: report.reporterId,
+ comment: report.comment
+ });
+ }
+ const meta = await fetchMeta();
+ if (meta.email) {
+ sendEmail(meta.email, "New abuse report", sanitizeHtml(ps.comment), sanitizeHtml(ps.comment));
+ }
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/users/search-by-username-and-host.js b/packages/backend/built/server/api/endpoints/users/search-by-username-and-host.js
new file mode 100644
index 0000000..aa89fce
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/search-by-username-and-host.js
@@ -0,0 +1,123 @@
+import { Brackets } from "typeorm";
+import { Followings, Users } from "../../../../models/index.js";
+import define from "../../define.js";
+import { sqlLikeEscape } from "../../../../misc/sql-like-escape.js";
+import { generateMinorBadgeUserVisibilityQuery } from "../../common/generate-minor-badge-visibility-query.js";
+export const meta = {
+ tags: [
+ "users"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ description: "Search for a user by username and/or host.",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "User"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ username: {
+ type: "string",
+ nullable: true
+ },
+ host: {
+ type: "string",
+ nullable: true
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ maxDaysSinceLastActive: {
+ type: "integer",
+ minimum: 1,
+ maximum: 1000,
+ nullable: true
+ },
+ detail: {
+ type: "boolean",
+ default: true
+ }
+ },
+ anyOf: [
+ {
+ required: [
+ "username"
+ ]
+ },
+ {
+ required: [
+ "host"
+ ]
+ }
+ ]
+};
+// TODO: avatar,bannerをJOINしたいけどエラーになる
+export default define(meta, paramDef, async (ps, me)=>{
+ const activeThreshold = ps.maxDaysSinceLastActive ? new Date(Date.now() - 1000 * 60 * 60 * 24 * ps.maxDaysSinceLastActive) : null;
+ if (ps.host) {
+ const q = Users.createQueryBuilder("user").where("user.isSuspended = FALSE").andWhere("user.host LIKE :host", {
+ host: `${sqlLikeEscape(ps.host.toLowerCase())}%`
+ });
+ if (ps.username) {
+ q.andWhere("user.usernameLower LIKE :username", {
+ username: `${sqlLikeEscape(ps.username.toLowerCase())}%`
+ });
+ }
+ q.andWhere("user.updatedAt IS NOT NULL");
+ generateMinorBadgeUserVisibilityQuery(q, me);
+ q.orderBy("user.updatedAt", "DESC");
+ const users = await q.take(ps.limit).getMany();
+ return await Users.packMany(users, me, {
+ detail: ps.detail
+ });
+ } else if (ps.username) {
+ let users = [];
+ if (me) {
+ const followingQuery = Followings.createQueryBuilder("following").select("following.followeeId").where("following.followerId = :followerId", {
+ followerId: me.id
+ });
+ const query = Users.createQueryBuilder("user").where(`user.id IN (${followingQuery.getQuery()})`).andWhere("user.isSuspended = FALSE").andWhere("user.usernameLower LIKE :username", {
+ username: `${sqlLikeEscape(ps.username.toLowerCase())}%`
+ });
+ if (activeThreshold) {
+ query.andWhere(new Brackets((qb)=>{
+ qb.where("user.updatedAt IS NULL").orWhere("user.updatedAt > :activeThreshold", {
+ activeThreshold: activeThreshold
+ });
+ }));
+ }
+ generateMinorBadgeUserVisibilityQuery(query, me);
+ query.setParameters(followingQuery.getParameters());
+ users = await query.orderBy("user.usernameLower", "ASC").take(ps.limit).getMany();
+ if (users.length < ps.limit) {
+ const otherQuery = await Users.createQueryBuilder("user").where(`user.id NOT IN (${followingQuery.getQuery()})`).andWhere("user.isSuspended = FALSE").andWhere("user.usernameLower LIKE :username", {
+ username: `${sqlLikeEscape(ps.username.toLowerCase())}%`
+ }).andWhere("user.updatedAt IS NOT NULL");
+ generateMinorBadgeUserVisibilityQuery(otherQuery, me);
+ otherQuery.setParameters(followingQuery.getParameters());
+ const otherUsers = await otherQuery.orderBy("user.updatedAt", "DESC").take(ps.limit - users.length).getMany();
+ users = users.concat(otherUsers);
+ }
+ } else {
+ users = await Users.createQueryBuilder("user").where("user.isSuspended = FALSE").andWhere("user.usernameLower LIKE :username", {
+ username: `${sqlLikeEscape(ps.username.toLowerCase())}%`
+ }).andWhere("user.updatedAt IS NOT NULL").andWhere("NOT ('E' = ANY(user.\"minorBadges\"))").orderBy("user.updatedAt", "DESC").take(ps.limit - users.length).getMany();
+ }
+ return await Users.packMany(users, me, {
+ detail: !!ps.detail
+ });
+ }
+ return [];
+});
diff --git a/packages/backend/built/server/api/endpoints/users/search.js b/packages/backend/built/server/api/endpoints/users/search.js
new file mode 100644
index 0000000..51d42e3
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/search.js
@@ -0,0 +1,122 @@
+import { Brackets } from "typeorm";
+import { UserProfiles, Users } from "../../../../models/index.js";
+import define from "../../define.js";
+import { sqlLikeEscape } from "../../../../misc/sql-like-escape.js";
+import { generateMinorBadgeUserVisibilityQuery } from "../../common/generate-minor-badge-visibility-query.js";
+export const meta = {
+ tags: [
+ "users"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ description: "Search for users.",
+ res: {
+ type: "array",
+ optional: false,
+ nullable: false,
+ items: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ ref: "User"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ query: {
+ type: "string"
+ },
+ offset: {
+ type: "integer",
+ default: 0
+ },
+ limit: {
+ type: "integer",
+ minimum: 1,
+ maximum: 100,
+ default: 10
+ },
+ origin: {
+ type: "string",
+ enum: [
+ "local",
+ "remote",
+ "combined"
+ ],
+ default: "combined"
+ },
+ detail: {
+ type: "boolean",
+ default: true
+ }
+ },
+ required: [
+ "query"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const activeThreshold = new Date(Date.now() - 1000 * 60 * 60 * 24 * 30); // 30日
+ const isUsername = ps.query.startsWith("@");
+ let users = [];
+ if (isUsername) {
+ const usernameQuery = Users.createQueryBuilder("user").where("user.usernameLower LIKE :username", {
+ username: `${sqlLikeEscape(ps.query.replace("@", "").toLowerCase())}%`
+ }).andWhere(new Brackets((qb)=>{
+ qb.where("user.updatedAt IS NULL").orWhere("user.updatedAt > :activeThreshold", {
+ activeThreshold: activeThreshold
+ });
+ })).andWhere("user.isSuspended = FALSE");
+ if (ps.origin === "local") {
+ usernameQuery.andWhere("user.host IS NULL");
+ } else if (ps.origin === "remote") {
+ usernameQuery.andWhere("user.host IS NOT NULL");
+ }
+ generateMinorBadgeUserVisibilityQuery(usernameQuery, me);
+ users = await usernameQuery.orderBy("user.updatedAt", "DESC", "NULLS LAST").take(ps.limit).skip(ps.offset).getMany();
+ } else {
+ const nameQuery = Users.createQueryBuilder("user").where(new Brackets((qb)=>{
+ qb.where("user.name ILIKE :query", {
+ query: `%${sqlLikeEscape(ps.query)}%`
+ });
+ // Also search username if it qualifies as username
+ if (Users.validateLocalUsername(ps.query)) {
+ qb.orWhere("user.usernameLower LIKE :username", {
+ username: `%${sqlLikeEscape(ps.query.toLowerCase())}%`
+ });
+ }
+ })).andWhere(new Brackets((qb)=>{
+ qb.where("user.updatedAt IS NULL").orWhere("user.updatedAt > :activeThreshold", {
+ activeThreshold: activeThreshold
+ });
+ })).andWhere("user.isSuspended = FALSE");
+ if (ps.origin === "local") {
+ nameQuery.andWhere("user.host IS NULL");
+ } else if (ps.origin === "remote") {
+ nameQuery.andWhere("user.host IS NOT NULL");
+ }
+ generateMinorBadgeUserVisibilityQuery(nameQuery, me);
+ users = await nameQuery.orderBy("user.updatedAt", "DESC", "NULLS LAST").take(ps.limit).skip(ps.offset).getMany();
+ if (users.length < ps.limit) {
+ const profQuery = UserProfiles.createQueryBuilder("prof").select("prof.userId").where("prof.description ILIKE :query", {
+ query: `%${sqlLikeEscape(ps.query)}%`
+ });
+ if (ps.origin === "local") {
+ profQuery.andWhere("prof.userHost IS NULL");
+ } else if (ps.origin === "remote") {
+ profQuery.andWhere("prof.userHost IS NOT NULL");
+ }
+ const query = Users.createQueryBuilder("user").where(`user.id IN (${profQuery.getQuery()})`).andWhere(new Brackets((qb)=>{
+ qb.where("user.updatedAt IS NULL").orWhere("user.updatedAt > :activeThreshold", {
+ activeThreshold: activeThreshold
+ });
+ })).andWhere("user.isSuspended = FALSE").setParameters(profQuery.getParameters());
+ generateMinorBadgeUserVisibilityQuery(query, me);
+ users = users.concat(await query.orderBy("user.updatedAt", "DESC", "NULLS LAST").take(ps.limit).skip(ps.offset).getMany());
+ }
+ }
+ return await Users.packMany(users, me, {
+ detail: ps.detail
+ });
+});
diff --git a/packages/backend/built/server/api/endpoints/users/show.js b/packages/backend/built/server/api/endpoints/users/show.js
new file mode 100644
index 0000000..d4d1070
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/show.js
@@ -0,0 +1,154 @@
+import { In, IsNull } from "typeorm";
+import { resolveUser } from "../../../../remote/resolve-user.js";
+import { Users } from "../../../../models/index.js";
+import define from "../../define.js";
+import { apiLogger } from "../../logger.js";
+import { ApiError } from "../../error.js";
+import { fetchMeta } from "../../../../misc/fetch-meta.js";
+import { shouldHideEUsersFor } from "../../common/generate-minor-badge-visibility-query.js";
+export const meta = {
+ tags: [
+ "users"
+ ],
+ // TODO: determine if should allow this in private mode or to create a new endpoint just for 2fa
+ requireCredential: false,
+ requireCredentialPrivateMode: false,
+ description: "Show the properties of a user.",
+ res: {
+ optional: false,
+ nullable: false,
+ oneOf: [
+ {
+ type: "object",
+ ref: "UserDetailed"
+ },
+ {
+ type: "array",
+ items: {
+ type: "object",
+ ref: "UserDetailed"
+ }
+ }
+ ]
+ },
+ errors: {
+ failedToResolveRemoteUser: {
+ message: "Failed to resolve remote user.",
+ code: "FAILED_TO_RESOLVE_REMOTE_USER",
+ id: "ef7b9be4-9cba-4e6f-ab41-90ed171c7d3c",
+ kind: "server"
+ },
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "4362f8dc-731f-4ad8-a694-be5a88922a24"
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ anyOf: [
+ {
+ properties: {
+ userId: {
+ type: "string"
+ }
+ },
+ required: [
+ "userId"
+ ]
+ },
+ {
+ properties: {
+ userIds: {
+ type: "array",
+ uniqueItems: true,
+ items: {
+ type: "string"
+ }
+ }
+ },
+ required: [
+ "userIds"
+ ]
+ },
+ {
+ properties: {
+ username: {
+ type: "string"
+ },
+ host: {
+ type: "string",
+ nullable: true,
+ description: "The local host is represented with `null`."
+ }
+ },
+ required: [
+ "username"
+ ]
+ }
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ let user;
+ const isAdminOrModerator = me && (me.isAdmin || me.isModerator);
+ if (ps.userIds) {
+ if (ps.userIds.length === 0) {
+ return [];
+ }
+ const isUrl = ps.userIds[0].startsWith("http");
+ let users;
+ 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 = [];
+ for (const id of ps.userIds){
+ const res = users.find((x)=>isUrl ? x.uri === id : x.id === id);
+ if (res && !(shouldHideEUsersFor(me) && res.id !== me.id && (res.minorBadges ?? []).includes("E"))) _users.push(res);
+ }
+ return await Promise.all(_users.map((u)=>Users.pack(u, me, {
+ detail: true
+ })));
+ } else {
+ // Lookup user
+ if (typeof ps.host === "string" && typeof ps.username === "string") {
+ user = await resolveUser(ps.username, ps.host).catch((e)=>{
+ apiLogger.warn(`failed to resolve remote user: ${e}`);
+ throw new ApiError(meta.errors.failedToResolveRemoteUser);
+ });
+ } else {
+ const q = ps.userId != null ? ps.userId.startsWith("http") ? {
+ uri: ps.userId
+ } : {
+ id: ps.userId
+ } : {
+ usernameLower: ps.username.toLowerCase(),
+ host: IsNull()
+ };
+ user = await Users.findOneBy(q);
+ }
+ if (user == null || !isAdminOrModerator && user.isSuspended || shouldHideEUsersFor(me) && user.id !== me.id && (user.minorBadges ?? []).includes("E")) {
+ throw new ApiError(meta.errors.noSuchUser);
+ }
+ // apiLogger.debug(`packed (detailed): ${JSON.stringify(await Users.pack(user, me, {detail: true}))}`);
+ // apiLogger.debug(`packed (private): ${JSON.stringify(await Users.pack(user, me, {detail: true, isPrivateMode: true}))}`);
+ const serverMeta = await fetchMeta();
+ return await Users.pack(user, me, {
+ detail: true,
+ isPrivateMode: me !== null ? false : serverMeta.privateMode
+ });
+ }
+});
diff --git a/packages/backend/built/server/api/endpoints/users/stats.js b/packages/backend/built/server/api/endpoints/users/stats.js
new file mode 100644
index 0000000..460c983
--- /dev/null
+++ b/packages/backend/built/server/api/endpoints/users/stats.js
@@ -0,0 +1,204 @@
+import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, Users } from "../../../../models/index.js";
+import { awaitAll } from "../../../../prelude/await-all.js";
+import define from "../../define.js";
+import { ApiError } from "../../error.js";
+export const meta = {
+ tags: [
+ "users"
+ ],
+ requireCredential: false,
+ requireCredentialPrivateMode: true,
+ description: "Show statistics about a user.",
+ errors: {
+ noSuchUser: {
+ message: "No such user.",
+ code: "NO_SUCH_USER",
+ id: "9e638e45-3b25-4ef7-8f95-07e8498f1819"
+ }
+ },
+ res: {
+ type: "object",
+ optional: false,
+ nullable: false,
+ properties: {
+ notesCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ repliesCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ renotesCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ repliedCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ renotedCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ pollVotesCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ pollVotedCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ localFollowingCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ remoteFollowingCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ localFollowersCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ remoteFollowersCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ followingCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ followersCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ sentReactionsCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ receivedReactionsCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ noteFavoritesCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ pageLikesCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ pageLikedCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ driveFilesCount: {
+ type: "integer",
+ optional: false,
+ nullable: false
+ },
+ driveUsage: {
+ type: "integer",
+ optional: false,
+ nullable: false,
+ description: "Drive usage in bytes"
+ }
+ }
+ }
+};
+export const paramDef = {
+ type: "object",
+ properties: {
+ userId: {
+ type: "string",
+ format: "misskey:id"
+ }
+ },
+ required: [
+ "userId"
+ ]
+};
+export default define(meta, paramDef, async (ps, me)=>{
+ const user = await Users.findOneBy({
+ id: ps.userId
+ });
+ if (user == null) {
+ throw new ApiError(meta.errors.noSuchUser);
+ }
+ const result = await awaitAll({
+ notesCount: Notes.createQueryBuilder("note").where("note.userId = :userId", {
+ userId: user.id
+ }).getCount(),
+ repliesCount: Notes.createQueryBuilder("note").where("note.userId = :userId", {
+ userId: user.id
+ }).andWhere("note.replyId IS NOT NULL").getCount(),
+ renotesCount: Notes.createQueryBuilder("note").where("note.userId = :userId", {
+ userId: user.id
+ }).andWhere("note.renoteId IS NOT NULL").getCount(),
+ repliedCount: Notes.createQueryBuilder("note").where("note.replyUserId = :userId", {
+ userId: user.id
+ }).getCount(),
+ renotedCount: Notes.createQueryBuilder("note").where("note.renoteUserId = :userId", {
+ userId: user.id
+ }).getCount(),
+ pollVotesCount: PollVotes.createQueryBuilder("vote").where("vote.userId = :userId", {
+ userId: user.id
+ }).getCount(),
+ pollVotedCount: PollVotes.createQueryBuilder("vote").innerJoin("vote.note", "note").where("note.userId = :userId", {
+ userId: user.id
+ }).getCount(),
+ localFollowingCount: Followings.createQueryBuilder("following").where("following.followerId = :userId", {
+ userId: user.id
+ }).andWhere("following.followeeHost IS NULL").getCount(),
+ remoteFollowingCount: Followings.createQueryBuilder("following").where("following.followerId = :userId", {
+ userId: user.id
+ }).andWhere("following.followeeHost IS NOT NULL").getCount(),
+ localFollowersCount: Followings.createQueryBuilder("following").where("following.followeeId = :userId", {
+ userId: user.id
+ }).andWhere("following.followerHost IS NULL").getCount(),
+ remoteFollowersCount: Followings.createQueryBuilder("following").where("following.followeeId = :userId", {
+ userId: user.id
+ }).andWhere("following.followerHost IS NOT NULL").getCount(),
+ sentReactionsCount: NoteReactions.createQueryBuilder("reaction").where("reaction.userId = :userId", {
+ userId: user.id
+ }).getCount(),
+ receivedReactionsCount: NoteReactions.createQueryBuilder("reaction").innerJoin("reaction.note", "note").where("note.userId = :userId", {
+ userId: user.id
+ }).getCount(),
+ noteFavoritesCount: NoteFavorites.createQueryBuilder("favorite").where("favorite.userId = :userId", {
+ userId: user.id
+ }).getCount(),
+ pageLikesCount: PageLikes.createQueryBuilder("like").where("like.userId = :userId", {
+ userId: user.id
+ }).getCount(),
+ pageLikedCount: PageLikes.createQueryBuilder("like").innerJoin("like.page", "page").where("page.userId = :userId", {
+ userId: user.id
+ }).getCount(),
+ driveFilesCount: DriveFiles.createQueryBuilder("file").where("file.userId = :userId", {
+ userId: user.id
+ }).getCount(),
+ driveUsage: DriveFiles.calcDriveUsageOf(user)
+ });
+ result.followingCount = result.localFollowingCount + result.remoteFollowingCount;
+ result.followersCount = result.localFollowersCount + result.remoteFollowersCount;
+ return result;
+});
diff --git a/packages/backend/built/server/api/error.js b/packages/backend/built/server/api/error.js
new file mode 100644
index 0000000..0b71f45
--- /dev/null
+++ b/packages/backend/built/server/api/error.js
@@ -0,0 +1,24 @@
+export class ApiError extends Error {
+ message;
+ code;
+ id;
+ kind;
+ httpStatusCode;
+ info;
+ constructor(e, info){
+ if (e == null) e = {
+ message: "Internal error occurred. Please contact us if the error persists.",
+ code: "INTERNAL_ERROR",
+ id: "5d37dbcb-891e-41ca-a3d6-e690c97775ac",
+ kind: "server",
+ httpStatusCode: 500
+ };
+ super(e.message);
+ this.message = e.message;
+ this.code = e.code;
+ this.id = e.id;
+ this.kind = e.kind || "client";
+ this.httpStatusCode = e.httpStatusCode;
+ this.info = info;
+ }
+}
diff --git a/packages/backend/built/server/api/index.js b/packages/backend/built/server/api/index.js
new file mode 100644
index 0000000..4c21fa2
--- /dev/null
+++ b/packages/backend/built/server/api/index.js
@@ -0,0 +1,119 @@
+/**
+ * API Server
+ */ import Koa from "koa";
+import Router from "@koa/router";
+import multer from "@koa/multer";
+import bodyParser from "koa-bodyparser";
+import cors from "@koa/cors";
+import { setupMastodonApi } from "./mastodon/index.js";
+import { AccessTokens, Users } from "../../models/index.js";
+import config from "../../config/index.js";
+import endpoints from "./endpoints.js";
+import compatibility from "./compatibility.js";
+import handler from "./api-handler.js";
+import signup from "./private/signup.js";
+import signin from "./private/signin.js";
+import signupPending from "./private/signup-pending.js";
+import verifyEmail from "./private/verify-email.js";
+import discord from "./service/discord.js";
+import github from "./service/github.js";
+import { serverLogger } from "../index.js";
+import { isIgnorableConnectionError } from "../is-ignorable-connection-error.js";
+// Init app
+const app = new Koa();
+app.on("error", (err)=>{
+ if (isIgnorableConnectionError(err)) return;
+ serverLogger.error(err);
+});
+app.use(cors({
+ origin: "*"
+}));
+// No caching
+app.use(async (ctx, next)=>{
+ ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
+ await next();
+});
+// Init router
+const router = new Router();
+const mastoRouter = new Router();
+const errorRouter = new Router();
+// Init multer instance
+const upload = multer({
+ storage: multer.diskStorage({}),
+ limits: {
+ fileSize: config.maxFileSize || 262144000,
+ files: 1
+ }
+});
+router.use(bodyParser({
+ // リクエストが multipart/form-data でない限りはJSONだと見なす
+ detectJSON: (ctx)=>!(ctx.is("multipart/form-data") || ctx.is("application/x-www-form-urlencoded"))
+}));
+setupMastodonApi(mastoRouter);
+/**
+ * Register endpoint handlers
+ */ for (const endpoint of [
+ ...endpoints,
+ ...compatibility
+]){
+ if (endpoint.meta.requireFile) {
+ router.post(`/${endpoint.name}`, upload.single("file"), handler.bind(null, endpoint));
+ } else {
+ // 後方互換性のため
+ if (endpoint.name.includes("-")) {
+ router.post(`/${endpoint.name.replace(/-/g, "_")}`, handler.bind(null, endpoint));
+ if (endpoint.meta.allowGet) {
+ router.get(`/${endpoint.name.replace(/-/g, "_")}`, handler.bind(null, endpoint));
+ } else {
+ router.get(`/${endpoint.name.replace(/-/g, "_")}`, async (ctx)=>{
+ ctx.status = 405;
+ });
+ }
+ }
+ router.post(`/${endpoint.name}`, handler.bind(null, endpoint));
+ if (endpoint.meta.allowGet) {
+ router.get(`/${endpoint.name}`, handler.bind(null, endpoint));
+ } else {
+ router.get(`/${endpoint.name}`, async (ctx)=>{
+ ctx.status = 405;
+ });
+ }
+ }
+}
+router.post("/signup", signup);
+router.post("/signin", signin);
+router.post("/signup-pending", signupPending);
+router.post("/verify-email", verifyEmail);
+router.use(discord.routes());
+router.use(github.routes());
+router.post("/miauth/:session/check", async (ctx)=>{
+ const token = await AccessTokens.findOneBy({
+ session: ctx.params.session
+ });
+ if (token?.session != null && !token.fetched) {
+ AccessTokens.update(token.id, {
+ fetched: true
+ });
+ ctx.body = {
+ ok: true,
+ token: token.token,
+ user: await Users.pack(token.userId, null, {
+ detail: true
+ })
+ };
+ } else {
+ ctx.body = {
+ ok: false
+ };
+ }
+});
+// Return 404 for unknown API
+errorRouter.all("{*splat}", async (ctx)=>{
+ ctx.status = 404;
+});
+// Register router
+app.use(mastoRouter.routes());
+app.use(mastoRouter.allowedMethods());
+app.use(router.routes());
+app.use(errorRouter.routes());
+export default app;
diff --git a/packages/backend/built/server/api/limiter.js b/packages/backend/built/server/api/limiter.js
new file mode 100644
index 0000000..3590f35
--- /dev/null
+++ b/packages/backend/built/server/api/limiter.js
@@ -0,0 +1,64 @@
+import Limiter from "ratelimiter";
+import Logger from "../../services/logger.js";
+import { redisClient } from "../../db/redis.js";
+import { convertMilliseconds } from "../../misc/convert-milliseconds.js";
+const logger = new Logger("limiter");
+export const limiter = (limitation, actor)=>new Promise((ok, reject)=>{
+ if (process.env.NODE_ENV === "test") ok();
+ const hasShortTermLimit = typeof limitation.minInterval === "number";
+ const hasLongTermLimit = typeof limitation.duration === "number" && typeof limitation.max === "number";
+ if (hasShortTermLimit) {
+ min();
+ } else if (hasLongTermLimit) {
+ max();
+ } else {
+ ok();
+ }
+ // Short-term limit
+ function min() {
+ const minIntervalLimiter = new Limiter({
+ id: `${actor}:${limitation.key}:min`,
+ duration: limitation.minInterval,
+ max: 1,
+ db: redisClient
+ });
+ minIntervalLimiter.get((err, info)=>{
+ if (err) {
+ return reject("ERR");
+ }
+ logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`);
+ if (info.remaining === 0) {
+ reject("BRIEF_REQUEST_INTERVAL");
+ } else {
+ if (hasLongTermLimit) {
+ max();
+ } else {
+ ok();
+ }
+ }
+ });
+ }
+ // Long term limit
+ function max() {
+ const limiter = new Limiter({
+ id: `${actor}:${limitation.key}`,
+ duration: limitation.duration,
+ max: limitation.max,
+ db: redisClient
+ });
+ limiter.get((err, info)=>{
+ if (err) {
+ return reject("ERR");
+ }
+ logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`);
+ if (info.remaining === 0) {
+ reject({
+ message: "RATE_LIMIT_EXCEEDED",
+ remainingTime: convertMilliseconds(info.resetMs - Date.now())
+ });
+ } else {
+ ok();
+ }
+ });
+ }
+ });
diff --git a/packages/backend/built/server/api/logger.js b/packages/backend/built/server/api/logger.js
new file mode 100644
index 0000000..de1e9cf
--- /dev/null
+++ b/packages/backend/built/server/api/logger.js
@@ -0,0 +1,2 @@
+import Logger from "../../services/logger.js";
+export const apiLogger = new Logger("api");
diff --git a/packages/backend/built/server/api/mastodon/converters/announcement.js b/packages/backend/built/server/api/mastodon/converters/announcement.js
new file mode 100644
index 0000000..5178315
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/converters/announcement.js
@@ -0,0 +1,22 @@
+import { MfmHelpers } from "../helpers/mfm.js";
+import mfm from "mfm-js";
+export class AnnouncementConverter {
+ static async encode(announcement, isRead) {
+ return {
+ id: announcement.id,
+ content: `${await MfmHelpers.toHtml(mfm.parse(announcement.title), [], null) ?? 'Announcement'}
${await MfmHelpers.toHtml(mfm.parse(announcement.text), [], null) ?? ''}`,
+ starts_at: null,
+ ends_at: null,
+ published: true,
+ all_day: false,
+ published_at: announcement.createdAt.toISOString(),
+ updated_at: announcement.updatedAt?.toISOString() ?? announcement.createdAt.toISOString(),
+ read: isRead,
+ mentions: [],
+ statuses: [],
+ tags: [],
+ emojis: [],
+ reactions: []
+ };
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/converters/emoji.js b/packages/backend/built/server/api/mastodon/converters/emoji.js
new file mode 100644
index 0000000..210d400
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/converters/emoji.js
@@ -0,0 +1,11 @@
+export class EmojiConverter {
+ static encode(e) {
+ return {
+ shortcode: e.name,
+ static_url: e.url,
+ url: e.url,
+ visible_in_picker: true,
+ category: undefined
+ };
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/converters/file.js b/packages/backend/built/server/api/mastodon/converters/file.js
new file mode 100644
index 0000000..b28b9a3
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/converters/file.js
@@ -0,0 +1,33 @@
+export class FileConverter {
+ static encode(f) {
+ return {
+ id: f.id,
+ type: this.encodefileType(f.type),
+ url: f.url ?? "",
+ remote_url: f.url,
+ preview_url: f.thumbnailUrl ?? f.url ?? "",
+ text_url: f.url,
+ meta: {
+ width: f.properties.width,
+ height: f.properties.height
+ },
+ description: f.comment,
+ blurhash: f.blurhash
+ };
+ }
+ static encodefileType(s) {
+ if (s === "image/gif") {
+ return "gifv";
+ }
+ if (s.includes("image")) {
+ return "image";
+ }
+ if (s.includes("video")) {
+ return "video";
+ }
+ if (s.includes("audio")) {
+ return "audio";
+ }
+ return "unknown";
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/converters/mention.js b/packages/backend/built/server/api/mastodon/converters/mention.js
new file mode 100644
index 0000000..bf8ccb5
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/converters/mention.js
@@ -0,0 +1,20 @@
+import config from "../../../../config/index.js";
+export class MentionConverter {
+ static encode(u, m) {
+ let acct = u.username;
+ let acctUrl = `https://${u.host || config.host}/@${u.username}`;
+ let url = null;
+ if (u.host) {
+ const info = m.find((r)=>r.username === u.username && r.host === u.host);
+ acct = `${u.username}@${u.host}`;
+ acctUrl = `https://${u.host}/@${u.username}`;
+ if (info) url = info.url ?? info.uri;
+ }
+ return {
+ id: u.id,
+ username: u.username,
+ acct: acct,
+ url: url ?? acctUrl
+ };
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/converters/mfm.js b/packages/backend/built/server/api/mastodon/converters/mfm.js
new file mode 100644
index 0000000..99d3e2b
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/converters/mfm.js
@@ -0,0 +1 @@
+export const escapeMFM = (text)=>text.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/`/g, "`").replace(/\r?\n/g, "
");
diff --git a/packages/backend/built/server/api/mastodon/converters/note.js b/packages/backend/built/server/api/mastodon/converters/note.js
new file mode 100644
index 0000000..cb97697
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/converters/note.js
@@ -0,0 +1,326 @@
+import { getNote } from "../../common/getters.js";
+import config from "../../../../config/index.js";
+import mfm from "mfm-js";
+import { UserConverter } from "./user.js";
+import { VisibilityConverter } from "./visibility.js";
+import { escapeMFM } from "./mfm.js";
+import { aggregateNoteEmojis, populateEmojis, prefetchEmojis } from "../../../../misc/populate-emojis.js";
+import { EmojiConverter } from "./emoji.js";
+import { DriveFiles, HtmlNoteCacheEntries, NoteFavorites, NoteReactions, Notes, NoteThreadMutings, UserNotePinings } from "../../../../models/index.js";
+import { decodeReaction } from "../../../../misc/reaction-lib.js";
+import { MentionConverter } from "./mention.js";
+import { PollConverter } from "./poll.js";
+import { populatePoll } from "../../../../models/repositories/note.js";
+import { FileConverter } from "./file.js";
+import { awaitAll } from "../../../../prelude/await-all.js";
+import { UserHelpers } from "../helpers/user.js";
+import { In, IsNull } from "typeorm";
+import { MfmHelpers } from "../helpers/mfm.js";
+import { getStubMastoContext } from "../index.js";
+import { NoteHelpers } from "../helpers/note.js";
+import isQuote from "../../../../misc/is-quote.js";
+import { unique } from "../../../../prelude/array.js";
+import { Cache } from "../../../../misc/cache.js";
+import { isFiltered } from "../../../../misc/is-filtered.js";
+export class NoteConverter {
+ static noteContentHtmlCache = new Cache('html:note:content', config.htmlCache?.ttlSeconds ?? 60 * 60);
+ static async encode(note, ctx, recurseCounter = 2) {
+ const user = ctx.user;
+ const noteUser = note.user ?? UserHelpers.getUserCached(note.userId, ctx);
+ if (!await Notes.isVisibleForMe(note, user?.id ?? null)) throw new Error('Cannot encode note not visible for user');
+ const host = Promise.resolve(noteUser).then((noteUser)=>noteUser.host ?? null);
+ const reactionEmojiNames = Object.keys(note.reactions).filter((x)=>x?.startsWith(":")).map((x)=>decodeReaction(x).reaction).map((x)=>x.replace(/:/g, ""));
+ const populated = host.then(async (host)=>populateEmojis(note.emojis.concat(reactionEmojiNames), host));
+ const noteEmoji = populated.then((noteEmoji)=>noteEmoji.filter((e)=>e.name.indexOf("@") === -1).map((e)=>EmojiConverter.encode(e)));
+ const reactionCount = Object.values(note.reactions).reduce((a, b)=>a + b, 0);
+ const aggregateReaction = ctx.reactionAggregate?.get(note.id);
+ const reaction = aggregateReaction !== undefined ? aggregateReaction : user ? NoteReactions.findOneBy({
+ userId: user.id,
+ noteId: note.id
+ }) : null;
+ const isFavorited = Promise.resolve(reaction).then((p)=>!!p);
+ const isReblogged = ctx.renoteAggregate?.get(note.id) ?? (user ? Notes.exist({
+ where: {
+ userId: user.id,
+ renoteId: note.id,
+ text: IsNull()
+ }
+ }) : null);
+ const renote = note.renote ?? (note.renoteId && recurseCounter > 0 ? getNote(note.renoteId, user) : null);
+ const isBookmarked = ctx.bookmarkAggregate?.get(note.id) ?? (user ? NoteFavorites.exist({
+ where: {
+ userId: user.id,
+ noteId: note.id
+ },
+ take: 1
+ }) : false);
+ const isMuted = ctx.mutingAggregate?.get(note.threadId ?? note.id) ?? (user ? NoteThreadMutings.exist({
+ where: {
+ userId: user.id,
+ threadId: note.threadId || note.id
+ }
+ }) : false);
+ const files = DriveFiles.packMany(note.fileIds);
+ const mentions = Promise.all(note.mentions.map((p)=>UserHelpers.getUserCached(p, ctx).then((u)=>MentionConverter.encode(u, JSON.parse(note.mentionedRemoteUsers))).catch(()=>null))).then((p)=>p.filter((m)=>m));
+ const quoteUri = Promise.resolve(renote).then((renote)=>{
+ if (!renote || !isQuote(note)) return null;
+ return renote.url ?? renote.uri ?? `${config.url}/notes/${renote.id}`;
+ });
+ const identifier = `${note.id}:${(note.updatedAt ?? note.createdAt).getTime()}`;
+ const text = quoteUri.then((quoteUri)=>note.text !== null ? quoteUri !== null ? note.text.replaceAll(`RE: ${quoteUri}`, '').replaceAll(quoteUri, '').trimEnd() : note.text : null);
+ const content = this.noteContentHtmlCache.fetch(identifier, async ()=>Promise.resolve(await this.fetchFromCacheWithFallback(note, ctx) ?? text.then((text)=>text !== null ? quoteUri.then((quoteUri)=>MfmHelpers.toHtml(mfm.parse(text), JSON.parse(note.mentionedRemoteUsers), note.userHost, false, quoteUri)).then((p)=>p ?? escapeMFM(text)) : "")), true).then((p)=>p ?? '');
+ const isPinned = ctx.pinAggregate?.get(note.id) ?? (user && note.userId === user.id ? UserNotePinings.exist({
+ where: {
+ userId: user.id,
+ noteId: note.id
+ }
+ }) : undefined);
+ const tags = note.tags.map((tag)=>{
+ return {
+ name: tag,
+ url: `${config.url}/tags/${tag}`
+ };
+ });
+ const reblog = Promise.resolve(renote).then((renote)=>recurseCounter > 0 && renote ? this.encode(renote, ctx, isQuote(renote) && !isQuote(note) ? --recurseCounter : 0) : null);
+ const filtered = isFiltered(note, user).then((res)=>{
+ if (!res || ctx.filterContext == null || ![
+ 'home',
+ 'public'
+ ].includes(ctx.filterContext)) return null;
+ return [
+ {
+ filter: {
+ id: '0',
+ title: 'Hard word mutes',
+ context: [
+ 'home',
+ 'public'
+ ],
+ expires_at: null,
+ filter_action: 'hide',
+ keywords: [],
+ statuses: []
+ }
+ }
+ ];
+ });
+ // noinspection ES6MissingAwait
+ return await awaitAll({
+ id: note.id,
+ uri: note.uri ?? `https://${config.host}/notes/${note.id}`,
+ url: note.url ?? note.uri ?? `https://${config.host}/notes/${note.id}`,
+ account: Promise.resolve(noteUser).then((p)=>UserConverter.encode(p, ctx)),
+ in_reply_to_id: note.replyId,
+ in_reply_to_account_id: note.replyUserId,
+ reblog: reblog.then((reblog)=>!isQuote(note) ? reblog : null),
+ content: content,
+ content_type: 'text/x.misskeymarkdown',
+ text: text,
+ created_at: note.createdAt.toISOString(),
+ emojis: noteEmoji,
+ replies_count: note.repliesCount,
+ reblogs_count: note.renoteCount,
+ favourites_count: reactionCount,
+ reblogged: isReblogged,
+ favourited: isFavorited,
+ muted: isMuted,
+ sensitive: files.then((files)=>files.length > 0 ? files.some((f)=>f.isSensitive) : false),
+ spoiler_text: note.cw ? note.cw : "",
+ visibility: VisibilityConverter.encode(note.visibility),
+ media_attachments: files.then((files)=>files.length > 0 ? files.map((f)=>FileConverter.encode(f)) : []),
+ mentions: mentions,
+ tags: tags,
+ card: null,
+ poll: note.hasPoll ? populatePoll(note, user?.id ?? null).then((p)=>noteEmoji.then((emojis)=>PollConverter.encode(p, note.id, emojis))) : null,
+ application: null,
+ language: null,
+ pinned: isPinned,
+ reactions: populated.then((populated)=>Promise.resolve(reaction).then((reaction)=>this.encodeReactions(note.reactions, reaction?.reaction, populated))),
+ bookmarked: isBookmarked,
+ quote: reblog.then((reblog)=>reblog !== null && isQuote(note) ? {
+ state: "accepted",
+ quoted_status: reblog,
+ ...reblog
+ } : null),
+ quote_id: isQuote(note) ? note.renoteId : null,
+ edited_at: note.updatedAt?.toISOString() ?? null,
+ filtered: filtered,
+ quote_approval: {
+ automatic: [
+ "public"
+ ],
+ manual: [],
+ current_user: "automatic"
+ }
+ });
+ }
+ static async encodeMany(notes, ctx) {
+ await this.aggregateData(notes, ctx);
+ const encoded = notes.map((n)=>this.encode(n, ctx));
+ return Promise.all(encoded);
+ }
+ static async aggregateData(notes, ctx) {
+ if (notes.length === 0) return;
+ const user = ctx.user;
+ const reactionAggregate = new Map();
+ const renoteAggregate = new Map();
+ const mutingAggregate = new Map();
+ const bookmarkAggregate = new Map();
+ const pinAggregate = new Map();
+ const htmlNoteCacheAggregate = new Map();
+ const renoteIds = notes.filter((n)=>n.renoteId != null).map((n)=>n.renoteId);
+ const noteIds = unique(notes.map((n)=>n.id));
+ const targets = unique([
+ ...noteIds,
+ ...renoteIds
+ ]);
+ if (config.htmlCache?.dbFallback) {
+ const htmlNoteCacheEntries = await HtmlNoteCacheEntries.findBy({
+ noteId: In(targets)
+ });
+ for (const target of targets){
+ htmlNoteCacheAggregate.set(target, htmlNoteCacheEntries.find((n)=>n.noteId === target) ?? null);
+ }
+ }
+ if (user?.id != null) {
+ const mutingTargets = unique([
+ ...notes.map((n)=>n.threadId ?? n.id)
+ ]);
+ const pinTargets = unique([
+ ...notes.filter((n)=>n.userId === user.id).map((n)=>n.id)
+ ]);
+ const reactions = await NoteReactions.findBy({
+ userId: user.id,
+ noteId: In(targets)
+ });
+ const renotes = await Notes.createQueryBuilder('note').select('note.renoteId').where('note.userId = :meId', {
+ meId: user.id
+ }).andWhere('note.renoteId IN (:...targets)', {
+ targets
+ }).andWhere('note.text IS NULL').andWhere('note.hasPoll = FALSE').andWhere(`note.fileIds = '{}'`).getMany();
+ const mutings = await NoteThreadMutings.createQueryBuilder('muting').select('muting.threadId').where('muting.userId = :meId', {
+ meId: user.id
+ }).andWhere('muting.threadId IN (:...targets)', {
+ targets: mutingTargets
+ }).getMany();
+ const bookmarks = await NoteFavorites.createQueryBuilder('bookmark').select('bookmark.noteId').where('bookmark.userId = :meId', {
+ meId: user.id
+ }).andWhere('bookmark.noteId IN (:...targets)', {
+ targets
+ }).getMany();
+ const pins = pinTargets.length > 0 ? await UserNotePinings.createQueryBuilder('pin').select('pin.noteId').where('pin.userId = :meId', {
+ meId: user.id
+ }).andWhere('pin.noteId IN (:...targets)', {
+ targets: pinTargets
+ }).getMany() : [];
+ for (const target of targets){
+ reactionAggregate.set(target, reactions.find((r)=>r.noteId === target) ?? null);
+ renoteAggregate.set(target, !!renotes.find((n)=>n.renoteId === target));
+ bookmarkAggregate.set(target, !!bookmarks.find((b)=>b.noteId === target));
+ }
+ for (const target of mutingTargets){
+ mutingAggregate.set(target, !!mutings.find((m)=>m.threadId === target));
+ }
+ for (const target of pinTargets){
+ mutingAggregate.set(target, !!pins.find((m)=>m.noteId === target));
+ }
+ }
+ ctx.reactionAggregate = reactionAggregate;
+ ctx.renoteAggregate = renoteAggregate;
+ ctx.mutingAggregate = mutingAggregate;
+ ctx.bookmarkAggregate = bookmarkAggregate;
+ ctx.pinAggregate = pinAggregate;
+ ctx.htmlNoteCacheAggregate = htmlNoteCacheAggregate;
+ const users = notes.filter((p)=>!!p.user).map((p)=>p.user);
+ const renoteUserIds = notes.filter((p)=>p.renoteUserId !== null).map((p)=>p.renoteUserId);
+ await UserConverter.aggregateData([
+ ...users
+ ], ctx);
+ await UserConverter.aggregateDataByIds(renoteUserIds, ctx);
+ await prefetchEmojis(aggregateNoteEmojis(notes));
+ }
+ static encodeReactions(reactions, myReaction, populated) {
+ return Object.keys(reactions).map((key)=>{
+ const isCustom = key.startsWith(':') && key.endsWith(':');
+ const name = isCustom ? key.substring(1, key.length - 1) : key;
+ const populatedName = isCustom && name.indexOf('@') === -1 ? `${name}@.` : name;
+ const url = isCustom ? populated.find((p)=>p.name === populatedName)?.url : undefined;
+ return {
+ count: reactions[key],
+ me: key === myReaction,
+ name: name,
+ url: url,
+ static_url: url
+ };
+ }).filter((r)=>r.count > 0);
+ }
+ static async encodeEvent(note, user, filterContext) {
+ const ctx = getStubMastoContext(user, filterContext);
+ NoteHelpers.fixupEventNote(note);
+ return NoteConverter.encode(note, ctx);
+ }
+ static async fetchFromCacheWithFallback(note, ctx) {
+ if (!config.htmlCache?.dbFallback) return null;
+ let dbHit = ctx.htmlNoteCacheAggregate?.get(note.id);
+ if (dbHit === undefined) dbHit = HtmlNoteCacheEntries.findOneBy({
+ noteId: note.id
+ });
+ return Promise.resolve(dbHit).then((res)=>{
+ if (res === null || res.updatedAt?.getTime() !== note.updatedAt?.getTime()) {
+ return this.dbCacheMiss(note, ctx);
+ }
+ return res;
+ }).then((hit)=>hit?.updatedAt === note.updatedAt ? hit?.content ?? null : null);
+ }
+ static async dbCacheMiss(note, ctx) {
+ const identifier = `${note.id}:${(note.updatedAt ?? note.createdAt).getTime()}`;
+ const cache = ctx.cache;
+ return cache.locks.acquire(identifier, async ()=>{
+ const cachedContent = await this.noteContentHtmlCache.get(identifier);
+ if (cachedContent !== undefined) {
+ return {
+ content: cachedContent
+ };
+ }
+ const quoteUri = note.renote ? isQuote(note) ? note.renote.url ?? note.renote.uri ?? `${config.url}/notes/${note.renote.id}` : null : null;
+ const text = note.text !== null ? quoteUri !== null ? note.text.replaceAll(`RE: ${quoteUri}`, '').replaceAll(quoteUri, '').trimEnd() : note.text : null;
+ const content = text !== null ? MfmHelpers.toHtml(mfm.parse(text), JSON.parse(note.mentionedRemoteUsers), note.userHost, false, quoteUri).then((p)=>p ?? escapeMFM(text)) : null;
+ HtmlNoteCacheEntries.upsert({
+ noteId: note.id,
+ updatedAt: note.updatedAt ?? note.createdAt,
+ content: await content
+ }, [
+ "noteId"
+ ]);
+ await this.noteContentHtmlCache.set(identifier, await content);
+ return {
+ content
+ };
+ });
+ }
+ static async prewarmCache(note) {
+ if (!config.htmlCache?.prewarm) return;
+ const identifier = `${note.id}:${(note.updatedAt ?? note.createdAt).getTime()}`;
+ if (await this.noteContentHtmlCache.get(identifier) !== undefined) return;
+ if (note.renoteId !== null && !note.renote) {
+ note.renote = await Notes.findOneBy({
+ id: note.renoteId
+ });
+ }
+ const quoteUri = note.renote ? isQuote(note) ? note.renote.url ?? note.renote.uri ?? `${config.url}/notes/${note.renote.id}` : null : null;
+ const text = note.text !== null ? quoteUri !== null ? note.text.replaceAll(`RE: ${quoteUri}`, '').replaceAll(quoteUri, '').trimEnd() : note.text : null;
+ const content = text !== null ? MfmHelpers.toHtml(mfm.parse(text), JSON.parse(note.mentionedRemoteUsers), note.userHost, false, quoteUri).then((p)=>p ?? escapeMFM(text)) : null;
+ if (note.user) UserConverter.prewarmCache(note.user);
+ else if (note.userId) UserConverter.prewarmCacheById(note.userId);
+ if (note.replyUserId) UserConverter.prewarmCacheById(note.replyUserId);
+ if (note.renoteUserId) UserConverter.prewarmCacheById(note.renoteUserId);
+ this.noteContentHtmlCache.set(identifier, await content);
+ if (config.htmlCache?.dbFallback) HtmlNoteCacheEntries.upsert({
+ noteId: note.id,
+ updatedAt: note.updatedAt ?? note.createdAt,
+ content: await content
+ }, [
+ "noteId"
+ ]);
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/converters/notification.js b/packages/backend/built/server/api/mastodon/converters/notification.js
new file mode 100644
index 0000000..c2a25a1
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/converters/notification.js
@@ -0,0 +1,84 @@
+import { UserConverter } from "./user.js";
+import { UserHelpers } from "../helpers/user.js";
+import { awaitAll } from "../../../../prelude/await-all.js";
+import { NoteConverter } from "./note.js";
+import { getNote } from "../../common/getters.js";
+import { getStubMastoContext } from "../index.js";
+import { Notifications } from "../../../../models/index.js";
+import isQuote from "../../../../misc/is-quote.js";
+import { unique } from "../../../../prelude/array.js";
+export class NotificationConverter {
+ static async encode(notification, ctx) {
+ const localUser = ctx.user;
+ if (notification.notifieeId !== localUser.id) throw new Error('User is not recipient of notification');
+ const account = notification.notifierId ? UserHelpers.getUserCached(notification.notifierId, ctx).then((p)=>UserConverter.encode(p, ctx)) : UserConverter.encode(localUser, ctx);
+ let result = {
+ id: notification.id,
+ account: account,
+ created_at: notification.createdAt.toISOString(),
+ type: this.encodeNotificationType(notification.type)
+ };
+ const note = notification.note ?? (notification.noteId ? await getNote(notification.noteId, localUser) : null);
+ if (note) {
+ const isPureRenote = note.renoteId !== null && !isQuote(note);
+ const encodedNote = isPureRenote ? getNote(note.renoteId, localUser).then((note)=>NoteConverter.encode(note, ctx)) : NoteConverter.encode(note, ctx);
+ result = Object.assign(result, {
+ status: encodedNote
+ });
+ if (result.type === 'poll') {
+ result = Object.assign(result, {
+ account: encodedNote.then((p)=>p.account)
+ });
+ }
+ if (notification.reaction) {
+ //FIXME: Implement reactions;
+ }
+ }
+ return awaitAll(result);
+ }
+ static async encodeMany(notifications, ctx) {
+ await this.aggregateData(notifications, ctx);
+ const encoded = notifications.map((u)=>this.encode(u, ctx));
+ return Promise.all(encoded).then((p)=>p.filter((n)=>n !== null));
+ }
+ static async aggregateData(notifications, ctx) {
+ if (notifications.length === 0) return;
+ const notes = unique(notifications.filter((p)=>p.note != null).map((n)=>n.note));
+ const users = unique(notifications.filter((p)=>p.notifier != null).map((n)=>n.notifier).concat(notifications.filter((p)=>p.notifiee != null).map((n)=>n.notifiee)));
+ await NoteConverter.aggregateData(notes, ctx);
+ await UserConverter.aggregateData(users, ctx);
+ }
+ static encodeNotificationType(t) {
+ //FIXME: Implement custom notification for followRequestAccepted
+ //FIXME: Implement mastodon notification type 'update' on misskey side
+ switch(t){
+ case "follow":
+ return 'follow';
+ case "mention":
+ case "reply":
+ return 'mention';
+ case "renote":
+ return 'reblog';
+ case "quote":
+ return 'reblog';
+ case "reaction":
+ return 'favourite';
+ case "pollEnded":
+ return 'poll';
+ case "receiveFollowRequest":
+ return 'follow_request';
+ case "followRequestAccepted":
+ case "pollVote":
+ case "groupInvited":
+ case "app":
+ throw new Error(`Notification type ${t} not supported`);
+ }
+ }
+ static async encodeEvent(target, user, filterContext) {
+ const ctx = getStubMastoContext(user, filterContext);
+ const notification = await Notifications.findOneByOrFail({
+ id: target
+ });
+ return this.encode(notification, ctx).catch((_)=>null);
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/converters/poll.js b/packages/backend/built/server/api/mastodon/converters/poll.js
new file mode 100644
index 0000000..524a152
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/converters/poll.js
@@ -0,0 +1,23 @@
+export class PollConverter {
+ static encode(p, noteId, emojis) {
+ const now = new Date();
+ const count = p.choices.reduce((sum, choice)=>sum + choice.votes, 0);
+ return {
+ id: noteId,
+ expires_at: p.expiresAt?.toISOString() ?? null,
+ expired: p.expiresAt == null ? false : now > p.expiresAt,
+ multiple: p.multiple,
+ votes_count: count,
+ options: p.choices.map((c)=>this.encodeChoice(c)),
+ emojis: emojis,
+ voted: p.choices.some((c)=>c.isVoted),
+ own_votes: p.choices.filter((c)=>c.isVoted).map((c)=>p.choices.indexOf(c))
+ };
+ }
+ static encodeChoice(c) {
+ return {
+ title: c.text,
+ votes_count: c.votes
+ };
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/converters/user.js b/packages/backend/built/server/api/mastodon/converters/user.js
new file mode 100644
index 0000000..ec9bf4f
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/converters/user.js
@@ -0,0 +1,289 @@
+import config from "../../../../config/index.js";
+import { DriveFiles, Followings, HtmlUserCacheEntries, UserProfiles, Users } from "../../../../models/index.js";
+import { EmojiConverter } from "./emoji.js";
+import { populateEmojis } from "../../../../misc/populate-emojis.js";
+import { escapeMFM } from "./mfm.js";
+import mfm from "mfm-js";
+import { awaitAll } from "../../../../prelude/await-all.js";
+import { UserHelpers } from "../helpers/user.js";
+import { MfmHelpers } from "../helpers/mfm.js";
+import { In } from "typeorm";
+import { unique } from "../../../../prelude/array.js";
+import { Cache } from "../../../../misc/cache.js";
+import { getUser } from "../../common/getters.js";
+import AsyncLock from "async-lock";
+export class UserConverter {
+ static userBioHtmlCache = new Cache('html:user:bio', config.htmlCache?.ttlSeconds ?? 60 * 60);
+ static userFieldsHtmlCache = new Cache('html:user:fields', config.htmlCache?.ttlSeconds ?? 60 * 60);
+ static async encode(u, ctx) {
+ const localUser = ctx.user;
+ const cache = ctx.cache;
+ return cache.locks.acquire(u.id, async ()=>{
+ const cacheHit = cache.accounts.find((p)=>p.id == u.id);
+ if (cacheHit) return cacheHit;
+ const identifier = `${u.id}:${(u.lastFetchedAt ?? u.updatedAt ?? u.createdAt).getTime()}`;
+ let fqn = `${u.username}@${u.host ?? config.domain}`;
+ let acct = u.username;
+ let acctUrl = `https://${u.host || config.host}/@${u.username}`;
+ if (u.host) {
+ acct = `${u.username}@${u.host}`;
+ acctUrl = `https://${u.host}/@${u.username}`;
+ }
+ const aggregateProfile = ctx.userProfileAggregate?.get(u.id);
+ let htmlCacheEntry = undefined;
+ const htmlCacheEntryLock = new AsyncLock();
+ const profile = aggregateProfile !== undefined ? aggregateProfile : UserProfiles.findOneBy({
+ userId: u.id
+ });
+ const bio = this.userBioHtmlCache.fetch(identifier, async ()=>{
+ return htmlCacheEntryLock.acquire(u.id, async ()=>{
+ if (htmlCacheEntry === undefined) htmlCacheEntry = await this.fetchFromCacheWithFallback(u, await profile, ctx);
+ if (htmlCacheEntry === null) {
+ return Promise.resolve(profile).then(async (profile)=>{
+ return MfmHelpers.toHtml(mfm.parse(profile?.description ?? ""), profile?.mentions, u.host).then((p)=>p ?? escapeMFM(profile?.description ?? "")).then((p)=>p !== '' ? p : null);
+ });
+ }
+ return htmlCacheEntry?.bio ?? null;
+ });
+ }, true).then((p)=>p ?? '');
+ const avatar = u.avatarId ? DriveFiles.getFinalUrlMaybe(u.avatarUrl) ?? DriveFiles.findOneBy({
+ id: u.avatarId
+ }).then((p)=>p?.url ?? Users.getIdenticonUrl(u.id)).then((p)=>DriveFiles.getFinalUrl(p)) : Users.getIdenticonUrl(u.id);
+ const banner = u.bannerId ? DriveFiles.getFinalUrlMaybe(u.bannerUrl) ?? DriveFiles.findOneBy({
+ id: u.bannerId
+ }).then((p)=>p?.url ?? `${config.url}/static-assets/transparent.png`).then((p)=>DriveFiles.getFinalUrl(p)) : `${config.url}/static-assets/transparent.png`;
+ const isFollowedOrSelf = ctx.followedOrSelfAggregate?.get(u.id) ?? (!!localUser && (localUser.id === u.id || Followings.exist({
+ where: {
+ followeeId: u.id,
+ followerId: localUser.id
+ }
+ })));
+ const followersCount = Promise.resolve(profile).then(async (profile)=>{
+ if (profile === null) return u.followersCount;
+ switch(profile.ffVisibility){
+ case "public":
+ return u.followersCount;
+ case "followers":
+ return Promise.resolve(isFollowedOrSelf).then((isFollowedOrSelf)=>isFollowedOrSelf ? u.followersCount : 0);
+ case "private":
+ return localUser?.id === profile.userId ? u.followersCount : 0;
+ }
+ });
+ const followingCount = Promise.resolve(profile).then(async (profile)=>{
+ if (profile === null) return u.followingCount;
+ switch(profile.ffVisibility){
+ case "public":
+ return u.followingCount;
+ case "followers":
+ return Promise.resolve(isFollowedOrSelf).then((isFollowedOrSelf)=>isFollowedOrSelf ? u.followingCount : 0);
+ case "private":
+ return localUser?.id === profile.userId ? u.followingCount : 0;
+ }
+ });
+ const fields = this.userFieldsHtmlCache.fetch(identifier, async ()=>{
+ return htmlCacheEntryLock.acquire(u.id, async ()=>{
+ if (htmlCacheEntry === undefined) htmlCacheEntry = await this.fetchFromCacheWithFallback(u, await profile, ctx);
+ if (htmlCacheEntry === null) {
+ return Promise.resolve(profile).then((profile)=>Promise.all(profile?.fields.map(async (p)=>this.encodeField(p, u.host, profile?.mentions)) ?? []));
+ }
+ return htmlCacheEntry?.fields ?? [];
+ });
+ }, true);
+ return awaitAll({
+ id: u.id,
+ username: u.username,
+ acct: acct,
+ fqn: fqn,
+ display_name: u.name || u.username,
+ locked: u.isLocked,
+ created_at: u.createdAt.toISOString(),
+ followers_count: followersCount,
+ following_count: followingCount,
+ statuses_count: u.notesCount,
+ note: bio,
+ url: u.uri ?? acctUrl,
+ avatar: avatar,
+ avatar_static: avatar,
+ header: banner,
+ header_static: banner,
+ emojis: populateEmojis(u.emojis, u.host).then((emoji)=>emoji.map((e)=>EmojiConverter.encode(e))),
+ moved: null,
+ fields: fields,
+ bot: u.isBot,
+ discoverable: u.isExplorable
+ }).then((p)=>{
+ // noinspection ES6MissingAwait
+ UserHelpers.updateUserInBackground(u);
+ cache.accounts.push(p);
+ return p;
+ });
+ });
+ }
+ static async aggregateData(users, ctx) {
+ const user = ctx.user;
+ const targets = unique(users.map((u)=>u.id));
+ const followedOrSelfAggregate = new Map();
+ const userProfileAggregate = new Map();
+ const htmlUserCacheAggregate = ctx.htmlUserCacheAggregate ?? new Map();
+ if (config.htmlCache?.dbFallback) {
+ const htmlUserCacheEntries = await HtmlUserCacheEntries.findBy({
+ userId: In(targets)
+ });
+ for (const target of targets){
+ htmlUserCacheAggregate.set(target, htmlUserCacheEntries.find((n)=>n.userId === target) ?? null);
+ }
+ }
+ if (user) {
+ const targetsWithoutSelf = targets.filter((u)=>u !== user.id);
+ if (targetsWithoutSelf.length > 0) {
+ const followings = await Followings.createQueryBuilder('following').select('following.followeeId').where('following.followerId = :meId', {
+ meId: user.id
+ }).andWhere('following.followeeId IN (:...targets)', {
+ targets: targetsWithoutSelf
+ }).getMany();
+ for (const userId of targetsWithoutSelf){
+ followedOrSelfAggregate.set(userId, !!followings.find((f)=>f.followerId === userId));
+ }
+ }
+ followedOrSelfAggregate.set(user.id, true);
+ }
+ const profiles = await UserProfiles.findBy({
+ userId: In(targets)
+ });
+ for (const userId of targets){
+ userProfileAggregate.set(userId, profiles.find((p)=>p.userId === userId) ?? null);
+ }
+ ctx.followedOrSelfAggregate = followedOrSelfAggregate;
+ ctx.htmlUserCacheAggregate = htmlUserCacheAggregate;
+ }
+ static async aggregateDataByIds(userIds, ctx) {
+ const targets = unique(userIds);
+ const htmlUserCacheAggregate = ctx.htmlUserCacheAggregate ?? new Map();
+ if (config.htmlCache?.dbFallback) {
+ const htmlUserCacheEntries = await HtmlUserCacheEntries.findBy({
+ userId: In(targets)
+ });
+ for (const target of targets){
+ htmlUserCacheAggregate.set(target, htmlUserCacheEntries.find((n)=>n.userId === target) ?? null);
+ }
+ }
+ ctx.htmlUserCacheAggregate = htmlUserCacheAggregate;
+ }
+ static async encodeMany(users, ctx) {
+ await this.aggregateData(users, ctx);
+ const encoded = users.map((u)=>this.encode(u, ctx));
+ return Promise.all(encoded);
+ }
+ static async encodeField(f, host, mentions) {
+ return {
+ name: f.name,
+ value: await MfmHelpers.toHtml(mfm.parse(f.value), mentions, host, true) ?? escapeMFM(f.value),
+ verified_at: f.verified ? new Date().toISOString() : null
+ };
+ }
+ static async fetchFromCacheWithFallback(user, profile, ctx) {
+ if (!config.htmlCache?.dbFallback) return null;
+ let dbHit = ctx.htmlUserCacheAggregate?.get(user.id);
+ if (dbHit === undefined) dbHit = HtmlUserCacheEntries.findOneBy({
+ userId: user.id
+ });
+ return Promise.resolve(dbHit).then((res)=>{
+ if (res === null || res.updatedAt.getTime() !== (user.lastFetchedAt ?? user.updatedAt ?? user.createdAt).getTime()) {
+ return this.dbCacheMiss(user, profile, ctx);
+ }
+ return res;
+ });
+ }
+ static async dbCacheMiss(user, profile, ctx) {
+ const identifier = `${user.id}:${(user.lastFetchedAt ?? user.updatedAt ?? user.createdAt).getTime()}`;
+ const cache = ctx.cache;
+ return cache.locks.acquire(identifier, async ()=>{
+ const cachedBio = await this.userBioHtmlCache.get(identifier);
+ const cachedFields = await this.userFieldsHtmlCache.get(identifier);
+ if (cachedBio !== undefined && cachedFields !== undefined) {
+ return {
+ bio: cachedBio,
+ fields: cachedFields
+ };
+ }
+ if (profile === undefined) {
+ profile = await UserProfiles.findOneBy({
+ userId: user.id
+ });
+ }
+ let bio = cachedBio;
+ let fields = cachedFields;
+ if (bio === undefined) {
+ bio = MfmHelpers.toHtml(mfm.parse(profile?.description ?? ""), profile?.mentions, user.host).then((p)=>p ?? escapeMFM(profile?.description ?? "")).then((p)=>p !== '' ? p : null);
+ }
+ if (fields === undefined) {
+ fields = Promise.all(profile.fields.map(async (p)=>this.encodeField(p, user.host, profile.mentions)) ?? []);
+ }
+ HtmlUserCacheEntries.upsert({
+ userId: user.id,
+ updatedAt: user.lastFetchedAt ?? user.updatedAt ?? user.createdAt,
+ bio: await bio,
+ fields: await fields
+ }, [
+ "userId"
+ ]);
+ await this.userBioHtmlCache.set(identifier, await bio);
+ await this.userFieldsHtmlCache.set(identifier, await fields);
+ return {
+ bio,
+ fields
+ };
+ });
+ }
+ static async prewarmCache(user, profile, oldProfile) {
+ const identifier = `${user.id}:${(user.lastFetchedAt ?? user.updatedAt ?? user.createdAt).getTime()}`;
+ if (profile !== null) {
+ if (config.htmlCache?.dbFallback) {
+ if (profile === undefined) {
+ profile = await UserProfiles.findOneBy({
+ userId: user.id
+ });
+ }
+ if (oldProfile !== undefined && profile?.fields === oldProfile?.fields && profile?.description === oldProfile?.description) {
+ HtmlUserCacheEntries.update({
+ userId: user.id
+ }, {
+ updatedAt: user.lastFetchedAt ?? user.updatedAt ?? user.createdAt
+ });
+ return;
+ }
+ }
+ if (!config.htmlCache?.prewarm) return;
+ if (profile === undefined) {
+ profile = await UserProfiles.findOneBy({
+ userId: user.id
+ });
+ }
+ if (await this.userBioHtmlCache.get(identifier) === undefined) {
+ const bio = MfmHelpers.toHtml(mfm.parse(profile?.description ?? ""), profile?.mentions, user.host).then((p)=>p ?? escapeMFM(profile?.description ?? "")).then((p)=>p !== '' ? p : null);
+ this.userBioHtmlCache.set(identifier, await bio);
+ if (config.htmlCache?.dbFallback) HtmlUserCacheEntries.upsert({
+ userId: user.id,
+ updatedAt: user.lastFetchedAt ?? user.updatedAt ?? user.createdAt,
+ bio: await bio
+ }, [
+ "userId"
+ ]);
+ }
+ if (await this.userFieldsHtmlCache.get(identifier) === undefined) {
+ const fields = await Promise.all(profile.fields.map(async (p)=>this.encodeField(p, user.host, profile.mentions)) ?? []);
+ this.userFieldsHtmlCache.set(identifier, fields);
+ if (config.htmlCache?.dbFallback) HtmlUserCacheEntries.upsert({
+ userId: user.id,
+ updatedAt: user.lastFetchedAt ?? user.updatedAt ?? user.createdAt,
+ fields: fields
+ }, [
+ "userId"
+ ]);
+ }
+ }
+ }
+ static async prewarmCacheById(userId, oldProfile) {
+ await this.prewarmCache(await getUser(userId), undefined, oldProfile);
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/converters/visibility.js b/packages/backend/built/server/api/mastodon/converters/visibility.js
new file mode 100644
index 0000000..1b1c6be
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/converters/visibility.js
@@ -0,0 +1,28 @@
+export class VisibilityConverter {
+ static encode(v) {
+ switch(v){
+ case "public":
+ return v;
+ case "home":
+ return "unlisted";
+ case "followers":
+ return "private";
+ case "specified":
+ return "direct";
+ case "hidden":
+ throw new Error();
+ }
+ }
+ static decode(v) {
+ switch(v){
+ case "public":
+ return v;
+ case "unlisted":
+ return "home";
+ case "private":
+ return "followers";
+ case "direct":
+ return "specified";
+ }
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/endpoints/account.js b/packages/backend/built/server/api/mastodon/endpoints/account.js
new file mode 100644
index 0000000..3fb0cce
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/endpoints/account.js
@@ -0,0 +1,172 @@
+import { argsToBools, limitToInt, normalizeUrlQuery } from "./timeline.js";
+import { UserConverter } from "../converters/user.js";
+import { NoteConverter } from "../converters/note.js";
+import { UserHelpers } from "../helpers/user.js";
+import { ListHelpers } from "../helpers/list.js";
+import { auth } from "../middleware/auth.js";
+import { SearchHelpers } from "../helpers/search.js";
+import { filterContext } from "../middleware/filter-context.js";
+export function setupEndpointsAccount(router) {
+ router.get("/v1/accounts/verify_credentials", auth(true, [
+ 'read:accounts'
+ ]), async (ctx)=>{
+ ctx.body = await UserHelpers.verifyCredentials(ctx);
+ });
+ router.patch("/v1/accounts/update_credentials", auth(true, [
+ 'write:accounts'
+ ]), async (ctx)=>{
+ ctx.body = await UserHelpers.updateCredentials(ctx);
+ });
+ router.get("/v1/accounts/lookup", async (ctx)=>{
+ const args = normalizeUrlQuery(ctx.query);
+ const user = await UserHelpers.getUserFromAcct(args.acct);
+ ctx.body = await UserConverter.encode(user, ctx);
+ });
+ router.get("/v1/accounts/relationships", auth(true, [
+ 'read:follows'
+ ]), async (ctx)=>{
+ const ids = normalizeUrlQuery(ctx.query, [
+ 'id[]'
+ ])['id[]'] ?? normalizeUrlQuery(ctx.query, [
+ 'id'
+ ])['id'] ?? [];
+ ctx.body = await UserHelpers.getUserRelationhipToMany(ids, ctx.user.id);
+ });
+ // This must come before /accounts/:id, otherwise that will take precedence
+ router.get("/v1/accounts/search", auth(true, [
+ 'read:accounts'
+ ]), async (ctx)=>{
+ const args = normalizeUrlQuery(argsToBools(limitToInt(ctx.query), [
+ 'resolve',
+ 'following'
+ ]));
+ ctx.body = await SearchHelpers.search(args.q, 'accounts', args.resolve, args.following, undefined, false, undefined, undefined, args.limit, args.offset, ctx).then((p)=>p.accounts);
+ });
+ router.get("/v1/accounts/:id", auth(false), async (ctx)=>{
+ ctx.body = await UserConverter.encode(await UserHelpers.getUserOr404(ctx.params.id), ctx);
+ });
+ router.get("/v1/accounts/:id/statuses", auth(false, [
+ "read:statuses"
+ ]), filterContext('account'), async (ctx)=>{
+ const query = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
+ const args = normalizeUrlQuery(argsToBools(limitToInt(ctx.query)));
+ const res = await UserHelpers.getUserStatuses(query, args.max_id, args.since_id, args.min_id, args.limit, args['only_media'], args['exclude_replies'], args['exclude_reblogs'], args.pinned, args.tagged, ctx);
+ ctx.body = await NoteConverter.encodeMany(res, ctx);
+ });
+ router.get("/v1/accounts/:id/featured_tags", async (ctx)=>{
+ ctx.body = [];
+ });
+ router.get("/v1/accounts/:id/followers", auth(false), async (ctx)=>{
+ const query = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
+ const args = normalizeUrlQuery(limitToInt(ctx.query));
+ const res = await UserHelpers.getUserFollowers(query, args.max_id, args.since_id, args.min_id, args.limit, ctx);
+ ctx.body = await UserConverter.encodeMany(res, ctx);
+ });
+ router.get("/v1/accounts/:id/following", auth(false), async (ctx)=>{
+ const query = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
+ const args = normalizeUrlQuery(limitToInt(ctx.query));
+ const res = await UserHelpers.getUserFollowing(query, args.max_id, args.since_id, args.min_id, args.limit, ctx);
+ ctx.body = await UserConverter.encodeMany(res, ctx);
+ });
+ router.get("/v1/accounts/:id/lists", auth(true, [
+ "read:lists"
+ ]), async (ctx)=>{
+ const member = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
+ ctx.body = await ListHelpers.getListsByMember(member, ctx);
+ });
+ router.post("/v1/accounts/:id/follow", auth(true, [
+ "write:follows"
+ ]), async (ctx)=>{
+ const target = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
+ //FIXME: Parse form data
+ ctx.body = await UserHelpers.followUser(target, true, false, ctx);
+ });
+ router.post("/v1/accounts/:id/unfollow", auth(true, [
+ "write:follows"
+ ]), async (ctx)=>{
+ const target = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
+ ctx.body = await UserHelpers.unfollowUser(target, ctx);
+ });
+ router.post("/v1/accounts/:id/block", auth(true, [
+ "write:blocks"
+ ]), async (ctx)=>{
+ const target = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
+ ctx.body = await UserHelpers.blockUser(target, ctx);
+ });
+ router.post("/v1/accounts/:id/unblock", auth(true, [
+ "write:blocks"
+ ]), async (ctx)=>{
+ const target = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
+ ctx.body = await UserHelpers.unblockUser(target, ctx);
+ });
+ router.post("/v1/accounts/:id/mute", auth(true, [
+ "write:mutes"
+ ]), async (ctx)=>{
+ //FIXME: parse form data
+ const args = normalizeUrlQuery(argsToBools(limitToInt(ctx.query, [
+ 'duration'
+ ]), [
+ 'notifications'
+ ]));
+ const target = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
+ ctx.body = await UserHelpers.muteUser(target, args.notifications, args.duration, ctx);
+ });
+ router.post("/v1/accounts/:id/unmute", auth(true, [
+ "write:mutes"
+ ]), async (ctx)=>{
+ const target = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
+ ctx.body = await UserHelpers.unmuteUser(target, ctx);
+ });
+ router.get("/v1/featured_tags", async (ctx)=>{
+ ctx.body = [];
+ });
+ router.get("/v1/followed_tags", async (ctx)=>{
+ ctx.body = [];
+ });
+ router.get("/v1/bookmarks", auth(true, [
+ "read:bookmarks"
+ ]), async (ctx)=>{
+ const args = normalizeUrlQuery(limitToInt(ctx.query));
+ const res = await UserHelpers.getUserBookmarks(args.max_id, args.since_id, args.min_id, args.limit, ctx);
+ ctx.body = await NoteConverter.encodeMany(res, ctx);
+ });
+ router.get("/v1/favourites", auth(true, [
+ "read:favourites"
+ ]), async (ctx)=>{
+ const args = normalizeUrlQuery(limitToInt(ctx.query));
+ const res = await UserHelpers.getUserFavorites(args.max_id, args.since_id, args.min_id, args.limit, ctx);
+ ctx.body = await NoteConverter.encodeMany(res, ctx);
+ });
+ router.get("/v1/mutes", auth(true, [
+ "read:mutes"
+ ]), async (ctx)=>{
+ const args = normalizeUrlQuery(limitToInt(ctx.query));
+ ctx.body = await UserHelpers.getUserMutes(args.max_id, args.since_id, args.min_id, args.limit, ctx);
+ });
+ router.get("/v1/blocks", auth(true, [
+ "read:blocks"
+ ]), async (ctx)=>{
+ const args = normalizeUrlQuery(limitToInt(ctx.query));
+ const res = await UserHelpers.getUserBlocks(args.max_id, args.since_id, args.min_id, args.limit, ctx);
+ ctx.body = await UserConverter.encodeMany(res, ctx);
+ });
+ router.get("/v1/follow_requests", auth(true, [
+ "read:follows"
+ ]), async (ctx)=>{
+ const args = normalizeUrlQuery(limitToInt(ctx.query));
+ const res = await UserHelpers.getUserFollowRequests(args.max_id, args.since_id, args.min_id, args.limit, ctx);
+ ctx.body = await UserConverter.encodeMany(res, ctx);
+ });
+ router.post("/v1/follow_requests/:id/authorize", auth(true, [
+ "write:follows"
+ ]), async (ctx)=>{
+ const target = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
+ ctx.body = await UserHelpers.acceptFollowRequest(target, ctx);
+ });
+ router.post("/v1/follow_requests/:id/reject", auth(true, [
+ "write:follows"
+ ]), async (ctx)=>{
+ const target = await UserHelpers.getUserCachedOr404(ctx.params.id, ctx);
+ ctx.body = await UserHelpers.rejectFollowRequest(target, ctx);
+ });
+}
diff --git a/packages/backend/built/server/api/mastodon/endpoints/auth.js b/packages/backend/built/server/api/mastodon/endpoints/auth.js
new file mode 100644
index 0000000..86e896d
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/endpoints/auth.js
@@ -0,0 +1,24 @@
+import { AuthHelpers } from "../helpers/auth.js";
+import { MiAuth } from "../middleware/auth.js";
+export function setupEndpointsAuth(router) {
+ router.post("/v1/apps", async (ctx)=>{
+ ctx.body = await AuthHelpers.registerApp(ctx);
+ });
+ router.get("/v1/apps/verify_credentials", async (ctx)=>{
+ ctx.body = await AuthHelpers.verifyAppCredentials(ctx);
+ });
+ router.post("/v1/iceshrimp/apps/info", MiAuth(true), async (ctx)=>{
+ ctx.body = await AuthHelpers.getAppInfo(ctx);
+ });
+ router.post("/v1/iceshrimp/auth/code", MiAuth(true), async (ctx)=>{
+ ctx.body = await AuthHelpers.getAuthCode(ctx);
+ });
+}
+export function setupEndpointsAuthRoot(router) {
+ router.post("/oauth/token", async (ctx)=>{
+ ctx.body = await AuthHelpers.getAuthToken(ctx);
+ });
+ router.post("/oauth/revoke", async (ctx)=>{
+ ctx.body = await AuthHelpers.revokeAuthToken(ctx);
+ });
+}
diff --git a/packages/backend/built/server/api/mastodon/endpoints/filter.js b/packages/backend/built/server/api/mastodon/endpoints/filter.js
new file mode 100644
index 0000000..4d0b892
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/endpoints/filter.js
@@ -0,0 +1,20 @@
+import { auth } from "../middleware/auth.js";
+import { MastoApiError } from "../middleware/catch-errors.js";
+export function setupEndpointsFilter(router) {
+ router.get([
+ "/v1/filters",
+ "/v2/filters"
+ ], auth(true, [
+ 'read:filters'
+ ]), async (ctx)=>{
+ ctx.body = [];
+ });
+ router.post([
+ "/v1/filters",
+ "/v2/filters"
+ ], auth(true, [
+ 'write:filters'
+ ]), async (ctx)=>{
+ throw new MastoApiError(400, "Please change word mute settings in the web frontend settings.");
+ });
+}
diff --git a/packages/backend/built/server/api/mastodon/endpoints/list.js b/packages/backend/built/server/api/mastodon/endpoints/list.js
new file mode 100644
index 0000000..2848802
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/endpoints/list.js
@@ -0,0 +1,88 @@
+import { limitToInt, normalizeUrlQuery } from "./timeline.js";
+import { ListHelpers } from "../helpers/list.js";
+import { UserConverter } from "../converters/user.js";
+import { UserLists } from "../../../../models/index.js";
+import { getUser } from "../../common/getters.js";
+import { toArray } from "../../../../prelude/array.js";
+import { auth } from "../middleware/auth.js";
+import { MastoApiError } from "../middleware/catch-errors.js";
+export function setupEndpointsList(router) {
+ router.get("/v1/lists", auth(true, [
+ 'read:lists'
+ ]), async (ctx, reply)=>{
+ ctx.body = await ListHelpers.getLists(ctx);
+ });
+ router.get("/v1/lists/:id", auth(true, [
+ 'read:lists'
+ ]), async (ctx, reply)=>{
+ ctx.body = await ListHelpers.getListOr404(ctx.params.id, ctx);
+ });
+ router.post("/v1/lists", auth(true, [
+ 'write:lists'
+ ]), async (ctx, reply)=>{
+ const body = ctx.request.body;
+ const title = (body.title ?? '').trim();
+ ctx.body = await ListHelpers.createList(title, ctx);
+ });
+ router.put("/v1/lists/:id", auth(true, [
+ 'write:lists'
+ ]), async (ctx, reply)=>{
+ const list = await UserLists.findOneBy({
+ userId: ctx.user.id,
+ id: ctx.params.id
+ });
+ if (!list) throw new MastoApiError(404);
+ const body = ctx.request.body;
+ const title = (body.title ?? '').trim();
+ const exclusive = body.exclusive ?? undefined;
+ ctx.body = await ListHelpers.updateList(list, title, exclusive, ctx);
+ });
+ router.delete("/v1/lists/:id", auth(true, [
+ 'write:lists'
+ ]), async (ctx, reply)=>{
+ const list = await UserLists.findOneBy({
+ userId: ctx.user.id,
+ id: ctx.params.id
+ });
+ if (!list) throw new MastoApiError(404);
+ await ListHelpers.deleteList(list, ctx);
+ ctx.body = {};
+ });
+ router.get("/v1/lists/:id/accounts", auth(true, [
+ 'read:lists'
+ ]), async (ctx, reply)=>{
+ const args = normalizeUrlQuery(limitToInt(ctx.query));
+ const res = await ListHelpers.getListUsers(ctx.params.id, args.max_id, args.since_id, args.min_id, args.limit, ctx);
+ ctx.body = await UserConverter.encodeMany(res, ctx);
+ });
+ router.post("/v1/lists/:id/accounts", auth(true, [
+ 'write:lists'
+ ]), async (ctx, reply)=>{
+ const list = await UserLists.findOneBy({
+ userId: ctx.user.id,
+ id: ctx.params.id
+ });
+ if (!list) throw new MastoApiError(404);
+ const body = ctx.request.body;
+ if (!body['account_ids']) throw new MastoApiError(400, "Missing account_ids[] field");
+ const ids = toArray(body['account_ids']);
+ const targets = await Promise.all(ids.map((p)=>getUser(p)));
+ await ListHelpers.addToList(list, targets, ctx);
+ ctx.body = {};
+ });
+ router.delete("/v1/lists/:id/accounts", auth(true, [
+ 'write:lists'
+ ]), async (ctx, reply)=>{
+ const list = await UserLists.findOneBy({
+ userId: ctx.user.id,
+ id: ctx.params.id
+ });
+ if (!list) throw new MastoApiError(404);
+ const body = ctx.request.body;
+ if (!body['account_ids']) throw new MastoApiError(400, "Missing account_ids[] field");
+ const ids = toArray(body['account_ids']);
+ const targets = await Promise.all(ids.map((p)=>getUser(p)));
+ await ListHelpers.removeFromList(list, targets, ctx);
+ ctx.body = {};
+ });
+}
diff --git a/packages/backend/built/server/api/mastodon/endpoints/media.js b/packages/backend/built/server/api/mastodon/endpoints/media.js
new file mode 100644
index 0000000..bf1c871
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/endpoints/media.js
@@ -0,0 +1,25 @@
+import { MediaHelpers } from "../helpers/media.js";
+import { FileConverter } from "../converters/file.js";
+import { auth } from "../middleware/auth.js";
+export function setupEndpointsMedia(router) {
+ router.get("/v1/media/:id", auth(true, [
+ 'write:media'
+ ]), async (ctx)=>{
+ const file = await MediaHelpers.getMediaPackedOr404(ctx.params.id, ctx);
+ ctx.body = FileConverter.encode(file);
+ });
+ router.put("/v1/media/:id", auth(true, [
+ 'write:media'
+ ]), async (ctx)=>{
+ const file = await MediaHelpers.getMediaOr404(ctx.params.id, ctx);
+ ctx.body = await MediaHelpers.updateMedia(file, ctx).then((p)=>FileConverter.encode(p));
+ });
+ router.post([
+ "/v2/media",
+ "/v1/media"
+ ], auth(true, [
+ 'write:media'
+ ]), async (ctx)=>{
+ ctx.body = await MediaHelpers.uploadMedia(ctx).then((p)=>FileConverter.encode(p));
+ });
+}
diff --git a/packages/backend/built/server/api/mastodon/endpoints/misc.js b/packages/backend/built/server/api/mastodon/endpoints/misc.js
new file mode 100644
index 0000000..62a2b4c
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/endpoints/misc.js
@@ -0,0 +1,57 @@
+import { MiscHelpers } from "../helpers/misc.js";
+import { argsToBools, limitToInt } from "./timeline.js";
+import { Announcements } from "../../../../models/index.js";
+import { auth } from "../middleware/auth.js";
+import { MastoApiError } from "../middleware/catch-errors.js";
+import { filterContext } from "../middleware/filter-context.js";
+export function setupEndpointsMisc(router) {
+ router.get("/v1/custom_emojis", async (ctx)=>{
+ ctx.body = await MiscHelpers.getCustomEmoji();
+ });
+ router.get("/v1/instance", async (ctx)=>{
+ ctx.body = await MiscHelpers.getInstance(ctx);
+ });
+ router.get("/v1/announcements", auth(true), async (ctx)=>{
+ const args = argsToBools(ctx.query, [
+ 'with_dismissed'
+ ]);
+ ctx.body = await MiscHelpers.getAnnouncements(args['with_dismissed'], ctx);
+ });
+ router.post("/v1/announcements/:id/dismiss", auth(true, [
+ 'write:accounts'
+ ]), async (ctx)=>{
+ const announcement = await Announcements.findOneBy({
+ id: ctx.params.id
+ });
+ if (!announcement) throw new MastoApiError(404);
+ await MiscHelpers.dismissAnnouncement(announcement, ctx);
+ ctx.body = {};
+ });
+ //FIXME: add link pagination to trends (ref: https://mastodon.social/api/v1/trends/tags?offset=10&limit=1)
+ router.get([
+ "/v1/trends/tags",
+ "/v1/trends"
+ ], async (ctx)=>{
+ const args = limitToInt(ctx.query);
+ ctx.body = await MiscHelpers.getTrendingHashtags(args.limit, args.offset);
+ //FIXME: convert ids
+ });
+ router.get("/v1/trends/statuses", filterContext('public'), async (ctx)=>{
+ const args = limitToInt(ctx.query);
+ ctx.body = await MiscHelpers.getTrendingStatuses(args.limit, args.offset, ctx);
+ });
+ router.get("/v1/trends/links", async (ctx)=>{
+ ctx.body = [];
+ });
+ router.get("/v1/preferences", auth(true, [
+ 'read:accounts'
+ ]), async (ctx)=>{
+ ctx.body = await MiscHelpers.getPreferences(ctx);
+ });
+ router.get("/v2/suggestions", auth(true, [
+ 'read:accounts'
+ ]), async (ctx)=>{
+ const args = limitToInt(ctx.query);
+ ctx.body = await MiscHelpers.getFollowSuggestions(args.limit, ctx);
+ });
+}
diff --git a/packages/backend/built/server/api/mastodon/endpoints/notifications.js b/packages/backend/built/server/api/mastodon/endpoints/notifications.js
new file mode 100644
index 0000000..c3c072b
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/endpoints/notifications.js
@@ -0,0 +1,42 @@
+import { limitToInt, normalizeUrlQuery } from "./timeline.js";
+import { NotificationHelpers } from "../helpers/notification.js";
+import { NotificationConverter } from "../converters/notification.js";
+import { auth } from "../middleware/auth.js";
+import { filterContext } from "../middleware/filter-context.js";
+export function setupEndpointsNotifications(router) {
+ router.get("/v1/notifications", auth(true, [
+ 'read:notifications'
+ ]), filterContext('notifications'), async (ctx)=>{
+ const args = normalizeUrlQuery(limitToInt(ctx.query), [
+ 'types[]',
+ 'exclude_types[]'
+ ]);
+ const res = await NotificationHelpers.getNotifications(args.max_id, args.since_id, args.min_id, args.limit, args['types[]'], args['exclude_types[]'], args.account_id, ctx);
+ ctx.body = await NotificationConverter.encodeMany(res, ctx);
+ });
+ router.get("/v1/notifications/:id", auth(true, [
+ 'read:notifications'
+ ]), filterContext('notifications'), async (ctx)=>{
+ const notification = await NotificationHelpers.getNotificationOr404(ctx.params.id, ctx);
+ ctx.body = await NotificationConverter.encode(notification, ctx);
+ });
+ router.post("/v1/notifications/clear", auth(true, [
+ 'write:notifications'
+ ]), async (ctx)=>{
+ await NotificationHelpers.clearAllNotifications(ctx);
+ ctx.body = {};
+ });
+ router.post("/v1/notifications/:id/dismiss", auth(true, [
+ 'write:notifications'
+ ]), async (ctx)=>{
+ const notification = await NotificationHelpers.getNotificationOr404(ctx.params.id, ctx);
+ await NotificationHelpers.dismissNotification(notification.id, ctx);
+ ctx.body = {};
+ });
+ router.post("/v1/conversations/:id/read", auth(true, [
+ 'write:conversations'
+ ]), async (ctx, reply)=>{
+ await NotificationHelpers.markConversationAsRead(ctx.params.id, ctx);
+ ctx.body = {};
+ });
+}
diff --git a/packages/backend/built/server/api/mastodon/endpoints/search.js b/packages/backend/built/server/api/mastodon/endpoints/search.js
new file mode 100644
index 0000000..4d26320
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/endpoints/search.js
@@ -0,0 +1,24 @@
+import { argsToBools, limitToInt, normalizeUrlQuery } from "./timeline.js";
+import { SearchHelpers } from "../helpers/search.js";
+import { auth } from "../middleware/auth.js";
+export function setupEndpointsSearch(router) {
+ router.get([
+ "/v1/search",
+ "/v2/search"
+ ], auth(true, [
+ 'read:search'
+ ]), async (ctx)=>{
+ const args = normalizeUrlQuery(argsToBools(limitToInt(ctx.query), [
+ 'resolve',
+ 'following',
+ 'exclude_unreviewed'
+ ]));
+ ctx.body = await SearchHelpers.search(args.q, args.type, args.resolve, args.following, args.account_id, args['exclude_unreviewed'], args.max_id, args.min_id, args.limit, args.offset, ctx);
+ if (ctx.path === "/v1/search") {
+ ctx.body = {
+ ...ctx.body,
+ hashtags: ctx.body.hashtags.map((p)=>p.name)
+ };
+ }
+ });
+}
diff --git a/packages/backend/built/server/api/mastodon/endpoints/status.js b/packages/backend/built/server/api/mastodon/endpoints/status.js
new file mode 100644
index 0000000..598c9a4
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/endpoints/status.js
@@ -0,0 +1,163 @@
+import { NoteConverter } from "../converters/note.js";
+import { NoteHelpers } from "../helpers/note.js";
+import { limitToInt, normalizeUrlQuery } from "./timeline.js";
+import { UserConverter } from "../converters/user.js";
+import { PollHelpers } from "../helpers/poll.js";
+import { toArray } from "../../../../prelude/array.js";
+import { auth } from "../middleware/auth.js";
+import { MastoApiError } from "../middleware/catch-errors.js";
+import { filterContext } from "../middleware/filter-context.js";
+export function setupEndpointsStatus(router) {
+ router.post("/v1/statuses", auth(true, [
+ 'write:statuses'
+ ]), async (ctx)=>{
+ const key = NoteHelpers.getIdempotencyKey(ctx);
+ if (key !== null) {
+ const result = await NoteHelpers.getFromIdempotencyCache(key);
+ if (result) {
+ ctx.body = result;
+ return;
+ }
+ }
+ let request = NoteHelpers.normalizeComposeOptions(ctx.request.body);
+ ctx.body = await NoteHelpers.createNote(request, ctx).then((p)=>NoteConverter.encode(p, ctx));
+ if (key !== null) NoteHelpers.postIdempotencyCache.set(key, {
+ status: ctx.body
+ });
+ });
+ router.put("/v1/statuses/:id", auth(true, [
+ 'write:statuses'
+ ]), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ let request = NoteHelpers.normalizeEditOptions(ctx.request.body);
+ ctx.body = await NoteHelpers.editNote(request, note, ctx).then((p)=>NoteConverter.encode(p, ctx));
+ });
+ router.get("/v1/statuses/:id", auth(false, [
+ "read:statuses"
+ ]), filterContext('thread'), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ ctx.body = await NoteConverter.encode(note, ctx);
+ });
+ router.delete("/v1/statuses/:id", auth(true, [
+ 'write:statuses'
+ ]), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ ctx.body = await NoteHelpers.deleteNote(note, ctx);
+ });
+ router.get("/v1/statuses/:id/context", auth(false, [
+ "read:statuses"
+ ]), filterContext('thread'), async (ctx)=>{
+ //FIXME: determine final limits within helper functions instead of here
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ const ancestors = await NoteHelpers.getNoteAncestors(note, ctx.user ? 4096 : 60, ctx).then((n)=>NoteConverter.encodeMany(n, ctx));
+ const descendants = await NoteHelpers.getNoteDescendants(note, ctx.user ? 4096 : 40, ctx.user ? 4096 : 20, ctx).then((n)=>NoteConverter.encodeMany(n, ctx));
+ ctx.body = {
+ ancestors,
+ descendants
+ };
+ });
+ router.get("/v1/statuses/:id/history", auth(false, [
+ "read:statuses"
+ ]), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ ctx.body = await NoteHelpers.getNoteEditHistory(note, ctx);
+ });
+ router.get("/v1/statuses/:id/source", auth(true, [
+ "read:statuses"
+ ]), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ ctx.body = NoteHelpers.getNoteSource(note);
+ });
+ router.get("/v1/statuses/:id/reblogged_by", auth(false, [
+ "read:statuses"
+ ]), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ const args = normalizeUrlQuery(limitToInt(ctx.query));
+ const res = await NoteHelpers.getNoteRebloggedBy(note, args.max_id, args.since_id, args.min_id, args.limit, ctx);
+ ctx.body = await UserConverter.encodeMany(res, ctx);
+ });
+ router.get("/v1/statuses/:id/favourited_by", auth(false, [
+ "read:statuses"
+ ]), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ const args = normalizeUrlQuery(limitToInt(ctx.query));
+ const res = await NoteHelpers.getNoteFavoritedBy(note, args.max_id, args.since_id, args.min_id, args.limit, ctx);
+ ctx.body = await UserConverter.encodeMany(res, ctx);
+ });
+ router.post("/v1/statuses/:id/favourite", auth(true, [
+ "write:favourites"
+ ]), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ const reaction = await NoteHelpers.getDefaultReaction();
+ ctx.body = await NoteHelpers.reactToNote(note, reaction, ctx).then((p)=>NoteConverter.encode(p, ctx));
+ });
+ router.post("/v1/statuses/:id/unfavourite", auth(true, [
+ "write:favourites"
+ ]), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ ctx.body = await NoteHelpers.removeReactFromNote(note, ctx).then((p)=>NoteConverter.encode(p, ctx));
+ });
+ router.post("/v1/statuses/:id/reblog", auth(true, [
+ "write:statuses"
+ ]), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ ctx.body = await NoteHelpers.reblogNote(note, ctx).then((p)=>NoteConverter.encode(p, ctx));
+ });
+ router.post("/v1/statuses/:id/unreblog", auth(true, [
+ "write:statuses"
+ ]), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ ctx.body = await NoteHelpers.unreblogNote(note, ctx).then((p)=>NoteConverter.encode(p, ctx));
+ });
+ router.post("/v1/statuses/:id/bookmark", auth(true, [
+ "write:bookmarks"
+ ]), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ ctx.body = await NoteHelpers.bookmarkNote(note, ctx).then((p)=>NoteConverter.encode(p, ctx));
+ });
+ router.post("/v1/statuses/:id/unbookmark", auth(true, [
+ "write:bookmarks"
+ ]), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ ctx.body = await NoteHelpers.unbookmarkNote(note, ctx).then((p)=>NoteConverter.encode(p, ctx));
+ });
+ router.post("/v1/statuses/:id/pin", auth(true, [
+ "write:accounts"
+ ]), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ ctx.body = await NoteHelpers.pinNote(note, ctx).then((p)=>NoteConverter.encode(p, ctx));
+ });
+ router.post("/v1/statuses/:id/unpin", auth(true, [
+ "write:accounts"
+ ]), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ ctx.body = await NoteHelpers.unpinNote(note, ctx).then((p)=>NoteConverter.encode(p, ctx));
+ });
+ router.post("/v1/statuses/:id/react/:name", auth(true, [
+ "write:favourites"
+ ]), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ ctx.body = await NoteHelpers.reactToNote(note, ctx.params.name, ctx).then((p)=>NoteConverter.encode(p, ctx));
+ });
+ router.post("/v1/statuses/:id/unreact/:name", auth(true, [
+ "write:favourites"
+ ]), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ ctx.body = await NoteHelpers.removeReactFromNote(note, ctx).then((p)=>NoteConverter.encode(p, ctx));
+ });
+ router.get("/v1/polls/:id", auth(false, [
+ "read:statuses"
+ ]), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ ctx.body = await PollHelpers.getPoll(note, ctx);
+ });
+ router.post("/v1/polls/:id/votes", auth(true, [
+ "write:statuses"
+ ]), async (ctx)=>{
+ const note = await NoteHelpers.getNoteOr404(ctx.params.id, ctx);
+ const body = ctx.request.body;
+ const choices = toArray(body.choices ?? []).map((p)=>parseInt(p));
+ if (choices.length < 1) throw new MastoApiError(400, "Must vote for at least one option");
+ ctx.body = await PollHelpers.voteInPoll(choices, note, ctx);
+ });
+}
diff --git a/packages/backend/built/server/api/mastodon/endpoints/streaming.js b/packages/backend/built/server/api/mastodon/endpoints/streaming.js
new file mode 100644
index 0000000..7359df2
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/endpoints/streaming.js
@@ -0,0 +1,5 @@
+export function setupEndpointsStreaming(router) {
+ router.get("/v1/streaming/health", async (ctx)=>{
+ ctx.body = "OK";
+ });
+}
diff --git a/packages/backend/built/server/api/mastodon/endpoints/timeline.js b/packages/backend/built/server/api/mastodon/endpoints/timeline.js
new file mode 100644
index 0000000..5c66133
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/endpoints/timeline.js
@@ -0,0 +1,101 @@
+import { TimelineHelpers } from "../helpers/timeline.js";
+import { NoteConverter } from "../converters/note.js";
+import { UserLists } from "../../../../models/index.js";
+import { auth } from "../middleware/auth.js";
+import { MastoApiError } from "../middleware/catch-errors.js";
+import { filterContext } from "../middleware/filter-context.js";
+//TODO: Move helper functions to a helper class
+export function limitToInt(q, additional = []) {
+ let object = q;
+ if (q.limit) {
+ if (typeof q.limit === "string") object.limit = parseInt(q.limit, 10);
+ }
+ if (q.offset) {
+ if (typeof q.offset === "string") object.offset = parseInt(q.offset, 10);
+ }
+ for (const key of additional)if (typeof q[key] === "string") object[key] = parseInt(q[key], 10);
+ return object;
+}
+export function argsToBools(q, additional = []) {
+ // Values taken from https://docs.joinmastodon.org/client/intro/#boolean
+ const toBoolean = (value)=>![
+ "0",
+ "f",
+ "F",
+ "false",
+ "FALSE",
+ "off",
+ "OFF"
+ ].includes(value);
+ // Keys taken from:
+ // - https://docs.joinmastodon.org/methods/accounts/#statuses
+ // - https://docs.joinmastodon.org/methods/timelines/#public
+ // - https://docs.joinmastodon.org/methods/timelines/#tag
+ let keys = [
+ 'only_media',
+ 'exclude_replies',
+ 'exclude_reblogs',
+ 'pinned',
+ 'local',
+ 'remote'
+ ].concat(additional);
+ let object = q;
+ for (const key of keys)if (q[key] && typeof q[key] === "string") object[key] = toBoolean(q[key]);
+ return object;
+}
+export function normalizeUrlQuery(q, arrayKeys = []) {
+ const dict = {};
+ for(const k in q){
+ if (arrayKeys.includes(k)) dict[k] = Array.isArray(q[k]) ? q[k] : [
+ q[k]
+ ];
+ else dict[k] = Array.isArray(q[k]) ? q[k]?.at(-1) : q[k];
+ }
+ return dict;
+}
+export function setupEndpointsTimeline(router) {
+ router.get("/v1/timelines/public", auth(true, [
+ 'read:statuses'
+ ]), filterContext('public'), async (ctx, reply)=>{
+ const args = normalizeUrlQuery(argsToBools(limitToInt(ctx.query)));
+ const res = await TimelineHelpers.getPublicTimeline(args.max_id, args.since_id, args.min_id, args.limit, args.only_media, args.local, args.remote, ctx);
+ ctx.body = await NoteConverter.encodeMany(res, ctx);
+ });
+ router.get("/v1/timelines/tag/:hashtag", auth(false, [
+ 'read:statuses'
+ ]), filterContext('public'), async (ctx, reply)=>{
+ const tag = (ctx.params.hashtag ?? '').trim().toLowerCase();
+ const args = normalizeUrlQuery(argsToBools(limitToInt(ctx.query)), [
+ 'any[]',
+ 'all[]',
+ 'none[]'
+ ]);
+ const res = await TimelineHelpers.getTagTimeline(tag, args.max_id, args.since_id, args.min_id, args.limit, args['any[]'] ?? [], args['all[]'] ?? [], args['none[]'] ?? [], args.only_media, args.local, args.remote, ctx);
+ ctx.body = await NoteConverter.encodeMany(res, ctx);
+ });
+ router.get("/v1/timelines/home", auth(true, [
+ 'read:statuses'
+ ]), filterContext('home'), async (ctx, reply)=>{
+ const args = normalizeUrlQuery(limitToInt(ctx.query));
+ const res = await TimelineHelpers.getHomeTimeline(args.max_id, args.since_id, args.min_id, args.limit, ctx);
+ ctx.body = await NoteConverter.encodeMany(res, ctx);
+ });
+ router.get("/v1/timelines/list/:listId", auth(true, [
+ 'read:lists'
+ ]), filterContext('home'), async (ctx, reply)=>{
+ const list = await UserLists.findOneBy({
+ userId: ctx.user.id,
+ id: ctx.params.listId
+ });
+ if (!list) throw new MastoApiError(404);
+ const args = normalizeUrlQuery(limitToInt(ctx.query));
+ const res = await TimelineHelpers.getListTimeline(list, args.max_id, args.since_id, args.min_id, args.limit, ctx);
+ ctx.body = await NoteConverter.encodeMany(res, ctx);
+ });
+ router.get("/v1/conversations", auth(true, [
+ 'read:statuses'
+ ]), async (ctx, reply)=>{
+ const args = normalizeUrlQuery(limitToInt(ctx.query));
+ ctx.body = await TimelineHelpers.getConversations(args.max_id, args.since_id, args.min_id, args.limit, ctx);
+ });
+}
diff --git a/packages/backend/built/server/api/mastodon/entities/account.js b/packages/backend/built/server/api/mastodon/entities/account.js
new file mode 100644
index 0000000..e92f845
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/entities/account.js
@@ -0,0 +1,3 @@
+///
+///
+///
diff --git a/packages/backend/built/server/api/mastodon/entities/activity.js b/packages/backend/built/server/api/mastodon/entities/activity.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/announcement.js b/packages/backend/built/server/api/mastodon/entities/announcement.js
new file mode 100644
index 0000000..b3538e7
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/entities/announcement.js
@@ -0,0 +1,3 @@
+///
+///
+///
diff --git a/packages/backend/built/server/api/mastodon/entities/application.js b/packages/backend/built/server/api/mastodon/entities/application.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/async_attachment.js b/packages/backend/built/server/api/mastodon/entities/async_attachment.js
new file mode 100644
index 0000000..7acb42b
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/entities/async_attachment.js
@@ -0,0 +1 @@
+///
diff --git a/packages/backend/built/server/api/mastodon/entities/attachment.js b/packages/backend/built/server/api/mastodon/entities/attachment.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/card.js b/packages/backend/built/server/api/mastodon/entities/card.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/context.js b/packages/backend/built/server/api/mastodon/entities/context.js
new file mode 100644
index 0000000..dc23d36
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/entities/context.js
@@ -0,0 +1 @@
+///
diff --git a/packages/backend/built/server/api/mastodon/entities/conversation.js b/packages/backend/built/server/api/mastodon/entities/conversation.js
new file mode 100644
index 0000000..b5ae79a
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/entities/conversation.js
@@ -0,0 +1,2 @@
+///
+///
diff --git a/packages/backend/built/server/api/mastodon/entities/emoji.js b/packages/backend/built/server/api/mastodon/entities/emoji.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/featured_tag.js b/packages/backend/built/server/api/mastodon/entities/featured_tag.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/field.js b/packages/backend/built/server/api/mastodon/entities/field.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/filter.js b/packages/backend/built/server/api/mastodon/entities/filter.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/filter_keyword.js b/packages/backend/built/server/api/mastodon/entities/filter_keyword.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/filter_result.js b/packages/backend/built/server/api/mastodon/entities/filter_result.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/filter_status.js b/packages/backend/built/server/api/mastodon/entities/filter_status.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/history.js b/packages/backend/built/server/api/mastodon/entities/history.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/identity_proof.js b/packages/backend/built/server/api/mastodon/entities/identity_proof.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/instance.js b/packages/backend/built/server/api/mastodon/entities/instance.js
new file mode 100644
index 0000000..8ac657d
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/entities/instance.js
@@ -0,0 +1,3 @@
+///
+///
+///
diff --git a/packages/backend/built/server/api/mastodon/entities/list.js b/packages/backend/built/server/api/mastodon/entities/list.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/marker.js b/packages/backend/built/server/api/mastodon/entities/marker.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/mention.js b/packages/backend/built/server/api/mastodon/entities/mention.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/notification.js b/packages/backend/built/server/api/mastodon/entities/notification.js
new file mode 100644
index 0000000..b5ae79a
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/entities/notification.js
@@ -0,0 +1,2 @@
+///
+///
diff --git a/packages/backend/built/server/api/mastodon/entities/oauth/oauth.js b/packages/backend/built/server/api/mastodon/entities/oauth/oauth.js
new file mode 100644
index 0000000..bae8dbc
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/entities/oauth/oauth.js
@@ -0,0 +1,54 @@
+/**
+ * OAuth
+ * Response data when oauth request.
+ **/ var OAuth;
+(function(OAuth) {
+ class TokenData {
+ access_token;
+ token_type;
+ created_at;
+ expires_in;
+ refresh_token;
+ _scope;
+ constructor(access_token, token_type, scope, created_at, expires_in = null, refresh_token = null){
+ this.access_token = access_token;
+ this.token_type = token_type;
+ this.created_at = created_at;
+ this.expires_in = expires_in;
+ this.refresh_token = refresh_token;
+ this._scope = scope;
+ }
+ /**
+ * Serialize raw token data from server
+ * @param raw from server
+ */ static from(raw) {
+ return new this(raw.access_token, raw.token_type, raw.scope, raw.created_at, raw.expires_in, raw.refresh_token);
+ }
+ /**
+ * OAuth Aceess Token
+ */ get accessToken() {
+ return this.access_token;
+ }
+ get tokenType() {
+ return this.token_type;
+ }
+ get scope() {
+ return this._scope;
+ }
+ /**
+ * Application ID
+ */ get createdAt() {
+ return this.created_at;
+ }
+ get expiresIn() {
+ return this.expires_in;
+ }
+ /**
+ * OAuth Refresh Token
+ */ get refreshToken() {
+ return this.refresh_token;
+ }
+ }
+ OAuth.TokenData = TokenData;
+})(OAuth || (OAuth = {}));
+export default OAuth;
diff --git a/packages/backend/built/server/api/mastodon/entities/poll.js b/packages/backend/built/server/api/mastodon/entities/poll.js
new file mode 100644
index 0000000..3752871
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/entities/poll.js
@@ -0,0 +1 @@
+///
diff --git a/packages/backend/built/server/api/mastodon/entities/poll_option.js b/packages/backend/built/server/api/mastodon/entities/poll_option.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/preferences.js b/packages/backend/built/server/api/mastodon/entities/preferences.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/push_subscription.js b/packages/backend/built/server/api/mastodon/entities/push_subscription.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/reaction.js b/packages/backend/built/server/api/mastodon/entities/reaction.js
new file mode 100644
index 0000000..0eacb5a
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/entities/reaction.js
@@ -0,0 +1 @@
+///
diff --git a/packages/backend/built/server/api/mastodon/entities/relationship.js b/packages/backend/built/server/api/mastodon/entities/relationship.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/report.js b/packages/backend/built/server/api/mastodon/entities/report.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/results.js b/packages/backend/built/server/api/mastodon/entities/results.js
new file mode 100644
index 0000000..595385b
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/entities/results.js
@@ -0,0 +1,3 @@
+///
+///
+///
diff --git a/packages/backend/built/server/api/mastodon/entities/scheduled_status.js b/packages/backend/built/server/api/mastodon/entities/scheduled_status.js
new file mode 100644
index 0000000..b6b8929
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/entities/scheduled_status.js
@@ -0,0 +1,2 @@
+///
+///
diff --git a/packages/backend/built/server/api/mastodon/entities/source.js b/packages/backend/built/server/api/mastodon/entities/source.js
new file mode 100644
index 0000000..ba69d7f
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/entities/source.js
@@ -0,0 +1 @@
+///
diff --git a/packages/backend/built/server/api/mastodon/entities/stats.js b/packages/backend/built/server/api/mastodon/entities/stats.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/status.js b/packages/backend/built/server/api/mastodon/entities/status.js
new file mode 100644
index 0000000..c603644
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/entities/status.js
@@ -0,0 +1,9 @@
+///
+///
+///
+///
+///
+///
+///
+///
+///
diff --git a/packages/backend/built/server/api/mastodon/entities/status_edit.js b/packages/backend/built/server/api/mastodon/entities/status_edit.js
new file mode 100644
index 0000000..c603644
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/entities/status_edit.js
@@ -0,0 +1,9 @@
+///
+///
+///
+///
+///
+///
+///
+///
+///
diff --git a/packages/backend/built/server/api/mastodon/entities/status_params.js b/packages/backend/built/server/api/mastodon/entities/status_params.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/status_source.js b/packages/backend/built/server/api/mastodon/entities/status_source.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/tag.js b/packages/backend/built/server/api/mastodon/entities/tag.js
new file mode 100644
index 0000000..1ac4861
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/entities/tag.js
@@ -0,0 +1 @@
+///
diff --git a/packages/backend/built/server/api/mastodon/entities/token.js b/packages/backend/built/server/api/mastodon/entities/token.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entities/urls.js b/packages/backend/built/server/api/mastodon/entities/urls.js
new file mode 100644
index 0000000..e69de29
diff --git a/packages/backend/built/server/api/mastodon/entity.js b/packages/backend/built/server/api/mastodon/entity.js
new file mode 100644
index 0000000..aa466ee
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/entity.js
@@ -0,0 +1,37 @@
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+export default MastodonEntity;
diff --git a/packages/backend/built/server/api/mastodon/helpers/auth.js b/packages/backend/built/server/api/mastodon/helpers/auth.js
new file mode 100644
index 0000000..817a949
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/helpers/auth.js
@@ -0,0 +1,212 @@
+import { secureRndstr } from "../../../../misc/secure-rndstr.js";
+import { OAuthApps, OAuthTokens } from "../../../../models/index.js";
+import { genId } from "../../../../misc/gen-id.js";
+import { fetchMeta } from "../../../../misc/fetch-meta.js";
+import { MastoApiError } from "../middleware/catch-errors.js";
+import { difference, toSingleLast, unique } from "../../../../prelude/array.js";
+export class AuthHelpers {
+ static async registerApp(ctx) {
+ const body = ctx.request.body || ctx.request.query;
+ const scopes = (typeof body.scopes === "string" ? body.scopes.split(' ') : body.scopes) ?? [
+ 'read'
+ ];
+ const redirect_uris = body.redirect_uris?.split('\n');
+ const client_name = body.client_name;
+ const website = body.website;
+ if (client_name == null) throw new MastoApiError(400, 'Missing client_name param');
+ if (redirect_uris == null || redirect_uris.length < 1) throw new MastoApiError(400, 'Missing redirect_uris param');
+ try {
+ redirect_uris.every((u)=>this.validateRedirectUri(u));
+ } catch {
+ throw new MastoApiError(400, 'Invalid redirect_uris');
+ }
+ const app = await OAuthApps.insert({
+ id: genId(),
+ clientId: secureRndstr(32),
+ clientSecret: secureRndstr(32),
+ createdAt: new Date(),
+ name: client_name,
+ website: website,
+ scopes: scopes,
+ redirectUris: redirect_uris
+ }).then((x)=>OAuthApps.findOneByOrFail(x.identifiers[0]));
+ return {
+ id: app.id,
+ name: app.name,
+ website: app.website,
+ redirect_uri: app.redirectUris.join('\n'),
+ client_id: app.clientId,
+ client_secret: app.clientSecret,
+ vapid_key: await fetchMeta().then((meta)=>meta.swPublicKey)
+ };
+ }
+ static async getAuthCode(ctx) {
+ const user = ctx.miauth[0];
+ if (!user) throw new MastoApiError(401, "Unauthorized");
+ const body = ctx.request.body;
+ const scopes = (typeof body.scopes === "string" ? body.scopes.split(' ') : body.scopes) ?? [
+ 'read'
+ ];
+ const clientId = toSingleLast(body.client_id);
+ if (clientId == null) throw new MastoApiError(400, "Invalid client_id");
+ const app = await OAuthApps.findOneBy({
+ clientId: clientId
+ });
+ this.validateRedirectUri(body.redirect_uri);
+ if (!app) throw new MastoApiError(400, "Invalid client_id");
+ if (!scopes.every((p)=>app.scopes.includes(p))) throw new MastoApiError(400, "Cannot request more scopes than application");
+ if (!app.redirectUris.includes(body.redirect_uri)) throw new MastoApiError(400, "Redirect URI not in list");
+ const token = await OAuthTokens.insert({
+ id: genId(),
+ active: false,
+ code: secureRndstr(32),
+ token: secureRndstr(32),
+ appId: app.id,
+ userId: user.id,
+ createdAt: new Date(),
+ scopes: scopes,
+ redirectUri: body.redirect_uri
+ }).then((x)=>OAuthTokens.findOneByOrFail(x.identifiers[0]));
+ return {
+ code: token.code
+ };
+ }
+ static async getAppInfo(ctx) {
+ const body = ctx.request.body;
+ const clientId = toSingleLast(body.client_id);
+ if (clientId == null) throw new MastoApiError(400, "Invalid client_id");
+ const app = await OAuthApps.findOneBy({
+ clientId: clientId
+ });
+ if (!app) throw new MastoApiError(400, "Invalid client_id");
+ return {
+ name: app.name
+ };
+ }
+ static async getAuthToken(ctx) {
+ const body = ctx.request.body || ctx.request.query;
+ const scopes = (typeof body.scope === "string" ? body.scope.split(' ') : body.scope) ?? [
+ 'read'
+ ];
+ const clientId = toSingleLast(body.client_id);
+ const code = toSingleLast(body.code);
+ const invalidScopeError = new MastoApiError(400, "invalid_scope", "The requested scope is invalid, unknown, or malformed.");
+ const invalidClientError = new MastoApiError(401, "invalid_client", "Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.");
+ if (clientId == null) throw invalidClientError;
+ if (code == null) throw new MastoApiError(401, "Invalid code");
+ const app = await OAuthApps.findOneBy({
+ clientId: clientId
+ });
+ const token = await OAuthTokens.findOneBy({
+ code: code
+ });
+ this.validateRedirectUri(body.redirect_uri);
+ if (body.grant_type !== 'authorization_code') throw new MastoApiError(400, "Invalid grant_type");
+ if (!app || body.client_secret !== app.clientSecret) throw invalidClientError;
+ if (!token || app.id !== token.appId) throw new MastoApiError(401, "Invalid code");
+ if (difference(scopes, app.scopes).length > 0) throw invalidScopeError;
+ if (!app.redirectUris.includes(body.redirect_uri)) throw new MastoApiError(400, "Redirect URI not in list");
+ await OAuthTokens.update(token.id, {
+ active: true
+ });
+ return {
+ "access_token": token.token,
+ "token_type": "Bearer",
+ "scope": token.scopes.join(' '),
+ "created_at": Math.floor(token.createdAt.getTime() / 1000)
+ };
+ }
+ static async revokeAuthToken(ctx) {
+ const error = new MastoApiError(403, "unauthorized_client", "You are not authorized to revoke this token");
+ const body = ctx.request.body || ctx.request.query;
+ const clientId = toSingleLast(body.client_id);
+ const clientSecret = toSingleLast(body.client_secret);
+ const token = toSingleLast(body.token);
+ if (clientId == null || clientSecret == null || token == null) throw error;
+ const app = await OAuthApps.findOneBy({
+ clientId: clientId,
+ clientSecret: clientSecret
+ });
+ const oatoken = await OAuthTokens.findOneBy({
+ token: token
+ });
+ if (!app || !oatoken || app.id !== oatoken.appId) throw error;
+ await OAuthTokens.delete(oatoken.id);
+ return {};
+ }
+ static async verifyAppCredentials(ctx) {
+ console.log(ctx.appId);
+ if (!ctx.appId) throw new MastoApiError(401, "The access token is invalid");
+ const app = await OAuthApps.findOneByOrFail({
+ id: ctx.appId
+ });
+ return {
+ name: app.name,
+ website: app.website,
+ vapid_key: await fetchMeta().then((meta)=>meta.swPublicKey ?? undefined)
+ };
+ }
+ static validateRedirectUri(redirectUri) {
+ const error = new MastoApiError(400, "Invalid redirect_uri");
+ if (redirectUri == null) throw error;
+ if (redirectUri === 'urn:ietf:wg:oauth:2.0:oob') return;
+ try {
+ const url = new URL(redirectUri);
+ if ([
+ "javascript:",
+ "file:",
+ "data:",
+ "mailto:",
+ "tel:"
+ ].includes(url.protocol)) throw error;
+ } catch {
+ throw error;
+ }
+ }
+ static readScopes = [
+ "read:accounts",
+ "read:blocks",
+ "read:bookmarks",
+ "read:favourites",
+ "read:filters",
+ "read:follows",
+ "read:lists",
+ "read:mutes",
+ "read:notifications",
+ "read:search",
+ "read:statuses"
+ ];
+ static writeScopes = [
+ "write:accounts",
+ "write:blocks",
+ "write:bookmarks",
+ "write:conversations",
+ "write:favourites",
+ "write:filters",
+ "write:follows",
+ "write:lists",
+ "write:media",
+ "write:mutes",
+ "write:notifications",
+ "write:reports",
+ "write:statuses"
+ ];
+ static followScopes = [
+ "read:follows",
+ "read:blocks",
+ "read:mutes",
+ "write:follows",
+ "write:blocks",
+ "write:mutes"
+ ];
+ static expandScopes(scopes) {
+ const res = [];
+ for (const scope of scopes){
+ if (scope === "read") res.push(...this.readScopes);
+ else if (scope === "write") res.push(...this.writeScopes);
+ else if (scope === "follow") res.push(...this.followScopes);
+ res.push(scope);
+ }
+ return unique(res);
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/helpers/list.js b/packages/backend/built/server/api/mastodon/helpers/list.js
new file mode 100644
index 0000000..4dd394d
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/helpers/list.js
@@ -0,0 +1,161 @@
+import { Blockings, Followings, UserListJoinings, UserLists } from "../../../../models/index.js";
+import { PaginationHelpers } from "./pagination.js";
+import { pushUserToUserList } from "../../../../services/user-list/push.js";
+import { genId } from "../../../../misc/gen-id.js";
+import { MastoApiError } from "../middleware/catch-errors.js";
+import { pullUserFromUserList } from "../../../../services/user-list/pull.js";
+import { publishUserEvent } from "../../../../services/stream.js";
+export class ListHelpers {
+ static async getLists(ctx) {
+ const user = ctx.user;
+ return UserLists.findBy({
+ userId: user.id
+ }).then((p)=>p.map((list)=>{
+ return {
+ id: list.id,
+ title: list.name,
+ exclusive: list.hideFromHomeTl
+ };
+ }));
+ }
+ static async getList(id, ctx) {
+ const user = ctx.user;
+ return UserLists.findOneByOrFail({
+ userId: user.id,
+ id: id
+ }).then((list)=>{
+ return {
+ id: list.id,
+ title: list.name,
+ exclusive: list.hideFromHomeTl
+ };
+ });
+ }
+ static async getListOr404(id, ctx) {
+ return this.getList(id, ctx).catch((_)=>{
+ throw new MastoApiError(404);
+ });
+ }
+ static async getListUsers(id, maxId, sinceId, minId, limit = 40, ctx) {
+ if (limit > 80) limit = 80;
+ const user = ctx.user;
+ const list = await UserLists.findOneBy({
+ userId: user.id,
+ id: id
+ });
+ if (!list) throw new MastoApiError(404);
+ const query = PaginationHelpers.makePaginationQuery(UserListJoinings.createQueryBuilder('member'), sinceId, maxId, minId).andWhere("member.userListId = :listId", {
+ listId: list.id
+ }).innerJoinAndSelect("member.user", "user");
+ return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx).then((members)=>{
+ return members.map((p)=>p.user).filter((p)=>p);
+ });
+ }
+ static async deleteList(list, ctx) {
+ const user = ctx.user;
+ if (user.id != list.userId) throw new Error("List is not owned by user");
+ await UserLists.delete(list.id);
+ }
+ static async addToList(list, usersToAdd, ctx) {
+ const localUser = ctx.user;
+ if (localUser.id != list.userId) throw new Error("List is not owned by user");
+ for (const user of usersToAdd){
+ if (user.id !== localUser.id) {
+ const isBlocked = await Blockings.exist({
+ where: {
+ blockerId: user.id,
+ blockeeId: localUser.id
+ }
+ });
+ const isFollowed = await Followings.exist({
+ where: {
+ followeeId: user.id,
+ followerId: localUser.id
+ }
+ });
+ if (isBlocked) throw Error("Can't add users you've been blocked by to list");
+ if (!isFollowed) throw Error("Can't add users you're not following to list");
+ }
+ const exist = await UserListJoinings.exist({
+ where: {
+ userListId: list.id,
+ userId: user.id
+ }
+ });
+ if (exist) continue;
+ await pushUserToUserList(user, list);
+ }
+ }
+ static async removeFromList(list, usersToRemove, ctx) {
+ const localUser = ctx.user;
+ if (localUser.id != list.userId) throw new Error("List is not owned by user");
+ for (const user of usersToRemove){
+ const exist = await UserListJoinings.exist({
+ where: {
+ userListId: list.id,
+ userId: user.id
+ }
+ });
+ if (!exist) continue;
+ await pullUserFromUserList(user, list);
+ }
+ }
+ static async createList(title, ctx) {
+ if (title.length < 1) throw new MastoApiError(400, "Title must not be empty");
+ const user = ctx.user;
+ const list = await UserLists.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ name: title
+ }).then(async (res)=>await UserLists.findOneByOrFail(res.identifiers[0]));
+ return {
+ id: list.id,
+ title: list.name,
+ exclusive: list.hideFromHomeTl
+ };
+ }
+ static async updateList(list, title, exclusive, ctx) {
+ if (title.length < 1 && exclusive === undefined) throw new MastoApiError(400, "Either title or exclusive must be set");
+ const user = ctx.user;
+ if (user.id != list.userId) throw new Error("List is not owned by user");
+ const name = title.length > 0 ? title : undefined;
+ const partial = {
+ name: name,
+ hideFromHomeTl: exclusive
+ };
+ const result = await UserLists.update(list.id, partial).then(async (_)=>await UserLists.findOneByOrFail({
+ id: list.id
+ }));
+ if (exclusive !== undefined) {
+ UserListJoinings.findBy({
+ userListId: list.id
+ }).then((members)=>{
+ for (const member of members){
+ publishUserEvent(list.userId, exclusive ? "userHidden" : "userUnhidden", member.userId);
+ }
+ });
+ }
+ return {
+ id: result.id,
+ title: result.name,
+ exclusive: result.hideFromHomeTl
+ };
+ }
+ static async getListsByMember(member, ctx) {
+ const user = ctx.user;
+ const joinQuery = UserListJoinings.createQueryBuilder('member').select("member.userListId").where("member.userId = :memberId");
+ const query = UserLists.createQueryBuilder('list').where("list.userId = :userId", {
+ userId: user.id
+ }).andWhere(`list.id IN (${joinQuery.getQuery()})`).setParameters({
+ memberId: member.id
+ });
+ return query.getMany().then((results)=>results.map((result)=>{
+ return {
+ id: result.id,
+ title: result.name,
+ exclusive: result.hideFromHomeTl
+ };
+ }));
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/helpers/media.js b/packages/backend/built/server/api/mastodon/helpers/media.js
new file mode 100644
index 0000000..2bcc2a2
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/helpers/media.js
@@ -0,0 +1,63 @@
+import { addFile } from "../../../../services/drive/add-file.js";
+import { DriveFiles } from "../../../../models/index.js";
+import { MastoApiError } from "../middleware/catch-errors.js";
+import { toSingleLast } from "../../../../prelude/array.js";
+export class MediaHelpers {
+ static async uploadMedia(ctx) {
+ const files = ctx.request.files;
+ const file = toSingleLast(files?.file);
+ const user = ctx.user;
+ const body = ctx.request.body;
+ if (!file) throw new MastoApiError(400, "Validation failed: File content type is invalid, File is invalid");
+ return addFile({
+ user: user,
+ path: file.filepath,
+ name: file.originalFilename !== null && file.originalFilename !== 'file' ? file.originalFilename : undefined,
+ comment: body?.description ?? undefined,
+ sensitive: false
+ }).then((p)=>DriveFiles.pack(p));
+ }
+ static async uploadMediaBasic(file, ctx) {
+ const user = ctx.user;
+ return addFile({
+ user: user,
+ path: file.filepath,
+ name: file.originalFilename !== null && file.originalFilename !== 'file' ? file.originalFilename : undefined,
+ sensitive: false
+ });
+ }
+ static async updateMedia(file, ctx) {
+ const user = ctx.user;
+ const body = ctx.request.body;
+ await DriveFiles.update(file.id, {
+ comment: body?.description ?? undefined
+ });
+ return DriveFiles.findOneByOrFail({
+ id: file.id,
+ userId: user.id
+ }).then((p)=>DriveFiles.pack(p));
+ }
+ static async getMediaPacked(id, ctx) {
+ const user = ctx.user;
+ return this.getMedia(id, ctx).then((p)=>p ? DriveFiles.pack(p) : null);
+ }
+ static async getMediaPackedOr404(id, ctx) {
+ return this.getMediaPacked(id, ctx).then((p)=>{
+ if (p) return p;
+ throw new MastoApiError(404);
+ });
+ }
+ static async getMedia(id, ctx) {
+ const user = ctx.user;
+ return DriveFiles.findOneBy({
+ id: id,
+ userId: user.id
+ });
+ }
+ static async getMediaOr404(id, ctx) {
+ return this.getMedia(id, ctx).then((p)=>{
+ if (p) return p;
+ throw new MastoApiError(404);
+ });
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/helpers/mfm.js b/packages/backend/built/server/api/mastodon/helpers/mfm.js
new file mode 100644
index 0000000..fa1defe
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/helpers/mfm.js
@@ -0,0 +1,191 @@
+import { Window as HappyDom } from "happy-dom";
+import config from "../../../../config/index.js";
+import { intersperse } from "../../../../prelude/array.js";
+import { resolveMentionFromCache } from "../../../../remote/resolve-user.js";
+export class MfmHelpers {
+ static async toHtml(nodes, mentionedRemoteUsers = [], objectHost, inline = false, quoteUri = null) {
+ if (nodes == null) {
+ return null;
+ }
+ const window = new HappyDom();
+ const doc = window.document;
+ function appendTextWithGlyphs(text, targetElement) {
+ const regexp = /;([^:;\s]{1,100});/g;
+ let last = 0;
+ for (const match of text.matchAll(regexp)){
+ if (match.index > last) {
+ targetElement.appendChild(doc.createTextNode(text.slice(last, match.index)));
+ }
+ targetElement.appendChild(doc.createTextNode(`\u200B:${match[1]}:\u200B`));
+ last = match.index + match[0].length;
+ }
+ if (last < text.length) {
+ targetElement.appendChild(doc.createTextNode(text.slice(last)));
+ }
+ }
+ async function appendChildren(children, targetElement) {
+ if (children) {
+ for (const child of (await Promise.all(children.map(async (x)=>await handlers[x.type](x)))))targetElement.appendChild(child);
+ }
+ }
+ const handlers = {
+ async bold (node) {
+ const el = doc.createElement("span");
+ el.textContent = '**';
+ await appendChildren(node.children, el);
+ el.textContent += '**';
+ return el;
+ },
+ async small (node) {
+ const el = doc.createElement("small");
+ await appendChildren(node.children, el);
+ return el;
+ },
+ async strike (node) {
+ const el = doc.createElement("span");
+ el.textContent = '~~';
+ await appendChildren(node.children, el);
+ el.textContent += '~~';
+ return el;
+ },
+ async italic (node) {
+ const el = doc.createElement("span");
+ el.textContent = '*';
+ await appendChildren(node.children, el);
+ el.textContent += '*';
+ return el;
+ },
+ async fn (node) {
+ const el = doc.createElement("span");
+ el.textContent = '*';
+ await appendChildren(node.children, el);
+ el.textContent += '*';
+ return el;
+ },
+ blockCode (node) {
+ const pre = doc.createElement("pre");
+ const inner = doc.createElement("code");
+ const nodes = node.props.code.split(/\r\n|\r|\n/).map((x)=>doc.createTextNode(x));
+ for (const x of intersperse("br", nodes)){
+ inner.appendChild(x === "br" ? doc.createElement("br") : x);
+ }
+ pre.appendChild(inner);
+ return pre;
+ },
+ async center (node) {
+ const el = doc.createElement("div");
+ await appendChildren(node.children, el);
+ return el;
+ },
+ emojiCode (node) {
+ return doc.createTextNode(`\u200B:${node.props.name}:\u200B`);
+ },
+ unicodeEmoji (node) {
+ return doc.createTextNode(node.props.emoji);
+ },
+ hashtag (node) {
+ const a = doc.createElement("a");
+ a.setAttribute('href', `${config.url}/tags/${node.props.hashtag}`);
+ a.textContent = `#${node.props.hashtag}`;
+ a.setAttribute("rel", "tag");
+ a.setAttribute("class", "hashtag");
+ return a;
+ },
+ inlineCode (node) {
+ const el = doc.createElement("code");
+ el.textContent = node.props.code;
+ return el;
+ },
+ mathInline (node) {
+ const el = doc.createElement("code");
+ el.textContent = node.props.formula;
+ return el;
+ },
+ mathBlock (node) {
+ const el = doc.createElement("code");
+ el.textContent = node.props.formula;
+ return el;
+ },
+ async link (node) {
+ const a = doc.createElement("a");
+ a.setAttribute("rel", "nofollow noopener noreferrer");
+ a.setAttribute("target", "_blank");
+ a.setAttribute('href', node.props.url);
+ await appendChildren(node.children, a);
+ return a;
+ },
+ async mention (node) {
+ const { username, host, acct } = node.props;
+ const resolved = await resolveMentionFromCache(username, host, objectHost, mentionedRemoteUsers);
+ const el = doc.createElement("span");
+ if (resolved === null) {
+ el.textContent = acct;
+ } else {
+ el.setAttribute("class", "h-card");
+ el.setAttribute("translate", "no");
+ const a = doc.createElement("a");
+ a.setAttribute('href', resolved.href);
+ a.className = "u-url mention";
+ const span = doc.createElement("span");
+ span.textContent = resolved.username;
+ a.textContent = '@';
+ a.appendChild(span);
+ el.appendChild(a);
+ }
+ return el;
+ },
+ async quote (node) {
+ const el = doc.createElement("blockquote");
+ await appendChildren(node.children, el);
+ return el;
+ },
+ text (node) {
+ const el = doc.createElement("span");
+ const lines = node.props.text.split(/\r\n|\r|\n/);
+ for (const x of intersperse("br", lines)){
+ if (x === "br") {
+ el.appendChild(doc.createElement("br"));
+ continue;
+ }
+ appendTextWithGlyphs(x, el);
+ }
+ return el;
+ },
+ url (node) {
+ const a = doc.createElement("a");
+ a.setAttribute("rel", "nofollow noopener noreferrer");
+ a.setAttribute("target", "_blank");
+ a.setAttribute('href', node.props.url);
+ a.textContent = node.props.url.replace(/^https?:\/\//, '');
+ return a;
+ },
+ search (node) {
+ const a = doc.createElement("a");
+ a.setAttribute('href', `${config.searchEngine}${node.props.query}`);
+ a.textContent = node.props.content;
+ return a;
+ },
+ async plain (node) {
+ const el = doc.createElement("span");
+ await appendChildren(node.children, el);
+ return el;
+ }
+ };
+ await appendChildren(nodes, doc.body);
+ if (quoteUri !== null) {
+ const a = doc.createElement("a");
+ a.setAttribute('href', quoteUri);
+ a.textContent = quoteUri.replace(/^https?:\/\//, '');
+ const quote = doc.createElement("span");
+ quote.setAttribute("class", "quote-inline");
+ quote.appendChild(doc.createElement("br"));
+ quote.appendChild(doc.createElement("br"));
+ quote.innerHTML += 'RE: ';
+ quote.appendChild(a);
+ doc.body.appendChild(quote);
+ }
+ const html = inline ? doc.body.innerHTML : `${doc.body.innerHTML}
`;
+ await window.happyDOM.close();
+ return html;
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/helpers/misc.js b/packages/backend/built/server/api/mastodon/helpers/misc.js
new file mode 100644
index 0000000..d266193
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/helpers/misc.js
@@ -0,0 +1,215 @@
+import config from "../../../../config/index.js";
+import { FILE_TYPE_BROWSERSAFE, MAX_NOTE_TEXT_LENGTH } from "../../../../const.js";
+import { fetchMeta } from "../../../../misc/fetch-meta.js";
+import { AnnouncementReads, Announcements, Emojis, Instances, Notes, UserProfiles, Users } from "../../../../models/index.js";
+import { IsNull } from "typeorm";
+import { awaitAll } from "../../../../prelude/await-all.js";
+import { UserConverter } from "../converters/user.js";
+import { AnnouncementConverter } from "../converters/announcement.js";
+import { genId } from "../../../../misc/gen-id.js";
+import * as Acct from "../../../../misc/acct.js";
+import { UserHelpers } from "./user.js";
+import { generateMutedUserQueryForUsers } from "../../common/generate-muted-user-query.js";
+import { generateBlockQueryForUsers } from "../../common/generate-block-query.js";
+import { uniqBy } from "../../../../prelude/array.js";
+import { EmojiConverter } from "../converters/emoji.js";
+import { populateEmojis } from "../../../../misc/populate-emojis.js";
+import { NoteConverter } from "../converters/note.js";
+import { VisibilityConverter } from "../converters/visibility.js";
+export class MiscHelpers {
+ static async getInstance(ctx) {
+ const userCount = Users.count({
+ where: {
+ host: IsNull()
+ }
+ });
+ const noteCount = Notes.count({
+ where: {
+ userHost: IsNull()
+ }
+ });
+ const instanceCount = Instances.count({
+ cache: 3600000
+ });
+ const contact = await Users.findOne({
+ where: {
+ host: IsNull(),
+ isAdmin: true,
+ isDeleted: false,
+ isSuspended: false
+ },
+ order: {
+ id: "ASC"
+ }
+ }).then((p)=>p ? UserConverter.encode(p, ctx) : null);
+ const meta = await fetchMeta(true);
+ const res = {
+ uri: config.domain,
+ title: meta.name || "FrozenFriendsYume",
+ short_description: meta.description?.substring(0, 50) || "This is an FrozenFriendsYume instance. It doesn't seem to have a description.",
+ description: meta.description || "This is an FrozenFriendsYume instance. It doesn't seem to have a description.",
+ email: meta.maintainerEmail || "",
+ version: `4.2.1 (compatible; FrozenFriendsYume ${config.version})`,
+ urls: {
+ streaming_api: `${config.url.replace(/^http(?=s?:\/\/)/, "ws")}`
+ },
+ stats: awaitAll({
+ user_count: userCount,
+ status_count: noteCount,
+ domain_count: instanceCount
+ }),
+ max_toot_chars: MAX_NOTE_TEXT_LENGTH,
+ thumbnail: meta.bannerUrl || "/static-assets/transparent.png",
+ languages: meta.langs,
+ registrations: !meta.disableRegistration,
+ approval_required: meta.disableRegistration,
+ invites_enabled: meta.disableRegistration,
+ configuration: {
+ accounts: {
+ max_featured_tags: 20
+ },
+ statuses: {
+ supported_mime_types: [
+ 'text/x.misskeymarkdown'
+ ],
+ max_characters: MAX_NOTE_TEXT_LENGTH,
+ max_media_attachments: 16,
+ characters_reserved_per_url: 23
+ },
+ media_attachments: {
+ supported_mime_types: FILE_TYPE_BROWSERSAFE,
+ image_size_limit: 10485760,
+ image_matrix_limit: 16777216,
+ video_size_limit: 41943040,
+ video_frame_limit: 60,
+ video_matrix_limit: 2304000
+ },
+ polls: {
+ max_options: 10,
+ max_characters_per_option: 50,
+ min_expiration: 50,
+ max_expiration: 2629746
+ },
+ reactions: {
+ max_reactions: 1,
+ default_reaction: meta.defaultReaction
+ }
+ },
+ contact_account: contact,
+ rules: []
+ };
+ return awaitAll(res);
+ }
+ static async getAnnouncements(includeRead = false, ctx) {
+ const user = ctx.user;
+ if (includeRead) {
+ const [announcements, reads] = await Promise.all([
+ Announcements.createQueryBuilder("announcement").orderBy({
+ "announcement.id": "DESC"
+ }).getMany(),
+ AnnouncementReads.findBy({
+ userId: user.id
+ }).then((p)=>p.map((x)=>x.announcementId))
+ ]);
+ return Promise.all(announcements.map(async (p)=>AnnouncementConverter.encode(p, reads.includes(p.id))));
+ }
+ const sq = AnnouncementReads.createQueryBuilder("reads").select("reads.announcementId").where("reads.userId = :userId");
+ const query = Announcements.createQueryBuilder("announcement").where(`announcement.id NOT IN (${sq.getQuery()})`).orderBy({
+ "announcement.id": "DESC"
+ }).setParameter("userId", user.id);
+ return query.getMany().then((p)=>Promise.all(p.map(async (x)=>AnnouncementConverter.encode(x, false))));
+ }
+ static async dismissAnnouncement(announcement, ctx) {
+ const user = ctx.user;
+ const exists = await AnnouncementReads.exist({
+ where: {
+ userId: user.id,
+ announcementId: announcement.id
+ }
+ });
+ if (!exists) {
+ await AnnouncementReads.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ announcementId: announcement.id
+ });
+ }
+ }
+ static async getFollowSuggestions(limit, ctx) {
+ const user = ctx.user;
+ const results = [];
+ const pinned = fetchMeta().then((meta)=>Promise.all(meta.pinnedUsers.map((acct)=>Acct.parse(acct)).map((acct)=>Users.findOneBy({
+ usernameLower: acct.username.toLowerCase(),
+ host: acct.host ?? IsNull()
+ }))).then((p)=>p.filter((x)=>!!x)).then((p)=>UserConverter.encodeMany(p, ctx)).then((p)=>p.map((x)=>{
+ return {
+ source: "staff",
+ account: x
+ };
+ })));
+ const query = Users.createQueryBuilder("user").where("user.isExplorable = TRUE").andWhere("user.host IS NULL").orderBy("user.followersCount", "DESC").andWhere("user.updatedAt > :date", {
+ date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5)
+ });
+ generateMutedUserQueryForUsers(query, user);
+ generateBlockQueryForUsers(query, user);
+ const global = query.take(limit).getMany().then((p)=>UserConverter.encodeMany(p, ctx)).then((p)=>p.map((x)=>{
+ return {
+ source: "global",
+ account: x
+ };
+ }));
+ results.push(pinned);
+ results.push(global);
+ return Promise.all(results).then((p)=>uniqBy(p.flat(), (x)=>x.account.id).slice(0, limit));
+ }
+ static async getCustomEmoji() {
+ return Emojis.find({
+ where: {
+ host: IsNull()
+ },
+ order: {
+ category: "ASC",
+ name: "ASC"
+ },
+ cache: {
+ id: "meta_emojis",
+ milliseconds: 3600000
+ }
+ }).then((dbRes)=>populateEmojis(dbRes.map((p)=>p.name), null).then((p)=>p.map((x)=>EmojiConverter.encode(x)).map((x)=>{
+ return {
+ ...x,
+ category: dbRes.find((y)=>y.name === x.shortcode)?.category ?? undefined
+ };
+ })));
+ }
+ static async getTrendingStatuses(limit = 20, offset = 0, ctx) {
+ if (limit > 40) limit = 40;
+ const query = Notes.createQueryBuilder("note").addSelect("note.score").andWhere("note.score > 0").andWhere("note.createdAt > :date", {
+ date: new Date(Date.now() - 1000 * 60 * 60 * 24)
+ }).andWhere("note.visibility = 'public'").andWhere("note.userHost IS NULL").orderBy("note.score", "DESC");
+ return query.skip(offset).take(limit).getMany().then((result)=>NoteConverter.encodeMany(result, ctx));
+ }
+ static async getTrendingHashtags(limit = 10, offset = 0) {
+ if (limit > 20) limit = 20;
+ return [];
+ //FIXME: This was already implemented in api/endpoints/hashtags/trend.ts, but the implementation is sketchy at best. Rewrite from scratch.
+ }
+ static getPreferences(ctx) {
+ const user = ctx.user;
+ const profile = UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ const sensitive = profile.then((p)=>p.alwaysMarkNsfw);
+ const language = profile.then((p)=>p.lang);
+ const privacy = UserHelpers.getDefaultNoteVisibility(ctx).then((p)=>VisibilityConverter.encode(p));
+ const res = {
+ "posting:default:visibility": privacy,
+ "posting:default:sensitive": sensitive,
+ "posting:default:language": language,
+ "reading:expand:media": "default",
+ "reading:expand:spoilers": false //FIXME: store this on server instead of client
+ };
+ return awaitAll(res);
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/helpers/note.js b/packages/backend/built/server/api/mastodon/helpers/note.js
new file mode 100644
index 0000000..bc81284
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/helpers/note.js
@@ -0,0 +1,366 @@
+import { makePaginationQuery } from "../../common/make-pagination-query.js";
+import { DriveFiles, Metas, NoteEdits, NoteFavorites, NoteReactions, Notes, UserNotePinings } from "../../../../models/index.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+import { getNote } from "../../common/getters.js";
+import createReaction from "../../../../services/note/reaction/create.js";
+import deleteReaction from "../../../../services/note/reaction/delete.js";
+import createNote, { extractMentionedUsers } from "../../../../services/note/create.js";
+import editNote from "../../../../services/note/edit.js";
+import deleteNote from "../../../../services/note/delete.js";
+import { genId } from "../../../../misc/gen-id.js";
+import { PaginationHelpers } from "./pagination.js";
+import { UserConverter } from "../converters/user.js";
+import { UserHelpers } from "./user.js";
+import { addPinned, removePinned } from "../../../../services/i/pin.js";
+import { NoteConverter } from "../converters/note.js";
+import { awaitAll } from "../../../../prelude/await-all.js";
+import { VisibilityConverter } from "../converters/visibility.js";
+import mfm from "mfm-js";
+import { FileConverter } from "../converters/file.js";
+import { MfmHelpers } from "./mfm.js";
+import { toArray, unique } from "../../../../prelude/array.js";
+import { MastoApiError } from "../middleware/catch-errors.js";
+import { Cache } from "../../../../misc/cache.js";
+import AsyncLock from "async-lock";
+import { IdentifiableError } from "../../../../misc/identifiable-error.js";
+import { IsNull } from "typeorm";
+import { getStubMastoContext } from "../index.js";
+export class NoteHelpers {
+ static postIdempotencyCache = new Cache('postIdempotencyCache', 60 * 60);
+ static postIdempotencyLocks = new AsyncLock();
+ static async getDefaultReaction() {
+ return Metas.createQueryBuilder().select('"defaultReaction"').execute().then((p)=>p[0].defaultReaction).then((p)=>{
+ if (p != null) return p;
+ throw new MastoApiError(500, "Failed to get default reaction");
+ });
+ }
+ static async reactToNote(note, reaction, ctx) {
+ const user = ctx.user;
+ await createReaction(user, note, reaction).catch((e)=>{
+ if (e instanceof IdentifiableError && e.id == '51c42bb4-931a-456b-bff7-e5a8a70dd298') return;
+ throw e;
+ });
+ return getNote(note.id, user);
+ }
+ static async removeReactFromNote(note, ctx) {
+ const user = ctx.user;
+ await deleteReaction(user, note);
+ return getNote(note.id, user);
+ }
+ static async reblogNote(note, ctx) {
+ const user = ctx.user;
+ const existingRenote = await Notes.findOneBy({
+ userId: user.id,
+ renoteId: note.id,
+ text: IsNull()
+ });
+ if (existingRenote) return existingRenote;
+ const data = {
+ createdAt: new Date(),
+ files: [],
+ renote: note
+ };
+ return await createNote(user, data);
+ }
+ static async unreblogNote(note, ctx) {
+ const user = ctx.user;
+ return Notes.findBy({
+ userId: user.id,
+ renoteId: note.id
+ }).then((p)=>p.map((n)=>deleteNote(user, n))).then((p)=>Promise.all(p)).then((_)=>getNote(note.id, user));
+ }
+ static async bookmarkNote(note, ctx) {
+ const user = ctx.user;
+ const bookmarked = await NoteFavorites.exist({
+ where: {
+ noteId: note.id,
+ userId: user.id
+ }
+ });
+ if (!bookmarked) {
+ await NoteFavorites.insert({
+ id: genId(),
+ createdAt: new Date(),
+ noteId: note.id,
+ userId: user.id
+ });
+ }
+ return note;
+ }
+ static async unbookmarkNote(note, ctx) {
+ const user = ctx.user;
+ return NoteFavorites.findOneBy({
+ noteId: note.id,
+ userId: user.id
+ }).then((p)=>p !== null ? NoteFavorites.delete(p.id) : null).then((_)=>note);
+ }
+ static async pinNote(note, ctx) {
+ const user = ctx.user;
+ const pinned = await UserNotePinings.exist({
+ where: {
+ userId: user.id,
+ noteId: note.id
+ }
+ });
+ if (!pinned) {
+ await addPinned(user, note.id);
+ }
+ return note;
+ }
+ static async unpinNote(note, ctx) {
+ const user = ctx.user;
+ const pinned = await UserNotePinings.exist({
+ where: {
+ userId: user.id,
+ noteId: note.id
+ }
+ });
+ if (pinned) {
+ await removePinned(user, note.id);
+ }
+ return note;
+ }
+ static async deleteNote(note, ctx) {
+ const user = ctx.user;
+ if (user.id !== note.userId) throw new MastoApiError(404);
+ const status = await NoteConverter.encode(note, ctx);
+ await deleteNote(user, note);
+ status.content = undefined;
+ return status;
+ }
+ static async getNoteFavoritedBy(note, maxId, sinceId, minId, limit = 40, ctx) {
+ if (limit > 80) limit = 80;
+ const query = PaginationHelpers.makePaginationQuery(NoteReactions.createQueryBuilder("reaction"), sinceId, maxId, minId).andWhere("reaction.noteId = :noteId", {
+ noteId: note.id
+ }).innerJoinAndSelect("reaction.user", "user");
+ return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx).then((reactions)=>{
+ return reactions.map((p)=>p.user).filter((p)=>p);
+ });
+ }
+ static async getNoteEditHistory(note, ctx) {
+ const user = Promise.resolve(note.user ?? await UserHelpers.getUserCached(note.userId, ctx));
+ const account = user.then((p)=>UserConverter.encode(p, ctx));
+ const edits = await NoteEdits.find({
+ where: {
+ noteId: note.id
+ },
+ order: {
+ id: "ASC"
+ }
+ });
+ const history = [];
+ const curr = {
+ id: note.id,
+ noteId: note.id,
+ note: note,
+ text: note.text,
+ cw: note.cw,
+ fileIds: note.fileIds,
+ updatedAt: note.updatedAt ?? note.createdAt
+ };
+ edits.push(curr);
+ let lastDate = note.createdAt;
+ for (const edit of edits){
+ const files = DriveFiles.packMany(edit.fileIds);
+ const item = {
+ account: account,
+ content: MfmHelpers.toHtml(mfm.parse(edit.text ?? ''), JSON.parse(note.mentionedRemoteUsers), note.userHost).then((p)=>p ?? ''),
+ created_at: lastDate.toISOString(),
+ emojis: [],
+ sensitive: files.then((files)=>files.length > 0 ? files.some((f)=>f.isSensitive) : false),
+ spoiler_text: edit.cw ?? '',
+ poll: null,
+ media_attachments: files.then((files)=>files.length > 0 ? files.map((f)=>FileConverter.encode(f)) : [])
+ };
+ lastDate = edit.updatedAt;
+ history.push(awaitAll(item));
+ }
+ return Promise.all(history);
+ }
+ static getNoteSource(note) {
+ return {
+ id: note.id,
+ text: note.text ?? '',
+ spoiler_text: note.cw ?? '',
+ content_type: 'text/x.misskeymarkdown'
+ };
+ }
+ static async getNoteRebloggedBy(note, maxId, sinceId, minId, limit = 40, ctx) {
+ if (limit > 80) limit = 80;
+ const user = ctx.user;
+ const query = PaginationHelpers.makePaginationQuery(Notes.createQueryBuilder("note"), sinceId, maxId, minId).andWhere("note.renoteId = :noteId", {
+ noteId: note.id
+ }).andWhere("note.text IS NULL") // We don't want to count quotes as renotes
+ .andWhere('note.hasPoll = FALSE').andWhere("note.fileIds = '{}'").innerJoinAndSelect("note.user", "user");
+ generateVisibilityQuery(query, user);
+ return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx).then((renotes)=>{
+ return renotes.map((p)=>p.user).filter((p)=>p);
+ });
+ }
+ static async getNoteDescendants(note, limit = 10, depth = 2, ctx) {
+ const user = ctx.user;
+ const noteId = typeof note === "string" ? note : note.id;
+ const query = makePaginationQuery(Notes.createQueryBuilder("note")).andWhere("note.id IN (SELECT id FROM note_replies(:noteId, :depth, :limit))", {
+ noteId,
+ depth,
+ limit
+ });
+ generateVisibilityQuery(query, user);
+ if (user) {
+ generateMutedUserQuery(query, user);
+ generateBlockedUserQuery(query, user);
+ }
+ return query.getMany().then((p)=>p.reverse());
+ }
+ static async getNoteAncestors(rootNote, limit = 10, ctx) {
+ const user = ctx.user;
+ const notes = new Array;
+ for(let i = 0; i < limit; i++){
+ const currentNote = notes.at(-1) ?? rootNote;
+ if (!currentNote.replyId) break;
+ const nextNote = await getNote(currentNote.replyId, user).catch((e)=>{
+ if (e.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") return null;
+ throw e;
+ });
+ if (nextNote && await Notes.isVisibleForMe(nextNote, user?.id ?? null)) notes.push(nextNote);
+ else break;
+ }
+ return notes.reverse();
+ }
+ static async createNote(request, ctx) {
+ const user = ctx.user;
+ const files = request.media_ids && request.media_ids.length > 0 ? DriveFiles.findByIds(request.media_ids) : [];
+ const reply = request.in_reply_to_id ? await getNote(request.in_reply_to_id, user) : undefined;
+ const renote = request.quote_id ? await getNote(request.quote_id, user) : undefined;
+ const visibility = request.visibility ?? UserHelpers.getDefaultNoteVisibility(ctx);
+ const data = {
+ createdAt: new Date(),
+ files: files,
+ poll: request.poll ? {
+ choices: request.poll.options,
+ multiple: request.poll.multiple,
+ expiresAt: request.poll.expires_in && request.poll.expires_in > 0 ? new Date(new Date().getTime() + request.poll.expires_in * 1000) : null
+ } : undefined,
+ text: request.text,
+ reply: reply,
+ renote: renote,
+ cw: request.spoiler_text,
+ visibility: visibility,
+ visibleUsers: Promise.resolve(visibility).then((p)=>p === 'specified' ? this.extractMentions(request.text ?? '', ctx) : undefined)
+ };
+ return createNote(user, await awaitAll(data));
+ }
+ static async editNote(request, note, ctx) {
+ const user = ctx.user;
+ const files = request.media_ids && request.media_ids.length > 0 ? DriveFiles.findByIds(request.media_ids) : [];
+ const data = {
+ files: files,
+ poll: request.poll ? {
+ choices: request.poll.options,
+ multiple: request.poll.multiple,
+ expiresAt: request.poll.expires_in && request.poll.expires_in > 0 ? new Date(new Date().getTime() + request.poll.expires_in * 1000) : null
+ } : null,
+ text: request.text,
+ cw: request.spoiler_text
+ };
+ return editNote(user, note, await awaitAll(data));
+ }
+ static async extractMentions(text, ctx) {
+ const user = ctx.user;
+ return extractMentionedUsers(user, mfm.parse(text));
+ }
+ static normalizeComposeOptions(body) {
+ const result = {};
+ if (body.status != null && body.status.trim().length > 0) result.text = body.status;
+ if (body.spoiler_text != null && body.spoiler_text.trim().length > 0) result.spoiler_text = body.spoiler_text;
+ if (body.visibility != null) result.visibility = VisibilityConverter.decode(body.visibility);
+ if (body.language != null) result.language = body.language;
+ if (body.scheduled_at != null) result.scheduled_at = new Date(Date.parse(body.scheduled_at));
+ if (body.in_reply_to_id) result.in_reply_to_id = body.in_reply_to_id;
+ if (body.quoted_status_id ?? body.quote_id) result.quote_id = body.quoted_status_id ?? body.quote_id;
+ if (body.media_ids) result.media_ids = body.media_ids && body.media_ids.length > 0 ? toArray(body.media_ids) : undefined;
+ if (body.poll) {
+ result.poll = {
+ expires_in: parseInt(body.poll.expires_in, 10),
+ options: body.poll.options,
+ multiple: !!body.poll.multiple
+ };
+ }
+ result.sensitive = !!body.sensitive;
+ return result;
+ }
+ static normalizeEditOptions(body) {
+ const result = {};
+ if (body.status != null && body.status.trim().length > 0) result.text = body.status;
+ if (body.spoiler_text != null && body.spoiler_text.trim().length > 0) result.spoiler_text = body.spoiler_text;
+ if (body.language != null) result.language = body.language;
+ if (body.media_ids) result.media_ids = body.media_ids && body.media_ids.length > 0 ? toArray(body.media_ids) : undefined;
+ if (body.poll) {
+ result.poll = {
+ expires_in: parseInt(body.poll.expires_in, 10),
+ options: body.poll.options,
+ multiple: !!body.poll.multiple
+ };
+ }
+ result.sensitive = !!body.sensitive;
+ return result;
+ }
+ static async getNoteOr404(id, ctx) {
+ const user = ctx.user;
+ return getNote(id, user).catch((_)=>{
+ throw new MastoApiError(404);
+ });
+ }
+ static async getConversationFromEvent(noteId, user) {
+ const ctx = getStubMastoContext(user);
+ const note = await getNote(noteId, ctx.user);
+ const conversationId = note.threadId ?? note.id;
+ const userIds = unique([
+ note.userId
+ ].concat(note.visibleUserIds).filter((p)=>p != ctx.user.id));
+ const users = userIds.map((id)=>UserHelpers.getUserCached(id, ctx).catch((_)=>null));
+ const accounts = Promise.all(users).then((u)=>UserConverter.encodeMany(u.filter((u)=>u), ctx));
+ const res = {
+ id: conversationId,
+ accounts: accounts.then((u)=>u.length > 0 ? u : UserConverter.encodeMany([
+ ctx.user
+ ], ctx)),
+ last_status: NoteConverter.encode(note, ctx),
+ unread: true
+ };
+ return awaitAll(res);
+ }
+ static fixupEventNote(note) {
+ note.createdAt = note.createdAt ? new Date(note.createdAt) : note.createdAt;
+ note.updatedAt = note.updatedAt ? new Date(note.updatedAt) : note.updatedAt;
+ note.reply = null;
+ note.renote = null;
+ note.user = null;
+ return note;
+ }
+ static getIdempotencyKey(ctx) {
+ const headers = ctx.headers;
+ const user = ctx.user;
+ if (headers["idempotency-key"] === undefined || headers["idempotency-key"] === null) return null;
+ return `${user.id}-${Array.isArray(headers["idempotency-key"]) ? headers["idempotency-key"].at(-1) : headers["idempotency-key"]}`;
+ }
+ static async getFromIdempotencyCache(key) {
+ return this.postIdempotencyLocks.acquire(key, async ()=>{
+ if (await this.postIdempotencyCache.get(key) !== undefined) {
+ let i = 5;
+ while((await this.postIdempotencyCache.get(key))?.status === undefined){
+ if (++i > 5) throw new Error('Post is duplicate but unable to resolve original');
+ await new Promise((resolve)=>{
+ setTimeout(resolve, 500);
+ });
+ }
+ return (await this.postIdempotencyCache.get(key))?.status;
+ } else {
+ await this.postIdempotencyCache.set(key, {});
+ return undefined;
+ }
+ });
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/helpers/notification.js b/packages/backend/built/server/api/mastodon/helpers/notification.js
new file mode 100644
index 0000000..339488d
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/helpers/notification.js
@@ -0,0 +1,83 @@
+import { Notes, Notifications } from "../../../../models/index.js";
+import { PaginationHelpers } from "./pagination.js";
+import { MastoApiError } from "../middleware/catch-errors.js";
+export class NotificationHelpers {
+ static async getNotifications(maxId, sinceId, minId, limit = 40, types, excludeTypes, accountId, ctx) {
+ if (limit > 80) limit = 80;
+ const user = ctx.user;
+ let requestedTypes = types ? this.decodeTypes(types) : [
+ 'follow',
+ 'mention',
+ 'reply',
+ 'renote',
+ 'quote',
+ 'reaction',
+ 'pollEnded',
+ 'receiveFollowRequest'
+ ];
+ if (excludeTypes) {
+ const excludedTypes = this.decodeTypes(excludeTypes);
+ requestedTypes = requestedTypes.filter((p)=>!excludedTypes.includes(p));
+ }
+ const query = PaginationHelpers.makePaginationQuery(Notifications.createQueryBuilder("notification"), sinceId, maxId, minId).andWhere("notification.notifieeId = :userId", {
+ userId: user.id
+ }).andWhere("notification.type IN (:...types)", {
+ types: requestedTypes
+ });
+ if (accountId !== undefined) query.andWhere("notification.notifierId = :notifierId", {
+ notifierId: accountId
+ });
+ query.leftJoinAndSelect("notification.note", "note").leftJoinAndSelect("notification.notifier", "notifier").leftJoinAndSelect("notification.notifiee", "notifiee");
+ return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx);
+ }
+ static async getNotification(id, ctx) {
+ const user = ctx.user;
+ return Notifications.findOneBy({
+ id: id,
+ notifieeId: user.id
+ });
+ }
+ static async getNotificationOr404(id, ctx) {
+ return this.getNotification(id, ctx).then((p)=>{
+ if (p) return p;
+ throw new MastoApiError(404);
+ });
+ }
+ static async dismissNotification(id, ctx) {
+ const user = ctx.user;
+ await Notifications.update({
+ id: id,
+ notifieeId: user.id
+ }, {
+ isRead: true
+ });
+ }
+ static async clearAllNotifications(ctx) {
+ const user = ctx.user;
+ await Notifications.update({
+ notifieeId: user.id
+ }, {
+ isRead: true
+ });
+ }
+ static async markConversationAsRead(id, ctx) {
+ const user = ctx.user;
+ const notesQuery = Notes.createQueryBuilder("note").select("note.id").andWhere("COALESCE(note.threadId, note.id) = :conversationId");
+ await Notifications.createQueryBuilder("notification").where(`notification."noteId" IN (${notesQuery.getQuery()})`).andWhere(`notification."notifieeId" = :userId`).andWhere(`notification."isRead" = FALSE`).andWhere("notification.type IN (:...types)").setParameter("userId", user.id).setParameter("conversationId", id).setParameter("types", [
+ 'reply',
+ 'mention'
+ ]).update().set({
+ isRead: true
+ }).execute();
+ }
+ static decodeTypes(types) {
+ const result = [];
+ if (types.includes('follow')) result.push('follow');
+ if (types.includes('mention')) result.push('mention', 'reply');
+ if (types.includes('reblog')) result.push('renote', 'quote');
+ if (types.includes('favourite')) result.push('reaction');
+ if (types.includes('poll')) result.push('pollEnded');
+ if (types.includes('follow_request')) result.push('receiveFollowRequest');
+ return result;
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/helpers/pagination.js b/packages/backend/built/server/api/mastodon/helpers/pagination.js
new file mode 100644
index 0000000..bb1e618
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/helpers/pagination.js
@@ -0,0 +1,56 @@
+import { generatePaginationData } from "../middleware/pagination.js";
+export class PaginationHelpers {
+ static makePaginationQuery(q, sinceId, maxId, minId, idField = `${q.alias}.id`) {
+ if (sinceId && minId) throw new Error("Can't user both sinceId and minId params");
+ if (sinceId && maxId) {
+ q.andWhere(`${idField} > :sinceId`, {
+ sinceId: sinceId
+ });
+ q.andWhere(`${idField} < :maxId`, {
+ maxId: maxId
+ });
+ q.orderBy(`${idField}`, "DESC");
+ }
+ if (minId && maxId) {
+ q.andWhere(`${idField} > :minId`, {
+ minId: minId
+ });
+ q.andWhere(`${idField} < :maxId`, {
+ maxId: maxId
+ });
+ q.orderBy(`${idField}`, "ASC");
+ } else if (sinceId) {
+ q.andWhere(`${idField} > :sinceId`, {
+ sinceId: sinceId
+ });
+ q.orderBy(`${idField}`, "DESC");
+ } else if (minId) {
+ q.andWhere(`${idField} > :minId`, {
+ minId: minId
+ });
+ q.orderBy(`${idField}`, "ASC");
+ } else if (maxId) {
+ q.andWhere(`${idField} < :maxId`, {
+ maxId: maxId
+ });
+ q.orderBy(`${idField}`, "DESC");
+ } else {
+ q.orderBy(`${idField}`, "DESC");
+ }
+ return q;
+ }
+ /**
+ *
+ * @param query
+ * @param limit
+ * @param reverse whether the result needs to be .reverse()'d. Set this to true when the parameter minId is not undefined in the original request.
+ */ static async execQuery(query, limit, reverse) {
+ return query.take(limit).getMany().then((found)=>reverse ? found.reverse() : found);
+ }
+ static async execQueryLinkPagination(query, limit, reverse, ctx) {
+ return this.execQuery(query, limit, reverse).then((p)=>{
+ ctx.pagination = generatePaginationData(p.map((x)=>x.id), limit);
+ return p;
+ });
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/helpers/poll.js b/packages/backend/built/server/api/mastodon/helpers/poll.js
new file mode 100644
index 0000000..0f8f1af
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/helpers/poll.js
@@ -0,0 +1,99 @@
+import { populatePoll } from "../../../../models/repositories/note.js";
+import { PollConverter } from "../converters/poll.js";
+import { Blockings, Notes, NoteWatchings, Polls, PollVotes, Users } from "../../../../models/index.js";
+import { genId } from "../../../../misc/gen-id.js";
+import { publishNoteStream } from "../../../../services/stream.js";
+import { createNotification } from "../../../../services/create-notification.js";
+import { deliver } from "../../../../queue/index.js";
+import { renderActivity } from "../../../../remote/activitypub/renderer/index.js";
+import renderVote from "../../../../remote/activitypub/renderer/vote.js";
+import { Not } from "typeorm";
+import { MastoApiError } from "../middleware/catch-errors.js";
+import { populateEmojis } from "../../../../misc/populate-emojis.js";
+import { EmojiConverter } from "../converters/emoji.js";
+import { UserHelpers } from "./user.js";
+export class PollHelpers {
+ static async getPoll(note, ctx) {
+ const user = ctx.user;
+ if (!await Notes.isVisibleForMe(note, user?.id ?? null)) throw new Error('Cannot encode poll not visible for user');
+ const noteUser = note.user ?? UserHelpers.getUserCached(note.userId, ctx);
+ const host = Promise.resolve(noteUser).then((noteUser)=>noteUser.host ?? null);
+ const noteEmoji = await host.then(async (host)=>populateEmojis(note.emojis, host).then((noteEmoji)=>noteEmoji.filter((e)=>e.name.indexOf("@") === -1).map((e)=>EmojiConverter.encode(e))));
+ return populatePoll(note, user?.id ?? null).then((p)=>PollConverter.encode(p, note.id, noteEmoji));
+ }
+ static async voteInPoll(choices, note, ctx) {
+ if (!note.hasPoll) throw new MastoApiError(404);
+ const user = ctx.user;
+ for (const choice of choices){
+ const createdAt = new Date();
+ if (!note.hasPoll) throw new MastoApiError(404);
+ // Check blocking
+ if (note.userId !== user.id) {
+ const block = await Blockings.findOneBy({
+ blockerId: note.userId,
+ blockeeId: user.id
+ });
+ if (block) throw new Error('You are blocked by the poll author');
+ }
+ const poll = await Polls.findOneByOrFail({
+ noteId: note.id
+ });
+ if (poll.expiresAt && poll.expiresAt < createdAt) throw new Error('Poll is expired');
+ if (poll.choices[choice] == null) throw new Error('Invalid choice');
+ // if already voted
+ const exist = await PollVotes.findBy({
+ noteId: note.id,
+ userId: user.id
+ });
+ if (exist.length) {
+ if (poll.multiple) {
+ if (exist.some((x)=>x.choice === choice)) throw new Error('You already voted for this option');
+ } else {
+ throw new Error('You already voted in this poll');
+ }
+ }
+ // Create vote
+ const vote = await PollVotes.insert({
+ id: genId(),
+ createdAt,
+ noteId: note.id,
+ userId: user.id,
+ choice: choice
+ }).then((x)=>PollVotes.findOneByOrFail(x.identifiers[0]));
+ // Increment votes count
+ const index = choice + 1; // In SQL, array index is 1 based
+ await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE "noteId" = '${poll.noteId}'`);
+ publishNoteStream(note.id, "pollVoted", {
+ choice: choice,
+ userId: user.id
+ });
+ // Notify
+ createNotification(note.userId, "pollVote", {
+ notifierId: user.id,
+ noteId: note.id,
+ choice: choice
+ });
+ // Fetch watchers
+ NoteWatchings.findBy({
+ noteId: note.id,
+ userId: Not(user.id)
+ }).then((watchers)=>{
+ for (const watcher of watchers){
+ createNotification(watcher.userId, "pollVote", {
+ notifierId: user.id,
+ noteId: note.id,
+ choice: choice
+ });
+ }
+ });
+ // リモート投票の場合リプライ送信
+ if (note.userHost != null) {
+ const pollOwner = await Users.findOneByOrFail({
+ id: note.userId
+ });
+ deliver(user, renderActivity(await renderVote(user, vote, note, poll, pollOwner)), pollOwner.inbox);
+ }
+ }
+ return this.getPoll(note, ctx);
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/helpers/search.js b/packages/backend/built/server/api/mastodon/helpers/search.js
new file mode 100644
index 0000000..a671c6d
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/helpers/search.js
@@ -0,0 +1,181 @@
+import { Followings, Hashtags, Notes, Users } from "../../../../models/index.js";
+import { sqlLikeEscape } from "../../../../misc/sql-like-escape.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+import { PaginationHelpers } from "./pagination.js";
+import { Brackets, IsNull } from "typeorm";
+import { awaitAll } from "../../../../prelude/await-all.js";
+import { NoteConverter } from "../converters/note.js";
+import Resolver from "../../../../remote/activitypub/resolver.js";
+import { getApId, isActor, isPost } from "../../../../remote/activitypub/type.js";
+import DbResolver from "../../../../remote/activitypub/db-resolver.js";
+import { createPerson } from "../../../../remote/activitypub/models/person.js";
+import { UserConverter } from "../converters/user.js";
+import { resolveUser } from "../../../../remote/resolve-user.js";
+import { createNote } from "../../../../remote/activitypub/models/note.js";
+import config from "../../../../config/index.js";
+import { logger } from "../index.js";
+import { generateFtsQuery } from "../../common/generate-fts-query.js";
+export class SearchHelpers {
+ static async search(q, type, resolve = false, following = false, accountId, excludeUnreviewed = false, maxId, minId, limit = 20, offset, ctx) {
+ if (q === undefined || q.trim().length === 0) throw new Error('Search query cannot be empty');
+ if (limit > 40) limit = 40;
+ const user = ctx.user;
+ const notes = type === 'statuses' || !type ? this.searchNotes(q, resolve, following, accountId, maxId, minId, limit, offset, ctx) : [];
+ const users = type === 'accounts' || !type ? this.searchUsers(q, resolve, following, maxId, minId, limit, offset, ctx) : [];
+ const tags = type === 'hashtags' || !type ? this.searchTags(q, excludeUnreviewed, limit, offset) : [];
+ const result = {
+ statuses: Promise.resolve(notes).then((p)=>NoteConverter.encodeMany(p, ctx)),
+ accounts: Promise.resolve(users).then((p)=>UserConverter.encodeMany(p, ctx)),
+ hashtags: Promise.resolve(tags)
+ };
+ return awaitAll(result);
+ }
+ static async searchUsers(q, resolve, following, maxId, minId, limit, offset, ctx) {
+ const user = ctx.user;
+ if (resolve) {
+ try {
+ if (q.startsWith('https://') || q.startsWith('http://')) {
+ // try resolving locally first
+ const dbResolver = new DbResolver();
+ const dbResult = await dbResolver.getUserFromApId(q);
+ if (dbResult) return [
+ dbResult
+ ];
+ // ask remote
+ const resolver = new Resolver();
+ resolver.setUser(user);
+ const object = await resolver.resolve(q);
+ if (q !== object.id) {
+ const result = await dbResolver.getUserFromApId(getApId(object));
+ if (result) return [
+ result
+ ];
+ }
+ return isActor(object) ? Promise.all([
+ createPerson(getApId(object), resolver.reset())
+ ]) : [];
+ } else {
+ let match = q.match(/^@?(?[a-zA-Z0-9_]+)@(?[a-zA-Z0-9-.]+\.[a-zA-Z0-9-]+)$/);
+ if (!match) match = q.match(/^@(?[a-zA-Z0-9_]+)$/);
+ if (match) {
+ // check if user is already in database
+ const dbResult = await Users.findOneBy({
+ usernameLower: match.groups.user.toLowerCase(),
+ host: match.groups?.host ?? IsNull()
+ });
+ if (dbResult) return [
+ dbResult
+ ];
+ const result = await resolveUser(match.groups.user.toLowerCase(), match.groups?.host ?? null);
+ if (result) return [
+ result
+ ];
+ // no matches found
+ return [];
+ }
+ }
+ } catch (e) {
+ console.log(`[mastodon-client] resolve user '${q}' failed: ${e.message}`);
+ return [];
+ }
+ }
+ const query = PaginationHelpers.makePaginationQuery(Users.createQueryBuilder("user"), undefined, minId, maxId);
+ if (following) {
+ const followingQuery = Followings.createQueryBuilder("following").select("following.followeeId").where("following.followerId = :followerId", {
+ followerId: user.id
+ });
+ query.andWhere(new Brackets((qb)=>{
+ qb.where(`user.id IN (${followingQuery.getQuery()} UNION ALL VALUES (:meId))`, {
+ meId: user.id
+ });
+ }));
+ }
+ query.andWhere(new Brackets((qb)=>{
+ qb.where("user.name ILIKE :q", {
+ q: `%${sqlLikeEscape(q)}%`
+ });
+ qb.orWhere("concat_ws('@', user.usernameLower, user.host) ILIKE :q", {
+ q: `%${sqlLikeEscape(q)}%`
+ });
+ }));
+ query.orderBy({
+ 'user.notesCount': 'DESC'
+ });
+ return query.skip(offset ?? 0).take(limit).getMany().then((p)=>minId ? p.reverse() : p);
+ }
+ static async searchNotes(q, resolve, following, accountId, maxId, minId, limit, offset, ctx) {
+ if (accountId && following) throw new Error("The 'following' and 'accountId' parameters cannot be used simultaneously");
+ const user = ctx.user;
+ if (resolve) {
+ try {
+ if (q.startsWith('https://') || q.startsWith('http://')) {
+ // try resolving locally first
+ const dbResolver = new DbResolver();
+ const dbResult = await dbResolver.getNoteFromApId(q);
+ if (dbResult) return [
+ dbResult
+ ];
+ // ask remote
+ const resolver = new Resolver();
+ resolver.setUser(user);
+ const object = await resolver.resolve(q);
+ if (q !== object.id) {
+ const result = await dbResolver.getNoteFromApId(getApId(object));
+ if (result) return [
+ result
+ ];
+ }
+ return isPost(object) ? createNote(getApId(object), resolver.reset(), true).then((p)=>p ? [
+ p
+ ] : []) : [];
+ }
+ } catch (e) {
+ logger.warn(`Resolving note '${q}' failed: ${e.message}`);
+ return [];
+ }
+ }
+ const query = PaginationHelpers.makePaginationQuery(Notes.createQueryBuilder("note"), undefined, minId, maxId);
+ if (accountId) {
+ query.andWhere("note.userId = :userId", {
+ userId: accountId
+ });
+ }
+ if (following) {
+ const followingQuery = Followings.createQueryBuilder("following").select("following.followeeId").where("following.followerId = :followerId", {
+ followerId: user.id
+ });
+ query.andWhere(new Brackets((qb)=>{
+ qb.where(`note.userId IN (${followingQuery.getQuery()} UNION ALL VALUES (:meId))`, {
+ meId: user.id
+ });
+ }));
+ }
+ query.leftJoinAndSelect("note.renote", "renote");
+ generateFtsQuery(query, q);
+ generateVisibilityQuery(query, user);
+ if (!accountId) {
+ generateMutedUserQuery(query, user);
+ generateBlockedUserQuery(query, user);
+ }
+ query.setParameter("meId", user.id);
+ return query.skip(offset ?? 0).take(limit).getMany().then((p)=>minId ? p.reverse() : p);
+ }
+ static async searchTags(q, excludeUnreviewed, limit, offset) {
+ const tags = Hashtags.createQueryBuilder('tag').select('tag.name').distinctOn([
+ 'tag.name'
+ ]).where("tag.name ILIKE :q", {
+ q: `%${sqlLikeEscape(q)}%`
+ }).orderBy({
+ 'tag.name': 'ASC'
+ }).skip(offset ?? 0).take(limit).getMany();
+ return tags.then((p)=>p.map((tag)=>{
+ return {
+ name: tag.name,
+ url: `${config.url}/tags/${tag.name}`,
+ history: null
+ };
+ }));
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/helpers/timeline.js b/packages/backend/built/server/api/mastodon/helpers/timeline.js
new file mode 100644
index 0000000..f58de79
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/helpers/timeline.js
@@ -0,0 +1,148 @@
+import { Notes, Notifications, UserListJoinings } from "../../../../models/index.js";
+import { Brackets } from "typeorm";
+import { generateChannelQuery } from "../../common/generate-channel-query.js";
+import { generateRepliesQuery } from "../../common/generate-replies-query.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+import { generateMutedUserRenotesQueryForNotes } from "../../common/generated-muted-renote-query.js";
+import { fetchMeta } from "../../../../misc/fetch-meta.js";
+import { PaginationHelpers } from "./pagination.js";
+import { UserHelpers } from "./user.js";
+import { UserConverter } from "../converters/user.js";
+import { NoteConverter } from "../converters/note.js";
+import { awaitAll } from "../../../../prelude/await-all.js";
+import { unique } from "../../../../prelude/array.js";
+import { MastoApiError } from "../middleware/catch-errors.js";
+import { generatePaginationData } from "../middleware/pagination.js";
+import { generateListQuery } from "../../common/generate-list-query.js";
+import { generateFollowingQuery } from "../../common/generate-following-query.js";
+export class TimelineHelpers {
+ static async getHomeTimeline(maxId, sinceId, minId, limit = 20, ctx) {
+ if (limit > 40) limit = 40;
+ const user = ctx.user;
+ const query = PaginationHelpers.makePaginationQuery(Notes.createQueryBuilder("note"), sinceId, maxId, minId).leftJoinAndSelect("note.user", "user").leftJoinAndSelect("note.renote", "renote");
+ await generateFollowingQuery(query, user);
+ generateListQuery(query, user);
+ generateChannelQuery(query, user);
+ generateRepliesQuery(query, true, user);
+ generateVisibilityQuery(query, user);
+ generateMutedUserQuery(query, user);
+ generateBlockedUserQuery(query, user);
+ generateMutedUserRenotesQueryForNotes(query, user);
+ query.andWhere("note.visibility != 'hidden'");
+ return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx);
+ }
+ static async getPublicTimeline(maxId, sinceId, minId, limit = 20, onlyMedia = false, local = false, remote = false, ctx) {
+ if (limit > 40) limit = 40;
+ const user = ctx.user;
+ if (local && remote) {
+ throw new Error("local and remote are mutually exclusive options");
+ }
+ if (!local) {
+ const m = await fetchMeta();
+ if (m.disableGlobalTimeline) {
+ if (user == null || !(user.isAdmin || user.isModerator)) {
+ throw new Error("global timeline is disabled");
+ }
+ }
+ }
+ const query = PaginationHelpers.makePaginationQuery(Notes.createQueryBuilder("note"), sinceId, maxId, minId).andWhere("note.visibility = 'public'");
+ if (remote) query.andWhere("note.userHost IS NOT NULL");
+ if (local) query.andWhere("note.userHost IS NULL");
+ if (!local) query.andWhere("note.channelId IS NULL");
+ query.leftJoinAndSelect("note.user", "user").leftJoinAndSelect("note.renote", "renote");
+ generateRepliesQuery(query, true, user);
+ if (user) {
+ generateMutedUserQuery(query, user);
+ generateBlockedUserQuery(query, user);
+ generateMutedUserRenotesQueryForNotes(query, user);
+ }
+ if (onlyMedia) query.andWhere("note.fileIds != '{}'");
+ return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx);
+ }
+ static async getListTimeline(list, maxId, sinceId, minId, limit = 20, ctx) {
+ if (limit > 40) limit = 40;
+ const user = ctx.user;
+ if (user.id != list.userId) throw new Error("List is not owned by user");
+ const listQuery = UserListJoinings.createQueryBuilder("member").select("member.userId", 'userId').where("member.userListId = :listId");
+ const query = PaginationHelpers.makePaginationQuery(Notes.createQueryBuilder("note"), sinceId, maxId, minId).andWhere(`note.userId IN (${listQuery.getQuery()})`).andWhere("note.visibility != 'specified'").leftJoinAndSelect("note.user", "user").leftJoinAndSelect("note.renote", "renote").setParameters({
+ listId: list.id
+ });
+ generateVisibilityQuery(query, user);
+ return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx);
+ }
+ static async getTagTimeline(tag, maxId, sinceId, minId, limit = 20, any, all, none, onlyMedia = false, local = false, remote = false, ctx) {
+ if (limit > 40) limit = 40;
+ const user = ctx.user;
+ if (tag.length < 1) throw new MastoApiError(400, "Tag cannot be empty");
+ if (local && remote) {
+ throw new Error("local and remote are mutually exclusive options");
+ }
+ const query = PaginationHelpers.makePaginationQuery(Notes.createQueryBuilder("note"), sinceId, maxId, minId).andWhere("note.visibility = 'public'").andWhere("note.tags @> array[:tag]::varchar[]", {
+ tag: tag
+ });
+ if (any.length > 0) query.andWhere("note.tags && array[:...any]::varchar[]", {
+ any: any
+ });
+ if (all.length > 0) query.andWhere("note.tags @> array[:...all]::varchar[]", {
+ all: all
+ });
+ if (none.length > 0) query.andWhere("NOT(note.tags @> array[:...none]::varchar[])", {
+ none: none
+ });
+ if (remote) query.andWhere("note.userHost IS NOT NULL");
+ if (local) query.andWhere("note.userHost IS NULL");
+ if (!local) query.andWhere("note.channelId IS NULL");
+ query.leftJoinAndSelect("note.user", "user").leftJoinAndSelect("note.renote", "renote");
+ generateRepliesQuery(query, true, user);
+ if (user) {
+ generateMutedUserQuery(query, user);
+ generateBlockedUserQuery(query, user);
+ generateMutedUserRenotesQueryForNotes(query, user);
+ }
+ if (onlyMedia) query.andWhere("note.fileIds != '{}'");
+ return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx);
+ }
+ static async getConversations(maxId, sinceId, minId, limit = 20, ctx) {
+ if (limit > 40) limit = 40;
+ const user = ctx.user;
+ const sq = Notes.createQueryBuilder("note").select("COALESCE(note.threadId, note.id)", "conversationId").addSelect("note.id", "latest").distinctOn([
+ "COALESCE(note.threadId, note.id)"
+ ]).orderBy({
+ "COALESCE(note.threadId, note.id)": minId ? "ASC" : "DESC",
+ "note.id": "DESC"
+ }).andWhere("note.visibility = 'specified'").andWhere(new Brackets((qb)=>{
+ qb.where("note.userId = :userId");
+ qb.orWhere("note.visibleUserIds @> array[:userId]::varchar[]");
+ }));
+ const query = PaginationHelpers.makePaginationQuery(Notes.createQueryBuilder("note"), sinceId, maxId, minId).innerJoin(`(${sq.getQuery()})`, "sq", "note.id = sq.latest").setParameters({
+ userId: user.id
+ });
+ return query.take(limit).getMany().then((p)=>{
+ if (minId !== undefined) p = p.reverse();
+ const conversations = p.map((c)=>{
+ // Gather all unique IDs except for the local user
+ const userIds = unique([
+ c.userId
+ ].concat(c.visibleUserIds).filter((p)=>p != user.id));
+ const users = userIds.map((id)=>UserHelpers.getUserCached(id, ctx).catch((_)=>null));
+ const accounts = Promise.all(users).then((u)=>UserConverter.encodeMany(u.filter((u)=>u), ctx));
+ const unread = Notifications.createQueryBuilder('notification').where("notification.noteId = :noteId").andWhere("notification.notifieeId = :userId").andWhere("notification.isRead = FALSE").andWhere("notification.type IN (:...types)").setParameter("noteId", c.id).setParameter("userId", user.id).setParameter("types", [
+ 'reply',
+ 'mention'
+ ]).getExists();
+ return {
+ id: c.threadId ?? c.id,
+ accounts: accounts.then((u)=>u.length > 0 ? u : UserConverter.encodeMany([
+ user
+ ], ctx)),
+ last_status: NoteConverter.encode(c, ctx),
+ unread: unread
+ };
+ });
+ ctx.pagination = generatePaginationData(p.map((p)=>p.threadId ?? p.id), limit);
+ return Promise.all(conversations.map((c)=>awaitAll(c)));
+ });
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/helpers/user.js b/packages/backend/built/server/api/mastodon/helpers/user.js
new file mode 100644
index 0000000..b13859c
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/helpers/user.js
@@ -0,0 +1,441 @@
+import { Blockings, DriveFiles, Followings, FollowRequests, Mutings, NoteFavorites, NoteReactions, Notes, NoteWatchings, RegistryItems, UserNotePinings, UserProfiles, Users } from "../../../../models/index.js";
+import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
+import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
+import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
+import AsyncLock from "async-lock";
+import { getUser } from "../../common/getters.js";
+import { PaginationHelpers } from "./pagination.js";
+import { awaitAll } from "../../../../prelude/await-all.js";
+import createFollowing from "../../../../services/following/create.js";
+import deleteFollowing from "../../../../services/following/delete.js";
+import cancelFollowRequest from "../../../../services/following/requests/cancel.js";
+import createBlocking from "../../../../services/blocking/create.js";
+import deleteBlocking from "../../../../services/blocking/delete.js";
+import { genId } from "../../../../misc/gen-id.js";
+import { publishUserEvent } from "../../../../services/stream.js";
+import { UserConverter } from "../converters/user.js";
+import acceptFollowRequest from "../../../../services/following/requests/accept.js";
+import { rejectFollowRequest } from "../../../../services/following/reject.js";
+import { Brackets, IsNull } from "typeorm";
+import { VisibilityConverter } from "../converters/visibility.js";
+import { toSingleLast } from "../../../../prelude/array.js";
+import { MediaHelpers } from "./media.js";
+import { verifyLink } from "../../../../services/fetch-rel-me.js";
+import { MastoApiError } from "../middleware/catch-errors.js";
+import { resolveUser } from "../../../../remote/resolve-user.js";
+import { updatePerson } from "../../../../remote/activitypub/models/person.js";
+import { promiseEarlyReturn } from "../../../../prelude/promise.js";
+import { updateUserProfileData } from "../../../../services/i/update.js";
+export class UserHelpers {
+ static async followUser(target, reblogs, notify, ctx) {
+ //FIXME: implement reblogs & notify params
+ const localUser = ctx.user;
+ const following = await Followings.exist({
+ where: {
+ followerId: localUser.id,
+ followeeId: target.id
+ }
+ });
+ const requested = await FollowRequests.exist({
+ where: {
+ followerId: localUser.id,
+ followeeId: target.id
+ }
+ });
+ if (!following && !requested) await createFollowing(localUser, target);
+ return this.getUserRelationshipTo(target.id, localUser.id);
+ }
+ static async unfollowUser(target, ctx) {
+ const localUser = ctx.user;
+ const following = await Followings.exist({
+ where: {
+ followerId: localUser.id,
+ followeeId: target.id
+ }
+ });
+ const requested = await FollowRequests.exist({
+ where: {
+ followerId: localUser.id,
+ followeeId: target.id
+ }
+ });
+ if (following) await deleteFollowing(localUser, target);
+ if (requested) await cancelFollowRequest(target, localUser);
+ return this.getUserRelationshipTo(target.id, localUser.id);
+ }
+ static async blockUser(target, ctx) {
+ const localUser = ctx.user;
+ const blocked = await Blockings.exist({
+ where: {
+ blockerId: localUser.id,
+ blockeeId: target.id
+ }
+ });
+ if (!blocked) await createBlocking(localUser, target);
+ return this.getUserRelationshipTo(target.id, localUser.id);
+ }
+ static async unblockUser(target, ctx) {
+ const localUser = ctx.user;
+ const blocked = await Blockings.exist({
+ where: {
+ blockerId: localUser.id,
+ blockeeId: target.id
+ }
+ });
+ if (blocked) await deleteBlocking(localUser, target);
+ return this.getUserRelationshipTo(target.id, localUser.id);
+ }
+ static async muteUser(target, notifications = true, duration = 0, ctx) {
+ //FIXME: respect notifications parameter
+ const localUser = ctx.user;
+ const muted = await Mutings.exist({
+ where: {
+ muterId: localUser.id,
+ muteeId: target.id
+ }
+ });
+ if (!muted) {
+ await Mutings.insert({
+ id: genId(),
+ createdAt: new Date(),
+ expiresAt: duration === 0 ? null : new Date(new Date().getTime() + duration * 1000),
+ muterId: localUser.id,
+ muteeId: target.id
+ });
+ publishUserEvent(localUser.id, "mute", target);
+ NoteWatchings.delete({
+ userId: localUser.id,
+ noteUserId: target.id
+ });
+ }
+ return this.getUserRelationshipTo(target.id, localUser.id);
+ }
+ static async unmuteUser(target, ctx) {
+ const localUser = ctx.user;
+ const muting = await Mutings.findOneBy({
+ muterId: localUser.id,
+ muteeId: target.id
+ });
+ if (muting) {
+ await Mutings.delete({
+ id: muting.id
+ });
+ publishUserEvent(localUser.id, "unmute", target);
+ }
+ return this.getUserRelationshipTo(target.id, localUser.id);
+ }
+ static async acceptFollowRequest(target, ctx) {
+ const localUser = ctx.user;
+ const pending = await FollowRequests.exist({
+ where: {
+ followerId: target.id,
+ followeeId: localUser.id
+ }
+ });
+ if (pending) await acceptFollowRequest(localUser, target);
+ return this.getUserRelationshipTo(target.id, localUser.id);
+ }
+ static async rejectFollowRequest(target, ctx) {
+ const localUser = ctx.user;
+ const pending = await FollowRequests.exist({
+ where: {
+ followerId: target.id,
+ followeeId: localUser.id
+ }
+ });
+ if (pending) await rejectFollowRequest(localUser, target);
+ return this.getUserRelationshipTo(target.id, localUser.id);
+ }
+ static async updateCredentials(ctx) {
+ const user = ctx.user;
+ const files = ctx.request.files;
+ const formData = ctx.request.body;
+ const updates = {};
+ const profileUpdates = {};
+ const avatar = toSingleLast(files?.avatar);
+ const header = toSingleLast(files?.header);
+ if (avatar) {
+ const file = await MediaHelpers.uploadMediaBasic(avatar, ctx);
+ updates.avatarId = file.id;
+ updates.avatarBlurhash = file.blurhash;
+ updates.avatarUrl = DriveFiles.getDatabasePrefetchUrl(file, true);
+ }
+ if (header) {
+ const file = await MediaHelpers.uploadMediaBasic(header, ctx);
+ updates.bannerId = file.id;
+ updates.bannerBlurhash = file.blurhash;
+ updates.bannerUrl = DriveFiles.getDatabasePrefetchUrl(file, false);
+ }
+ if (formData.fields_attributes) {
+ profileUpdates.fields = await Promise.all(formData.fields_attributes.map(async (field)=>{
+ if (!(field.name.trim() === "" && field.value.trim() === "")) {
+ if (field.name.trim() === "") throw new MastoApiError(400, "Field name can not be empty");
+ if (field.value.trim() === "") throw new MastoApiError(400, "Field value can not be empty");
+ }
+ const verified = field.value.startsWith("http") ? await promiseEarlyReturn(verifyLink(field.value, user.username), 1500) ?? false : undefined;
+ return {
+ ...field,
+ verified
+ };
+ })).then((p)=>p.filter((field)=>field.name.trim().length > 0 && field.value.length > 0));
+ }
+ if (formData.display_name) updates.name = formData.display_name;
+ if (formData.note) profileUpdates.description = formData.note;
+ if (formData.locked) updates.isLocked = formData.locked;
+ if (formData.bot) updates.isBot = formData.bot;
+ if (formData.discoverable) updates.isExplorable = formData.discoverable;
+ await updateUserProfileData(user, null, updates, profileUpdates, false);
+ return this.verifyCredentials(ctx);
+ }
+ static async verifyCredentials(ctx) {
+ const user = ctx.user;
+ const acct = UserConverter.encode(user, ctx);
+ const profile = UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ const followRequests = FollowRequests.count({
+ where: {
+ followeeId: user.id
+ }
+ });
+ const privacy = this.getDefaultNoteVisibility(ctx);
+ const fields = profile.then((profile)=>profile.fields.map((field)=>{
+ return {
+ name: field.name,
+ value: field.value
+ };
+ }));
+ return acct.then((acct)=>{
+ const source = {
+ note: profile.then((profile)=>profile.description ?? ''),
+ fields: fields,
+ privacy: privacy.then((p)=>VisibilityConverter.encode(p)),
+ sensitive: profile.then((p)=>p.alwaysMarkNsfw),
+ language: profile.then((p)=>p.lang ?? ''),
+ follow_requests_count: followRequests
+ };
+ const result = {
+ ...acct,
+ source: awaitAll(source)
+ };
+ return awaitAll(result);
+ });
+ }
+ static async getUserFromAcct(acct) {
+ const split = acct.toLowerCase().split('@');
+ if (split.length > 2) throw new Error('Invalid acct');
+ return split[1] == null ? Users.findOneBy({
+ usernameLower: split[0],
+ host: split[1] ?? IsNull()
+ }).then((p)=>{
+ if (p) return p;
+ throw new MastoApiError(404);
+ }) : resolveUser(split[0], split[1], 'no-refresh').catch(()=>{
+ throw new MastoApiError(404);
+ });
+ }
+ static async getUserMutes(maxId, sinceId, minId, limit = 40, ctx) {
+ if (limit > 80) limit = 80;
+ const user = ctx.user;
+ const query = PaginationHelpers.makePaginationQuery(Mutings.createQueryBuilder("muting"), sinceId, maxId, minId);
+ query.andWhere("muting.muterId = :userId", {
+ userId: user.id
+ }).innerJoinAndSelect("muting.mutee", "mutee");
+ return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx).then(async (mutes)=>{
+ const users = mutes.map((p)=>p.mutee).filter((p)=>p);
+ return await UserConverter.encodeMany(users, ctx).then((res)=>res.map((m)=>{
+ const muting = mutes.find((acc)=>acc.muteeId === m.id);
+ return {
+ ...m,
+ mute_expires_at: muting?.expiresAt?.toISOString() ?? null
+ };
+ }));
+ });
+ }
+ static async getUserBlocks(maxId, sinceId, minId, limit = 40, ctx) {
+ if (limit > 80) limit = 80;
+ const user = ctx.user;
+ const query = PaginationHelpers.makePaginationQuery(Blockings.createQueryBuilder("blocking"), sinceId, maxId, minId);
+ query.andWhere("blocking.blockerId = :userId", {
+ userId: user.id
+ }).innerJoinAndSelect("blocking.blockee", "blockee");
+ return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx).then((blocks)=>{
+ return blocks.map((p)=>p.blockee).filter((p)=>p);
+ });
+ }
+ static async getUserFollowRequests(maxId, sinceId, minId, limit = 40, ctx) {
+ if (limit > 80) limit = 80;
+ const user = ctx.user;
+ const query = PaginationHelpers.makePaginationQuery(FollowRequests.createQueryBuilder("request"), sinceId, maxId, minId);
+ query.andWhere("request.followeeId = :userId", {
+ userId: user.id
+ }).innerJoinAndSelect("request.follower", "follower");
+ return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx).then((requests)=>{
+ return requests.map((p)=>p.follower).filter((p)=>p);
+ });
+ }
+ static async getUserStatuses(user, maxId, sinceId, minId, limit = 20, onlyMedia = false, excludeReplies = false, excludeReblogs = false, pinned = false, tagged, ctx) {
+ if (limit > 40) limit = 40;
+ const localUser = ctx.user;
+ if (tagged !== undefined && tagged.length > 0) {
+ //FIXME respect tagged
+ return [];
+ }
+ const query = PaginationHelpers.makePaginationQuery(Notes.createQueryBuilder("note"), sinceId, maxId, minId).andWhere("note.userId = :userId");
+ if (pinned) {
+ const sq = UserNotePinings.createQueryBuilder("pin").select("pin.noteId").where("pin.userId = :userId");
+ query.andWhere(`note.id IN (${sq.getQuery()})`);
+ }
+ if (excludeReblogs) {
+ query.andWhere(new Brackets((qb)=>{
+ qb.where('note.renoteId IS NULL').orWhere('note.text IS NOT NULL').orWhere('note.hasPoll = TRUE').orWhere("note.fileIds != '{}'");
+ }));
+ }
+ if (excludeReplies) {
+ query.leftJoin("note", "thread", "note.threadId = thread.id").andWhere(new Brackets((qb)=>{
+ qb.where("note.replyId IS NULL").orWhere(new Brackets((qb)=>{
+ qb.where('note.mentions = :mentions', {
+ mentions: []
+ }).andWhere('thread.userId = :userId');
+ }));
+ }));
+ }
+ query.leftJoinAndSelect("note.renote", "renote");
+ generateVisibilityQuery(query, localUser);
+ if (localUser) {
+ generateMutedUserQuery(query, localUser, user);
+ generateBlockedUserQuery(query, localUser);
+ }
+ if (onlyMedia) query.andWhere("note.fileIds != '{}'");
+ query.andWhere("note.visibility != 'hidden'");
+ query.andWhere("note.visibility != 'specified'");
+ query.setParameters({
+ userId: user.id
+ });
+ return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx);
+ }
+ static async getUserBookmarks(maxId, sinceId, minId, limit = 20, ctx) {
+ if (limit > 40) limit = 40;
+ const localUser = ctx.user;
+ const query = PaginationHelpers.makePaginationQuery(NoteFavorites.createQueryBuilder("favorite"), sinceId, maxId, minId).andWhere("favorite.userId = :meId", {
+ meId: localUser.id
+ }).leftJoinAndSelect("favorite.note", "note");
+ generateVisibilityQuery(query, localUser);
+ return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx).then((res)=>res.map((p)=>p.note));
+ }
+ static async getUserFavorites(maxId, sinceId, minId, limit = 20, ctx) {
+ if (limit > 40) limit = 40;
+ const localUser = ctx.user;
+ const query = PaginationHelpers.makePaginationQuery(NoteReactions.createQueryBuilder("reaction"), sinceId, maxId, minId).andWhere("reaction.userId = :meId", {
+ meId: localUser.id
+ }).leftJoinAndSelect("reaction.note", "note");
+ generateVisibilityQuery(query, localUser);
+ return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx).then((res)=>res.map((p)=>p.note));
+ }
+ static async getUserRelationships(type, user, maxId, sinceId, minId, limit = 40, ctx) {
+ if (limit > 80) limit = 80;
+ const localUser = ctx.user;
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ if (profile.ffVisibility === "private") {
+ if (!localUser || user.id !== localUser.id) return [];
+ } else if (profile.ffVisibility === "followers") {
+ if (!localUser) return [];
+ if (user.id !== localUser.id) {
+ const isFollowed = await Followings.exist({
+ where: {
+ followeeId: user.id,
+ followerId: localUser.id
+ }
+ });
+ if (!isFollowed) return [];
+ }
+ }
+ const query = PaginationHelpers.makePaginationQuery(Followings.createQueryBuilder("following"), sinceId, maxId, minId);
+ if (type === "followers") {
+ query.andWhere("following.followeeId = :userId", {
+ userId: user.id
+ }).innerJoinAndSelect("following.follower", "follower");
+ } else {
+ query.andWhere("following.followerId = :userId", {
+ userId: user.id
+ }).innerJoinAndSelect("following.followee", "followee");
+ }
+ return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx).then((relations)=>relations.map((p)=>type === "followers" ? p.follower : p.followee).filter((p)=>p));
+ }
+ static async getUserFollowers(user, maxId, sinceId, minId, limit = 40, ctx) {
+ return this.getUserRelationships('followers', user, maxId, sinceId, minId, limit, ctx);
+ }
+ static async getUserFollowing(user, maxId, sinceId, minId, limit = 40, ctx) {
+ return this.getUserRelationships('following', user, maxId, sinceId, minId, limit, ctx);
+ }
+ static async getUserRelationhipToMany(targetIds, localUserId) {
+ return Promise.all(targetIds.map((targetId)=>this.getUserRelationshipTo(targetId, localUserId)));
+ }
+ static async getUserRelationshipTo(targetId, localUserId) {
+ const relation = await Users.getRelation(localUserId, targetId);
+ const response = {
+ id: targetId,
+ following: relation.isFollowing,
+ followed_by: relation.isFollowed,
+ blocking: relation.isBlocking,
+ blocked_by: relation.isBlocked,
+ muting: relation.isMuted,
+ muting_notifications: relation.isMuted,
+ requested: relation.hasPendingFollowRequestFromYou,
+ domain_blocking: false,
+ showing_reblogs: !relation.isRenoteMuted,
+ endorsed: false,
+ notifying: false,
+ note: '' //FIXME
+ };
+ return awaitAll(response);
+ }
+ static async getUserCached(id, ctx) {
+ const cache = ctx.cache;
+ return cache.locks.acquire(id, async ()=>{
+ const cacheHit = cache.users.find((p)=>p.id == id);
+ if (cacheHit) return cacheHit;
+ return getUser(id).then((p)=>{
+ cache.users.push(p);
+ return p;
+ });
+ });
+ }
+ static async getUserCachedOr404(id, ctx) {
+ return this.getUserCached(id, ctx).catch((_)=>{
+ throw new MastoApiError(404);
+ });
+ }
+ static async getUserOr404(id) {
+ return getUser(id).catch((_)=>{
+ throw new MastoApiError(404);
+ });
+ }
+ static async updateUserInBackground(user) {
+ if (Users.isLocalUser(user)) return;
+ if (user.lastFetchedAt != null && Date.now() - user.lastFetchedAt.getTime() < 1000 * 60 * 60 * 24) return;
+ await Users.update(user.id, {
+ lastFetchedAt: new Date()
+ });
+ // noinspection ES6MissingAwait
+ updatePerson(user.uri, undefined, undefined, user);
+ }
+ static getFreshAccountCache() {
+ return {
+ locks: new AsyncLock(),
+ accounts: [],
+ users: []
+ };
+ }
+ static async getDefaultNoteVisibility(ctx) {
+ const user = ctx.user;
+ return RegistryItems.findOneBy({
+ domain: IsNull(),
+ userId: user.id,
+ key: 'defaultNoteVisibility',
+ scope: '{client,base}'
+ }).then((p)=>p?.value ?? 'public');
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/index.js b/packages/backend/built/server/api/mastodon/index.js
new file mode 100644
index 0000000..3d8015f
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/index.js
@@ -0,0 +1,51 @@
+import { setupEndpointsAuth } from "./endpoints/auth.js";
+import { setupEndpointsAccount } from "./endpoints/account.js";
+import { setupEndpointsStatus } from "./endpoints/status.js";
+import { setupEndpointsFilter } from "./endpoints/filter.js";
+import { setupEndpointsTimeline } from "./endpoints/timeline.js";
+import { setupEndpointsNotifications } from "./endpoints/notifications.js";
+import { setupEndpointsSearch } from "./endpoints/search.js";
+import { setupEndpointsMedia } from "./endpoints/media.js";
+import { setupEndpointsMisc } from "./endpoints/misc.js";
+import { setupEndpointsList } from "./endpoints/list.js";
+import { AuthMiddleware } from "./middleware/auth.js";
+import { CatchErrorsMiddleware } from "./middleware/catch-errors.js";
+import { apiLogger } from "../logger.js";
+import { CacheMiddleware } from "./middleware/cache.js";
+import { KoaBodyMiddleware } from "./middleware/koa-body.js";
+import { NormalizeQueryMiddleware } from "./middleware/normalize-query.js";
+import { PaginationMiddleware } from "./middleware/pagination.js";
+import { SetHeadersMiddleware } from "./middleware/set-headers.js";
+import { UserHelpers } from "./helpers/user.js";
+import { setupEndpointsStreaming } from "./endpoints/streaming.js";
+export const logger = apiLogger.createSubLogger("mastodon");
+export function setupMastodonApi(router) {
+ setupMiddleware(router);
+ setupEndpointsAuth(router);
+ setupEndpointsAccount(router);
+ setupEndpointsStatus(router);
+ setupEndpointsFilter(router);
+ setupEndpointsTimeline(router);
+ setupEndpointsNotifications(router);
+ setupEndpointsStreaming(router);
+ setupEndpointsSearch(router);
+ setupEndpointsMedia(router);
+ setupEndpointsList(router);
+ setupEndpointsMisc(router);
+}
+function setupMiddleware(router) {
+ router.use(KoaBodyMiddleware());
+ router.use(SetHeadersMiddleware);
+ router.use(CatchErrorsMiddleware);
+ router.use(NormalizeQueryMiddleware);
+ router.use(PaginationMiddleware);
+ router.use(AuthMiddleware);
+ router.use(CacheMiddleware);
+}
+export function getStubMastoContext(user, filterContext) {
+ return {
+ user: user ?? null,
+ cache: UserHelpers.getFreshAccountCache(),
+ filterContext: filterContext
+ };
+}
diff --git a/packages/backend/built/server/api/mastodon/middleware/auth.js b/packages/backend/built/server/api/mastodon/middleware/auth.js
new file mode 100644
index 0000000..89c83fb
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/middleware/auth.js
@@ -0,0 +1,51 @@
+import { MastoApiError } from "./catch-errors.js";
+import { OAuthTokens } from "../../../../models/index.js";
+import authenticate from "../../authenticate.js";
+import { AuthHelpers } from "../helpers/auth.js";
+export async function AuthMiddleware(ctx, next) {
+ const token = await getTokenFromOAuth(ctx.headers.authorization);
+ ctx.appId = token?.appId;
+ ctx.user = token?.user ?? null;
+ ctx.scopes = token?.scopes ?? [];
+ await next();
+}
+export async function getTokenFromOAuth(authorization) {
+ if (authorization == null) return null;
+ if (authorization.substring(0, 7).toLowerCase() === "bearer ") authorization = authorization.substring(7);
+ return OAuthTokens.findOne({
+ where: {
+ token: authorization,
+ active: true
+ },
+ relations: [
+ 'user'
+ ]
+ }).then((token)=>{
+ if (!token) return null;
+ return {
+ ...token,
+ scopes: AuthHelpers.expandScopes(token.scopes)
+ };
+ });
+}
+export function auth(required, scopes = []) {
+ return async function auth(ctx, next) {
+ if (required && !ctx.user) throw new MastoApiError(401, "This method requires an authenticated user");
+ if (!scopes.every((p)=>ctx.scopes.includes(p))) {
+ if (required) throw new MastoApiError(403, "This action is outside the authorized scopes");
+ ctx.user = null;
+ ctx.scopes = [];
+ }
+ await next();
+ };
+}
+export function MiAuth(required) {
+ return async function MiAuth(ctx, next) {
+ ctx.miauth = await authenticate(ctx.headers.authorization, null, true).catch((_)=>[
+ null,
+ null
+ ]);
+ if (required && !ctx.miauth[0]) throw new MastoApiError(401, "Unauthorized");
+ await next();
+ };
+}
diff --git a/packages/backend/built/server/api/mastodon/middleware/cache.js b/packages/backend/built/server/api/mastodon/middleware/cache.js
new file mode 100644
index 0000000..f6018ab
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/middleware/cache.js
@@ -0,0 +1,5 @@
+import { UserHelpers } from "../helpers/user.js";
+export async function CacheMiddleware(ctx, next) {
+ ctx.cache = UserHelpers.getFreshAccountCache();
+ await next();
+}
diff --git a/packages/backend/built/server/api/mastodon/middleware/catch-errors.js b/packages/backend/built/server/api/mastodon/middleware/catch-errors.js
new file mode 100644
index 0000000..74fb1c3
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/middleware/catch-errors.js
@@ -0,0 +1,54 @@
+import { logger } from "../index.js";
+import { IdentifiableError } from "../../../../misc/identifiable-error.js";
+import { ApiError } from "../../error.js";
+export class MastoApiError extends Error {
+ statusCode;
+ errorDescription;
+ constructor(statusCode, message, description){
+ if (message == null) {
+ switch(statusCode){
+ case 404:
+ message = 'Record not found';
+ break;
+ default:
+ message = 'Unknown error occurred';
+ break;
+ }
+ }
+ super(message);
+ this.errorDescription = description;
+ this.statusCode = statusCode;
+ }
+}
+export async function CatchErrorsMiddleware(ctx, next) {
+ try {
+ await next();
+ } catch (e) {
+ if (e instanceof MastoApiError) {
+ ctx.status = e.statusCode;
+ ctx.body = {
+ error: e.message,
+ error_description: e.errorDescription
+ };
+ return;
+ } else if (e instanceof IdentifiableError) {
+ if (e.message.length < 1) e.message = e.id;
+ ctx.status = 400;
+ } else if (e instanceof ApiError) {
+ ctx.status = e.httpStatusCode ?? 500;
+ } else {
+ logger.error(`Error occured in ${ctx.method} ${ctx.path}:`);
+ if (e instanceof Error) {
+ if (e.stack) logger.error(e.stack);
+ else logger.error(`${e.name}: ${e.message}`);
+ } else {
+ logger.error(e);
+ }
+ ctx.status = 500;
+ }
+ ctx.body = {
+ error: e.message ?? e
+ };
+ return;
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/middleware/filter-context.js b/packages/backend/built/server/api/mastodon/middleware/filter-context.js
new file mode 100644
index 0000000..4f086c3
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/middleware/filter-context.js
@@ -0,0 +1,6 @@
+export function filterContext(context) {
+ return async function filterContext(ctx, next) {
+ ctx.filterContext = context;
+ await next();
+ };
+}
diff --git a/packages/backend/built/server/api/mastodon/middleware/koa-body.js b/packages/backend/built/server/api/mastodon/middleware/koa-body.js
new file mode 100644
index 0000000..6776c8c
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/middleware/koa-body.js
@@ -0,0 +1,14 @@
+import { HttpMethodEnum, koaBody } from "koa-body";
+export function KoaBodyMiddleware() {
+ const options = {
+ multipart: true,
+ urlencoded: true,
+ parsedMethods: [
+ HttpMethodEnum.POST,
+ HttpMethodEnum.PUT,
+ HttpMethodEnum.PATCH,
+ HttpMethodEnum.DELETE
+ ] // dear god mastodon why
+ };
+ return koaBody(options);
+}
diff --git a/packages/backend/built/server/api/mastodon/middleware/normalize-query.js b/packages/backend/built/server/api/mastodon/middleware/normalize-query.js
new file mode 100644
index 0000000..8d4bd39
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/middleware/normalize-query.js
@@ -0,0 +1,13 @@
+export async function NormalizeQueryMiddleware(ctx, next) {
+ if (ctx.request.query) {
+ if (!ctx.request.body || Object.keys(ctx.request.body).length === 0) {
+ ctx.request.body = ctx.request.query;
+ } else {
+ ctx.request.body = {
+ ...ctx.request.body,
+ ...ctx.request.query
+ };
+ }
+ }
+ await next();
+}
diff --git a/packages/backend/built/server/api/mastodon/middleware/pagination.js b/packages/backend/built/server/api/mastodon/middleware/pagination.js
new file mode 100644
index 0000000..b1213fa
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/middleware/pagination.js
@@ -0,0 +1,26 @@
+import config from "../../../../config/index.js";
+export async function PaginationMiddleware(ctx, next) {
+ await next();
+ if (!ctx.pagination) return;
+ const link = [];
+ const limit = ctx.pagination.limit;
+ if (ctx.pagination.maxId) {
+ const l = `<${config.url}/api${ctx.path}?limit=${limit}&max_id=${ctx.pagination.maxId}>; rel="next"`;
+ link.push(l);
+ }
+ if (ctx.pagination.minId) {
+ const l = `<${config.url}/api${ctx.path}?limit=${limit}&min_id=${ctx.pagination.minId}>; rel="prev"`;
+ link.push(l);
+ }
+ if (link.length > 0) {
+ ctx.response.append('Link', link.join(', '));
+ }
+}
+export function generatePaginationData(ids, limit) {
+ if (ids.length < 1) return undefined;
+ return {
+ limit: limit,
+ maxId: ids.length < limit ? undefined : ids.at(-1),
+ minId: ids.at(0)
+ };
+}
diff --git a/packages/backend/built/server/api/mastodon/middleware/set-headers.js b/packages/backend/built/server/api/mastodon/middleware/set-headers.js
new file mode 100644
index 0000000..47c1b61
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/middleware/set-headers.js
@@ -0,0 +1,7 @@
+const headers = {
+ "Access-Control-Expose-Headers": "Link,Connection,Sec-Websocket-Accept,Upgrade"
+};
+export async function SetHeadersMiddleware(ctx, next) {
+ ctx.set(headers);
+ await next();
+}
diff --git a/packages/backend/built/server/api/mastodon/streaming/channel.js b/packages/backend/built/server/api/mastodon/streaming/channel.js
new file mode 100644
index 0000000..c73f43f
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/streaming/channel.js
@@ -0,0 +1,35 @@
+export class MastodonStream {
+ connection;
+ chName;
+ static shouldShare;
+ static requireCredential;
+ static requiredScopes = [];
+ get user() {
+ return this.connection.user;
+ }
+ get userProfile() {
+ return this.connection.userProfile;
+ }
+ get following() {
+ return this.connection.following;
+ }
+ get muting() {
+ return this.connection.muting;
+ }
+ get renoteMuting() {
+ return this.connection.renoteMuting;
+ }
+ get blocking() {
+ return this.connection.blocking;
+ }
+ get hidden() {
+ return this.connection.hidden;
+ }
+ get subscriber() {
+ return this.connection.subscriber;
+ }
+ constructor(connection, name){
+ this.chName = name;
+ this.connection = connection;
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/streaming/channels/direct.js b/packages/backend/built/server/api/mastodon/streaming/channels/direct.js
new file mode 100644
index 0000000..bdacb84
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/streaming/channels/direct.js
@@ -0,0 +1,59 @@
+import { MastodonStream } from "../channel.js";
+import { NoteConverter } from "../../converters/note.js";
+import { NoteHelpers } from "../../helpers/note.js";
+export class MastodonStreamDirect extends MastodonStream {
+ static shouldShare = true;
+ static requireCredential = true;
+ static requiredScopes = [
+ 'read:statuses'
+ ];
+ constructor(connection, name){
+ super(connection, name);
+ this.onNote = this.onNote.bind(this);
+ this.onNoteEvent = this.onNoteEvent.bind(this);
+ }
+ get user() {
+ return this.connection.user;
+ }
+ async init() {
+ this.subscriber.on("notesStream", this.onNote);
+ this.subscriber.on("noteUpdatesStream", this.onNoteEvent);
+ }
+ async onNote(note) {
+ if (!this.shouldProcessNote(note)) return;
+ NoteConverter.encodeEvent(note, this.user).then((encoded)=>{
+ this.connection.send(this.chName, "update", encoded);
+ });
+ NoteHelpers.getConversationFromEvent(note.id, this.user).then((conversation)=>{
+ this.connection.send(this.chName, "conversation", conversation);
+ });
+ }
+ async onNoteEvent(data) {
+ const note = data.body;
+ if (!this.shouldProcessNote(note)) return;
+ NoteHelpers.getConversationFromEvent(note.id, this.user).then((conversation)=>{
+ this.connection.send(this.chName, "conversation", conversation);
+ });
+ switch(data.type){
+ case "updated":
+ NoteConverter.encodeEvent(note, this.user).then((encoded)=>{
+ this.connection.send(this.chName, "status.update", encoded);
+ });
+ break;
+ case "deleted":
+ this.connection.send(this.chName, "delete", note.id);
+ break;
+ default:
+ break;
+ }
+ }
+ shouldProcessNote(note) {
+ if (note.visibility !== "specified") return false;
+ if (note.userId !== this.user.id && !note.visibleUserIds?.includes(this.user.id)) return false;
+ return true;
+ }
+ dispose() {
+ this.subscriber.off("notesStream", this.onNote);
+ this.subscriber.off("noteUpdatesStream", this.onNoteEvent);
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/streaming/channels/list.js b/packages/backend/built/server/api/mastodon/streaming/channels/list.js
new file mode 100644
index 0000000..2ba2caa
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/streaming/channels/list.js
@@ -0,0 +1,74 @@
+import { MastodonStream } from "../channel.js";
+import { NoteConverter } from "../../converters/note.js";
+import { UserListJoinings } from "../../../../../models/index.js";
+export class MastodonStreamList extends MastodonStream {
+ static shouldShare = false;
+ static requireCredential = true;
+ static requiredScopes = [
+ 'read:statuses'
+ ];
+ listId;
+ listUsers = [];
+ listUsersClock;
+ constructor(connection, name, list){
+ super(connection, name);
+ this.listId = list;
+ this.onNote = this.onNote.bind(this);
+ this.onNoteEvent = this.onNoteEvent.bind(this);
+ this.updateListUsers = this.updateListUsers.bind(this);
+ }
+ get user() {
+ return this.connection.user;
+ }
+ async init() {
+ if (!this.listId) return;
+ this.subscriber.on("notesStream", this.onNote);
+ this.subscriber.on("noteUpdatesStream", this.onNoteEvent);
+ this.updateListUsers();
+ this.listUsersClock = setInterval(this.updateListUsers, 5000);
+ }
+ async updateListUsers() {
+ const users = await UserListJoinings.find({
+ where: {
+ userListId: this.listId
+ },
+ select: [
+ "userId"
+ ]
+ });
+ this.listUsers = users.map((x)=>x.userId);
+ }
+ async onNote(note) {
+ if (!await this.shouldProcessNote(note)) return;
+ const encoded = await NoteConverter.encodeEvent(note, this.user, 'home');
+ this.connection.send(this.chName, "update", encoded);
+ }
+ async onNoteEvent(data) {
+ const note = data.body;
+ if (!await this.shouldProcessNote(note)) return;
+ switch(data.type){
+ case "updated":
+ const encoded = await NoteConverter.encodeEvent(note, this.user, 'home');
+ this.connection.send(this.chName, "status.update", encoded);
+ break;
+ case "deleted":
+ this.connection.send(this.chName, "delete", note.id);
+ break;
+ default:
+ break;
+ }
+ }
+ async shouldProcessNote(note) {
+ if (!this.listUsers.includes(note.userId)) return false;
+ if (note.channelId) return false;
+ if (note.renoteId !== null && !note.text && this.renoteMuting.has(note.userId)) return false;
+ if (note.visibility === "specified") return !!note.visibleUserIds?.includes(this.user.id);
+ if (note.visibility === "followers") return this.following.has(note.userId);
+ return true;
+ }
+ dispose() {
+ this.subscriber.off("notesStream", this.onNote);
+ this.subscriber.off("noteUpdatesStream", this.onNoteEvent);
+ clearInterval(this.listUsersClock);
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/streaming/channels/public.js b/packages/backend/built/server/api/mastodon/streaming/channels/public.js
new file mode 100644
index 0000000..0df20ff
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/streaming/channels/public.js
@@ -0,0 +1,68 @@
+import { MastodonStream } from "../channel.js";
+import { isUserRelated } from "../../../../../misc/is-user-related.js";
+import { isInstanceMuted } from "../../../../../misc/is-instance-muted.js";
+import { NoteConverter } from "../../converters/note.js";
+import { fetchMeta } from "../../../../../misc/fetch-meta.js";
+import isQuote from "../../../../../misc/is-quote.js";
+export class MastodonStreamPublic extends MastodonStream {
+ static shouldShare = true;
+ static requireCredential = false;
+ mediaOnly;
+ localOnly;
+ remoteOnly;
+ allowLocalOnly;
+ constructor(connection, name){
+ super(connection, name);
+ this.mediaOnly = name.endsWith(":media");
+ this.localOnly = name.startsWith("public:local");
+ this.remoteOnly = name.startsWith("public:remote");
+ this.allowLocalOnly = name.startsWith("public:allow_local_only");
+ this.onNote = this.onNote.bind(this);
+ this.onNoteEvent = this.onNoteEvent.bind(this);
+ }
+ async init() {
+ const meta = await fetchMeta();
+ if (meta.disableGlobalTimeline) {
+ if (this.user == null || !(this.user.isAdmin || this.user.isModerator)) return;
+ }
+ this.subscriber.on("notesStream", this.onNote);
+ this.subscriber.on("noteUpdatesStream", this.onNoteEvent);
+ }
+ async onNote(note) {
+ if (!await this.shouldProcessNote(note)) return;
+ const encoded = await NoteConverter.encodeEvent(note, this.user, 'public');
+ this.connection.send(this.chName, "update", encoded);
+ }
+ async onNoteEvent(data) {
+ const note = data.body;
+ if (!await this.shouldProcessNote(note)) return;
+ switch(data.type){
+ case "updated":
+ const encoded = await NoteConverter.encodeEvent(note, this.user, 'public');
+ this.connection.send(this.chName, "status.update", encoded);
+ break;
+ case "deleted":
+ this.connection.send(this.chName, "delete", note.id);
+ break;
+ default:
+ break;
+ }
+ }
+ async shouldProcessNote(note) {
+ if (note.visibility !== "public") return false;
+ if (note.channelId != null) return false;
+ if (this.mediaOnly && note.fileIds.length < 1) return false;
+ if (this.localOnly && note.userHost !== null) return false;
+ if (this.remoteOnly && note.userHost === null) return false;
+ if (note.localOnly && !this.allowLocalOnly && !this.localOnly) return false;
+ if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return false;
+ if (isUserRelated(note, this.muting)) return false;
+ if (isUserRelated(note, this.blocking)) return false;
+ if (note.renoteId !== null && !isQuote(note) && this.renoteMuting.has(note.userId)) return false;
+ return true;
+ }
+ dispose() {
+ this.subscriber.off("notesStream", this.onNote);
+ this.subscriber.off("noteUpdatesStream", this.onNoteEvent);
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/streaming/channels/tag.js b/packages/backend/built/server/api/mastodon/streaming/channels/tag.js
new file mode 100644
index 0000000..ed32c8b
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/streaming/channels/tag.js
@@ -0,0 +1,61 @@
+import { MastodonStream } from "../channel.js";
+import { isUserRelated } from "../../../../../misc/is-user-related.js";
+import { isInstanceMuted } from "../../../../../misc/is-instance-muted.js";
+import { NoteConverter } from "../../converters/note.js";
+import isQuote from "../../../../../misc/is-quote.js";
+export class MastodonStreamTag extends MastodonStream {
+ static shouldShare = false;
+ static requireCredential = false;
+ localOnly;
+ tag;
+ constructor(connection, name, tag){
+ super(connection, name);
+ this.tag = tag;
+ this.localOnly = name.startsWith("hashtag:local");
+ this.onNote = this.onNote.bind(this);
+ this.onNoteEvent = this.onNoteEvent.bind(this);
+ }
+ get user() {
+ return this.connection.user;
+ }
+ async init() {
+ if (!this.tag) return;
+ this.subscriber.on("notesStream", this.onNote);
+ this.subscriber.on("noteUpdatesStream", this.onNoteEvent);
+ }
+ async onNote(note) {
+ if (!await this.shouldProcessNote(note)) return;
+ const encoded = await NoteConverter.encodeEvent(note, this.user, 'public');
+ this.connection.send(this.chName, "update", encoded);
+ }
+ async onNoteEvent(data) {
+ const note = data.body;
+ if (!await this.shouldProcessNote(note)) return;
+ switch(data.type){
+ case "updated":
+ const encoded = await NoteConverter.encodeEvent(note, this.user, 'public');
+ this.connection.send(this.chName, "status.update", encoded);
+ break;
+ case "deleted":
+ this.connection.send(this.chName, "delete", note.id);
+ break;
+ default:
+ break;
+ }
+ }
+ async shouldProcessNote(note) {
+ if (note.visibility !== "public") return false;
+ if (note.channelId != null) return false;
+ if (this.localOnly && note.userHost !== null) return false;
+ if (!note.tags?.includes(this.tag)) return false;
+ if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return false;
+ if (isUserRelated(note, this.muting)) return false;
+ if (isUserRelated(note, this.blocking)) return false;
+ if (note.renoteId !== null && !isQuote(note) && this.renoteMuting.has(note.userId)) return false;
+ return true;
+ }
+ dispose() {
+ this.subscriber.off("notesStream", this.onNote);
+ this.subscriber.off("noteUpdatesStream", this.onNoteEvent);
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/streaming/channels/user.js b/packages/backend/built/server/api/mastodon/streaming/channels/user.js
new file mode 100644
index 0000000..c2cb6fc
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/streaming/channels/user.js
@@ -0,0 +1,100 @@
+import { MastodonStream } from "../channel.js";
+import { isUserRelated } from "../../../../../misc/is-user-related.js";
+import { isInstanceMuted } from "../../../../../misc/is-instance-muted.js";
+import { NoteConverter } from "../../converters/note.js";
+import { NotificationConverter } from "../../converters/notification.js";
+import { AnnouncementConverter } from "../../converters/announcement.js";
+import isQuote from "../../../../../misc/is-quote.js";
+export class MastodonStreamUser extends MastodonStream {
+ static shouldShare = true;
+ static requireCredential = true;
+ static requiredScopes = [
+ 'read:statuses',
+ 'read:notifications'
+ ];
+ notificationsOnly;
+ constructor(connection, name){
+ super(connection, name);
+ this.notificationsOnly = name === "user:notification";
+ this.onNote = this.onNote.bind(this);
+ this.onNoteEvent = this.onNoteEvent.bind(this);
+ this.onUserEvent = this.onUserEvent.bind(this);
+ this.onBroadcastEvent = this.onBroadcastEvent.bind(this);
+ }
+ get user() {
+ return this.connection.user;
+ }
+ async init() {
+ this.subscriber.on(`mainStream:${this.user.id}`, this.onUserEvent);
+ if (!this.notificationsOnly) {
+ this.subscriber.on("notesStream", this.onNote);
+ this.subscriber.on("noteUpdatesStream", this.onNoteEvent);
+ this.subscriber.on("broadcast", this.onBroadcastEvent);
+ }
+ }
+ async onNote(note) {
+ if (!await this.shouldProcessNote(note)) return;
+ const encoded = await NoteConverter.encodeEvent(note, this.user, 'home');
+ this.connection.send(this.chName, "update", encoded);
+ }
+ async onNoteEvent(data) {
+ const note = data.body;
+ if (!await this.shouldProcessNote(note)) return;
+ switch(data.type){
+ case "updated":
+ const encoded = await NoteConverter.encodeEvent(note, this.user, 'home');
+ this.connection.send(this.chName, "status.update", encoded);
+ break;
+ case "deleted":
+ this.connection.send(this.chName, "delete", note.id);
+ break;
+ default:
+ break;
+ }
+ }
+ async onUserEvent(data) {
+ switch(data.type){
+ case "notification":
+ const encoded = await NotificationConverter.encodeEvent(data.body.id, this.user, 'notifications');
+ if (encoded) this.connection.send(this.chName, "notification", encoded);
+ break;
+ default:
+ break;
+ }
+ }
+ async onBroadcastEvent(data) {
+ switch(data.type){
+ case "announcementAdded":
+ // This shouldn't be necessary but is for some reason
+ data.body.createdAt = new Date(data.body.createdAt);
+ this.connection.send(this.chName, "announcement", await AnnouncementConverter.encode(data.body, false));
+ break;
+ case "announcementDeleted":
+ this.connection.send(this.chName, "announcement.delete", data.body);
+ break;
+ default:
+ break;
+ }
+ }
+ async shouldProcessNote(note) {
+ if (note.visibility === "hidden") return false;
+ if (note.userId === this.user.id) return true;
+ if (note.visibility === "specified") return note.visibleUserIds?.includes(this.user.id);
+ if (note.channelId) return false;
+ if (this.user.id !== note.userId && !this.following.has(note.userId)) return false;
+ if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return false;
+ if (isUserRelated(note, this.muting)) return false;
+ if (isUserRelated(note, this.blocking)) return false;
+ if (isUserRelated(note, this.hidden)) return false;
+ if (note.renoteId !== null && !isQuote(note) && this.renoteMuting.has(note.userId)) return false;
+ return true;
+ }
+ dispose() {
+ this.subscriber.off(`mainStream:${this.user.id}`, this.onUserEvent);
+ if (!this.notificationsOnly) {
+ this.subscriber.off("notesStream", this.onNote);
+ this.subscriber.off("noteUpdatesStream", this.onNoteEvent);
+ this.subscriber.off("broadcast", this.onBroadcastEvent);
+ }
+ }
+}
diff --git a/packages/backend/built/server/api/mastodon/streaming/index.js b/packages/backend/built/server/api/mastodon/streaming/index.js
new file mode 100644
index 0000000..3725ef3
--- /dev/null
+++ b/packages/backend/built/server/api/mastodon/streaming/index.js
@@ -0,0 +1,245 @@
+import { Blockings, Followings, Mutings, RenoteMutings, UserListJoinings, UserProfiles } from "../../../../models/index.js";
+import { apiLogger } from "../../logger.js";
+import { MastodonStreamUser } from "./channels/user.js";
+import { MastodonStreamDirect } from "./channels/direct.js";
+import { MastodonStreamPublic } from "./channels/public.js";
+import { MastodonStreamList } from "./channels/list.js";
+import { toSingleLast } from "../../../../prelude/array.js";
+import { MastodonStreamTag } from "./channels/tag.js";
+const logger = apiLogger.createSubLogger("streaming").createSubLogger("mastodon");
+const channels = {
+ "user": MastodonStreamUser,
+ "user:notification": MastodonStreamUser,
+ "direct": MastodonStreamDirect,
+ "list": MastodonStreamList,
+ "public": MastodonStreamPublic,
+ "public:media": MastodonStreamPublic,
+ "public:local": MastodonStreamPublic,
+ "public:local:media": MastodonStreamPublic,
+ "public:remote": MastodonStreamPublic,
+ "public:remote:media": MastodonStreamPublic,
+ "public:allow_local_only": MastodonStreamPublic,
+ "public:allow_local_only:media": MastodonStreamPublic,
+ "hashtag": MastodonStreamTag,
+ "hashtag:local": MastodonStreamTag
+};
+export class MastodonStreamingConnection {
+ user;
+ userProfile;
+ following = new Set();
+ muting = new Set();
+ renoteMuting = new Set();
+ blocking = new Set();
+ hidden = new Set();
+ token;
+ wsConnection;
+ channels = [];
+ subscriber;
+ constructor(wsConnection, subscriber, user, token, query){
+ const channel = toSingleLast(query.stream);
+ logger.debug(`New connection on channel: ${channel}`);
+ this.wsConnection = wsConnection;
+ this.subscriber = subscriber;
+ if (user) this.user = user;
+ if (token) this.token = token;
+ this.onMessage = this.onMessage.bind(this);
+ this.onUserEvent = this.onUserEvent.bind(this);
+ this.wsConnection.on("message", this.onMessage);
+ if (this.user) {
+ this.updateFollowing();
+ this.updateMuting();
+ this.updateRenoteMuting();
+ this.updateBlocking();
+ this.updateHidden();
+ this.updateUserProfile();
+ this.subscriber.on(`user:${this.user.id}`, this.onUserEvent);
+ }
+ if (channel) {
+ const list = toSingleLast(query.list);
+ const tag = toSingleLast(query.tag);
+ this.onMessage({
+ type: "utf8",
+ utf8Data: JSON.stringify({
+ stream: channel,
+ type: "subscribe",
+ list,
+ tag
+ })
+ });
+ }
+ }
+ onUserEvent(data) {
+ switch(data.type){
+ case "follow":
+ this.following.add(data.body.id);
+ break;
+ case "unfollow":
+ this.following.delete(data.body.id);
+ break;
+ case "mute":
+ this.muting.add(data.body.id);
+ break;
+ case "unmute":
+ this.muting.delete(data.body.id);
+ break;
+ case "userHidden":
+ this.hidden.add(data.body);
+ break;
+ case "userUnhidden":
+ this.hidden.delete(data.body);
+ break;
+ // TODO: renote mute events
+ // TODO: block events
+ case "updateUserProfile":
+ this.userProfile = data.body;
+ break;
+ case "terminate":
+ this.closeConnection();
+ break;
+ default:
+ break;
+ }
+ }
+ async onMessage(data) {
+ if (data.type !== "utf8") return;
+ if (data.utf8Data == null) return;
+ let message;
+ try {
+ message = JSON.parse(data.utf8Data);
+ } catch (e) {
+ logger.error("Failed to parse json data, ignoring");
+ return;
+ }
+ const { stream, type, list, tag } = message;
+ if (!message.stream || !message.type) {
+ logger.error("Invalid message received, ignoring");
+ return;
+ }
+ if (list ?? tag) logger.info(`${type}: ${stream} ${list ?? tag}`);
+ else logger.info(`${type}: ${stream}`);
+ switch(type){
+ case "subscribe":
+ this.connectChannel(stream, list, tag);
+ break;
+ case "unsubscribe":
+ this.disconnectChannel(stream);
+ break;
+ }
+ }
+ send(stream, event, payload) {
+ const json = JSON.stringify({
+ stream: [
+ stream
+ ],
+ event: event,
+ payload: typeof payload === "string" ? payload : JSON.stringify(payload)
+ });
+ this.wsConnection.send(json);
+ }
+ connectChannel(channel, list, tag) {
+ if (!channels[channel]) {
+ logger.info(`Ignoring connection to unknown channel ${channel}`);
+ return;
+ }
+ if (channels[channel].requireCredential) {
+ if (this.user == null) {
+ logger.info(`Refusing connection to channel ${channel} without authentication, terminating connection`);
+ this.closeConnection();
+ return;
+ } else if (!channels[channel].requiredScopes.every((p)=>this.token?.scopes?.includes(p))) {
+ logger.info(`Refusing connection to channel ${channel} without required OAuth scopes, terminating connection`);
+ this.closeConnection();
+ return;
+ }
+ }
+ if (channels[channel].shouldShare && this.channels.some((c)=>c.chName === channel)) {
+ return;
+ }
+ let ch;
+ if (channel === "list") {
+ ch = new channels[channel](this, channel, list);
+ } else if (channel.startsWith("hashtag")) ch = new channels[channel](this, channel, tag);
+ else ch = new channels[channel](this, channel);
+ this.channels.push(ch);
+ ch.init(null);
+ }
+ disconnectChannel(channelName) {
+ const channel = this.channels.find((c)=>c.chName === channelName);
+ if (channel) {
+ if (channel.dispose) channel.dispose();
+ this.channels = this.channels.filter((c)=>c.chName !== channelName);
+ }
+ }
+ async updateFollowing() {
+ const followings = await Followings.find({
+ where: {
+ followerId: this.user.id
+ },
+ select: [
+ "followeeId"
+ ]
+ });
+ this.following = new Set(followings.map((x)=>x.followeeId));
+ }
+ async updateMuting() {
+ const mutings = await Mutings.find({
+ where: {
+ muterId: this.user.id
+ },
+ select: [
+ "muteeId"
+ ]
+ });
+ this.muting = new Set(mutings.map((x)=>x.muteeId));
+ }
+ async updateRenoteMuting() {
+ const renoteMutings = await RenoteMutings.find({
+ where: {
+ muterId: this.user.id
+ },
+ select: [
+ "muteeId"
+ ]
+ });
+ this.renoteMuting = new Set(renoteMutings.map((x)=>x.muteeId));
+ }
+ async updateBlocking() {
+ const blockings = await Blockings.find({
+ where: {
+ blockeeId: this.user.id
+ },
+ select: [
+ "blockerId"
+ ]
+ });
+ this.blocking = new Set(blockings.map((x)=>x.blockerId));
+ }
+ async updateHidden() {
+ const hidden = await UserListJoinings.find({
+ where: {
+ userList: {
+ userId: this.user.id,
+ hideFromHomeTl: true
+ }
+ },
+ select: [
+ "userId"
+ ]
+ });
+ this.hidden = new Set(hidden.map((x)=>x.userId));
+ }
+ async updateUserProfile() {
+ this.userProfile = await UserProfiles.findOneBy({
+ userId: this.user.id
+ });
+ }
+ closeConnection() {
+ this.wsConnection.close();
+ this.dispose();
+ }
+ dispose() {
+ for (const c of this.channels.filter((c)=>c.dispose)){
+ if (c.dispose) c.dispose();
+ }
+ }
+}
diff --git a/packages/backend/built/server/api/openapi/errors.js b/packages/backend/built/server/api/openapi/errors.js
new file mode 100644
index 0000000..00d3082
--- /dev/null
+++ b/packages/backend/built/server/api/openapi/errors.js
@@ -0,0 +1,68 @@
+export const errors = {
+ "400": {
+ INVALID_PARAM: {
+ value: {
+ error: {
+ message: "Invalid parameter.",
+ code: "INVALID_PARAM",
+ id: "3d81ceae-475f-4600-b2a8-2bc116157532"
+ }
+ }
+ }
+ },
+ "401": {
+ CREDENTIAL_REQUIRED: {
+ value: {
+ error: {
+ message: "Credential required.",
+ code: "CREDENTIAL_REQUIRED",
+ id: "1384574d-a912-4b81-8601-c7b1c4085df1"
+ }
+ }
+ }
+ },
+ "403": {
+ AUTHENTICATION_FAILED: {
+ value: {
+ error: {
+ message: "Authentication failed.",
+ code: "AUTHENTICATION_FAILED",
+ id: "b0a7f5f8-dc2f-4171-b91f-de88ad238e14"
+ }
+ }
+ }
+ },
+ "418": {
+ I_AM_CALC: {
+ value: {
+ error: {
+ message: "You sent a request to Calc instead of the server. How did this happen?",
+ code: "I_AM_CALC",
+ id: "60c46cd1-f23a-46b1-bebe-5d2b73951a84"
+ }
+ }
+ }
+ },
+ "429": {
+ RATE_LIMIT_EXCEEDED: {
+ value: {
+ error: {
+ message: "Rate limit exceeded. Please try again later.",
+ code: "RATE_LIMIT_EXCEEDED",
+ id: "d5826d14-3982-4d2e-8011-b9e9f02499ef"
+ }
+ }
+ }
+ },
+ "500": {
+ INTERNAL_ERROR: {
+ value: {
+ error: {
+ message: "Internal error occurred. Please contact us if the error persists.",
+ code: "INTERNAL_ERROR",
+ id: "5d37dbcb-891e-41ca-a3d6-e690c97775ac"
+ }
+ }
+ }
+ }
+};
diff --git a/packages/backend/built/server/api/openapi/gen-spec.js b/packages/backend/built/server/api/openapi/gen-spec.js
new file mode 100644
index 0000000..8e3871f
--- /dev/null
+++ b/packages/backend/built/server/api/openapi/gen-spec.js
@@ -0,0 +1,200 @@
+import endpoints from "../endpoints.js";
+import config from "../../../config/index.js";
+import { errors as basicErrors } from "./errors.js";
+import { schemas, convertSchemaToOpenApiSchema } from "./schemas.js";
+export function genOpenapiSpec() {
+ const spec = {
+ openapi: "3.0.0",
+ info: {
+ version: "v1",
+ title: "FrozenFriendsYume API",
+ "x-logo": {
+ url: "/static-assets/api-doc.png"
+ }
+ },
+ externalDocs: {
+ description: "Repository",
+ url: "https://iceshrimp.dev/iceshrimp/iceshrimp"
+ },
+ servers: [
+ {
+ url: config.apiUrl
+ }
+ ],
+ paths: {},
+ components: {
+ schemas: schemas,
+ securitySchemes: {
+ ApiKeyAuth: {
+ type: "apiKey",
+ in: "body",
+ name: "i"
+ },
+ // TODO: change this to oauth2 when the remaining oauth stuff is set up
+ Bearer: {
+ type: "http",
+ scheme: "bearer"
+ }
+ }
+ }
+ };
+ for (const endpoint of endpoints.filter((ep)=>!ep.meta.secure)){
+ const errors = {};
+ if (endpoint.meta.errors) {
+ for (const e of Object.values(endpoint.meta.errors)){
+ errors[e.code] = {
+ value: {
+ error: e
+ }
+ };
+ }
+ }
+ const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res) : {};
+ let desc = (endpoint.meta.description ? endpoint.meta.description : "No description provided.") + "\n\n";
+ desc += `**Credential required**: *${endpoint.meta.requireCredential ? "Yes" : "No"}*`;
+ if (endpoint.meta.kind) {
+ const kind = endpoint.meta.kind;
+ desc += ` / **Permission**: *${kind}*`;
+ }
+ const requestType = endpoint.meta.requireFile ? "multipart/form-data" : "application/json";
+ const schema = endpoint.params;
+ if (endpoint.meta.requireFile) {
+ schema.properties.file = {
+ type: "string",
+ format: "binary",
+ description: "The file contents."
+ };
+ schema.required.push("file");
+ }
+ const security = [
+ {
+ ApiKeyAuth: []
+ },
+ {
+ Bearer: []
+ }
+ ];
+ if (!endpoint.meta.requireCredential) {
+ // add this to make authentication optional
+ security.push({});
+ }
+ const info = {
+ operationId: endpoint.name,
+ summary: endpoint.name,
+ description: desc,
+ externalDocs: {
+ description: "Source code",
+ url: `https://iceshrimp.dev/iceshrimp/iceshrimp/src/branch/dev/packages/backend/src/server/api/endpoints/${endpoint.name}.ts`
+ },
+ tags: endpoint.meta.tags || undefined,
+ security,
+ requestBody: {
+ required: true,
+ content: {
+ [requestType]: {
+ schema
+ }
+ }
+ },
+ responses: {
+ ...endpoint.meta.res ? {
+ "200": {
+ description: "OK (with results)",
+ content: {
+ "application/json": {
+ schema: resSchema
+ }
+ }
+ }
+ } : {
+ "204": {
+ description: "OK (without any results)"
+ }
+ },
+ "400": {
+ description: "Client error",
+ content: {
+ "application/json": {
+ schema: {
+ $ref: "#/components/schemas/Error"
+ },
+ examples: {
+ ...errors,
+ ...basicErrors["400"]
+ }
+ }
+ }
+ },
+ "401": {
+ description: "Authentication error",
+ content: {
+ "application/json": {
+ schema: {
+ $ref: "#/components/schemas/Error"
+ },
+ examples: basicErrors["401"]
+ }
+ }
+ },
+ "403": {
+ description: "Forbidden error",
+ content: {
+ "application/json": {
+ schema: {
+ $ref: "#/components/schemas/Error"
+ },
+ examples: basicErrors["403"]
+ }
+ }
+ },
+ "418": {
+ description: "I'm Calc",
+ content: {
+ "application/json": {
+ schema: {
+ $ref: "#/components/schemas/Error"
+ },
+ examples: basicErrors["418"]
+ }
+ }
+ },
+ ...endpoint.meta.limit ? {
+ "429": {
+ description: "Too many requests",
+ content: {
+ "application/json": {
+ schema: {
+ $ref: "#/components/schemas/Error"
+ },
+ examples: basicErrors["429"]
+ }
+ }
+ }
+ } : {},
+ "500": {
+ description: "Internal server error",
+ content: {
+ "application/json": {
+ schema: {
+ $ref: "#/components/schemas/Error"
+ },
+ examples: basicErrors["500"]
+ }
+ }
+ }
+ }
+ };
+ const path = {
+ post: info
+ };
+ if (endpoint.meta.allowGet) {
+ path.get = {
+ ...info
+ };
+ // API Key authentication is not permitted for GET requests
+ path.get.security = path.get.security.filter((elem)=>!Object.prototype.hasOwnProperty.call(elem, "ApiKeyAuth"));
+ }
+ spec.paths[`/${endpoint.name}`] = path;
+ }
+ return spec;
+}
diff --git a/packages/backend/built/server/api/openapi/schemas.js b/packages/backend/built/server/api/openapi/schemas.js
new file mode 100644
index 0000000..3940d11
--- /dev/null
+++ b/packages/backend/built/server/api/openapi/schemas.js
@@ -0,0 +1,58 @@
+import { refs } from "../../../misc/schema.js";
+export function convertSchemaToOpenApiSchema(schema) {
+ const res = schema;
+ if (schema.type === "object" && schema.properties) {
+ res.required = Object.entries(schema.properties).filter(([k, v])=>!v.optional).map(([k])=>k);
+ for (const k of Object.keys(schema.properties)){
+ res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k]);
+ }
+ }
+ if (schema.type === "array" && schema.items) {
+ res.items = convertSchemaToOpenApiSchema(schema.items);
+ }
+ if (schema.anyOf) res.anyOf = schema.anyOf.map(convertSchemaToOpenApiSchema);
+ if (schema.oneOf) res.oneOf = schema.oneOf.map(convertSchemaToOpenApiSchema);
+ if (schema.allOf) res.allOf = schema.allOf.map(convertSchemaToOpenApiSchema);
+ if (schema.ref) {
+ res.$ref = `#/components/schemas/${schema.ref}`;
+ }
+ return res;
+}
+export const schemas = {
+ Error: {
+ type: "object",
+ properties: {
+ error: {
+ type: "object",
+ description: "An error object.",
+ properties: {
+ code: {
+ type: "string",
+ description: "An error code. Unique within the endpoint."
+ },
+ message: {
+ type: "string",
+ description: "An error message."
+ },
+ id: {
+ type: "string",
+ format: "uuid",
+ description: "An error ID. This ID is static."
+ }
+ },
+ required: [
+ "code",
+ "id",
+ "message"
+ ]
+ }
+ },
+ required: [
+ "error"
+ ]
+ },
+ ...Object.fromEntries(Object.entries(refs).map(([key, schema])=>[
+ key,
+ convertSchemaToOpenApiSchema(schema)
+ ]))
+};
diff --git a/packages/backend/built/server/api/private/signin.js b/packages/backend/built/server/api/private/signin.js
new file mode 100644
index 0000000..98e1c08
--- /dev/null
+++ b/packages/backend/built/server/api/private/signin.js
@@ -0,0 +1,226 @@
+import * as OTPAuth from "otpauth";
+import signin from "../common/signin.js";
+import config from "../../../config/index.js";
+import { Users, Signins, UserProfiles, UserSecurityKeys, AttestationChallenges } from "../../../models/index.js";
+import { genId } from "../../../misc/gen-id.js";
+import { comparePassword, hashPassword, isOldAlgorithm } from "../../../misc/password.js";
+import { verifyLogin, hash } from "../2fa.js";
+import { randomBytes } from "node:crypto";
+import { IsNull } from "typeorm";
+import { limiter } from "../limiter.js";
+import { getIpHash } from "../../../misc/get-ip-hash.js";
+export default (async (ctx)=>{
+ ctx.set("Access-Control-Allow-Origin", config.url);
+ ctx.set("Access-Control-Allow-Credentials", "true");
+ const body = ctx.request.body;
+ const username = body["username"];
+ const password = body["password"];
+ const token = body["token"];
+ function error(status, error) {
+ ctx.status = status;
+ ctx.body = {
+ error
+ };
+ }
+ try {
+ // not more than 1 attempt per second and not more than 10 attempts per hour
+ await limiter({
+ key: "signin",
+ duration: 60 * 60 * 1000,
+ max: 10,
+ minInterval: 1000
+ }, getIpHash(ctx.ip));
+ } catch (err) {
+ ctx.status = 429;
+ ctx.body = {
+ error: {
+ message: "Too many failed attempts to sign in. Try again later.",
+ code: "TOO_MANY_AUTHENTICATION_FAILURES",
+ id: "22d05606-fbcf-421a-a2db-b32610dcfd1b"
+ }
+ };
+ return;
+ }
+ if (typeof username !== "string") {
+ ctx.status = 400;
+ return;
+ }
+ if (typeof password !== "string") {
+ ctx.status = 400;
+ return;
+ }
+ if (token != null && typeof token !== "string") {
+ ctx.status = 400;
+ return;
+ }
+ // Fetch user
+ const user = await Users.findOneBy({
+ usernameLower: username.toLowerCase(),
+ host: IsNull()
+ });
+ if (user == null) {
+ error(404, {
+ id: "6cc579cc-885d-43d8-95c2-b8c7fc963280"
+ });
+ return;
+ }
+ if (user.isSuspended) {
+ error(403, {
+ id: "e03a5f46-d309-4865-9b69-56282d94e1eb"
+ });
+ return;
+ }
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ // Compare password
+ const same = await comparePassword(password, profile.password);
+ if (same && isOldAlgorithm(profile.password)) {
+ profile.password = await hashPassword(password);
+ await UserProfiles.save(profile);
+ }
+ async function fail(status, failure) {
+ // Append signin history
+ await Signins.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ ip: ctx.ip,
+ headers: ctx.headers,
+ success: false
+ });
+ error(status || 500, failure || {
+ id: "4e30e80c-e338-45a0-8c8f-44455efa3b76"
+ });
+ }
+ if (!profile.twoFactorEnabled) {
+ if (same) {
+ signin(ctx, user);
+ return;
+ } else {
+ await fail(403, {
+ id: "932c904e-9460-45b7-9ce6-7ed33be7eb2c"
+ });
+ return;
+ }
+ }
+ if (token) {
+ if (!same) {
+ await fail(403, {
+ id: "932c904e-9460-45b7-9ce6-7ed33be7eb2c"
+ });
+ return;
+ }
+ if (profile.twoFactorSecret == null) {
+ throw new Error("Attempted 2FA signin without 2FA enabled.");
+ }
+ const delta = OTPAuth.TOTP.validate({
+ secret: OTPAuth.Secret.fromBase32(profile.twoFactorSecret),
+ digits: 6,
+ token,
+ window: 1
+ });
+ if (delta != null) {
+ signin(ctx, user);
+ return;
+ } else {
+ await fail(403, {
+ id: "cdf1235b-ac71-46d4-a3a6-84ccce48df6f"
+ });
+ return;
+ }
+ } else if (body.credentialId) {
+ if (!(same || profile.usePasswordLessLogin)) {
+ await fail(403, {
+ id: "932c904e-9460-45b7-9ce6-7ed33be7eb2c"
+ });
+ return;
+ }
+ const clientDataJSON = Buffer.from(body.clientDataJSON, "hex");
+ const clientData = JSON.parse(clientDataJSON.toString("utf-8"));
+ const challenge = await AttestationChallenges.findOneBy({
+ userId: user.id,
+ id: body.challengeId,
+ registrationChallenge: false,
+ challenge: hash(clientData.challenge).toString("hex")
+ });
+ if (!challenge) {
+ await fail(403, {
+ id: "2715a88a-2125-4013-932f-aa6fe72792da"
+ });
+ return;
+ }
+ await AttestationChallenges.delete({
+ userId: user.id,
+ id: body.challengeId
+ });
+ if (new Date().getTime() - challenge.createdAt.getTime() >= 5 * 60 * 1000) {
+ await fail(403, {
+ id: "2715a88a-2125-4013-932f-aa6fe72792da"
+ });
+ return;
+ }
+ const securityKey = await UserSecurityKeys.findOneBy({
+ id: Buffer.from(body.credentialId.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("hex")
+ });
+ if (!securityKey) {
+ await fail(403, {
+ id: "66269679-aeaf-4474-862b-eb761197e046"
+ });
+ return;
+ }
+ const isValid = verifyLogin({
+ publicKey: Buffer.from(securityKey.publicKey, "hex"),
+ authenticatorData: Buffer.from(body.authenticatorData, "hex"),
+ clientDataJSON,
+ clientData,
+ signature: Buffer.from(body.signature, "hex"),
+ challenge: challenge.challenge
+ });
+ if (isValid) {
+ signin(ctx, user);
+ return;
+ } else {
+ await fail(403, {
+ id: "93b86c4b-72f9-40eb-9815-798928603d1e"
+ });
+ return;
+ }
+ } else {
+ if (!(same || profile.usePasswordLessLogin)) {
+ await fail(403, {
+ id: "932c904e-9460-45b7-9ce6-7ed33be7eb2c"
+ });
+ return;
+ }
+ const keys = await UserSecurityKeys.findBy({
+ userId: user.id
+ });
+ if (keys.length === 0) {
+ await fail(403, {
+ id: "f27fd449-9af4-4841-9249-1f989b9fa4a4"
+ });
+ return;
+ }
+ // 32 byte challenge
+ const challenge = randomBytes(32).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
+ const challengeId = genId();
+ await AttestationChallenges.insert({
+ userId: user.id,
+ id: challengeId,
+ challenge: hash(Buffer.from(challenge, "utf-8")).toString("hex"),
+ createdAt: new Date(),
+ registrationChallenge: false
+ });
+ ctx.body = {
+ challenge,
+ challengeId,
+ securityKeys: keys.map((key)=>({
+ id: key.id
+ }))
+ };
+ ctx.status = 200;
+ return;
+ }
+// never get here
+});
diff --git a/packages/backend/built/server/api/private/signup-pending.js b/packages/backend/built/server/api/private/signup-pending.js
new file mode 100644
index 0000000..78b9e14
--- /dev/null
+++ b/packages/backend/built/server/api/private/signup-pending.js
@@ -0,0 +1,32 @@
+import { UserPendings, UserProfiles } from "../../../models/index.js";
+import { signup } from "../common/signup.js";
+import signin from "../common/signin.js";
+export default (async (ctx)=>{
+ const body = ctx.request.body;
+ const code = body["code"];
+ try {
+ const pendingUser = await UserPendings.findOneByOrFail({
+ code
+ });
+ const { account, secret } = await signup({
+ username: pendingUser.username,
+ passwordHash: pendingUser.password
+ });
+ UserPendings.delete({
+ id: pendingUser.id
+ });
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: account.id
+ });
+ await UserProfiles.update({
+ userId: profile.userId
+ }, {
+ email: pendingUser.email,
+ emailVerified: true,
+ emailVerifyCode: null
+ });
+ signin(ctx, account);
+ } catch (e) {
+ ctx.throw(400, e);
+ }
+});
diff --git a/packages/backend/built/server/api/private/signup.js b/packages/backend/built/server/api/private/signup.js
new file mode 100644
index 0000000..e45ea53
--- /dev/null
+++ b/packages/backend/built/server/api/private/signup.js
@@ -0,0 +1,94 @@
+import rndstr from "rndstr";
+import { fetchMeta } from "../../../misc/fetch-meta.js";
+import { verifyHcaptcha, verifyRecaptcha } from "../../../misc/captcha.js";
+import { Users, RegistrationTickets, UserPendings } from "../../../models/index.js";
+import { signup } from "../common/signup.js";
+import config from "../../../config/index.js";
+import { sendEmail } from "../../../services/send-email.js";
+import { genId } from "../../../misc/gen-id.js";
+import { validateEmailForAccount } from "../../../services/validate-email-for-account.js";
+import { hashPassword } from "../../../misc/password.js";
+export default (async (ctx)=>{
+ const body = ctx.request.body;
+ const instance = await fetchMeta(true);
+ // Verify *Captcha
+ // ただしテスト時はこの機構は障害となるため無効にする
+ if (process.env.NODE_ENV !== "test") {
+ if (instance.enableHcaptcha && instance.hcaptchaSecretKey) {
+ await verifyHcaptcha(instance.hcaptchaSecretKey, body["hcaptcha-response"]).catch((e)=>{
+ ctx.throw(400, e);
+ });
+ }
+ if (instance.enableRecaptcha && instance.recaptchaSecretKey) {
+ await verifyRecaptcha(instance.recaptchaSecretKey, body["g-recaptcha-response"]).catch((e)=>{
+ ctx.throw(400, e);
+ });
+ }
+ }
+ const username = body["username"];
+ const password = body["password"];
+ const host = process.env.NODE_ENV === "test" ? body["host"] || null : null;
+ const invitationCode = body["invitationCode"];
+ const emailAddress = body["emailAddress"];
+ if (config.reservedUsernames?.includes(username.toLowerCase())) {
+ ctx.status = 400;
+ return;
+ }
+ if (instance.emailRequiredForSignup) {
+ if (emailAddress == null || typeof emailAddress !== "string") {
+ ctx.status = 400;
+ return;
+ }
+ const { available } = await validateEmailForAccount(emailAddress);
+ if (!available) {
+ ctx.status = 400;
+ return;
+ }
+ }
+ if (instance.disableRegistration) {
+ if (invitationCode == null || typeof invitationCode !== "string") {
+ ctx.status = 400;
+ return;
+ }
+ const ticket = await RegistrationTickets.findOneBy({
+ code: invitationCode
+ });
+ if (ticket == null) {
+ ctx.status = 400;
+ return;
+ }
+ RegistrationTickets.delete(ticket.id);
+ }
+ if (instance.emailRequiredForSignup) {
+ const code = rndstr("a-z0-9", 16);
+ // Generate hash of password
+ const hash = await hashPassword(password);
+ await UserPendings.insert({
+ id: genId(),
+ createdAt: new Date(),
+ code,
+ email: emailAddress,
+ username: username,
+ password: hash
+ });
+ const link = `${config.url}/signup-complete/${code}`;
+ sendEmail(emailAddress, "Signup", `To complete signup, please click this link:
${link}`, `To complete signup, please click this link: ${link}`);
+ ctx.status = 204;
+ } else {
+ try {
+ const { account, secret } = await signup({
+ username,
+ password,
+ host
+ });
+ const res = await Users.pack(account, account, {
+ detail: true,
+ includeSecrets: true
+ });
+ res.token = secret;
+ ctx.body = res;
+ } catch (e) {
+ ctx.throw(400, e);
+ }
+ }
+});
diff --git a/packages/backend/built/server/api/private/verify-email.js b/packages/backend/built/server/api/private/verify-email.js
new file mode 100644
index 0000000..8ce5707
--- /dev/null
+++ b/packages/backend/built/server/api/private/verify-email.js
@@ -0,0 +1,26 @@
+import { Users, UserProfiles } from "../../../models/index.js";
+import { publishMainStream } from "../../../services/stream.js";
+export default (async (ctx)=>{
+ const body = ctx.request.body;
+ const code = body["code"];
+ const profile = await UserProfiles.findOneByOrFail({
+ emailVerifyCode: code
+ });
+ if (profile != null) {
+ ctx.body = "Verify succeeded!";
+ await UserProfiles.update({
+ userId: profile.userId
+ }, {
+ emailVerified: true,
+ emailVerifyCode: null
+ });
+ publishMainStream(profile.userId, "meUpdated", await Users.pack(profile.userId, {
+ id: profile.userId
+ }, {
+ detail: true,
+ includeSecrets: true
+ }));
+ } else {
+ ctx.throw(404);
+ }
+});
diff --git a/packages/backend/built/server/api/service/discord.js b/packages/backend/built/server/api/service/discord.js
new file mode 100644
index 0000000..b232916
--- /dev/null
+++ b/packages/backend/built/server/api/service/discord.js
@@ -0,0 +1,239 @@
+import Router from "@koa/router";
+import { OAuth2 } from "oauth";
+import { v4 as uuid } from "uuid";
+import { IsNull } from "typeorm";
+import { getJson } from "../../../misc/fetch.js";
+import config from "../../../config/index.js";
+import { publishMainStream } from "../../../services/stream.js";
+import { fetchMeta } from "../../../misc/fetch-meta.js";
+import { Users, UserProfiles } from "../../../models/index.js";
+import { redisClient } from "../../../db/redis.js";
+import signin from "../common/signin.js";
+function getUserToken(ctx) {
+ return ((ctx.headers["cookie"] || "").match(/igi=(\w+)/) || [
+ null,
+ null
+ ])[1];
+}
+function compareOrigin(ctx) {
+ function normalizeUrl(url) {
+ return url ? url.endsWith("/") ? url.slice(0, url.length - 1) : url : "";
+ }
+ const referer = ctx.headers["referer"];
+ return normalizeUrl(referer) === normalizeUrl(config.url);
+}
+// Init router
+const router = new Router();
+router.get("/disconnect/discord", async (ctx)=>{
+ if (!compareOrigin(ctx)) {
+ ctx.throw(400, "invalid origin");
+ return;
+ }
+ const userToken = getUserToken(ctx);
+ if (!userToken) {
+ ctx.throw(400, "signin required");
+ return;
+ }
+ const user = await Users.findOneByOrFail({
+ host: IsNull(),
+ token: userToken
+ });
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ profile.integrations.discord = undefined;
+ await UserProfiles.update(user.id, {
+ integrations: profile.integrations
+ });
+ ctx.body = "Discordの連携を解除しました :v:";
+ // Publish i updated event
+ publishMainStream(user.id, "meUpdated", await Users.pack(user, user, {
+ detail: true,
+ includeSecrets: true
+ }));
+});
+async function getOAuth2() {
+ const meta = await fetchMeta(true);
+ if (meta.enableDiscordIntegration) {
+ return new OAuth2(meta.discordClientId, meta.discordClientSecret, "https://discord.com/", "api/oauth2/authorize", "api/oauth2/token");
+ } else {
+ return null;
+ }
+}
+router.get("/connect/discord", async (ctx)=>{
+ if (!compareOrigin(ctx)) {
+ ctx.throw(400, "invalid origin");
+ return;
+ }
+ const userToken = getUserToken(ctx);
+ if (!userToken) {
+ ctx.throw(400, "signin required");
+ return;
+ }
+ const params = {
+ redirect_uri: `${config.url}/api/dc/cb`,
+ scope: [
+ "identify"
+ ],
+ state: uuid(),
+ response_type: "code"
+ };
+ redisClient.set(userToken, JSON.stringify(params));
+ const oauth2 = await getOAuth2();
+ ctx.redirect(oauth2.getAuthorizeUrl(params));
+});
+router.get("/signin/discord", async (ctx)=>{
+ const sessid = uuid();
+ const params = {
+ redirect_uri: `${config.url}/api/dc/cb`,
+ scope: [
+ "identify"
+ ],
+ state: uuid(),
+ response_type: "code"
+ };
+ ctx.cookies.set("signin_with_discord_sid", sessid, {
+ path: "/",
+ secure: config.url.startsWith("https"),
+ httpOnly: true
+ });
+ redisClient.set(sessid, JSON.stringify(params));
+ const oauth2 = await getOAuth2();
+ ctx.redirect(oauth2.getAuthorizeUrl(params));
+});
+router.get("/dc/cb", async (ctx)=>{
+ const userToken = getUserToken(ctx);
+ const oauth2 = await getOAuth2();
+ if (!userToken) {
+ const sessid = ctx.cookies.get("signin_with_discord_sid");
+ if (!sessid) {
+ ctx.throw(400, "invalid session");
+ return;
+ }
+ const code = ctx.query.code;
+ if (!code || typeof code !== "string") {
+ ctx.throw(400, "invalid session");
+ return;
+ }
+ const { redirect_uri, state } = await new Promise((res, rej)=>{
+ redisClient.get(sessid, async (_, state)=>{
+ res(JSON.parse(state));
+ });
+ });
+ if (ctx.query.state !== state) {
+ ctx.throw(400, "invalid session");
+ return;
+ }
+ const { accessToken, refreshToken, expiresDate } = await new Promise((res, rej)=>oauth2.getOAuthAccessToken(code, {
+ grant_type: "authorization_code",
+ redirect_uri
+ }, (err, accessToken, refreshToken, result)=>{
+ if (err) {
+ rej(err);
+ } else if (result.error) {
+ rej(result.error);
+ } else {
+ res({
+ accessToken,
+ refreshToken,
+ expiresDate: Date.now() + Number(result.expires_in) * 1000
+ });
+ }
+ }));
+ const { id, username, discriminator } = await getJson("https://discord.com/api/users/@me", "*/*", 10 * 1000, {
+ Authorization: `Bearer ${accessToken}`
+ });
+ if (typeof id !== "string" || typeof username !== "string" || typeof discriminator !== "string") {
+ ctx.throw(400, "invalid session");
+ return;
+ }
+ const profile = await UserProfiles.createQueryBuilder().where("\"integrations\"->'discord'->>'id' = :id", {
+ id: id
+ }).andWhere('"userHost" IS NULL').getOne();
+ if (profile == null) {
+ ctx.throw(404, `@${username}#${discriminator}と連携しているMisskeyアカウントはありませんでした...`);
+ return;
+ }
+ await UserProfiles.update(profile.userId, {
+ integrations: {
+ ...profile.integrations,
+ discord: {
+ id: id,
+ accessToken: accessToken,
+ refreshToken: refreshToken,
+ expiresDate: expiresDate,
+ username: username,
+ discriminator: discriminator
+ }
+ }
+ });
+ signin(ctx, await Users.findOneBy({
+ id: profile.userId
+ }), true);
+ } else {
+ const code = ctx.query.code;
+ if (!code || typeof code !== "string") {
+ ctx.throw(400, "invalid session");
+ return;
+ }
+ const { redirect_uri, state } = await new Promise((res, rej)=>{
+ redisClient.get(userToken, async (_, state)=>{
+ res(JSON.parse(state));
+ });
+ });
+ if (ctx.query.state !== state) {
+ ctx.throw(400, "invalid session");
+ return;
+ }
+ const { accessToken, refreshToken, expiresDate } = await new Promise((res, rej)=>oauth2.getOAuthAccessToken(code, {
+ grant_type: "authorization_code",
+ redirect_uri
+ }, (err, accessToken, refreshToken, result)=>{
+ if (err) {
+ rej(err);
+ } else if (result.error) {
+ rej(result.error);
+ } else {
+ res({
+ accessToken,
+ refreshToken,
+ expiresDate: Date.now() + Number(result.expires_in) * 1000
+ });
+ }
+ }));
+ const { id, username, discriminator } = await getJson("https://discord.com/api/users/@me", "*/*", 10 * 1000, {
+ Authorization: `Bearer ${accessToken}`
+ });
+ if (typeof id !== "string" || typeof username !== "string" || typeof discriminator !== "string") {
+ ctx.throw(400, "invalid session");
+ return;
+ }
+ const user = await Users.findOneByOrFail({
+ host: IsNull(),
+ token: userToken
+ });
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ await UserProfiles.update(user.id, {
+ integrations: {
+ ...profile.integrations,
+ discord: {
+ accessToken: accessToken,
+ refreshToken: refreshToken,
+ expiresDate: expiresDate,
+ id: id,
+ username: username,
+ discriminator: discriminator
+ }
+ }
+ });
+ ctx.body = `Discord: @${username}#${discriminator} を、Misskey: @${user.username} に接続しました!`;
+ // Publish i updated event
+ publishMainStream(user.id, "meUpdated", await Users.pack(user, user, {
+ detail: true,
+ includeSecrets: true
+ }));
+ }
+});
+export default router;
diff --git a/packages/backend/built/server/api/service/github.js b/packages/backend/built/server/api/service/github.js
new file mode 100644
index 0000000..776b474
--- /dev/null
+++ b/packages/backend/built/server/api/service/github.js
@@ -0,0 +1,215 @@
+import Router from "@koa/router";
+import { OAuth2 } from "oauth";
+import { v4 as uuid } from "uuid";
+import { IsNull } from "typeorm";
+import { getJson } from "../../../misc/fetch.js";
+import config from "../../../config/index.js";
+import { publishMainStream } from "../../../services/stream.js";
+import { fetchMeta } from "../../../misc/fetch-meta.js";
+import { Users, UserProfiles } from "../../../models/index.js";
+import { redisClient } from "../../../db/redis.js";
+import signin from "../common/signin.js";
+function getUserToken(ctx) {
+ return ((ctx.headers["cookie"] || "").match(/igi=(\w+)/) || [
+ null,
+ null
+ ])[1];
+}
+function compareOrigin(ctx) {
+ function normalizeUrl(url) {
+ return url ? url.endsWith("/") ? url.slice(0, url.length - 1) : url : "";
+ }
+ const referer = ctx.headers["referer"];
+ return normalizeUrl(referer) === normalizeUrl(config.url);
+}
+// Init router
+const router = new Router();
+router.get("/disconnect/github", async (ctx)=>{
+ if (!compareOrigin(ctx)) {
+ ctx.throw(400, "invalid origin");
+ return;
+ }
+ const userToken = getUserToken(ctx);
+ if (!userToken) {
+ ctx.throw(400, "signin required");
+ return;
+ }
+ const user = await Users.findOneByOrFail({
+ host: IsNull(),
+ token: userToken
+ });
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ profile.integrations.github = undefined;
+ await UserProfiles.update(user.id, {
+ integrations: profile.integrations
+ });
+ ctx.body = "GitHubの連携を解除しました :v:";
+ // Publish i updated event
+ publishMainStream(user.id, "meUpdated", await Users.pack(user, user, {
+ detail: true,
+ includeSecrets: true
+ }));
+});
+async function getOath2() {
+ const meta = await fetchMeta(true);
+ if (meta.enableGithubIntegration && meta.githubClientId && meta.githubClientSecret) {
+ return new OAuth2(meta.githubClientId, meta.githubClientSecret, "https://github.com/", "login/oauth/authorize", "login/oauth/access_token");
+ } else {
+ return null;
+ }
+}
+router.get("/connect/github", async (ctx)=>{
+ if (!compareOrigin(ctx)) {
+ ctx.throw(400, "invalid origin");
+ return;
+ }
+ const userToken = getUserToken(ctx);
+ if (!userToken) {
+ ctx.throw(400, "signin required");
+ return;
+ }
+ const params = {
+ redirect_uri: `${config.url}/api/gh/cb`,
+ scope: [
+ "read:user"
+ ],
+ state: uuid()
+ };
+ redisClient.set(userToken, JSON.stringify(params));
+ const oauth2 = await getOath2();
+ ctx.redirect(oauth2.getAuthorizeUrl(params));
+});
+router.get("/signin/github", async (ctx)=>{
+ const sessid = uuid();
+ const params = {
+ redirect_uri: `${config.url}/api/gh/cb`,
+ scope: [
+ "read:user"
+ ],
+ state: uuid()
+ };
+ ctx.cookies.set("signin_with_github_sid", sessid, {
+ path: "/",
+ secure: config.url.startsWith("https"),
+ httpOnly: true
+ });
+ redisClient.set(sessid, JSON.stringify(params));
+ const oauth2 = await getOath2();
+ ctx.redirect(oauth2.getAuthorizeUrl(params));
+});
+router.get("/gh/cb", async (ctx)=>{
+ const userToken = getUserToken(ctx);
+ const oauth2 = await getOath2();
+ if (!userToken) {
+ const sessid = ctx.cookies.get("signin_with_github_sid");
+ if (!sessid) {
+ ctx.throw(400, "invalid session");
+ return;
+ }
+ const code = ctx.query.code;
+ if (!code || typeof code !== "string") {
+ ctx.throw(400, "invalid session");
+ return;
+ }
+ const { redirect_uri, state } = await new Promise((res, rej)=>{
+ redisClient.get(sessid, async (_, state)=>{
+ res(JSON.parse(state));
+ });
+ });
+ if (ctx.query.state !== state) {
+ ctx.throw(400, "invalid session");
+ return;
+ }
+ const { accessToken } = await new Promise((res, rej)=>oauth2.getOAuthAccessToken(code, {
+ redirect_uri
+ }, (err, accessToken, refresh, result)=>{
+ if (err) {
+ rej(err);
+ } else if (result.error) {
+ rej(result.error);
+ } else {
+ res({
+ accessToken
+ });
+ }
+ }));
+ const { login, id } = await getJson("https://api.github.com/user", "application/vnd.github.v3+json", 10 * 1000, {
+ Authorization: `bearer ${accessToken}`
+ });
+ if (typeof login !== "string" || typeof id !== "string") {
+ ctx.throw(400, "invalid session");
+ return;
+ }
+ const link = await UserProfiles.createQueryBuilder().where("\"integrations\"->'github'->>'id' = :id", {
+ id: id
+ }).andWhere('"userHost" IS NULL').getOne();
+ if (link == null) {
+ ctx.throw(404, `@${login}と連携しているMisskeyアカウントはありませんでした...`);
+ return;
+ }
+ signin(ctx, await Users.findOneBy({
+ id: link.userId
+ }), true);
+ } else {
+ const code = ctx.query.code;
+ if (!code || typeof code !== "string") {
+ ctx.throw(400, "invalid session");
+ return;
+ }
+ const { redirect_uri, state } = await new Promise((res, rej)=>{
+ redisClient.get(userToken, async (_, state)=>{
+ res(JSON.parse(state));
+ });
+ });
+ if (ctx.query.state !== state) {
+ ctx.throw(400, "invalid session");
+ return;
+ }
+ const { accessToken } = await new Promise((res, rej)=>oauth2.getOAuthAccessToken(code, {
+ redirect_uri
+ }, (err, accessToken, refresh, result)=>{
+ if (err) {
+ rej(err);
+ } else if (result.error) {
+ rej(result.error);
+ } else {
+ res({
+ accessToken
+ });
+ }
+ }));
+ const { login, id } = await getJson("https://api.github.com/user", "application/vnd.github.v3+json", 10 * 1000, {
+ Authorization: `bearer ${accessToken}`
+ });
+ if (typeof login !== "string" || typeof id !== "string") {
+ ctx.throw(400, "invalid session");
+ return;
+ }
+ const user = await Users.findOneByOrFail({
+ host: IsNull(),
+ token: userToken
+ });
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ await UserProfiles.update(user.id, {
+ integrations: {
+ ...profile.integrations,
+ github: {
+ accessToken: accessToken,
+ id: id,
+ login: login
+ }
+ }
+ });
+ ctx.body = `GitHub: @${login} を、Misskey: @${user.username} に接続しました!`;
+ // Publish i updated event
+ publishMainStream(user.id, "meUpdated", await Users.pack(user, user, {
+ detail: true,
+ includeSecrets: true
+ }));
+ }
+});
+export default router;
diff --git a/packages/backend/built/server/api/stream/channel.js b/packages/backend/built/server/api/stream/channel.js
new file mode 100644
index 0000000..bc8ea3c
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channel.js
@@ -0,0 +1,75 @@
+import { Notes } from "../../../models/index.js";
+import { IdentifiableError } from "../../../misc/identifiable-error.js";
+/**
+ * Stream channel
+ */ export default class Channel {
+ connection;
+ id;
+ static shouldShare;
+ static requireCredential;
+ get user() {
+ return this.connection.user;
+ }
+ get userProfile() {
+ return this.connection.userProfile;
+ }
+ get following() {
+ return this.connection.following;
+ }
+ get muting() {
+ return this.connection.muting;
+ }
+ get renoteMuting() {
+ return this.connection.renoteMuting;
+ }
+ get blocking() {
+ return this.connection.blocking;
+ }
+ get hidden() {
+ return this.connection.hidden;
+ }
+ get followingChannels() {
+ return this.connection.followingChannels;
+ }
+ get subscriber() {
+ return this.connection.subscriber;
+ }
+ constructor(id, connection){
+ this.id = id;
+ this.connection = connection;
+ }
+ send(typeOrPayload, payload) {
+ const type = payload === undefined ? typeOrPayload.type : typeOrPayload;
+ const body = payload === undefined ? typeOrPayload.body : payload;
+ this.connection.sendMessageToWs("channel", {
+ id: this.id,
+ type: type,
+ body: body
+ });
+ }
+ withPackedNote(callback) {
+ return async (note)=>{
+ try {
+ // because `note` was previously JSON.stringify'ed, the fields that
+ // were objects before are now strings and have to be restored or
+ // removed from the object
+ note.createdAt = new Date(note.createdAt);
+ note.reply = undefined;
+ note.renote = undefined;
+ note.user = undefined;
+ note.channel = undefined;
+ const packed = await Notes.pack(note, this.user, {
+ detail: true
+ });
+ callback(packed);
+ } catch (err) {
+ if (err instanceof IdentifiableError && err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") {
+ // skip: note not visible to user
+ return;
+ } else {
+ throw err;
+ }
+ }
+ };
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/admin.js b/packages/backend/built/server/api/stream/channels/admin.js
new file mode 100644
index 0000000..5ce0ff7
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/admin.js
@@ -0,0 +1,12 @@
+import Channel from "../channel.js";
+export default class extends Channel {
+ chName = "admin";
+ static shouldShare = true;
+ static requireCredential = true;
+ async init(params) {
+ // Subscribe admin stream
+ this.subscriber.on(`adminStream:${this.user.id}`, (data)=>{
+ this.send(data);
+ });
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/antenna.js b/packages/backend/built/server/api/stream/channels/antenna.js
new file mode 100644
index 0000000..4620c13
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/antenna.js
@@ -0,0 +1,48 @@
+import Channel from "../channel.js";
+import { Notes } from "../../../../models/index.js";
+import { isUserRelated } from "../../../../misc/is-user-related.js";
+import { IdentifiableError } from "../../../../misc/identifiable-error.js";
+export default class extends Channel {
+ chName = "antenna";
+ static shouldShare = false;
+ static requireCredential = false;
+ antennaId;
+ constructor(id, connection){
+ super(id, connection);
+ this.onEvent = this.onEvent.bind(this);
+ }
+ async init(params) {
+ this.antennaId = params.antennaId;
+ // Subscribe stream
+ this.subscriber.on(`antennaStream:${this.antennaId}`, this.onEvent);
+ }
+ async onEvent(data) {
+ if (data.type === "note") {
+ try {
+ const note = await Notes.pack(data.body.id, this.user, {
+ detail: true
+ });
+ // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.muting)) return;
+ // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.blocking)) return;
+ if (note.renote && !note.text && this.renoteMuting.has(note.userId)) return;
+ this.connection.cacheNote(note);
+ this.send("note", note);
+ } catch (e) {
+ if (e instanceof IdentifiableError && e.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") {
+ // skip: note not visible to user
+ return;
+ } else {
+ throw e;
+ }
+ }
+ } else {
+ this.send(data.type, data.body);
+ }
+ }
+ dispose() {
+ // Unsubscribe events
+ this.subscriber.off(`antennaStream:${this.antennaId}`, this.onEvent);
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/channel.js b/packages/backend/built/server/api/stream/channels/channel.js
new file mode 100644
index 0000000..1213af4
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/channel.js
@@ -0,0 +1,65 @@
+import Channel from "../channel.js";
+import { Users } from "../../../../models/index.js";
+import { isUserRelated } from "../../../../misc/is-user-related.js";
+export default class extends Channel {
+ chName = "channel";
+ static shouldShare = false;
+ static requireCredential = false;
+ channelId;
+ typers = new Map();
+ emitTypersIntervalId;
+ constructor(id, connection){
+ super(id, connection);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
+ this.emitTypers = this.emitTypers.bind(this);
+ }
+ async init(params) {
+ this.channelId = params.channelId;
+ // Subscribe stream
+ this.subscriber.on("notesStream", this.onNote);
+ this.subscriber.on(`channelStream:${this.channelId}`, this.onEvent);
+ this.emitTypersIntervalId = setInterval(this.emitTypers, 5000);
+ }
+ async onNote(note) {
+ if (note.visibility === "hidden") return;
+ if (note.channelId !== this.channelId) return;
+ // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.muting)) return;
+ // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.blocking)) return;
+ if (note.renote && !note.text && this.renoteMuting.has(note.userId)) return;
+ this.connection.cacheNote(note);
+ this.send("note", note);
+ }
+ onEvent(data) {
+ if (data.type === "typing") {
+ const id = data.body;
+ const begin = !this.typers.has(id);
+ this.typers.set(id, new Date());
+ if (begin) {
+ this.emitTypers();
+ }
+ }
+ }
+ async emitTypers() {
+ const now = new Date();
+ // Remove not typing users
+ for (const [userId, date] of Object.entries(this.typers)){
+ if (now.getTime() - date.getTime() > 5000) this.typers.delete(userId);
+ }
+ const userIds = Array.from(this.typers.keys());
+ const users = await Users.packMany(userIds, null, {
+ detail: false
+ });
+ this.send({
+ type: "typers",
+ body: users
+ });
+ }
+ dispose() {
+ // Unsubscribe events
+ this.subscriber.off("notesStream", this.onNote);
+ this.subscriber.off(`channelStream:${this.channelId}`, this.onEvent);
+ clearInterval(this.emitTypersIntervalId);
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/drive.js b/packages/backend/built/server/api/stream/channels/drive.js
new file mode 100644
index 0000000..c2a401c
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/drive.js
@@ -0,0 +1,12 @@
+import Channel from "../channel.js";
+export default class extends Channel {
+ chName = "drive";
+ static shouldShare = true;
+ static requireCredential = true;
+ async init(params) {
+ // Subscribe drive stream
+ this.subscriber.on(`driveStream:${this.user.id}`, (data)=>{
+ this.send(data);
+ });
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/global-timeline.js b/packages/backend/built/server/api/stream/channels/global-timeline.js
new file mode 100644
index 0000000..84f6f7f
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/global-timeline.js
@@ -0,0 +1,53 @@
+import Channel from "../channel.js";
+import { fetchMeta } from "../../../../misc/fetch-meta.js";
+import { isInstanceMuted } from "../../../../misc/is-instance-muted.js";
+import { isUserRelated } from "../../../../misc/is-user-related.js";
+import { isFiltered } from "../../../../misc/is-filtered.js";
+export default class extends Channel {
+ chName = "globalTimeline";
+ static shouldShare = true;
+ static requireCredential = false;
+ withReplies;
+ constructor(id, connection){
+ super(id, connection);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
+ }
+ async init(params) {
+ const meta = await fetchMeta();
+ if (meta.disableGlobalTimeline) {
+ if (this.user == null || !(this.user.isAdmin || this.user.isModerator)) return;
+ }
+ this.withReplies = params.withReplies;
+ // Subscribe events
+ this.subscriber.on("notesStream", this.onNote);
+ }
+ async onNote(note) {
+ if (note.visibility !== "public") return;
+ if (note.channelId != null) return;
+ // 関係ない返信は除外
+ if (note.reply && !this.withReplies) {
+ const reply = note.reply;
+ // 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
+ if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
+ }
+ // Ignore notes from instances the user has muted
+ if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return;
+ // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.muting)) return;
+ // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.blocking)) return;
+ if (note.renote && !note.text && this.renoteMuting.has(note.userId)) return;
+ // 流れてきたNoteがミュートすべきNoteだったら無視する
+ // TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
+ // 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
+ // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
+ // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
+ if (this.userProfile && await isFiltered(note, this.user, this.userProfile)) return;
+ this.connection.cacheNote(note);
+ this.send("note", note);
+ }
+ dispose() {
+ // Unsubscribe events
+ this.subscriber.off("notesStream", this.onNote);
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/home-timeline.js b/packages/backend/built/server/api/stream/channels/home-timeline.js
new file mode 100644
index 0000000..572d6f3
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/home-timeline.js
@@ -0,0 +1,55 @@
+import Channel from "../channel.js";
+import { isUserRelated } from "../../../../misc/is-user-related.js";
+import { isInstanceMuted } from "../../../../misc/is-instance-muted.js";
+import { isFiltered } from "../../../../misc/is-filtered.js";
+export default class extends Channel {
+ chName = "homeTimeline";
+ static shouldShare = true;
+ static requireCredential = true;
+ withReplies;
+ constructor(id, connection){
+ super(id, connection);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
+ }
+ async init(params) {
+ this.withReplies = params.withReplies;
+ // Subscribe events
+ this.subscriber.on("notesStream", this.onNote);
+ }
+ async onNote(note) {
+ if (note.visibility === "hidden") return;
+ if (note.channelId) {
+ if (!this.followingChannels.has(note.channelId)) return;
+ } else {
+ // その投稿のユーザーをフォローしていなかったら弾く
+ if (this.user.id !== note.userId && !this.following.has(note.userId)) return;
+ }
+ // Ignore notes from instances the user has muted
+ if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return;
+ // 関係ない返信は除外
+ if (note.reply && !this.withReplies) {
+ const reply = note.reply;
+ // 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
+ if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
+ }
+ // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.muting)) return;
+ // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.blocking)) return;
+ // Members of lists with hideFromHome set
+ if (note.userId !== this.user.id && isUserRelated(note, this.hidden)) return;
+ if (note.renote && !note.text && this.renoteMuting.has(note.userId)) return;
+ // 流れてきたNoteがミュートすべきNoteだったら無視する
+ // TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
+ // 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
+ // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
+ // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
+ if (this.userProfile && await isFiltered(note, this.user, this.userProfile)) return;
+ this.connection.cacheNote(note);
+ this.send("note", note);
+ }
+ dispose() {
+ // Unsubscribe events
+ this.subscriber.off("notesStream", this.onNote);
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/hybrid-timeline.js b/packages/backend/built/server/api/stream/channels/hybrid-timeline.js
new file mode 100644
index 0000000..8a44b2c
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/hybrid-timeline.js
@@ -0,0 +1,57 @@
+import Channel from "../channel.js";
+import { fetchMeta } from "../../../../misc/fetch-meta.js";
+import { isUserRelated } from "../../../../misc/is-user-related.js";
+import { isInstanceMuted } from "../../../../misc/is-instance-muted.js";
+import { isFiltered } from "../../../../misc/is-filtered.js";
+export default class extends Channel {
+ chName = "hybridTimeline";
+ static shouldShare = true;
+ static requireCredential = true;
+ withReplies;
+ constructor(id, connection){
+ super(id, connection);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
+ }
+ async init(params) {
+ const meta = await fetchMeta();
+ if (meta.disableLocalTimeline && !this.user.isAdmin && !this.user.isModerator) return;
+ this.withReplies = params.withReplies;
+ // Subscribe events
+ this.subscriber.on("notesStream", this.onNote);
+ }
+ async onNote(note) {
+ if (note.visibility === "hidden") return;
+ // チャンネルの投稿ではなく、自分自身の投稿 または
+ // チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または
+ // チャンネルの投稿ではなく、全体公開のローカルの投稿 または
+ // フォローしているチャンネルの投稿 の場合だけ
+ if (!(note.channelId == null && this.user.id === note.userId || note.channelId == null && this.following.has(note.userId) || note.channelId == null && note.user.host == null && note.visibility === "public" || note.channelId != null && this.followingChannels.has(note.channelId))) return;
+ // Ignore notes from instances the user has muted
+ if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return;
+ // 関係ない返信は除外
+ if (note.reply && !this.withReplies) {
+ const reply = note.reply;
+ // 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
+ if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
+ }
+ // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.muting)) return;
+ // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.blocking)) return;
+ // Members of lists with hideFromHome set
+ if (note.userId !== this.user.id && isUserRelated(note, this.hidden)) return;
+ if (note.renote && !note.text && this.renoteMuting.has(note.userId)) return;
+ // 流れてきたNoteがミュートすべきNoteだったら無視する
+ // TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
+ // 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
+ // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
+ // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
+ if (this.userProfile && await isFiltered(note, this.user, this.userProfile)) return;
+ this.connection.cacheNote(note);
+ this.send("note", note);
+ }
+ dispose() {
+ // Unsubscribe events
+ this.subscriber.off("notesStream", this.onNote);
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/index.js b/packages/backend/built/server/api/stream/channels/index.js
new file mode 100644
index 0000000..c95e8d1
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/index.js
@@ -0,0 +1,40 @@
+import main from "./main.js";
+import homeTimeline from "./home-timeline.js";
+import localTimeline from "./local-timeline.js";
+import hybridTimeline from "./hybrid-timeline.js";
+import recommendedTimeline from "./recommended-timeline.js";
+import globalTimeline from "./global-timeline.js";
+import serverStats from "./server-stats.js";
+import queueStats from "./queue-stats.js";
+import userList from "./user-list.js";
+import antenna from "./antenna.js";
+import messaging from "./messaging.js";
+import messagingIndex from "./messaging-index.js";
+import drive from "./drive.js";
+import channel from "./channel.js";
+import admin from "./admin.js";
+import reversi from "./reversi.js";
+import reversiGame from "./reversi-game.js";
+import shogi from "./shogi.js";
+import shogiGame from "./shogi-game.js";
+export default {
+ main,
+ homeTimeline,
+ localTimeline,
+ recommendedTimeline,
+ hybridTimeline,
+ globalTimeline,
+ serverStats,
+ queueStats,
+ userList,
+ antenna,
+ messaging,
+ messagingIndex,
+ drive,
+ channel,
+ admin,
+ reversi,
+ reversiGame,
+ shogi,
+ shogiGame
+};
diff --git a/packages/backend/built/server/api/stream/channels/local-timeline.js b/packages/backend/built/server/api/stream/channels/local-timeline.js
new file mode 100644
index 0000000..15c1955
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/local-timeline.js
@@ -0,0 +1,51 @@
+import Channel from "../channel.js";
+import { fetchMeta } from "../../../../misc/fetch-meta.js";
+import { isUserRelated } from "../../../../misc/is-user-related.js";
+import { isFiltered } from "../../../../misc/is-filtered.js";
+export default class extends Channel {
+ chName = "localTimeline";
+ static shouldShare = true;
+ static requireCredential = false;
+ withReplies;
+ constructor(id, connection){
+ super(id, connection);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
+ }
+ async init(params) {
+ const meta = await fetchMeta();
+ if (meta.disableLocalTimeline) {
+ if (this.user == null || !(this.user.isAdmin || this.user.isModerator)) return;
+ }
+ this.withReplies = params.withReplies;
+ // Subscribe events
+ this.subscriber.on("notesStream", this.onNote);
+ }
+ async onNote(note) {
+ if (note.user.host !== null) return;
+ if (note.visibility !== "public") return;
+ if (note.channelId != null && !this.followingChannels.has(note.channelId)) return;
+ // 関係ない返信は除外
+ if (note.reply && !this.withReplies) {
+ const reply = note.reply;
+ // 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
+ if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
+ }
+ // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.muting)) return;
+ // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.blocking)) return;
+ if (note.renote && !note.text && this.renoteMuting.has(note.userId)) return;
+ // 流れてきたNoteがミュートすべきNoteだったら無視する
+ // TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
+ // 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
+ // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
+ // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
+ if (this.userProfile && await isFiltered(note, this.user, this.userProfile)) return;
+ this.connection.cacheNote(note);
+ this.send("note", note);
+ }
+ dispose() {
+ // Unsubscribe events
+ this.subscriber.off("notesStream", this.onNote);
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/main.js b/packages/backend/built/server/api/stream/channels/main.js
new file mode 100644
index 0000000..ce7720c
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/main.js
@@ -0,0 +1,28 @@
+import Channel from "../channel.js";
+import { isInstanceMuted, isUserFromMutedInstance } from "../../../../misc/is-instance-muted.js";
+export default class extends Channel {
+ chName = "main";
+ static shouldShare = true;
+ static requireCredential = true;
+ async init(params) {
+ // Subscribe main stream channel
+ this.subscriber.on(`mainStream:${this.user.id}`, async (data)=>{
+ switch(data.type){
+ case "notification":
+ {
+ // Ignore notifications from instances the user has muted
+ if (isUserFromMutedInstance(data.body, new Set(this.userProfile?.mutedInstances ?? []))) return;
+ if (data.body.userId && this.muting.has(data.body.userId)) return;
+ break;
+ }
+ case "mention":
+ {
+ if (isInstanceMuted(data.body, new Set(this.userProfile?.mutedInstances ?? []))) return;
+ if (this.muting.has(data.body.userId)) return;
+ break;
+ }
+ }
+ this.send(data.type, data.body);
+ });
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/messaging-index.js b/packages/backend/built/server/api/stream/channels/messaging-index.js
new file mode 100644
index 0000000..152efac
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/messaging-index.js
@@ -0,0 +1,12 @@
+import Channel from "../channel.js";
+export default class extends Channel {
+ chName = "messagingIndex";
+ static shouldShare = true;
+ static requireCredential = true;
+ async init(params) {
+ // Subscribe messaging index stream
+ this.subscriber.on(`messagingIndexStream:${this.user.id}`, (data)=>{
+ this.send(data);
+ });
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/messaging.js b/packages/backend/built/server/api/stream/channels/messaging.js
new file mode 100644
index 0000000..b8d3b9b
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/messaging.js
@@ -0,0 +1,95 @@
+import { readUserMessagingMessage, readGroupMessagingMessage, deliverReadActivity } from "../../common/read-messaging-message.js";
+import Channel from "../channel.js";
+import { UserGroupJoinings, Users, MessagingMessages } from "../../../../models/index.js";
+export default class extends Channel {
+ chName = "messaging";
+ static shouldShare = false;
+ static requireCredential = true;
+ otherpartyId;
+ otherparty;
+ groupId;
+ subCh;
+ typers = new Map();
+ emitTypersIntervalId;
+ constructor(id, connection){
+ super(id, connection);
+ this.onEvent = this.onEvent.bind(this);
+ this.onMessage = this.onMessage.bind(this);
+ this.emitTypers = this.emitTypers.bind(this);
+ }
+ async init(params) {
+ this.otherpartyId = params.otherparty;
+ this.otherparty = this.otherpartyId ? await Users.findOneByOrFail({
+ id: this.otherpartyId
+ }) : null;
+ this.groupId = params.group;
+ // Check joining
+ if (this.groupId) {
+ const joining = await UserGroupJoinings.findOneBy({
+ userId: this.user.id,
+ userGroupId: this.groupId
+ });
+ if (joining == null) {
+ return;
+ }
+ }
+ this.emitTypersIntervalId = setInterval(this.emitTypers, 5000);
+ this.subCh = this.otherpartyId ? `messagingStream:${this.user.id}-${this.otherpartyId}` : `messagingStream:${this.groupId}`;
+ // Subscribe messaging stream
+ this.subscriber.on(this.subCh, this.onEvent);
+ }
+ onEvent(data) {
+ if (data.type === "typing") {
+ const id = data.body;
+ const begin = !this.typers.has(id);
+ this.typers.set(id, new Date());
+ if (begin) {
+ this.emitTypers();
+ }
+ } else {
+ this.send(data);
+ }
+ }
+ onMessage(type, body) {
+ switch(type){
+ case "read":
+ if (this.otherpartyId) {
+ readUserMessagingMessage(this.user.id, this.otherpartyId, [
+ body.id
+ ]);
+ // リモートユーザーからのメッセージだったら既読配信
+ if (Users.isLocalUser(this.user) && Users.isRemoteUser(this.otherparty)) {
+ MessagingMessages.findOneBy({
+ id: body.id
+ }).then((message)=>{
+ if (message) deliverReadActivity(this.user, this.otherparty, message);
+ });
+ }
+ } else if (this.groupId) {
+ readGroupMessagingMessage(this.user.id, this.groupId, [
+ body.id
+ ]);
+ }
+ break;
+ }
+ }
+ async emitTypers() {
+ const now = new Date();
+ // Remove not typing users
+ for (const [userId, date] of this.typers.entries()){
+ if (now.getTime() - date.getTime() > 5000) this.typers.delete(userId);
+ }
+ const userIds = Array.from(this.typers.keys());
+ const users = await Users.packMany(userIds, null, {
+ detail: false
+ });
+ this.send({
+ type: "typers",
+ body: users
+ });
+ }
+ dispose() {
+ this.subscriber.off(this.subCh, this.onEvent);
+ clearInterval(this.emitTypersIntervalId);
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/queue-stats.js b/packages/backend/built/server/api/stream/channels/queue-stats.js
new file mode 100644
index 0000000..cd51042
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/queue-stats.js
@@ -0,0 +1,35 @@
+import Xev from "xev";
+import Channel from "../channel.js";
+const ev = new Xev();
+export default class extends Channel {
+ chName = "queueStats";
+ static shouldShare = true;
+ static requireCredential = false;
+ constructor(id, connection){
+ super(id, connection);
+ this.onStats = this.onStats.bind(this);
+ this.onMessage = this.onMessage.bind(this);
+ }
+ async init(params) {
+ ev.addListener("queueStats", this.onStats);
+ }
+ onStats(stats) {
+ this.send("stats", stats);
+ }
+ onMessage(type, body) {
+ switch(type){
+ case "requestLog":
+ ev.once(`queueStatsLog:${body.id}`, (statsLog)=>{
+ this.send("statsLog", statsLog);
+ });
+ ev.emit("requestQueueStatsLog", {
+ id: body.id,
+ length: body.length
+ });
+ break;
+ }
+ }
+ dispose() {
+ ev.removeListener("queueStats", this.onStats);
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/recommended-timeline.js b/packages/backend/built/server/api/stream/channels/recommended-timeline.js
new file mode 100644
index 0000000..13ebaa5
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/recommended-timeline.js
@@ -0,0 +1,56 @@
+import Channel from "../channel.js";
+import { fetchMeta } from "../../../../misc/fetch-meta.js";
+import { isUserRelated } from "../../../../misc/is-user-related.js";
+import { isInstanceMuted } from "../../../../misc/is-instance-muted.js";
+import { isFiltered } from "../../../../misc/is-filtered.js";
+export default class extends Channel {
+ chName = "recommendedTimeline";
+ static shouldShare = true;
+ static requireCredential = true;
+ withReplies;
+ constructor(id, connection){
+ super(id, connection);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
+ }
+ async init(params) {
+ const meta = await fetchMeta();
+ if (meta.disableRecommendedTimeline && !this.user.isAdmin && !this.user.isModerator) return;
+ this.withReplies = params.withReplies;
+ // Subscribe events
+ this.subscriber.on("notesStream", this.onNote);
+ }
+ async onNote(note) {
+ if (note.visibility === "hidden") return;
+ // チャンネルの投稿ではなく、自分自身の投稿 または
+ // チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または
+ // チャンネルの投稿ではなく、全体公開のローカルの投稿 または
+ // フォローしているチャンネルの投稿 の場合だけ
+ const meta = await fetchMeta();
+ if (!(note.user.host != null && meta.recommendedInstances.includes(note.user.host) && note.visibility === "public")) return;
+ // Ignore notes from instances the user has muted
+ if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return;
+ // 関係ない返信は除外
+ if (note.reply && !this.withReplies) {
+ const reply = note.reply;
+ // 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
+ if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
+ }
+ // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.muting)) return;
+ // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.blocking)) return;
+ if (note.renote && !note.text && this.renoteMuting.has(note.userId)) return;
+ // 流れてきたNoteがミュートすべきNoteだったら無視する
+ // TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
+ // 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
+ // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
+ // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
+ if (this.userProfile && await isFiltered(note, this.user, this.userProfile)) return;
+ this.connection.cacheNote(note);
+ this.send("note", note);
+ }
+ dispose() {
+ // Unsubscribe events
+ this.subscriber.off("notesStream", this.onNote);
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/reversi-game.js b/packages/backend/built/server/api/stream/channels/reversi-game.js
new file mode 100644
index 0000000..8966068
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/reversi-game.js
@@ -0,0 +1,49 @@
+import Channel from "../channel.js";
+import { cancelGame, checkTimeout, gameReady, isValidUpdateKey, isValidUpdateValue, putStone, updateSettings } from "../../../../services/reversi/index.js";
+export default class extends Channel {
+ chName = "reversiGame";
+ static shouldShare = false;
+ static requireCredential = false;
+ gameId = null;
+ constructor(id, connection){
+ super(id, connection);
+ this.onEvent = this.onEvent.bind(this);
+ }
+ async init(params) {
+ if (typeof params.gameId !== "string") return;
+ this.gameId = params.gameId;
+ this.subscriber.on(`reversiGameStream:${this.gameId}`, this.onEvent);
+ }
+ onEvent(data) {
+ this.send(data);
+ }
+ onMessage(type, body) {
+ if (!this.gameId) return;
+ switch(type){
+ case "ready":
+ if (this.user && typeof body === "boolean") gameReady(this.gameId, this.user, body);
+ break;
+ case "updateSettings":
+ if (this.user && body && isValidUpdateKey(body.key) && isValidUpdateValue(body.key, body.value)) {
+ updateSettings(this.gameId, this.user, body.key, body.value);
+ }
+ break;
+ case "cancel":
+ if (this.user) cancelGame(this.gameId, this.user);
+ break;
+ case "putStone":
+ if (this.user && body && typeof body.pos === "number") {
+ putStone(this.gameId, this.user, body.pos, typeof body.id === "string" ? body.id : null);
+ }
+ break;
+ case "claimTimeIsUp":
+ checkTimeout(this.gameId);
+ break;
+ }
+ }
+ dispose() {
+ if (this.gameId) {
+ this.subscriber.off(`reversiGameStream:${this.gameId}`, this.onEvent);
+ }
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/reversi.js b/packages/backend/built/server/api/stream/channels/reversi.js
new file mode 100644
index 0000000..f0774d5
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/reversi.js
@@ -0,0 +1,19 @@
+import Channel from "../channel.js";
+export default class extends Channel {
+ chName = "reversi";
+ static shouldShare = true;
+ static requireCredential = true;
+ constructor(id, connection){
+ super(id, connection);
+ this.onEvent = this.onEvent.bind(this);
+ }
+ async init(params) {
+ this.subscriber.on(`reversiStream:${this.user.id}`, this.onEvent);
+ }
+ onEvent(data) {
+ this.send(data);
+ }
+ dispose() {
+ this.subscriber.off(`reversiStream:${this.user.id}`, this.onEvent);
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/server-stats.js b/packages/backend/built/server/api/stream/channels/server-stats.js
new file mode 100644
index 0000000..7854b68
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/server-stats.js
@@ -0,0 +1,35 @@
+import Xev from "xev";
+import Channel from "../channel.js";
+const ev = new Xev();
+export default class extends Channel {
+ chName = "serverStats";
+ static shouldShare = true;
+ static requireCredential = false;
+ constructor(id, connection){
+ super(id, connection);
+ this.onStats = this.onStats.bind(this);
+ this.onMessage = this.onMessage.bind(this);
+ }
+ async init(params) {
+ ev.addListener("serverStats", this.onStats);
+ }
+ onStats(stats) {
+ this.send("stats", stats);
+ }
+ onMessage(type, body) {
+ switch(type){
+ case "requestLog":
+ ev.once(`serverStatsLog:${body.id}`, (statsLog)=>{
+ this.send("statsLog", statsLog);
+ });
+ ev.emit("requestServerStatsLog", {
+ id: body.id,
+ length: body.length
+ });
+ break;
+ }
+ }
+ dispose() {
+ ev.removeListener("serverStats", this.onStats);
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/shogi-game.js b/packages/backend/built/server/api/stream/channels/shogi-game.js
new file mode 100644
index 0000000..5df6fcf
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/shogi-game.js
@@ -0,0 +1,41 @@
+import Channel from "../channel.js";
+import { cancelGame, gameReady, putMove } from "../../../../services/shogi/index.js";
+export default class extends Channel {
+ chName = "shogiGame";
+ static shouldShare = false;
+ static requireCredential = false;
+ gameId = null;
+ constructor(id, connection){
+ super(id, connection);
+ this.onEvent = this.onEvent.bind(this);
+ }
+ async init(params) {
+ if (typeof params.gameId !== "string") return;
+ this.gameId = params.gameId;
+ this.subscriber.on(`shogiGameStream:${this.gameId}`, this.onEvent);
+ }
+ onEvent(data) {
+ this.send(data);
+ }
+ onMessage(type, body) {
+ if (!this.gameId) return;
+ switch(type){
+ case "ready":
+ if (this.user && typeof body === "boolean") gameReady(this.gameId, this.user, body);
+ break;
+ case "cancel":
+ if (this.user) cancelGame(this.gameId, this.user);
+ break;
+ case "move":
+ if (this.user && body && typeof body.usi === "string") {
+ putMove(this.gameId, this.user, body.usi, typeof body.id === "string" ? body.id : null);
+ }
+ break;
+ }
+ }
+ dispose() {
+ if (this.gameId) {
+ this.subscriber.off(`shogiGameStream:${this.gameId}`, this.onEvent);
+ }
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/shogi.js b/packages/backend/built/server/api/stream/channels/shogi.js
new file mode 100644
index 0000000..1749c44
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/shogi.js
@@ -0,0 +1,19 @@
+import Channel from "../channel.js";
+export default class extends Channel {
+ chName = "shogi";
+ static shouldShare = true;
+ static requireCredential = true;
+ constructor(id, connection){
+ super(id, connection);
+ this.onEvent = this.onEvent.bind(this);
+ }
+ async init(params) {
+ this.subscriber.on(`shogiStream:${this.user.id}`, this.onEvent);
+ }
+ onEvent(data) {
+ this.send(data);
+ }
+ dispose() {
+ this.subscriber.off(`shogiStream:${this.user.id}`, this.onEvent);
+ }
+}
diff --git a/packages/backend/built/server/api/stream/channels/user-list.js b/packages/backend/built/server/api/stream/channels/user-list.js
new file mode 100644
index 0000000..0bffc66
--- /dev/null
+++ b/packages/backend/built/server/api/stream/channels/user-list.js
@@ -0,0 +1,59 @@
+import Channel from "../channel.js";
+import { UserListJoinings, UserLists } from "../../../../models/index.js";
+import { isUserRelated } from "../../../../misc/is-user-related.js";
+export default class extends Channel {
+ chName = "userList";
+ static shouldShare = false;
+ static requireCredential = false;
+ listId;
+ listUsers = [];
+ listUsersClock;
+ constructor(id, connection){
+ super(id, connection);
+ this.updateListUsers = this.updateListUsers.bind(this);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
+ }
+ async init(params) {
+ this.listId = params.listId;
+ // Check existence and owner
+ const exist = await UserLists.exist({
+ where: {
+ id: this.listId,
+ userId: this.user.id
+ }
+ });
+ if (!exist) return;
+ // Subscribe stream
+ this.subscriber.on(`userListStream:${this.listId}`, this.send);
+ this.subscriber.on("notesStream", this.onNote);
+ this.updateListUsers();
+ this.listUsersClock = setInterval(this.updateListUsers, 5000);
+ }
+ async updateListUsers() {
+ const users = await UserListJoinings.find({
+ where: {
+ userListId: this.listId
+ },
+ select: [
+ "userId"
+ ]
+ });
+ this.listUsers = users.map((x)=>x.userId);
+ }
+ async onNote(note) {
+ if (note.visibility === "hidden") return;
+ if (!this.listUsers.includes(note.userId)) return;
+ // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.muting)) return;
+ // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.blocking)) return;
+ if (note.renote && !note.text && this.renoteMuting.has(note.userId)) return;
+ this.send("note", note);
+ }
+ dispose() {
+ // Unsubscribe events
+ this.subscriber.off(`userListStream:${this.listId}`, this.send);
+ this.subscriber.off("notesStream", this.onNote);
+ clearInterval(this.listUsersClock);
+ }
+}
diff --git a/packages/backend/built/server/api/stream/index.js b/packages/backend/built/server/api/stream/index.js
new file mode 100644
index 0000000..469e442
--- /dev/null
+++ b/packages/backend/built/server/api/stream/index.js
@@ -0,0 +1,502 @@
+import readNote from "../../../services/note/read.js";
+import { Users, Followings, Mutings, RenoteMutings, UserProfiles, ChannelFollowings, Blockings, CallBlockings, UserListJoinings, UserGroupJoinings, UserGroups } from "../../../models/index.js";
+import { publishMainStream, publishChannelStream, publishGroupMessagingStream, publishMessagingStream } from "../../../services/stream.js";
+import { readNotification } from "../common/read-notification.js";
+import channels from "./channels/index.js";
+/**
+ * Main stream connection
+ */ export default class Connection {
+ user;
+ userProfile;
+ following = new Set();
+ muting = new Set();
+ renoteMuting = new Set();
+ blocking = new Set();
+ hidden = new Set();
+ followingChannels = new Set();
+ token;
+ wsConnection;
+ subscriber;
+ channels = [];
+ subscribingNotes = new Map();
+ cachedNotes = [];
+ host;
+ accessToken;
+ currentSubscribe = [];
+ constructor(wsConnection, subscriber, user, token, host, accessToken, prepareStream){
+ this.wsConnection = wsConnection;
+ this.subscriber = subscriber;
+ if (user) this.user = user;
+ if (token) this.token = token;
+ if (host) this.host = host;
+ if (accessToken) this.accessToken = accessToken;
+ this.onWsConnectionMessage = this.onWsConnectionMessage.bind(this);
+ this.onUserEvent = this.onUserEvent.bind(this);
+ this.onNoteStreamMessage = this.onNoteStreamMessage.bind(this);
+ this.onBroadcastMessage = this.onBroadcastMessage.bind(this);
+ this.wsConnection.on("message", this.onWsConnectionMessage);
+ this.subscriber.on("broadcast", (data)=>{
+ this.onBroadcastMessage(data);
+ });
+ if (this.user) {
+ this.updateFollowing();
+ this.updateMuting();
+ this.updateRenoteMuting();
+ this.updateBlocking();
+ this.updateHidden();
+ this.updateFollowingChannels();
+ this.updateUserProfile();
+ this.subscriber.on(`user:${this.user.id}`, this.onUserEvent);
+ }
+ if (prepareStream) {
+ this.onWsConnectionMessage({
+ type: "utf8",
+ utf8Data: JSON.stringify({
+ stream: prepareStream,
+ type: "subscribe"
+ })
+ });
+ }
+ }
+ onUserEvent(data) {
+ // { type, body }と展開するとそれぞれ型が分離してしまう
+ switch(data.type){
+ case "follow":
+ this.following.add(data.body.id);
+ break;
+ case "unfollow":
+ this.following.delete(data.body.id);
+ break;
+ case "mute":
+ this.muting.add(data.body.id);
+ break;
+ case "unmute":
+ this.muting.delete(data.body.id);
+ break;
+ // TODO: renote mute events
+ // TODO: block events
+ case "followChannel":
+ this.followingChannels.add(data.body.id);
+ break;
+ case "unfollowChannel":
+ this.followingChannels.delete(data.body.id);
+ break;
+ case "userHidden":
+ this.hidden.add(data.body);
+ break;
+ case "userUnhidden":
+ this.hidden.delete(data.body);
+ break;
+ case "updateUserProfile":
+ this.userProfile = data.body;
+ break;
+ case "terminate":
+ this.wsConnection.close();
+ this.dispose();
+ break;
+ default:
+ break;
+ }
+ }
+ /**
+ * クライアントからメッセージ受信時
+ */ async onWsConnectionMessage(data) {
+ if (data.type !== "utf8") return;
+ if (data.utf8Data == null) return;
+ let objs;
+ try {
+ objs = [
+ JSON.parse(data.utf8Data)
+ ];
+ } catch (e) {
+ return;
+ }
+ for (const obj of objs){
+ const { type, body } = obj;
+ // console.log(type, body);
+ switch(type){
+ case "readNotification":
+ this.onReadNotification(body);
+ break;
+ case "subNote":
+ this.onSubscribeNote(body);
+ break;
+ case "s":
+ this.onSubscribeNote(body);
+ break; // alias
+ case "sr":
+ this.onSubscribeNote(body);
+ this.readNote(body);
+ break;
+ case "unsubNote":
+ this.onUnsubscribeNote(body);
+ break;
+ case "un":
+ this.onUnsubscribeNote(body);
+ break; // alias
+ case "connect":
+ this.onChannelConnectRequested(body);
+ break;
+ case "disconnect":
+ this.onChannelDisconnectRequested(body);
+ break;
+ case "channel":
+ this.onChannelMessageRequested(body);
+ break;
+ case "ch":
+ this.onChannelMessageRequested(body);
+ break; // alias
+ // 個々のチャンネルではなくルートレベルでこれらのメッセージを受け取る理由は、
+ // クライアントの事情を考慮したとき、入力フォームはノートチャンネルやメッセージのメインコンポーネントとは別
+ // なこともあるため、それらのコンポーネントがそれぞれ各チャンネルに接続するようにするのは面倒なため。
+ case "typingOnChannel":
+ this.typingOnChannel(body.channel);
+ break;
+ case "typingOnMessaging":
+ this.typingOnMessaging(body);
+ break;
+ case "callSignal":
+ this.callSignal(body);
+ break;
+ }
+ }
+ }
+ onBroadcastMessage(data) {
+ this.sendMessageToWs(data.type, data.body);
+ }
+ cacheNote(note) {
+ const add = (note)=>{
+ const existIndex = this.cachedNotes.findIndex((n)=>n.id === note.id);
+ if (existIndex > -1) {
+ this.cachedNotes[existIndex] = note;
+ return;
+ }
+ this.cachedNotes.unshift(note);
+ if (this.cachedNotes.length > 32) {
+ this.cachedNotes.splice(32);
+ }
+ };
+ add(note);
+ if (note.reply) add(note.reply);
+ if (note.renote) add(note.renote);
+ }
+ readNote(body) {
+ const id = body.id;
+ const note = this.cachedNotes.find((n)=>n.id === id);
+ if (note == null) return;
+ if (this.user && note.userId !== this.user.id) {
+ readNote(this.user.id, [
+ note
+ ], {
+ following: this.following,
+ followingChannels: this.followingChannels
+ });
+ }
+ }
+ onReadNotification(payload) {
+ if (!payload.id) return;
+ readNotification(this.user.id, [
+ payload.id
+ ]);
+ }
+ /**
+ * 投稿購読要求時
+ */ onSubscribeNote(payload) {
+ if (!payload.id) return;
+ const current = this.subscribingNotes.get(payload.id) || 0;
+ this.subscribingNotes.set(payload.id, current + 1);
+ if (!current) {
+ this.subscriber.on(`noteStream:${payload.id}`, this.onNoteStreamMessage);
+ }
+ }
+ /**
+ * 投稿購読解除要求時
+ */ onUnsubscribeNote(payload) {
+ if (!payload.id) return;
+ const current = this.subscribingNotes.get(payload.id) || 0;
+ if (current <= 1) {
+ this.subscribingNotes.delete(payload.id);
+ this.subscriber.off(`noteStream:${payload.id}`, this.onNoteStreamMessage);
+ return;
+ }
+ this.subscribingNotes.set(payload.id, current - 1);
+ }
+ async onNoteStreamMessage(data) {
+ this.sendMessageToWs("noteUpdated", {
+ id: data.body.id,
+ type: data.type,
+ body: data.body.body
+ });
+ }
+ /**
+ * チャンネル接続要求時
+ */ onChannelConnectRequested(payload) {
+ const { channel, id, params, pong } = payload;
+ this.connectChannel(id, params, channel, pong);
+ }
+ /**
+ * チャンネル切断要求時
+ */ onChannelDisconnectRequested(payload) {
+ const { id } = payload;
+ this.disconnectChannel(id);
+ }
+ /**
+ * クライアントにメッセージ送信
+ */ sendMessageToWs(type, payload) {
+ this.wsConnection.send(JSON.stringify({
+ type: type,
+ body: payload
+ }));
+ }
+ /**
+ * チャンネルに接続
+ */ connectChannel(id, params, channel, pong = false) {
+ if (channels[channel].requireCredential && this.user == null) {
+ return;
+ }
+ // 共有可能チャンネルに接続しようとしていて、かつそのチャンネルに既に接続していたら無意味なので無視
+ if (channels[channel].shouldShare && this.channels.some((c)=>c.chName === channel)) {
+ return;
+ }
+ const ch = new channels[channel](id, this);
+ this.channels.push(ch);
+ ch.init(params);
+ if (pong) {
+ this.sendMessageToWs("connected", {
+ id: id
+ });
+ }
+ }
+ /**
+ * チャンネルから切断
+ * @param id チャンネルコネクションID
+ */ disconnectChannel(id) {
+ const channel = this.channels.find((c)=>c.id === id);
+ if (channel) {
+ if (channel.dispose) channel.dispose();
+ this.channels = this.channels.filter((c)=>c.id !== id);
+ }
+ }
+ /**
+ * チャンネルへメッセージ送信要求時
+ * @param data メッセージ
+ */ onChannelMessageRequested(data) {
+ const channel = this.channels.find((c)=>c.id === data.id);
+ if (channel?.onMessage != null) {
+ channel.onMessage(data.type, data.body);
+ }
+ }
+ typingOnChannel(channel) {
+ if (this.user) {
+ publishChannelStream(channel, "typing", this.user.id);
+ }
+ }
+ typingOnMessaging(param) {
+ if (this.user) {
+ if (param.partner) {
+ publishMessagingStream(param.partner, this.user.id, "typing", this.user.id);
+ } else if (param.group) {
+ publishGroupMessagingStream(param.group, "typing", this.user.id);
+ }
+ }
+ }
+ async callSignal(body) {
+ if (!this.user || !body.sessionId) return;
+ const recipients = new Set();
+ let recipientGroupId = null;
+ const target = typeof body.to === "string" ? body.to.trim() : "";
+ if (body.toUserId) {
+ const user = await Users.findOneBy({
+ id: body.toUserId,
+ host: null
+ });
+ if (user && user.id !== this.user.id) recipients.add(user.id);
+ } else if (target === "") {
+ if (body.signal?.type !== "offer") return;
+ const users = await Users.createQueryBuilder("user").select([
+ "user.id"
+ ]).where("user.host IS NULL").andWhere("user.id != :meId", {
+ meId: this.user.id
+ }).andWhere("user.isSuspended = FALSE").andWhere("user.isDeleted = FALSE").getMany();
+ for (const user of users)recipients.add(user.id);
+ } else if (target.startsWith("@@")) {
+ const group = await UserGroups.findOneBy({
+ username: target.slice(2).toLowerCase(),
+ allowCalls: true
+ });
+ if (!group) return;
+ recipientGroupId = group.id;
+ const joinings = await UserGroupJoinings.findBy({
+ userGroupId: group.id
+ });
+ if (group.userId !== this.user.id) recipients.add(group.userId);
+ for (const joining of joinings){
+ if (joining.userId !== this.user.id) recipients.add(joining.userId);
+ }
+ } else if (target.startsWith("@")) {
+ const username = target.slice(1).split("@")[0].toLowerCase();
+ const user = await Users.findOneBy({
+ usernameLower: username,
+ host: null
+ });
+ const profile = user ? await UserProfiles.findOneBy({
+ userId: user.id,
+ allowCalls: true
+ }) : null;
+ if (user && profile && user.id !== this.user.id) recipients.add(user.id);
+ } else {
+ const group = await UserGroups.findOneBy({
+ username: target.toLowerCase(),
+ allowCalls: true
+ });
+ if (!group) return;
+ recipientGroupId = group.id;
+ const isMember = group.userId === this.user.id || await UserGroupJoinings.exist({
+ where: {
+ userGroupId: group.id,
+ userId: this.user.id
+ }
+ });
+ if (!isMember) return;
+ const joinings = await UserGroupJoinings.findBy({
+ userGroupId: group.id
+ });
+ if (group.userId !== this.user.id) recipients.add(group.userId);
+ for (const joining of joinings){
+ if (joining.userId !== this.user.id) recipients.add(joining.userId);
+ }
+ }
+ if (recipientGroupId != null) {
+ const groupCallBlocked = await CallBlockings.exist({
+ where: {
+ groupId: recipientGroupId,
+ blockeeId: this.user.id
+ }
+ });
+ if (groupCallBlocked) return;
+ }
+ for (const recipient of recipients){
+ if (await this.isCallRecipientBlocked(recipient)) continue;
+ publishMainStream(recipient, "callSignal", {
+ fromUserId: this.user.id,
+ to: target,
+ sessionId: body.sessionId,
+ kind: body.kind,
+ signal: body.signal
+ });
+ }
+ }
+ async isCallRecipientBlocked(recipient) {
+ const normalBlocked = await Blockings.exist({
+ where: [
+ {
+ blockerId: recipient,
+ blockeeId: this.user.id,
+ groupId: null
+ },
+ {
+ blockerId: this.user.id,
+ blockeeId: recipient,
+ groupId: null
+ }
+ ]
+ });
+ if (normalBlocked) return true;
+ return await CallBlockings.exist({
+ where: [
+ {
+ blockerId: recipient,
+ blockeeId: this.user.id,
+ groupId: null
+ },
+ {
+ blockerId: this.user.id,
+ blockeeId: recipient,
+ groupId: null
+ }
+ ]
+ });
+ }
+ async updateFollowing() {
+ const followings = await Followings.find({
+ where: {
+ followerId: this.user.id
+ },
+ select: [
+ "followeeId"
+ ]
+ });
+ this.following = new Set(followings.map((x)=>x.followeeId));
+ }
+ async updateMuting() {
+ const mutings = await Mutings.find({
+ where: {
+ muterId: this.user.id
+ },
+ select: [
+ "muteeId"
+ ]
+ });
+ this.muting = new Set(mutings.map((x)=>x.muteeId));
+ }
+ async updateRenoteMuting() {
+ const renoteMutings = await RenoteMutings.find({
+ where: {
+ muterId: this.user.id
+ },
+ select: [
+ "muteeId"
+ ]
+ });
+ this.renoteMuting = new Set(renoteMutings.map((x)=>x.muteeId));
+ }
+ async updateBlocking() {
+ // ここでいうBlockingは被Blockingの意
+ const blockings = await Blockings.find({
+ where: {
+ blockeeId: this.user.id
+ },
+ select: [
+ "blockerId"
+ ]
+ });
+ this.blocking = new Set(blockings.map((x)=>x.blockerId));
+ }
+ async updateHidden() {
+ const hidden = await UserListJoinings.find({
+ where: {
+ userList: {
+ userId: this.user.id,
+ hideFromHomeTl: true
+ }
+ },
+ select: [
+ "userId"
+ ]
+ });
+ this.hidden = new Set(hidden.map((x)=>x.userId));
+ }
+ async updateFollowingChannels() {
+ const followings = await ChannelFollowings.find({
+ where: {
+ followerId: this.user.id
+ },
+ select: [
+ "followeeId"
+ ]
+ });
+ this.followingChannels = new Set(followings.map((x)=>x.followeeId));
+ }
+ async updateUserProfile() {
+ this.userProfile = await UserProfiles.findOneBy({
+ userId: this.user.id
+ });
+ }
+ /**
+ * ストリームが切れたとき
+ */ dispose() {
+ for (const c of this.channels.filter((c)=>c.dispose)){
+ if (c.dispose) c.dispose();
+ }
+ }
+}
diff --git a/packages/backend/built/server/api/stream/types.js b/packages/backend/built/server/api/stream/types.js
new file mode 100644
index 0000000..2234b9c
--- /dev/null
+++ b/packages/backend/built/server/api/stream/types.js
@@ -0,0 +1 @@
+export { };
diff --git a/packages/backend/built/server/api/streaming.js b/packages/backend/built/server/api/streaming.js
new file mode 100644
index 0000000..b14bf29
--- /dev/null
+++ b/packages/backend/built/server/api/streaming.js
@@ -0,0 +1,76 @@
+import { EventEmitter } from "events";
+import * as websocket from "websocket";
+import { subscriber as redisClient } from "../../db/redis.js";
+import { Users } from "../../models/index.js";
+import MainStreamConnection from "./stream/index.js";
+import authenticate from "./authenticate.js";
+import { apiLogger } from "./logger.js";
+import { MastodonStreamingConnection } from "./mastodon/streaming/index.js";
+import { getTokenFromOAuth } from "./mastodon/middleware/auth.js";
+export const streamingLogger = apiLogger.createSubLogger("streaming");
+export const initializeStreamingServer = (server)=>{
+ // Init websocket server
+ const ws = new websocket.server({
+ httpServer: server
+ });
+ ws.on("request", async (request)=>{
+ const q = request.resourceURL.query;
+ const headers = request.httpRequest.headers["sec-websocket-protocol"] || "";
+ const cred = q.i || q.access_token || headers;
+ const rawAccessToken = cred.toString();
+ const accessToken = rawAccessToken.length > 0 ? rawAccessToken : null;
+ const isMastodon = request.resourceURL.pathname?.startsWith('/api/v1/streaming');
+ let main;
+ let user;
+ let app;
+ let token;
+ if (!isMastodon) {
+ [user, app] = await authenticate(request.httpRequest.headers.authorization, accessToken).catch((err)=>{
+ request.reject(403, err.message);
+ return [];
+ });
+ } else {
+ token = await getTokenFromOAuth(accessToken);
+ if (!token || !token.user) {
+ request.reject(400);
+ return;
+ }
+ user = token.user;
+ }
+ if (user?.isSuspended) {
+ request.reject(400);
+ return;
+ }
+ const connection = request.accept(request.requestedProtocols[0] ?? undefined);
+ const ev = new EventEmitter();
+ async function onRedisMessage(_, data) {
+ const parsed = JSON.parse(data);
+ ev.emit(parsed.channel, parsed.message);
+ }
+ redisClient.on("message", onRedisMessage);
+ const host = `https://${request.host}`;
+ const prepareStream = q.stream?.toString();
+ main = isMastodon ? new MastodonStreamingConnection(connection, ev, user, token, q) : new MainStreamConnection(connection, ev, user, app, host, accessToken ?? "", prepareStream);
+ const intervalId = user ? setInterval(()=>{
+ Users.update(user.id, {
+ lastActiveDate: new Date()
+ });
+ }, 1000 * 60 * 5) : null;
+ if (user) {
+ Users.update(user.id, {
+ lastActiveDate: new Date()
+ });
+ }
+ connection.once("close", ()=>{
+ ev.removeAllListeners();
+ main.dispose();
+ redisClient.off("message", onRedisMessage);
+ if (intervalId) clearInterval(intervalId);
+ });
+ connection.on("message", async (data)=>{
+ if (data.type === "utf8" && data.utf8Data === "ping") {
+ connection.send("pong");
+ }
+ });
+ });
+};
diff --git a/packages/backend/built/server/file/index.js b/packages/backend/built/server/file/index.js
new file mode 100644
index 0000000..204389e
--- /dev/null
+++ b/packages/backend/built/server/file/index.js
@@ -0,0 +1,37 @@
+/**
+ * File Server
+ */ import * as fs from "node:fs";
+import { fileURLToPath } from "node:url";
+import { dirname } from "node:path";
+import Koa from "koa";
+import cors from "@koa/cors";
+import Router from "@koa/router";
+import sendDriveFile from "./send-drive-file.js";
+import { serverLogger } from "../index.js";
+import { isIgnorableConnectionError } from "../is-ignorable-connection-error.js";
+const _filename = fileURLToPath(import.meta.url);
+const _dirname = dirname(_filename);
+// Init app
+const app = new Koa();
+app.on("error", (err)=>{
+ if (isIgnorableConnectionError(err)) return;
+ serverLogger.error(err);
+});
+app.use(cors());
+app.use(async (ctx, next)=>{
+ ctx.set("Content-Security-Policy", `default-src 'none'; img-src 'self'; media-src 'self'; style-src 'unsafe-inline'`);
+ await next();
+});
+// Init router
+const router = new Router();
+router.get("/app-default.jpg", (ctx)=>{
+ const file = fs.createReadStream(`${_dirname}/assets/dummy.png`);
+ ctx.body = file;
+ ctx.set("Content-Type", "image/jpeg");
+ ctx.set("Cache-Control", "max-age=31536000, immutable");
+});
+router.get("/:key", sendDriveFile);
+router.get("/:key/{*splat}", sendDriveFile);
+// Register router
+app.use(router.routes());
+export default app;
diff --git a/packages/backend/built/server/file/send-drive-file.js b/packages/backend/built/server/file/send-drive-file.js
new file mode 100644
index 0000000..3cf34ba
--- /dev/null
+++ b/packages/backend/built/server/file/send-drive-file.js
@@ -0,0 +1,207 @@
+import * as fs from "node:fs";
+import { fileURLToPath } from "node:url";
+import { dirname } from "node:path";
+import send from "koa-send";
+import rename from "rename";
+import { serverLogger } from "../index.js";
+import { contentDisposition } from "../../misc/content-disposition.js";
+import { DriveFiles } from "../../models/index.js";
+import { InternalStorage } from "../../services/drive/internal-storage.js";
+import { createTemp } from "../../misc/create-temp.js";
+import { downloadUrl } from "../../misc/download-url.js";
+import { detectType } from "../../misc/get-file-info.js";
+import { convertToWebp } from "../../services/drive/image-processor.js";
+import { GenerateVideoThumbnail } from "../../services/drive/generate-video-thumbnail.js";
+import { StatusError } from "../../misc/fetch.js";
+import { FILE_TYPE_BROWSERSAFE, MINUTE } from "../../const.js";
+import { getIpHash } from "../../misc/get-ip-hash.js";
+import { limiter } from "../api/limiter.js";
+import authenticate from "../api/authenticate.js";
+import { isIgnorableConnectionError } from "../is-ignorable-connection-error.js";
+const _filename = fileURLToPath(import.meta.url);
+const _dirname = dirname(_filename);
+const assets = `${_dirname}/../../server/file/assets/`;
+const commonReadableHandlerGenerator = (ctx)=>(e)=>{
+ if (isIgnorableConnectionError(e)) return;
+ serverLogger.error(e);
+ ctx.status = 500;
+ ctx.set("Cache-Control", "max-age=300");
+ };
+export default async function(ctx) {
+ const key = ctx.params.key;
+ // Fetch drive file
+ const file = await DriveFiles.createQueryBuilder("file").where("file.accessKey = :accessKey", {
+ accessKey: key
+ }).orWhere("file.thumbnailAccessKey = :thumbnailAccessKey", {
+ thumbnailAccessKey: key
+ }).orWhere("file.webpublicAccessKey = :webpublicAccessKey", {
+ webpublicAccessKey: key
+ }).getOne();
+ if (file == null) {
+ ctx.status = 404;
+ ctx.set("Cache-Control", "max-age=86400");
+ await send(ctx, "/dummy.png", {
+ root: assets
+ });
+ return;
+ }
+ ctx.set("X-Content-Type-Options", "nosniff");
+ const isThumbnail = file.thumbnailAccessKey === key;
+ const isWebpublic = file.webpublicAccessKey === key;
+ const requestedDownload = ctx.query.download === "1";
+ const requestedStream = ctx.query.stream === "1";
+ const isStreamableMedia = file.type.startsWith("video/") || file.type.startsWith("audio/");
+ // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
+ const limitActor = getIpHash(ctx.ip);
+ const isMediaPlayback = isStreamableMedia && !requestedDownload;
+ const limit = {
+ key: `drive-file:${key}`,
+ duration: MINUTE * 10,
+ max: isMediaPlayback ? 600 : 10
+ };
+ await limiter(limit, limitActor).catch((e)=>{
+ const remainingTime = e.remainingTime ? `Please try again in ${e.remainingTime}.` : "Please try again later.";
+ ctx.status = 429;
+ ctx.body = "Rate limit exceeded. " + remainingTime;
+ ctx.set("Content-Type", "text/plain; charset=utf-8");
+ ctx.set("Cache-Control", "no-store");
+ });
+ if (ctx.status == 429) return;
+ const requesterId = requestedDownload ? await getRequesterId(ctx) : null;
+ const isOwner = requesterId != null && requesterId === file.userId;
+ const protectedOriginal = !isThumbnail && !isWebpublic && !file.allowDownload && !isOwner;
+ if (requestedDownload && protectedOriginal) {
+ ctx.status = 403;
+ ctx.body = "Download is not allowed for this file.";
+ ctx.set("Cache-Control", "max-age=300");
+ return;
+ }
+ if (!file.storedInternal) {
+ if (file.isLink && file.uri) {
+ // 期限切れリモートファイル
+ const [path, cleanup] = await createTemp();
+ try {
+ await downloadUrl(file.uri, path);
+ const { mime, ext } = await detectType(path);
+ const convertFile = async ()=>{
+ if (isThumbnail) {
+ if ([
+ "image/jpeg",
+ "image/webp",
+ "image/png",
+ "image/svg+xml",
+ "image/avif"
+ ].includes(mime)) {
+ return await convertToWebp(path, 996, 560);
+ } else if (mime.startsWith("video/")) {
+ return await GenerateVideoThumbnail(path);
+ }
+ }
+ if (isWebpublic) {
+ if ([
+ "image/svg+xml"
+ ].includes(mime)) {
+ return await convertToWebp(path, 2048, 2048, 100);
+ }
+ }
+ return {
+ data: fs.readFileSync(path),
+ ext,
+ type: mime
+ };
+ };
+ const image = await convertFile();
+ ctx.body = image.data;
+ ctx.set("Content-Type", FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : "application/octet-stream");
+ ctx.set("Cache-Control", "max-age=31536000, immutable");
+ } catch (e) {
+ if (isIgnorableConnectionError(e)) return;
+ serverLogger.error(`${e}`);
+ if (e instanceof StatusError && !e.isRetryable) {
+ ctx.status = e.statusCode;
+ ctx.set("Cache-Control", "max-age=86400");
+ } else {
+ ctx.status = 500;
+ ctx.set("Cache-Control", "max-age=300");
+ }
+ } finally{
+ cleanup();
+ }
+ return;
+ }
+ ctx.status = 204;
+ ctx.set("Cache-Control", "max-age=86400");
+ return;
+ }
+ if (isThumbnail || isWebpublic) {
+ const { mime, ext } = await detectType(InternalStorage.resolvePath(key));
+ const filename = rename(file.name, {
+ suffix: isThumbnail ? "-thumb" : "-web",
+ extname: ext ? `.${ext}` : undefined
+ }).toString();
+ ctx.body = InternalStorage.read(key);
+ ctx.set("Content-Type", FILE_TYPE_BROWSERSAFE.includes(mime) ? mime : "application/octet-stream");
+ ctx.set("Cache-Control", "max-age=31536000, immutable");
+ ctx.set("Content-Disposition", contentDisposition("inline", filename));
+ } else {
+ const storageKey = file.accessKey;
+ const disposition = requestedDownload ? "attachment" : "inline";
+ ctx.set("Content-Type", isStreamableMedia || FILE_TYPE_BROWSERSAFE.includes(file.type) ? file.type : "application/octet-stream");
+ ctx.set("Cache-Control", "max-age=31536000, immutable");
+ ctx.set("Content-Disposition", contentDisposition(disposition, file.name));
+ if (!requestedDownload && isStreamableMedia) {
+ const servedRange = await sendRange(ctx, storageKey);
+ if (servedRange) return;
+ }
+ const readable = InternalStorage.read(storageKey);
+ readable.on("error", commonReadableHandlerGenerator(ctx));
+ ctx.body = readable;
+ }
+}
+async function getRequesterId(ctx) {
+ const queryToken = typeof ctx.query.i === "string" ? ctx.query.i : null;
+ try {
+ const [user] = await authenticate(ctx.get("authorization") || null, queryToken, true);
+ return user?.id ?? null;
+ } catch {
+ return null;
+ }
+}
+async function sendRange(ctx, key) {
+ const range = ctx.get("range");
+ if (!range) {
+ ctx.set("Accept-Ranges", "bytes");
+ return false;
+ }
+ const path = InternalStorage.resolvePath(key);
+ const stat = await fs.promises.stat(path);
+ const match = /^bytes=(\d*)-(\d*)$/.exec(range);
+ if (!match) {
+ ctx.status = 416;
+ ctx.set("Content-Range", `bytes */${stat.size}`);
+ return true;
+ }
+ let start = match[1] === "" ? 0 : Number(match[1]);
+ let end = match[2] === "" ? stat.size - 1 : Number(match[2]);
+ if (match[1] === "" && match[2] !== "") {
+ const suffixLength = Number(match[2]);
+ start = Math.max(stat.size - suffixLength, 0);
+ end = stat.size - 1;
+ }
+ if (!Number.isFinite(start) || !Number.isFinite(end) || start > end || start < 0 || end >= stat.size) {
+ ctx.status = 416;
+ ctx.set("Content-Range", `bytes */${stat.size}`);
+ return true;
+ }
+ ctx.status = 206;
+ ctx.set("Accept-Ranges", "bytes");
+ ctx.set("Content-Range", `bytes ${start}-${end}/${stat.size}`);
+ ctx.set("Content-Length", String(end - start + 1));
+ const readable = fs.createReadStream(path, {
+ start,
+ end
+ });
+ readable.on("error", commonReadableHandlerGenerator(ctx));
+ ctx.body = readable;
+ return true;
+}
diff --git a/packages/backend/built/server/index.js b/packages/backend/built/server/index.js
new file mode 100644
index 0000000..632202f
--- /dev/null
+++ b/packages/backend/built/server/index.js
@@ -0,0 +1,177 @@
+/**
+ * Core Server
+ */ import cluster from "node:cluster";
+import * as fs from "node:fs";
+import * as http from "node:http";
+import * as https from "node:https";
+import Koa from "koa";
+import Router from "@koa/router";
+import cors from "@koa/cors";
+import mount from "koa-mount";
+import koaLogger from "koa-logger";
+import * as slow from "koa-slow";
+import { IsNull } from "typeorm";
+import config from "../config/index.js";
+import Logger from "../services/logger.js";
+import { Users } from "../models/index.js";
+import { fetchMeta } from "../misc/fetch-meta.js";
+import { genIdenticon } from "../misc/gen-identicon.js";
+import { createTemp } from "../misc/create-temp.js";
+import * as Acct from "../misc/acct.js";
+import { envOption } from "../env.js";
+import activityPub from "./activitypub.js";
+import nodeinfo from "./nodeinfo.js";
+import wellKnown from "./well-known.js";
+import apiServer from "./api/index.js";
+import fileServer from "./file/index.js";
+import proxyServer from "./proxy/index.js";
+import webServer from "./web/index.js";
+import { initializeStreamingServer } from "./api/streaming.js";
+import removeTrailingSlash from "koa-remove-trailing-slashes";
+import { koaBody } from "koa-body";
+import { setupEndpointsAuthRoot } from "./api/mastodon/endpoints/auth.js";
+import { CatchErrorsMiddleware } from "./api/mastodon/middleware/catch-errors.js";
+import { handleMetrics } from "../metrics.js";
+import { isIgnorableConnectionError } from "./is-ignorable-connection-error.js";
+export const serverLogger = new Logger("server", "gray", false);
+// Init app
+const app = new Koa();
+app.proxy = true;
+app.on("error", (err)=>{
+ if (isIgnorableConnectionError(err)) return;
+ serverLogger.error(err);
+});
+app.use(removeTrailingSlash());
+app.use(cors({
+ origin: "*"
+}));
+if (![
+ "production",
+ "test"
+].includes(process.env.NODE_ENV || "")) {
+ // Logger
+ app.use(koaLogger((str)=>{
+ serverLogger.info(str);
+ }));
+ // Delay
+ if (envOption.slow) {
+ app.use(slow({
+ delay: 3000
+ }));
+ }
+}
+// HSTS
+// 6months (15552000sec)
+if (config.url.startsWith("https") && !config.disableHsts) {
+ app.use(async (ctx, next)=>{
+ ctx.set("strict-transport-security", "max-age=15552000; preload");
+ await next();
+ });
+}
+app.use(mount("/api", apiServer));
+app.use(mount("/files", fileServer));
+app.use(mount("/proxy", proxyServer));
+// Init router
+const router = new Router();
+const mastoRouter = new Router();
+// Routing
+router.use(activityPub.routes());
+router.use(nodeinfo.routes());
+router.use(wellKnown.routes());
+router.get("/avatar/@:acct", async (ctx)=>{
+ const { username, host } = Acct.parse(ctx.params.acct);
+ const user = await Users.findOne({
+ where: {
+ usernameLower: username.toLowerCase(),
+ host: host == null || host === config.host || host === config.domain ? IsNull() : host,
+ isSuspended: false
+ },
+ relations: [
+ "avatar"
+ ]
+ });
+ if (user) {
+ ctx.redirect(Users.getAvatarUrlSync(user));
+ } else {
+ ctx.redirect("/static-assets/user-unknown.png");
+ }
+});
+router.get("/identicon/:x", async (ctx)=>{
+ const meta = await fetchMeta();
+ if (meta.enableIdenticonGeneration) {
+ const [temp, cleanup] = await createTemp();
+ await genIdenticon(ctx.params.x, fs.createWriteStream(temp));
+ ctx.set("Content-Type", "image/png");
+ ctx.body = fs.createReadStream(temp).on("close", ()=>cleanup());
+ } else {
+ ctx.redirect("/static-assets/avatar.png");
+ }
+});
+if (config.metrics?.enable) {
+ router.get("/metrics", handleMetrics);
+}
+mastoRouter.use(koaBody({
+ urlencoded: true,
+ multipart: true
+}));
+mastoRouter.use(async (ctx, next)=>{
+ if (ctx.request.query) {
+ if (!ctx.request.body || Object.keys(ctx.request.body).length === 0) {
+ ctx.request.body = ctx.request.query;
+ } else {
+ ctx.request.body = {
+ ...ctx.request.body,
+ ...ctx.request.query
+ };
+ }
+ }
+ await next();
+});
+mastoRouter.use(CatchErrorsMiddleware);
+setupEndpointsAuthRoot(mastoRouter);
+// Register router
+app.use(router.routes());
+app.use(mastoRouter.routes());
+app.use(mount(webServer));
+function createServer() {
+ if (config.tls?.keyPath && config.tls?.certPath) {
+ return https.createServer({
+ key: fs.readFileSync(config.tls.keyPath),
+ cert: fs.readFileSync(config.tls.certPath)
+ }, app.callback());
+ }
+ return http.createServer(app.callback());
+}
+// For testing
+export const startServer = ()=>{
+ const server = createServer();
+ initializeStreamingServer(server);
+ server.listen(config.port, config.listen);
+ return server;
+};
+export default (()=>new Promise((resolve)=>{
+ const server = createServer();
+ initializeStreamingServer(server);
+ server.on("error", (e)=>{
+ if (isIgnorableConnectionError(e)) return;
+ switch(e.code){
+ case "EACCES":
+ serverLogger.error(`You do not have permission to listen on port ${config.port}.`);
+ break;
+ case "EADDRINUSE":
+ serverLogger.error(`Port ${config.port} is already in use by another process.`);
+ break;
+ default:
+ serverLogger.error(e);
+ break;
+ }
+ if (cluster.isWorker) {
+ process.send("listenFailed");
+ } else {
+ // disableClustering
+ process.exit(1);
+ }
+ });
+ // @ts-ignore
+ server.listen(config.port, config.listen, resolve);
+ }));
diff --git a/packages/backend/built/server/is-ignorable-connection-error.js b/packages/backend/built/server/is-ignorable-connection-error.js
new file mode 100644
index 0000000..b7a2b08
--- /dev/null
+++ b/packages/backend/built/server/is-ignorable-connection-error.js
@@ -0,0 +1,4 @@
+export function isIgnorableConnectionError(err) {
+ const code = err?.code;
+ return code === "EPIPE" || code === "ECONNRESET" || code === "ERR_STREAM_DESTROYED";
+}
diff --git a/packages/backend/built/server/nodeinfo.js b/packages/backend/built/server/nodeinfo.js
new file mode 100644
index 0000000..3204750
--- /dev/null
+++ b/packages/backend/built/server/nodeinfo.js
@@ -0,0 +1,124 @@
+import Router from "@koa/router";
+import config from "../config/index.js";
+import { fetchMeta } from "../misc/fetch-meta.js";
+import { Users, Notes } from "../models/index.js";
+import { IsNull, MoreThan } from "typeorm";
+import { MAX_NOTE_TEXT_LENGTH, MAX_CAPTION_TEXT_LENGTH } from "../const.js";
+import { Cache } from "../misc/cache.js";
+const router = new Router();
+const nodeinfo2_1path = "/nodeinfo/2.1";
+const nodeinfo2_0path = "/nodeinfo/2.0";
+// to cleo: leave this http or bonks
+export const links = [
+ {
+ rel: "http://nodeinfo.diaspora.software/ns/schema/2.1",
+ href: config.url + nodeinfo2_1path
+ },
+ {
+ rel: "http://nodeinfo.diaspora.software/ns/schema/2.0",
+ href: config.url + nodeinfo2_0path
+ }
+];
+const nodeinfo2 = async ()=>{
+ const now = Date.now();
+ const [meta, total, activeHalfyear, activeMonth, localPosts] = await Promise.all([
+ fetchMeta(true),
+ Users.count({
+ where: {
+ host: IsNull()
+ }
+ }),
+ Users.count({
+ where: {
+ host: IsNull(),
+ lastActiveDate: MoreThan(new Date(now - 15552000000))
+ }
+ }),
+ Users.count({
+ where: {
+ host: IsNull(),
+ lastActiveDate: MoreThan(new Date(now - 2592000000))
+ }
+ }),
+ Notes.count({
+ where: {
+ userHost: IsNull()
+ }
+ })
+ ]);
+ return {
+ software: {
+ name: "iceshrimp",
+ version: config.version,
+ repository: meta.repositoryUrl,
+ homepage: "https://iceshrimp.dev/"
+ },
+ protocols: [
+ "activitypub"
+ ],
+ services: {
+ inbound: [],
+ outbound: [
+ "atom1.0",
+ "rss2.0"
+ ]
+ },
+ openRegistrations: !meta.disableRegistration,
+ usage: {
+ users: {
+ total,
+ activeHalfyear,
+ activeMonth
+ },
+ localPosts,
+ localComments: 0
+ },
+ metadata: {
+ nodeName: meta.name,
+ nodeDescription: meta.description,
+ maintainer: {
+ name: meta.maintainerName,
+ email: meta.maintainerEmail
+ },
+ langs: meta.langs,
+ tosUrl: meta.ToSUrl,
+ repositoryUrl: meta.repositoryUrl,
+ feedbackUrl: meta.feedbackUrl,
+ disableRegistration: meta.disableRegistration,
+ disableLocalTimeline: meta.disableLocalTimeline,
+ disableRecommendedTimeline: meta.disableRecommendedTimeline,
+ disableGlobalTimeline: meta.disableGlobalTimeline,
+ emailRequiredForSignup: meta.emailRequiredForSignup,
+ postEditing: true,
+ postImports: meta.experimentalFeatures?.postImports || false,
+ enableHcaptcha: meta.enableHcaptcha,
+ enableRecaptcha: meta.enableRecaptcha,
+ maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
+ maxCaptionTextLength: MAX_CAPTION_TEXT_LENGTH,
+ enableGithubIntegration: meta.enableGithubIntegration,
+ enableDiscordIntegration: meta.enableDiscordIntegration,
+ enableEmail: meta.enableEmail,
+ themeColor: meta.themeColor || "#31748f"
+ }
+ };
+};
+const cache = new Cache("nodeinfo", 60 * 10);
+router.get(nodeinfo2_1path, async (ctx)=>{
+ const base = await cache.fetch(null, ()=>nodeinfo2());
+ ctx.body = {
+ version: "2.1",
+ ...base
+ };
+ ctx.set("Cache-Control", "public, max-age=600");
+});
+router.get(nodeinfo2_0path, async (ctx)=>{
+ const base = await cache.fetch(null, ()=>nodeinfo2());
+ // @ts-ignore
+ base.software.repository = undefined;
+ ctx.body = {
+ version: "2.0",
+ ...base
+ };
+ ctx.set("Cache-Control", "public, max-age=600");
+});
+export default router;
diff --git a/packages/backend/built/server/proxy/index.js b/packages/backend/built/server/proxy/index.js
new file mode 100644
index 0000000..eaf2b68
--- /dev/null
+++ b/packages/backend/built/server/proxy/index.js
@@ -0,0 +1,25 @@
+/**
+ * Media Proxy
+ */ import Koa from "koa";
+import cors from "@koa/cors";
+import Router from "@koa/router";
+import { proxyMedia } from "./proxy-media.js";
+import { serverLogger } from "../index.js";
+import { isIgnorableConnectionError } from "../is-ignorable-connection-error.js";
+// Init app
+const app = new Koa();
+app.on("error", (err)=>{
+ if (isIgnorableConnectionError(err)) return;
+ serverLogger.error(err);
+});
+app.use(cors());
+app.use(async (ctx, next)=>{
+ ctx.set("Content-Security-Policy", `default-src 'none'; img-src 'self'; media-src 'self'; style-src 'unsafe-inline'`);
+ await next();
+});
+// Init router
+const router = new Router();
+router.get("/*url", proxyMedia);
+// Register router
+app.use(router.routes());
+export default app;
diff --git a/packages/backend/built/server/proxy/proxy-media.js b/packages/backend/built/server/proxy/proxy-media.js
new file mode 100644
index 0000000..cb437e4
--- /dev/null
+++ b/packages/backend/built/server/proxy/proxy-media.js
@@ -0,0 +1,115 @@
+import * as fs from "node:fs";
+import sharp from "sharp";
+import { convertToWebp } from "../../services/drive/image-processor.js";
+import { createTemp } from "../../misc/create-temp.js";
+import { downloadUrl } from "../../misc/download-url.js";
+import { detectType } from "../../misc/get-file-info.js";
+import { StatusError } from "../../misc/fetch.js";
+import { FILE_TYPE_BROWSERSAFE, MINUTE } from "../../const.js";
+import { serverLogger } from "../index.js";
+import { isMimeImage } from "../../misc/is-mime-image.js";
+import { getIpHash } from "../../misc/get-ip-hash.js";
+import { limiter } from "../api/limiter.js";
+import { isIgnorableConnectionError } from "../is-ignorable-connection-error.js";
+export async function proxyMedia(ctx) {
+ const url = "url" in ctx.query ? ctx.query.url : `https://${ctx.params.url}`;
+ if (typeof url !== "string") {
+ ctx.status = 400;
+ return;
+ }
+ // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
+ let limitActor;
+ limitActor = getIpHash(ctx.ip);
+ const parsedUrl = new URL(url);
+ const limit = {
+ key: `media-proxy:${parsedUrl.host}:${parsedUrl.pathname}`,
+ duration: MINUTE * 10,
+ max: 10
+ };
+ // Rate limit
+ await limiter(limit, limitActor).catch((e)=>{
+ const remainingTime = e.remainingTime ? `Please try again in ${e.remainingTime}.` : "Please try again later.";
+ ctx.status = 429;
+ ctx.body = "Rate limit exceeded. " + remainingTime;
+ });
+ if (ctx.status == 429) return;
+ // Create temp file
+ const [path, cleanup] = await createTemp();
+ try {
+ await downloadUrl(url, path);
+ const { mime, ext } = await detectType(path);
+ const isConvertibleImage = isMimeImage(mime, "sharp-convertible-image");
+ let image;
+ if ("glyph" in ctx.query) {
+ if (mime !== "image/svg+xml") {
+ throw new StatusError("Unexpected mime", 404);
+ }
+ ctx.set("Content-Type", "text/plain; charset=utf-8");
+ ctx.set("Cache-Control", "max-age=31536000, immutable");
+ ctx.body = fs.readFileSync(path, "utf8");
+ return;
+ } else if ("static" in ctx.query && isConvertibleImage) {
+ image = await convertToWebp(path, 996, 560);
+ } else if ("preview" in ctx.query && isConvertibleImage) {
+ image = await convertToWebp(path, 400, 400);
+ } else if ("badge" in ctx.query) {
+ if (!isConvertibleImage) {
+ // 画像でないなら404でお茶を濁す
+ throw new StatusError("Unexpected mime", 404);
+ }
+ const mask = sharp(path).resize(96, 96, {
+ fit: "inside",
+ withoutEnlargement: false
+ }).greyscale().normalise().linear(1.75, -128 * 1.75 + 128) // 1.75x contrast
+ .flatten({
+ background: "#000"
+ }).toColorspace("b-w");
+ const stats = await mask.clone().stats();
+ if (stats.entropy < 0.1) {
+ // エントロピーがあまりない場合は404にする
+ throw new StatusError("Skip to provide badge", 404);
+ }
+ const data = sharp({
+ create: {
+ width: 96,
+ height: 96,
+ channels: 4,
+ background: {
+ r: 0,
+ g: 0,
+ b: 0,
+ alpha: 0
+ }
+ }
+ }).pipelineColorspace("b-w").boolean(await mask.png().toBuffer(), "eor");
+ image = {
+ data: await data.png().toBuffer(),
+ ext: "png",
+ type: "image/png"
+ };
+ } else if (mime === "image/svg+xml") {
+ image = await convertToWebp(path, 2048, 2048, 1);
+ } else if (!(mime.startsWith("image/") && FILE_TYPE_BROWSERSAFE.includes(mime))) {
+ throw new StatusError("Rejected type", 403, "Rejected type");
+ } else {
+ image = {
+ data: fs.readFileSync(path),
+ ext,
+ type: mime
+ };
+ }
+ ctx.set("Content-Type", image.type);
+ ctx.set("Cache-Control", "max-age=31536000, immutable");
+ ctx.body = image.data;
+ } catch (e) {
+ if (isIgnorableConnectionError(e)) return;
+ serverLogger.error(`${e}`);
+ if (e instanceof StatusError && (e.statusCode === 302 || e.isClientError)) {
+ ctx.status = e.statusCode;
+ } else {
+ ctx.status = 500;
+ }
+ } finally{
+ cleanup();
+ }
+}
diff --git a/packages/backend/built/server/web/bios.css b/packages/backend/built/server/web/bios.css
new file mode 100644
index 0000000..567a59a
--- /dev/null
+++ b/packages/backend/built/server/web/bios.css
@@ -0,0 +1 @@
+main>.tabs{padding:16px;border-bottom:4px solid #908caa}#lsEditor>.adder{margin:16px;padding:16px;border:2px solid #908caa}#lsEditor>.adder>textarea{display:block;width:100%;min-height:5em;box-sizing:border-box}#lsEditor>.record{padding:16px;border-bottom:1px solid #908caa}#lsEditor>.record>header{font-weight:700}#lsEditor>.record>textarea{display:block;width:100%;min-height:5em;box-sizing:border-box}html{background:#191724}main{background:#1f1d2e;border-radius:10px}#tl>div{padding:16px;border-bottom:1px solid #908caa}#tl>div>header{font-weight:700}*{font-family:BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif}#iceshrimp_app{display:none!important}body,html{background-color:#191724;color:#e0def4;justify-content:center;margin:auto;padding:10px;text-align:center}button{border-radius:999px;border:none;cursor:pointer;margin-bottom:12px;background:linear-gradient(-45deg,#9ccfd8,#31748f);line-height:50px;color:#191724;font-weight:700;font-size:20px;padding:12px}button{border-radius:999px;padding:0 12px;border:none;cursor:pointer;margin-bottom:12px}button{background:#444;line-height:40px;color:#9ccfd8;font-size:16px;padding:0 20px;margin-right:5px;margin-left:5px}button:hover{background:#555}#ls{background:linear-gradient(-45deg,#9ccfd8,#31748f);line-height:30px;color:#191724;font-weight:700;font-size:18px;padding:12px}#ls:hover{background:#9ccfd8}a{color:#9ccfd8;text-decoration:none}p,li{font-size:16px}h1{font-size:32px}code{font-family:Fira,FiraCode,monospace}textarea{background-color:#444;border:solid #aaa;border-radius:10px;color:#e0def4;margin-top:1rem;margin-bottom:1rem;width:20rem;height:7.5rem;padding:.5rem}textarea:focus{border:solid #eee}input{background-color:#666;border:solid #aaa;border-radius:10px;color:#e0def4;margin-top:1rem;margin-bottom:1rem;width:10rem;height:1rem;padding:.5rem}input:focus{border:solid #eee}
diff --git a/packages/backend/built/server/web/bios.js b/packages/backend/built/server/web/bios.js
new file mode 100644
index 0000000..1004669
--- /dev/null
+++ b/packages/backend/built/server/web/bios.js
@@ -0,0 +1 @@
+"use strict";window.onload=async()=>{const m=JSON.parse(localStorage.getItem("account")).token,E=(e,t={})=>new Promise((d,a)=>{m&&(t.i=m),fetch(e.indexOf("://")>-1?e:`/api/${e}`,{method:"POST",body:JSON.stringify(t),credentials:"omit",cache:"no-cache"}).then(async n=>{const o=n.status===204?null:await n.json();n.status===200?d(o):n.status===204?d():a(o.error)}).catch(a)}),u=document.getElementById("content");document.getElementById("ls").addEventListener("click",()=>{u.innerHTML="";const e=document.createElement("div");e.id="lsEditor";const t=document.createElement("div");t.classList.add("adder");const l=document.createElement("input"),d=document.createElement("textarea"),a=document.createElement("button");a.textContent="Add",a.addEventListener("click",()=>{localStorage.setItem(l.value,d.value),location.reload()}),t.appendChild(l),t.appendChild(d),t.appendChild(a),e.appendChild(t);for(let n=0;n{localStorage.setItem(o,r.value),location.reload()});const s=document.createElement("button");s.textContent="Remove",s.addEventListener("click",()=>{localStorage.removeItem(o),location.reload()}),c.appendChild(p),c.appendChild(r),c.appendChild(i),c.appendChild(s),e.appendChild(c)}u.appendChild(e)})};
diff --git a/packages/backend/built/server/web/boot.js b/packages/backend/built/server/web/boot.js
new file mode 100644
index 0000000..4e88735
--- /dev/null
+++ b/packages/backend/built/server/web/boot.js
@@ -0,0 +1,149 @@
+"use strict";var b=["ar-SA","bn-BD","bul-BG","ca-ES","cs-CZ","da-DK","de-DE","el-GR","en-US","es-ES","et","fi","fil","fr-FR","gl","id-ID","it-IT","ja-JP","ja-KS","kab-KAB","kn-IN","ko-KR","nl-NL","no-NO","pl-PL","pt-BR","pt-PT","ro-RO","ru-RU","sk-SK","sv-SE","th-TH","tr-TR","ug-CN","uk-UA","vi-VN","zh-CN","zh-TW"];(async()=>{window.onerror=e=>{console.error(e),a("SOMETHING_HAPPENED",e)},window.onunhandledrejection=e=>{console.error(e),a("SOMETHING_HAPPENED_IN_PROMISE",e)};const f=localStorage.getItem("v"),s=VERSION;f!==VERSION&&localStorage.setItem("v",VERSION);const l=b;let o=localStorage.getItem("lang");(o==null||!l.includes(o))&&(l.includes(navigator.language)?o=navigator.language:(o=l.find(e=>e.split("-")[0]===navigator.language),o==null&&(o="en-US")));const c=await fetch(`/assets/locales/${o}.${s}.json`);if(c.status===200)localStorage.setItem("lang",o),localStorage.setItem("locale",await c.text()),localStorage.setItem("localeVersion",s);else{await p(),a("LOCALE_FETCH");return}function d(){import(`/assets/${CLIENT_ENTRY}`).catch(async e=>{await p(),console.error(e),a("APP_IMPORT",e)})}document.readyState!=="loading"?d():window.addEventListener("DOMContentLoaded",()=>{d()});const m=localStorage.getItem("theme");if(m){for(const[e,t]of Object.entries(JSON.parse(m)))if(document.documentElement.style.setProperty(`--${e}`,t.toString()),e==="htmlThemeColor"){for(const n of document.head.children)if(n.tagName==="META"&&n.getAttribute("name")==="theme-color"){n.setAttribute("content",t);break}}}const u=localStorage.getItem("colorSchema");u&&document.documentElement.style.setProperty("color-schema",u);let r=localStorage.getItem("fontSize");r&&(r<10&&(localStorage.setItem("fontSize",null),r=localStorage.getItem("fontSize")),document.documentElement.style.fontSize=`${r}px`),["ja-JP","ja-KS","ko-KR","zh-CN","zh-TW"].includes(o)&&document.documentElement.classList.add("useCJKFont"),localStorage.getItem("useSystemFont")&&document.documentElement.classList.add("useSystemFont");const g=localStorage.getItem("wallpaper");g&&(document.documentElement.style.backgroundImage=`url(${g})`);const i=localStorage.getItem("customCss");if(i&&i.length>0){const e=document.createElement("style");e.innerHTML=i,document.head.appendChild(e)}async function S(e){const t=document.createElement("style");t.appendChild(document.createTextNode(e)),document.head.appendChild(t)}function a(e,t){let n=document.getElementById("errors");n||(document.body.innerHTML=`
+
+ An error has occurred!
+
+ Don't worry, it's (probably) not your fault.
+ Please make sure your browser is up-to-date.
+ While we don't serve ads, ad-blockers might interfere with this application,
so disabling them might also fix this issue.
+ If the problem persists after refreshing, please contact your instance's administrator.
You may also try the following options:
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,n=document.getElementById("errors"));const h=document.createElement("details");h.innerHTML=`
+
+
+ ERROR CODE: ${e}
+
+ ${JSON.stringify(t)}`,n.appendChild(h),S(`
+ * {
+ font-family: Roboto,HelveticaNeue,Arial,sans-serif;
+ }
+
+ #iceshrimp_app,
+ #splash {
+ display: none !important;
+ }
+
+ body,
+ html {
+ background-color: #3b364c;
+ color: rgb(231, 237, 255);
+ justify-content: center;
+ margin: auto;
+ padding: 10px;
+ text-align: center;
+ }
+
+ button {
+ border-radius: 999px;
+ padding: 0px 12px 0px 12px;
+ border: none;
+ cursor: pointer;
+ margin-bottom: 12px;
+ }
+
+ .button-big {
+ background: linear-gradient(-45deg, rgb(154, 146, 255), rgb(131, 114, 245));
+ line-height: 50px;
+ }
+
+ .button-big:hover {
+ background: rgb(201, 197, 255);
+ }
+
+ .button-small {
+ background: #544d77;
+ line-height: 40px;
+ }
+
+ .button-small:hover {
+ background: #504967;
+ }
+
+ .button-label-big {
+ color: #3b364c;
+ font-weight: bold;
+ font-size: 2em;
+ padding: 12px;
+ }
+
+ .button-label-small {
+ color: rgb(231, 237, 255);
+ font-size: 16px;
+ padding: 12px;
+ }
+
+ a {
+ color: rgb(255, 123, 114);
+ text-decoration: none;
+ }
+
+ p,
+ li {
+ font-size: 16px;
+ }
+
+ .dont-worry,
+ #msg {
+ font-size: 18px;
+ }
+
+ .icon-warning {
+ color: rgb(236, 182, 55);
+ height: 4rem;
+ padding-top: 2rem;
+ }
+
+ h1 {
+ font-size: 32px;
+ }
+
+ code {
+ font-family: Fira, FiraCode, monospace;
+ }
+
+ details {
+ background: #423c55;
+ margin-bottom: 2rem;
+ padding: 0.5rem 1rem;
+ width: 40rem;
+ border-radius: 10px;
+ justify-content: center;
+ margin: auto;
+ }
+
+ summary {
+ cursor: pointer;
+ }
+
+ summary > * {
+ display: inline;
+ }
+
+ @media screen and (max-width: 500px) {
+ details {
+ width: 50%;
+ }
+ `)}async function p(){try{const t=await(await fetch("/api/meta",{method:"POST",cache:"no-cache"})).json();t.version!=s&&(localStorage.setItem("v",t.version),y())}catch(e){throw console.error(e),a("UPDATE_CHECK",e),e}}function y(){try{navigator.serviceWorker.controller.postMessage("clear"),navigator.serviceWorker.getRegistrations().then(e=>{e.forEach(t=>t.unregister())})}catch(e){console.error(e)}location.reload()}})();
diff --git a/packages/backend/built/server/web/cli.css b/packages/backend/built/server/web/cli.css
new file mode 100644
index 0000000..d2a5115
--- /dev/null
+++ b/packages/backend/built/server/web/cli.css
@@ -0,0 +1 @@
+html{background:#191724}main{background:#1f1d2e;border-radius:10px}#tl>div{border:1px solid #908caa;border-radius:10px;margin:10px;padding:10px;width:fit-content}#tl>div>header{font-weight:700;display:inline-flex}img{border-radius:10px;margin-right:10px}#form{text-align:center}#iceshrimp_app{display:none!important}body,html{font-family:BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;background-color:#191724;color:#e0def4;justify-content:center;margin:auto;padding:10px}button{border-radius:999px;padding:0 40px;margin-top:1rem;border:none;cursor:pointer;margin-bottom:12px;background:linear-gradient(-45deg,#9ccfd8,#31748f);line-height:50px;color:#191724;font-weight:700;font-size:20px}button:hover{background:#9ccfd8}a{color:#9ccfd8;text-decoration:none}p,li{font-size:16px}h1{font-size:32px}code{font-family:Fira,FiraCode,monospace}#text{background-color:#444;border:solid #aaa;border-radius:10px;color:#e0def4;margin-top:3rem;width:20rem;height:5rem;padding:.5rem}#text:focus{border:solid #eee}@media screen and (max-width:500px){#text{width:80%}}
diff --git a/packages/backend/built/server/web/cli.js b/packages/backend/built/server/web/cli.js
new file mode 100644
index 0000000..d757c1c
--- /dev/null
+++ b/packages/backend/built/server/web/cli.js
@@ -0,0 +1 @@
+"use strict";window.onload=async()=>{const i=JSON.parse(localStorage.getItem("account")).token,l=(a,s={})=>new Promise((t,c)=>{i&&(s.i=i),fetch(a.indexOf("://")>-1?a:`/api/${a}`,{method:"POST",body:JSON.stringify(s),credentials:"omit",cache:"no-cache"}).then(async n=>{const o=n.status===204?null:await n.json();n.status===200?t(o):n.status===204?t():c(o.error)}).catch(c)});document.getElementById("submit").addEventListener("click",()=>{l("notes/create",{text:document.getElementById("text").value}).then(()=>{location.reload()})}),l("notes/timeline").then(a=>{const s=document.getElementById("tl");for(const e of a){const t=document.createElement("div"),c=document.createElement("header"),n=document.createElement("p"),o=document.createElement("img");n.textContent=`${e.user.name} @${e.user.username}`,o.src=e.user.avatarUrl,o.style="height: 40px";const d=document.createElement("div");if(d.textContent=`${e.text}`,t.appendChild(c),c.appendChild(o),c.appendChild(n),e.text&&t.appendChild(d),e.files)for(const r of e.files){const m=document.createElement("img");m.src=r.properties.thumbnailUrl,t.appendChild(m)}s.appendChild(t)}})};
diff --git a/packages/backend/built/server/web/feed.js b/packages/backend/built/server/web/feed.js
new file mode 100644
index 0000000..923fc7d
--- /dev/null
+++ b/packages/backend/built/server/web/feed.js
@@ -0,0 +1,130 @@
+import { Feed } from "feed";
+import { In, IsNull } from "typeorm";
+import config from "../../config/index.js";
+import { Notes, DriveFiles, UserProfiles, Users } from "../../models/index.js";
+export default async function(user, threadDepth = 5, history = 20, noteintitle = false, renotes = true, replies = true) {
+ const author = {
+ link: `${config.url}/@${user.username}`,
+ email: `${user.username}@${config.domain}`,
+ name: user.name || user.username
+ };
+ const profile = await UserProfiles.findOneByOrFail({
+ userId: user.id
+ });
+ const searchCriteria = {
+ userId: user.id,
+ visibility: In([
+ "public",
+ "home"
+ ])
+ };
+ if (!renotes) {
+ searchCriteria.renoteId = IsNull();
+ }
+ if (!replies) {
+ searchCriteria.replyId = IsNull();
+ }
+ const notes = await Notes.find({
+ where: searchCriteria,
+ order: {
+ createdAt: -1
+ },
+ take: history
+ });
+ const feed = new Feed({
+ id: author.link,
+ title: `${author.name} (@${user.username}@${config.domain})`,
+ updated: notes[0].createdAt,
+ generator: "FrozenFriendsYume",
+ description: `${user.notesCount} Notes, ${profile.ffVisibility === "public" ? user.followingCount : "?"} Following, ${profile.ffVisibility === "public" ? user.followersCount : "?"} Followers${profile.description ? ` · ${profile.description}` : ""}`,
+ link: author.link,
+ image: await Users.getAvatarUrl(user),
+ feedLinks: {
+ json: `${author.link}.json`,
+ atom: `${author.link}.atom`
+ },
+ author,
+ copyright: user.name || user.username
+ });
+ for (const note of notes){
+ let contentStr = await noteToString(note, true);
+ let next = note.renoteId ? note.renoteId : note.replyId;
+ let depth = threadDepth;
+ while(depth > 0 && next){
+ const finding = await findById(next);
+ contentStr += finding.text;
+ next = finding.next;
+ depth -= 1;
+ }
+ let title = `${author.name} `;
+ if (note.renoteId) {
+ title += "renotes";
+ } else if (note.replyId) {
+ title += "replies";
+ } else {
+ title += "says";
+ }
+ if (noteintitle) {
+ const content = note.cw ?? note.text;
+ if (content) {
+ title += `: ${content}`;
+ } else {
+ title += "something";
+ }
+ }
+ feed.addItem({
+ title: title.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "").substring(0, 100),
+ link: `${config.url}/notes/${note.id}`,
+ date: note.createdAt,
+ description: note.cw ? note.cw.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "") : undefined,
+ content: contentStr.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "")
+ });
+ }
+ async function noteToString(note, isTheNote = false) {
+ const author = isTheNote ? null : await Users.findOneBy({
+ id: note.userId
+ });
+ let outstr = author ? `${author.name}(@${author.username}@${author.host ? author.host : config.host}) ${note.renoteId ? "renotes" : note.replyId ? "replies" : "says"}:
` : "";
+ const files = note.fileIds.length > 0 ? await DriveFiles.findBy({
+ id: In(note.fileIds)
+ }) : [];
+ let fileEle = "";
+ for (const file of files){
+ if (file.type.startsWith("image/")) {
+ fileEle += `
`;
+ } else if (file.type.startsWith("audio/")) {
+ fileEle += `