Fixed 267U.pre2
This commit is contained in:
@@ -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)));
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -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)
|
||||
} : {}
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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)));
|
||||
}
|
||||
});
|
||||
@@ -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)));
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
} : {}
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -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)));
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
})
|
||||
} : {}
|
||||
} : {}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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)));
|
||||
}
|
||||
});
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -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)));
|
||||
}
|
||||
});
|
||||
@@ -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)));
|
||||
}
|
||||
});
|
||||
@@ -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)));
|
||||
}
|
||||
});
|
||||
@@ -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)));
|
||||
}
|
||||
});
|
||||
@@ -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)));
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -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)));
|
||||
}
|
||||
});
|
||||
@@ -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)));
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
] : []));
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
] : []);
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
] : []);
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
@@ -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)));
|
||||
}
|
||||
});
|
||||
@@ -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)));
|
||||
}
|
||||
});
|
||||
@@ -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)));
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
import { db } from "../../db/postgre.js";
|
||||
import { Relay } from "../entities/relay.js";
|
||||
export const RelayRepository = db.getRepository(Relay).extend({});
|
||||
@@ -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)));
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
@@ -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)));
|
||||
}
|
||||
});
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
@@ -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)));
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user