12.109.1-nca10.net-v1

This commit is contained in:
nullnyat
2022-04-02 17:20:22 +09:00
parent 483c97e224
commit 2f621ceb7a
439 changed files with 4682 additions and 3360 deletions

View File

@ -6,7 +6,7 @@ import cluster from 'node:cluster';
import chalk from 'chalk';
import chalkTemplate from 'chalk-template';
import * as portscanner from 'portscanner';
import { getConnection } from 'typeorm';
import semver from 'semver';
import Logger from '@/services/logger.js';
import loadConfig from '@/config/load.js';
@ -14,7 +14,7 @@ import { Config } from '@/config/types.js';
import { lessThan } from '@/prelude/array.js';
import { envOption } from '../env.js';
import { showMachineInfo } from '@/misc/show-machine-info.js';
import { initDb } from '../db/postgre.js';
import { db, initDb } from '../db/postgre.js';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
@ -88,10 +88,6 @@ export async function masterMain() {
}
}
const runningNodejsVersion = process.version.slice(1).split('.').map(x => parseInt(x, 10));
const requiredNodejsVersion = [11, 7, 0];
const satisfyNodejsVersion = !lessThan(runningNodejsVersion, requiredNodejsVersion);
function showEnvironment(): void {
const env = process.env.NODE_ENV;
const logger = bootLogger.createSubLogger('env');
@ -108,10 +104,11 @@ function showEnvironment(): void {
function showNodejsVersion(): void {
const nodejsLogger = bootLogger.createSubLogger('nodejs');
nodejsLogger.info(`Version ${runningNodejsVersion.join('.')}`);
nodejsLogger.info(`Version ${process.version} detected.`);
if (!satisfyNodejsVersion) {
nodejsLogger.error(`Node.js version is less than ${requiredNodejsVersion.join('.')}. Please upgrade it.`, null, true);
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);
}
}
@ -146,7 +143,7 @@ async function connectDb(): Promise<void> {
try {
dbLogger.info('Connecting...');
await initDb();
const v = await getConnection().query('SHOW server_version').then(x => x[0].server_version);
const v = await db.query('SHOW server_version').then(x => x[0].server_version);
dbLogger.succ(`Connected: v${v}`);
} catch (e) {
dbLogger.error('Cannot connect', null, true);

View File

@ -2,9 +2,10 @@
import pg from 'pg';
pg.types.setTypeParser(20, Number);
import { createConnection, Logger, getConnection } from 'typeorm';
import { Logger, DataSource } from 'typeorm';
import * as highlight from 'cli-highlight';
import config from '@/config/index.js';
import { envOption } from '../env.js';
import { dbLogger } from './logger.js';
@ -61,7 +62,6 @@ import { Antenna } from '@/models/entities/antenna.js';
import { AntennaNote } from '@/models/entities/antenna-note.js';
import { PromoNote } from '@/models/entities/promo-note.js';
import { PromoRead } from '@/models/entities/promo-read.js';
import { envOption } from '../env.js';
import { Relay } from '@/models/entities/relay.js';
import { MutedNote } from '@/models/entities/muted-note.js';
import { Channel } from '@/models/entities/channel.js';
@ -73,8 +73,9 @@ import { PasswordResetRequest } from '@/models/entities/password-reset-request.j
import { UserPending } from '@/models/entities/user-pending.js';
import { entities as charts } from '@/services/chart/entities.js';
import { Webhook } from '@/models/entities/webhook.js';
const sqlLogger = dbLogger.createSubLogger('sql', 'white', false);
const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false);
class MyCustomLogger implements Logger {
private highlight(sql: string) {
@ -84,9 +85,7 @@ class MyCustomLogger implements Logger {
}
public logQuery(query: string, parameters?: any[]) {
if (envOption.verbose) {
sqlLogger.info(this.highlight(query));
}
sqlLogger.info(this.highlight(query).substring(0, 100));
}
public logQueryError(error: string, query: string, parameters?: any[]) {
@ -173,58 +172,55 @@ export const entities = [
Ad,
PasswordResetRequest,
UserPending,
Webhook,
...charts,
];
export function initDb(justBorrow = false, sync = false, forceRecreate = false) {
if (!forceRecreate) {
try {
const conn = getConnection();
return Promise.resolve(conn);
} catch (e) {}
}
const log = process.env.NODE_ENV !== 'production';
const log = process.env.NODE_ENV !== 'production';
return createConnection({
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,
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: 'redis',
options: {
host: config.redis.host,
port: config.redis.port,
password: config.redis.pass,
prefix: `${config.redis.prefix}:query:`,
db: config.redis.db || 0,
},
synchronize: process.env.NODE_ENV === 'test' || sync,
dropSchema: process.env.NODE_ENV === 'test' && !justBorrow,
cache: !config.db.disableCache ? {
type: 'redis',
options: {
host: config.redis.host,
port: config.redis.port,
password: config.redis.pass,
prefix: `${config.redis.prefix}:query:`,
db: config.redis.db || 0,
},
} : false,
logging: log,
logger: log ? new MyCustomLogger() : undefined,
entities: entities,
});
} : false,
logging: log,
logger: log ? new MyCustomLogger() : undefined,
maxQueryExecutionTime: 300,
entities: entities,
migrations: ['../../migration/*.js'],
});
export async function initDb() {
await db.connect();
}
export async function resetDb() {
const reset = async () => {
const conn = await getConnection();
const tables = await conn.query(`SELECT relname AS "table"
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 conn.query(`DELETE FROM "${table.table}" CASCADE`);
await db.query(`DELETE FROM "${table.table}" CASCADE`);
}
};

View File

@ -1,5 +1,5 @@
export class Cache<T> {
private cache: Map<string | null, { date: number; value: T; }>;
public cache: Map<string | null, { date: number; value: T; }>;
private lifetime: number;
constructor(lifetime: Cache<never>['lifetime']) {
@ -28,16 +28,52 @@ export class Cache<T> {
this.cache.delete(key);
}
public async fetch(key: string | null, fetcher: () => Promise<T>): Promise<T> {
/**
* キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
*/
public async fetch(key: string | null, fetcher: () => Promise<T>, validator?: (cachedValue: T) => boolean): Promise<T> {
const cachedValue = this.get(key);
if (cachedValue !== undefined) {
// Cache HIT
return cachedValue;
if (validator) {
if (validator(cachedValue)) {
// Cache HIT
return cachedValue;
}
} else {
// Cache HIT
return cachedValue;
}
}
// Cache MISS
const value = await fetcher();
this.set(key, value);
return value;
}
/**
* キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
*/
public async fetchMaybe(key: string | null, fetcher: () => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> {
const cachedValue = this.get(key);
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) {
this.set(key, value);
}
return value;
}
}

View File

@ -1,17 +1,26 @@
import { Antenna } from '@/models/entities/antenna.js';
import { Note } from '@/models/entities/note.js';
import { User } from '@/models/entities/user.js';
import { UserListJoinings, UserGroupJoinings } from '@/models/index.js';
import { UserListJoinings, UserGroupJoinings, Blockings } from '@/models/index.js';
import { getFullApAccount } from './convert-host.js';
import * as Acct from '@/misc/acct.js';
import { Packed } from './schema.js';
import { Cache } from './cache.js';
const blockingCache = new Cache<User['id'][]>(1000 * 60 * 5);
// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている
/**
* noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい
*/
export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise<boolean> {
export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise<boolean> {
if (note.visibility === 'specified') 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;
@ -23,15 +32,15 @@ export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'No
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.find({
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.findOneOrFail(antenna.userGroupJoiningId!);
const joining = await UserGroupJoinings.findOneByOrFail({ id: antenna.userGroupJoiningId! });
const groupUsers = (await UserGroupJoinings.find({
const groupUsers = (await UserGroupJoinings.findBy({
userGroupId: joining.userGroupId,
})).map(x => x.userId);

View File

@ -1,19 +1,21 @@
import { db } from '@/db/postgre.js';
import { Meta } from '@/models/entities/meta.js';
import { getConnection } from 'typeorm';
let cache: Meta;
export async function fetchMeta(noCache = false): Promise<Meta> {
if (!noCache && cache) return cache;
return await getConnection().transaction(async transactionalEntityManager => {
return await db.transaction(async transactionalEntityManager => {
// 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する
const meta = await transactionalEntityManager.findOne(Meta, {
const metas = await transactionalEntityManager.find(Meta, {
order: {
id: 'DESC',
},
});
const meta = metas[0];
if (meta) {
cache = meta;
return meta;

View File

@ -5,5 +5,5 @@ import { Users } from '@/models/index.js';
export async function fetchProxyAccount(): Promise<ILocalUser | null> {
const meta = await fetchMeta();
if (meta.proxyAccountId == null) return null;
return await Users.findOneOrFail(meta.proxyAccountId) as ILocalUser;
return await Users.findOneByOrFail({ id: meta.proxyAccountId }) as ILocalUser;
}

View File

@ -6,5 +6,5 @@ import { Cache } from './cache.js';
const cache = new Cache<UserKeypair>(Infinity);
export async function getUserKeypair(userId: User['id']): Promise<UserKeypair> {
return await cache.fetch(userId, () => UserKeypairs.findOneOrFail(userId));
return await cache.fetch(userId, () => UserKeypairs.findOneByOrFail({ userId: userId }));
}

View File

@ -1,4 +1,4 @@
import { In } from 'typeorm';
import { In, IsNull } from 'typeorm';
import { Emojis } from '@/models/index.js';
import { Emoji } from '@/models/entities/emoji.js';
import { Note } from '@/models/entities/note.js';
@ -52,9 +52,9 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu
const { name, host } = parseEmojiStr(emojiName, noteUserHost);
if (name == null) return null;
const queryOrNull = async () => (await Emojis.findOne({
const queryOrNull = async () => (await Emojis.findOneBy({
name,
host,
host: host ?? IsNull(),
})) || null;
const emoji = await cache.fetch(`${name} ${host}`, queryOrNull);
@ -112,7 +112,7 @@ export async function prefetchEmojis(emojis: { name: string; host: string | null
for (const host of hosts) {
emojisQuery.push({
name: In(notCachedEmojis.filter(e => e.host === host).map(e => e.name)),
host: host,
host: host ?? IsNull(),
});
}
const _emojis = emojisQuery.length > 0 ? await Emojis.find({

View File

@ -3,6 +3,7 @@ import { emojiRegex } from './emoji-regex.js';
import { fetchMeta } from './fetch-meta.js';
import { Emojis } from '@/models/index.js';
import { toPunyNullable } from './convert-host.js';
import { IsNull } from 'typeorm';
const legacies: Record<string, string> = {
'like': '👍',
@ -74,8 +75,8 @@ export async function toDbReaction(reaction?: string | null, reacterHost?: strin
const custom = reaction.match(/^:([\w+-]+)(?:@\.)?:$/);
if (custom) {
const name = custom[1];
const emoji = await Emojis.findOne({
host: reacterHost || null,
const emoji = await Emojis.findOneBy({
host: reacterHost ?? IsNull(),
name,
});

View File

@ -0,0 +1,49 @@
import { Webhooks } from '@/models/index.js';
import { Webhook } from '@/models/entities/webhook.js';
import { subsdcriber } from '../db/redis.js';
let webhooksFetched = false;
let webhooks: Webhook[] = [];
export async function getActiveWebhooks() {
if (!webhooksFetched) {
webhooks = await Webhooks.findBy({
active: true,
});
webhooksFetched = true;
}
return webhooks;
}
subsdcriber.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;
}
}
});

View File

@ -234,3 +234,9 @@ export interface ILocalUser extends User {
export interface IRemoteUser extends User {
host: string;
}
export type CacheableLocalUser = ILocalUser;
export type CacheableRemoteUser = IRemoteUser;
export type CacheableUser = CacheableLocalUser | CacheableRemoteUser;

View File

@ -0,0 +1,73 @@
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'] as const;
@Entity()
export class Webhook {
@PrimaryColumn(id())
public id: string;
@Column('timestamp with time zone', {
comment: 'The created date of the Antenna.',
})
public createdAt: Date;
@Index()
@Column({
...id(),
comment: 'The owner ID.',
})
public userId: User['id'];
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user: User | null;
@Column('varchar', {
length: 128,
comment: 'The name of the Antenna.',
})
public name: string;
@Index()
@Column('varchar', {
length: 128, array: true, default: '{}',
})
public on: (typeof webhookEventTypes)[number][];
@Column('varchar', {
length: 1024,
})
public url: string;
@Column('varchar', {
length: 1024,
})
public secret: string;
@Index()
@Column('boolean', {
default: true,
})
public active: boolean;
/**
* 直近のリクエスト送信日時
*/
@Column('timestamp with time zone', {
nullable: true,
})
public latestSentAt: Date | null;
/**
* 直近のリクエスト送信時のHTTPステータスコード
*/
@Column('integer', {
nullable: true,
})
public latestStatus: number | null;
}

View File

@ -1,4 +1,6 @@
import { getRepository, getCustomRepository } from 'typeorm';
import { } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Announcement } from './entities/announcement.js';
import { AnnouncementRead } from './entities/announcement-read.js';
import { Instance } from './entities/instance.js';
@ -62,66 +64,68 @@ import { Ad } from './entities/ad.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';
export const Announcements = getRepository(Announcement);
export const AnnouncementReads = getRepository(AnnouncementRead);
export const Apps = getCustomRepository(AppRepository);
export const Notes = getCustomRepository(NoteRepository);
export const NoteFavorites = getCustomRepository(NoteFavoriteRepository);
export const NoteWatchings = getRepository(NoteWatching);
export const NoteThreadMutings = getRepository(NoteThreadMuting);
export const NoteReactions = getCustomRepository(NoteReactionRepository);
export const NoteUnreads = getRepository(NoteUnread);
export const Polls = getRepository(Poll);
export const PollVotes = getRepository(PollVote);
export const Users = getCustomRepository(UserRepository);
export const UserProfiles = getRepository(UserProfile);
export const UserKeypairs = getRepository(UserKeypair);
export const UserPendings = getRepository(UserPending);
export const AttestationChallenges = getRepository(AttestationChallenge);
export const UserSecurityKeys = getRepository(UserSecurityKey);
export const UserPublickeys = getRepository(UserPublickey);
export const UserLists = getCustomRepository(UserListRepository);
export const UserListJoinings = getRepository(UserListJoining);
export const UserGroups = getCustomRepository(UserGroupRepository);
export const UserGroupJoinings = getRepository(UserGroupJoining);
export const UserGroupInvitations = getCustomRepository(UserGroupInvitationRepository);
export const UserNotePinings = getRepository(UserNotePining);
export const UsedUsernames = getRepository(UsedUsername);
export const Followings = getCustomRepository(FollowingRepository);
export const FollowRequests = getCustomRepository(FollowRequestRepository);
export const Instances = getCustomRepository(InstanceRepository);
export const Emojis = getCustomRepository(EmojiRepository);
export const DriveFiles = getCustomRepository(DriveFileRepository);
export const DriveFolders = getCustomRepository(DriveFolderRepository);
export const Notifications = getCustomRepository(NotificationRepository);
export const Metas = getRepository(Meta);
export const Mutings = getCustomRepository(MutingRepository);
export const Blockings = getCustomRepository(BlockingRepository);
export const SwSubscriptions = getRepository(SwSubscription);
export const Hashtags = getCustomRepository(HashtagRepository);
export const AbuseUserReports = getCustomRepository(AbuseUserReportRepository);
export const RegistrationTickets = getRepository(RegistrationTicket);
export const AuthSessions = getCustomRepository(AuthSessionRepository);
export const AccessTokens = getRepository(AccessToken);
export const Signins = getCustomRepository(SigninRepository);
export const MessagingMessages = getCustomRepository(MessagingMessageRepository);
export const Pages = getCustomRepository(PageRepository);
export const PageLikes = getCustomRepository(PageLikeRepository);
export const GalleryPosts = getCustomRepository(GalleryPostRepository);
export const GalleryLikes = getCustomRepository(GalleryLikeRepository);
export const ModerationLogs = getCustomRepository(ModerationLogRepository);
export const Clips = getCustomRepository(ClipRepository);
export const ClipNotes = getRepository(ClipNote);
export const Antennas = getCustomRepository(AntennaRepository);
export const AntennaNotes = getRepository(AntennaNote);
export const PromoNotes = getRepository(PromoNote);
export const PromoReads = getRepository(PromoRead);
export const Relays = getCustomRepository(RelayRepository);
export const MutedNotes = getRepository(MutedNote);
export const Channels = getCustomRepository(ChannelRepository);
export const ChannelFollowings = getRepository(ChannelFollowing);
export const ChannelNotePinings = getRepository(ChannelNotePining);
export const RegistryItems = getRepository(RegistryItem);
export const Ads = getRepository(Ad);
export const PasswordResetRequests = getRepository(PasswordResetRequest);
export const Announcements = db.getRepository(Announcement);
export const AnnouncementReads = db.getRepository(AnnouncementRead);
export const Apps = (AppRepository);
export const Notes = (NoteRepository);
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 = db.getRepository(UserProfile);
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 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 Blockings = (BlockingRepository);
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 AntennaNotes = db.getRepository(AntennaNote);
export const PromoNotes = db.getRepository(PromoNote);
export const PromoReads = db.getRepository(PromoRead);
export const Relays = (RelayRepository);
export const MutedNotes = db.getRepository(MutedNote);
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 Ads = db.getRepository(Ad);
export const PasswordResetRequests = db.getRepository(PasswordResetRequest);

View File

@ -1,14 +1,13 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Users } from '../index.js';
import { AbuseUserReport } from '@/models/entities/abuse-user-report.js';
import { awaitAll } from '@/prelude/await-all.js';
@EntityRepository(AbuseUserReport)
export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
public async pack(
export const AbuseUserReportRepository = db.getRepository(AbuseUserReport).extend({
async pack(
src: AbuseUserReport['id'] | AbuseUserReport,
) {
const report = typeof src === 'object' ? src : await this.findOneOrFail(src);
const report = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({
id: report.id,
@ -29,11 +28,11 @@ export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
}) : null,
forwarded: report.forwarded,
});
}
},
public packMany(
packMany(
reports: any[],
) {
return Promise.all(reports.map(x => this.pack(x)));
}
}
},
});

View File

@ -1,17 +1,16 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Antenna } from '@/models/entities/antenna.js';
import { Packed } from '@/misc/schema.js';
import { AntennaNotes, UserGroupJoinings } from '../index.js';
@EntityRepository(Antenna)
export class AntennaRepository extends Repository<Antenna> {
public async pack(
export const AntennaRepository = db.getRepository(Antenna).extend({
async pack(
src: Antenna['id'] | Antenna,
): Promise<Packed<'Antenna'>> {
const antenna = typeof src === 'object' ? src : await this.findOneOrFail(src);
const antenna = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const hasUnreadNote = (await AntennaNotes.findOne({ antennaId: antenna.id, read: false })) != null;
const userGroupJoining = antenna.userGroupJoiningId ? await UserGroupJoinings.findOne(antenna.userGroupJoiningId) : null;
const hasUnreadNote = (await AntennaNotes.findOneBy({ antennaId: antenna.id, read: false })) != null;
const userGroupJoining = antenna.userGroupJoiningId ? await UserGroupJoinings.findOneBy({ id: antenna.userGroupJoiningId }) : null;
return {
id: antenna.id,
@ -29,5 +28,5 @@ export class AntennaRepository extends Repository<Antenna> {
withFile: antenna.withFile,
hasUnreadNote,
};
}
}
},
});

View File

@ -1,12 +1,11 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { App } from '@/models/entities/app.js';
import { AccessTokens } from '../index.js';
import { Packed } from '@/misc/schema.js';
import { User } from '../entities/user.js';
@EntityRepository(App)
export class AppRepository extends Repository<App> {
public async pack(
export const AppRepository = db.getRepository(App).extend({
async pack(
src: App['id'] | App,
me?: { id: User['id'] } | null | undefined,
options?: {
@ -21,7 +20,7 @@ export class AppRepository extends Repository<App> {
includeProfileImageIds: false,
}, options);
const app = typeof src === 'object' ? src : await this.findOneOrFail(src);
const app = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return {
id: app.id,
@ -30,11 +29,11 @@ export class AppRepository extends Repository<App> {
permission: app.permission,
...(opts.includeSecret ? { secret: app.secret } : {}),
...(me ? {
isAuthorized: await AccessTokens.count({
isAuthorized: await AccessTokens.countBy({
appId: app.id,
userId: me.id,
}).then(count => count > 0),
} : {}),
};
}
}
},
});

View File

@ -1,21 +1,20 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Apps } from '../index.js';
import { AuthSession } from '@/models/entities/auth-session.js';
import { awaitAll } from '@/prelude/await-all.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(AuthSession)
export class AuthSessionRepository extends Repository<AuthSession> {
public async pack(
export const AuthSessionRepository = db.getRepository(AuthSession).extend({
async pack(
src: AuthSession['id'] | AuthSession,
me?: { id: User['id'] } | null | undefined
) {
const session = typeof src === 'object' ? src : await this.findOneOrFail(src);
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,
});
}
}
},
});

View File

@ -1,17 +1,16 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Users } from '../index.js';
import { Blocking } from '@/models/entities/blocking.js';
import { awaitAll } from '@/prelude/await-all.js';
import { Packed } from '@/misc/schema.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(Blocking)
export class BlockingRepository extends Repository<Blocking> {
public async pack(
export const BlockingRepository = db.getRepository(Blocking).extend({
async pack(
src: Blocking['id'] | Blocking,
me?: { id: User['id'] } | null | undefined
): Promise<Packed<'Blocking'>> {
const blocking = typeof src === 'object' ? src : await this.findOneOrFail(src);
const blocking = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({
id: blocking.id,
@ -21,12 +20,12 @@ export class BlockingRepository extends Repository<Blocking> {
detail: true,
}),
});
}
},
public packMany(
packMany(
blockings: any[],
me: { id: User['id'] }
) {
return Promise.all(blockings.map(x => this.pack(x, me)));
}
}
},
});

View File

@ -1,23 +1,22 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Channel } from '@/models/entities/channel.js';
import { Packed } from '@/misc/schema.js';
import { DriveFiles, ChannelFollowings, NoteUnreads } from '../index.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(Channel)
export class ChannelRepository extends Repository<Channel> {
public async pack(
export const ChannelRepository = db.getRepository(Channel).extend({
async pack(
src: Channel['id'] | Channel,
me?: { id: User['id'] } | null | undefined,
): Promise<Packed<'Channel'>> {
const channel = typeof src === 'object' ? src : await this.findOneOrFail(src);
const channel = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const meId = me ? me.id : null;
const banner = channel.bannerId ? await DriveFiles.findOne(channel.bannerId) : null;
const banner = channel.bannerId ? await DriveFiles.findOneBy({ id: channel.bannerId }) : null;
const hasUnreadNote = meId ? (await NoteUnreads.findOne({ noteChannelId: channel.id, userId: meId })) != null : undefined;
const hasUnreadNote = meId ? (await NoteUnreads.findOneBy({ noteChannelId: channel.id, userId: meId })) != null : undefined;
const following = meId ? await ChannelFollowings.findOne({
const following = meId ? await ChannelFollowings.findOneBy({
followerId: meId,
followeeId: channel.id,
}) : null;
@ -38,5 +37,5 @@ export class ChannelRepository extends Repository<Channel> {
hasUnreadNote,
} : {}),
};
}
}
},
});

View File

@ -1,15 +1,14 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Clip } from '@/models/entities/clip.js';
import { Packed } from '@/misc/schema.js';
import { Users } from '../index.js';
import { awaitAll } from '@/prelude/await-all.js';
@EntityRepository(Clip)
export class ClipRepository extends Repository<Clip> {
public async pack(
export const ClipRepository = db.getRepository(Clip).extend({
async pack(
src: Clip['id'] | Clip,
): Promise<Packed<'Clip'>> {
const clip = typeof src === 'object' ? src : await this.findOneOrFail(src);
const clip = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({
id: clip.id,
@ -20,12 +19,12 @@ export class ClipRepository extends Repository<Clip> {
description: clip.description,
isPublic: clip.isPublic,
});
}
},
public packMany(
packMany(
clips: Clip[],
) {
return Promise.all(clips.map(x => this.pack(x)));
}
}
},
});

View File

@ -1,4 +1,4 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { Users, DriveFolders } from '../index.js';
import { User } from '@/models/entities/user.js';
@ -16,9 +16,8 @@ type PackOptions = {
withUser?: boolean,
};
@EntityRepository(DriveFile)
export class DriveFileRepository extends Repository<DriveFile> {
public validateFileName(name: string): boolean {
export const DriveFileRepository = db.getRepository(DriveFile).extend({
validateFileName(name: string): boolean {
return (
(name.trim().length > 0) &&
(name.length <= 200) &&
@ -26,9 +25,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
(name.indexOf('/') === -1) &&
(name.indexOf('..') === -1)
);
}
},
public getPublicProperties(file: DriveFile): DriveFile['properties'] {
getPublicProperties(file: DriveFile): DriveFile['properties'] {
if (file.properties.orientation != null) {
const properties = JSON.parse(JSON.stringify(file.properties));
if (file.properties.orientation >= 5) {
@ -39,9 +38,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
}
return file.properties;
}
},
public getPublicUrl(file: DriveFile, thumbnail = false): string | null {
getPublicUrl(file: DriveFile, thumbnail = false): string | null {
// リモートかつメディアプロキシ
if (file.uri != null && file.userHost != null && config.mediaProxy != null) {
return appendQuery(config.mediaProxy, query({
@ -62,9 +61,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
const isImage = file.type && ['image/png', 'image/apng', 'image/gif', 'image/jpeg', 'image/webp', 'image/svg+xml'].includes(file.type);
return thumbnail ? (file.thumbnailUrl || (isImage ? (file.webpublicUrl || file.url) : null)) : (file.webpublicUrl || file.url);
}
},
public async calcDriveUsageOf(user: User['id'] | { id: User['id'] }): Promise<number> {
async calcDriveUsageOf(user: User['id'] | { id: User['id'] }): Promise<number> {
const id = typeof user === 'object' ? user.id : user;
const { sum } = await this
@ -75,9 +74,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
.getRawOne();
return parseInt(sum, 10) || 0;
}
},
public async calcDriveUsageOfHost(host: string): Promise<number> {
async calcDriveUsageOfHost(host: string): Promise<number> {
const { sum } = await this
.createQueryBuilder('file')
.where('file.userHost = :host', { host: toPuny(host) })
@ -86,9 +85,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
.getRawOne();
return parseInt(sum, 10) || 0;
}
},
public async calcDriveUsageOfLocal(): Promise<number> {
async calcDriveUsageOfLocal(): Promise<number> {
const { sum } = await this
.createQueryBuilder('file')
.where('file.userHost IS NULL')
@ -97,9 +96,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
.getRawOne();
return parseInt(sum, 10) || 0;
}
},
public async calcDriveUsageOfRemote(): Promise<number> {
async calcDriveUsageOfRemote(): Promise<number> {
const { sum } = await this
.createQueryBuilder('file')
.where('file.userHost IS NOT NULL')
@ -108,11 +107,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
.getRawOne();
return parseInt(sum, 10) || 0;
}
},
public async pack(src: DriveFile['id'], options?: PackOptions): Promise<Packed<'DriveFile'> | null>;
public async pack(src: DriveFile, options?: PackOptions): Promise<Packed<'DriveFile'>>;
public async pack(
async pack(
src: DriveFile['id'] | DriveFile,
options?: PackOptions
): Promise<Packed<'DriveFile'> | null> {
@ -121,11 +118,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
self: false,
}, options);
const file = typeof src === 'object' ? src : await this.findOne(src);
const file = typeof src === 'object' ? src : await this.findOneBy({ id: src });
if (file == null) return null;
const meta = await fetchMeta();
return await awaitAll<Packed<'DriveFile'>>({
id: file.id,
createdAt: file.createdAt.toISOString(),
@ -146,13 +141,13 @@ export class DriveFileRepository extends Repository<DriveFile> {
userId: opts.withUser ? file.userId : null,
user: (opts.withUser && file.userId) ? Users.pack(file.userId) : null,
});
}
},
public async packMany(
async packMany(
files: (DriveFile['id'] | DriveFile)[],
options?: PackOptions
) {
const items = await Promise.all(files.map(f => this.pack(f, options)));
return items.filter(x => x != null);
}
}
},
});

View File

@ -1,12 +1,11 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { DriveFolders, DriveFiles } from '../index.js';
import { DriveFolder } from '@/models/entities/drive-folder.js';
import { awaitAll } from '@/prelude/await-all.js';
import { Packed } from '@/misc/schema.js';
@EntityRepository(DriveFolder)
export class DriveFolderRepository extends Repository<DriveFolder> {
public async pack(
export const DriveFolderRepository = db.getRepository(DriveFolder).extend({
async pack(
src: DriveFolder['id'] | DriveFolder,
options?: {
detail: boolean
@ -16,7 +15,7 @@ export class DriveFolderRepository extends Repository<DriveFolder> {
detail: false,
}, options);
const folder = typeof src === 'object' ? src : await this.findOneOrFail(src);
const folder = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({
id: folder.id,
@ -25,10 +24,10 @@ export class DriveFolderRepository extends Repository<DriveFolder> {
parentId: folder.parentId,
...(opts.detail ? {
foldersCount: DriveFolders.count({
foldersCount: DriveFolders.countBy({
parentId: folder.id,
}),
filesCount: DriveFiles.count({
filesCount: DriveFiles.countBy({
folderId: folder.id,
}),
@ -39,5 +38,5 @@ export class DriveFolderRepository extends Repository<DriveFolder> {
} : {}),
} : {}),
});
}
}
},
});

View File

@ -1,13 +1,12 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Emoji } from '@/models/entities/emoji.js';
import { Packed } from '@/misc/schema.js';
@EntityRepository(Emoji)
export class EmojiRepository extends Repository<Emoji> {
public async pack(
export const EmojiRepository = db.getRepository(Emoji).extend({
async pack(
src: Emoji['id'] | Emoji,
): Promise<Packed<'Emoji'>> {
const emoji = typeof src === 'object' ? src : await this.findOneOrFail(src);
const emoji = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return {
id: emoji.id,
@ -18,11 +17,11 @@ export class EmojiRepository extends Repository<Emoji> {
// || emoji.originalUrl してるのは後方互換性のため
url: emoji.publicUrl || emoji.originalUrl,
};
}
},
public packMany(
packMany(
emojis: any[],
) {
return Promise.all(emojis.map(x => this.pack(x)));
}
}
},
});

View File

@ -1,20 +1,19 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { FollowRequest } from '@/models/entities/follow-request.js';
import { Users } from '../index.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(FollowRequest)
export class FollowRequestRepository extends Repository<FollowRequest> {
public async pack(
export const FollowRequestRepository = db.getRepository(FollowRequest).extend({
async pack(
src: FollowRequest['id'] | FollowRequest,
me?: { id: User['id'] } | null | undefined
) {
const request = typeof src === 'object' ? src : await this.findOneOrFail(src);
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),
};
}
}
},
});

View File

@ -1,4 +1,4 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Users } from '../index.js';
import { Following } from '@/models/entities/following.js';
import { awaitAll } from '@/prelude/await-all.js';
@ -29,25 +29,24 @@ type RemoteFolloweeFollowing = Following & {
followeeSharedInbox: string;
};
@EntityRepository(Following)
export class FollowingRepository extends Repository<Following> {
public isLocalFollower(following: Following): following is LocalFollowerFollowing {
export const FollowingRepository = db.getRepository(Following).extend({
isLocalFollower(following: Following): following is LocalFollowerFollowing {
return following.followerHost == null;
}
},
public isRemoteFollower(following: Following): following is RemoteFollowerFollowing {
isRemoteFollower(following: Following): following is RemoteFollowerFollowing {
return following.followerHost != null;
}
},
public isLocalFollowee(following: Following): following is LocalFolloweeFollowing {
isLocalFollowee(following: Following): following is LocalFolloweeFollowing {
return following.followeeHost == null;
}
},
public isRemoteFollowee(following: Following): following is RemoteFolloweeFollowing {
isRemoteFollowee(following: Following): following is RemoteFolloweeFollowing {
return following.followeeHost != null;
}
},
public async pack(
async pack(
src: Following['id'] | Following,
me?: { id: User['id'] } | null | undefined,
opts?: {
@ -55,7 +54,7 @@ export class FollowingRepository extends Repository<Following> {
populateFollower?: boolean;
}
): Promise<Packed<'Following'>> {
const following = typeof src === 'object' ? src : await this.findOneOrFail(src);
const following = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
if (opts == null) opts = {};
@ -71,9 +70,9 @@ export class FollowingRepository extends Repository<Following> {
detail: true,
}) : undefined,
});
}
},
public packMany(
packMany(
followings: any[],
me?: { id: User['id'] } | null | undefined,
opts?: {
@ -82,5 +81,5 @@ export class FollowingRepository extends Repository<Following> {
}
) {
return Promise.all(followings.map(x => this.pack(x, me, opts)));
}
}
},
});

View File

@ -1,25 +1,24 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { GalleryLike } from '@/models/entities/gallery-like.js';
import { GalleryPosts } from '../index.js';
@EntityRepository(GalleryLike)
export class GalleryLikeRepository extends Repository<GalleryLike> {
public async pack(
export const GalleryLikeRepository = db.getRepository(GalleryLike).extend({
async pack(
src: GalleryLike['id'] | GalleryLike,
me?: any
) {
const like = typeof src === 'object' ? src : await this.findOneOrFail(src);
const like = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return {
id: like.id,
post: await GalleryPosts.pack(like.post || like.postId, me),
};
}
},
public packMany(
packMany(
likes: any[],
me: any
) {
return Promise.all(likes.map(x => this.pack(x, me)));
}
}
},
});

View File

@ -1,18 +1,17 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { GalleryPost } from '@/models/entities/gallery-post.js';
import { Packed } from '@/misc/schema.js';
import { Users, DriveFiles, GalleryLikes } from '../index.js';
import { awaitAll } from '@/prelude/await-all.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(GalleryPost)
export class GalleryPostRepository extends Repository<GalleryPost> {
public async pack(
export const GalleryPostRepository = db.getRepository(GalleryPost).extend({
async pack(
src: GalleryPost['id'] | GalleryPost,
me?: { id: User['id'] } | null | undefined,
): Promise<Packed<'GalleryPost'>> {
const meId = me ? me.id : null;
const post = typeof src === 'object' ? src : await this.findOneOrFail(src);
const post = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({
id: post.id,
@ -27,14 +26,14 @@ export class GalleryPostRepository extends Repository<GalleryPost> {
tags: post.tags.length > 0 ? post.tags : undefined,
isSensitive: post.isSensitive,
likedCount: post.likedCount,
isLiked: meId ? await GalleryLikes.findOne({ postId: post.id, userId: meId }).then(x => x != null) : undefined,
isLiked: meId ? await GalleryLikes.findOneBy({ postId: post.id, userId: meId }).then(x => x != null) : undefined,
});
}
},
public packMany(
packMany(
posts: GalleryPost[],
me?: { id: User['id'] } | null | undefined,
) {
return Promise.all(posts.map(x => this.pack(x, me)));
}
}
},
});

View File

@ -1,10 +1,9 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Hashtag } from '@/models/entities/hashtag.js';
import { Packed } from '@/misc/schema.js';
@EntityRepository(Hashtag)
export class HashtagRepository extends Repository<Hashtag> {
public async pack(
export const HashtagRepository = db.getRepository(Hashtag).extend({
async pack(
src: Hashtag,
): Promise<Packed<'Hashtag'>> {
return {
@ -16,11 +15,11 @@ export class HashtagRepository extends Repository<Hashtag> {
attachedLocalUsersCount: src.attachedLocalUsersCount,
attachedRemoteUsersCount: src.attachedRemoteUsersCount,
};
}
},
public packMany(
packMany(
hashtags: Hashtag[],
) {
return Promise.all(hashtags.map(x => this.pack(x)));
}
}
},
});

View File

@ -1,10 +1,9 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Instance } from '@/models/entities/instance.js';
import { Packed } from '@/misc/schema.js';
@EntityRepository(Instance)
export class InstanceRepository extends Repository<Instance> {
public async pack(
export const InstanceRepository = db.getRepository(Instance).extend({
async pack(
instance: Instance,
): Promise<Packed<'FederationInstance'>> {
return {
@ -29,11 +28,11 @@ export class InstanceRepository extends Repository<Instance> {
iconUrl: instance.iconUrl,
infoUpdatedAt: instance.infoUpdatedAt ? instance.infoUpdatedAt.toISOString() : null,
};
}
},
public packMany(
packMany(
instances: Instance[],
) {
return Promise.all(instances.map(x => this.pack(x)));
}
}
},
});

View File

@ -1,12 +1,11 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { MessagingMessage } from '@/models/entities/messaging-message.js';
import { Users, DriveFiles, UserGroups } from '../index.js';
import { Packed } from '@/misc/schema.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(MessagingMessage)
export class MessagingMessageRepository extends Repository<MessagingMessage> {
public async pack(
export const MessagingMessageRepository = db.getRepository(MessagingMessage).extend({
async pack(
src: MessagingMessage['id'] | MessagingMessage,
me?: { id: User['id'] } | null | undefined,
options?: {
@ -19,7 +18,7 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> {
populateGroup: true,
};
const message = typeof src === 'object' ? src : await this.findOneOrFail(src);
const message = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return {
id: message.id,
@ -36,5 +35,5 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> {
isRead: message.isRead,
reads: message.reads,
};
}
}
},
});

View File

@ -1,14 +1,13 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Users } from '../index.js';
import { ModerationLog } from '@/models/entities/moderation-log.js';
import { awaitAll } from '@/prelude/await-all.js';
@EntityRepository(ModerationLog)
export class ModerationLogRepository extends Repository<ModerationLog> {
public async pack(
export const ModerationLogRepository = db.getRepository(ModerationLog).extend({
async pack(
src: ModerationLog['id'] | ModerationLog,
) {
const log = typeof src === 'object' ? src : await this.findOneOrFail(src);
const log = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({
id: log.id,
@ -20,11 +19,11 @@ export class ModerationLogRepository extends Repository<ModerationLog> {
detail: true,
}),
});
}
},
public packMany(
packMany(
reports: any[],
) {
return Promise.all(reports.map(x => this.pack(x)));
}
}
},
});

View File

@ -1,17 +1,16 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Users } from '../index.js';
import { Muting } from '@/models/entities/muting.js';
import { awaitAll } from '@/prelude/await-all.js';
import { Packed } from '@/misc/schema.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(Muting)
export class MutingRepository extends Repository<Muting> {
public async pack(
export const MutingRepository = db.getRepository(Muting).extend({
async pack(
src: Muting['id'] | Muting,
me?: { id: User['id'] } | null | undefined
): Promise<Packed<'Muting'>> {
const muting = typeof src === 'object' ? src : await this.findOneOrFail(src);
const muting = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({
id: muting.id,
@ -22,12 +21,12 @@ export class MutingRepository extends Repository<Muting> {
detail: true,
}),
});
}
},
public packMany(
packMany(
mutings: any[],
me: { id: User['id'] }
) {
return Promise.all(mutings.map(x => this.pack(x, me)));
}
}
},
});

View File

@ -1,15 +1,14 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { NoteFavorite } from '@/models/entities/note-favorite.js';
import { Notes } from '../index.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(NoteFavorite)
export class NoteFavoriteRepository extends Repository<NoteFavorite> {
public async pack(
export const NoteFavoriteRepository = db.getRepository(NoteFavorite).extend({
async pack(
src: NoteFavorite['id'] | NoteFavorite,
me?: { id: User['id'] } | null | undefined
) {
const favorite = typeof src === 'object' ? src : await this.findOneOrFail(src);
const favorite = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return {
id: favorite.id,
@ -17,12 +16,12 @@ export class NoteFavoriteRepository extends Repository<NoteFavorite> {
noteId: favorite.noteId,
note: await Notes.pack(favorite.note || favorite.noteId, me),
};
}
},
public packMany(
packMany(
favorites: any[],
me: { id: User['id'] }
) {
return Promise.all(favorites.map(x => this.pack(x, me)));
}
}
},
});

View File

@ -1,13 +1,12 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { NoteReaction } from '@/models/entities/note-reaction.js';
import { Notes, Users } from '../index.js';
import { Packed } from '@/misc/schema.js';
import { convertLegacyReaction } from '@/misc/reaction-lib.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(NoteReaction)
export class NoteReactionRepository extends Repository<NoteReaction> {
public async pack(
export const NoteReactionRepository = db.getRepository(NoteReaction).extend({
async pack(
src: NoteReaction['id'] | NoteReaction,
me?: { id: User['id'] } | null | undefined,
options?: {
@ -18,7 +17,7 @@ export class NoteReactionRepository extends Repository<NoteReaction> {
withNote: false,
}, options);
const reaction = typeof src === 'object' ? src : await this.findOneOrFail(src);
const reaction = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return {
id: reaction.id,
@ -29,5 +28,5 @@ export class NoteReactionRepository extends Repository<NoteReaction> {
note: await Notes.pack(reaction.note ?? reaction.noteId, me),
} : {}),
};
}
}
},
});

View File

@ -1,4 +1,4 @@
import { EntityRepository, Repository, In } from 'typeorm';
import { In } from 'typeorm';
import * as mfm from 'mfm-js';
import { Note } from '@/models/entities/note.js';
import { User } from '@/models/entities/user.js';
@ -9,10 +9,133 @@ import { awaitAll } from '@/prelude/await-all.js';
import { convertLegacyReaction, convertLegacyReactions, decodeReaction } from '@/misc/reaction-lib.js';
import { NoteReaction } from '@/models/entities/note-reaction.js';
import { aggregateNoteEmojis, populateEmojis, prefetchEmojis } from '@/misc/populate-emojis.js';
import { db } from '@/db/postgre.js';
@EntityRepository(Note)
export class NoteRepository extends Repository<Note> {
public async isVisibleForMe(note: Note, meId: User['id'] | null): Promise<boolean> {
async function hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) {
// TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
let hide = false;
// visibility が specified かつ自分が指定されていなかったら非表示
if (packedNote.visibility === 'specified') {
if (meId == null) {
hide = true;
} else if (meId === packedNote.userId) {
hide = false;
} else {
// 指定されているかどうか
const specified = packedNote.visibleUserIds!.some((id: any) => meId === id);
if (specified) {
hide = false;
} else {
hide = true;
}
}
}
// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
if (packedNote.visibility === 'followers') {
if (meId == null) {
hide = true;
} else if (meId === packedNote.userId) {
hide = false;
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
// 自分の投稿に対するリプライ
hide = false;
} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
// 自分へのメンション
hide = false;
} else {
// フォロワーかどうか
const following = await Followings.findOneBy({
followeeId: packedNote.userId,
followerId: meId,
});
if (following == null) {
hide = true;
} else {
hide = false;
}
}
}
if (hide) {
packedNote.visibleUserIds = undefined;
packedNote.fileIds = [];
packedNote.files = [];
packedNote.text = null;
packedNote.poll = undefined;
packedNote.cw = null;
packedNote.isHidden = true;
}
}
async function populatePoll(note: Note, meId: User['id'] | null) {
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: Note, meId: User['id'], _hint_?: {
myReactions: Map<Note['id'], NoteReaction | null>;
}) {
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;
}
export const NoteRepository = db.getRepository(Note).extend({
async isVisibleForMe(note: Note, meId: User['id'] | null): Promise<boolean> {
// visibility が specified かつ自分が指定されていなかったら非表示
if (note.visibility === 'specified') {
if (meId == null) {
@ -45,7 +168,7 @@ export class NoteRepository extends Repository<Note> {
return true;
} else {
// フォロワーかどうか
const following = await Followings.findOne({
const following = await Followings.findOneBy({
followeeId: note.userId,
followerId: meId,
});
@ -59,69 +182,9 @@ export class NoteRepository extends Repository<Note> {
}
return true;
}
},
private async hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) {
// TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
let hide = false;
// visibility が specified かつ自分が指定されていなかったら非表示
if (packedNote.visibility === 'specified') {
if (meId == null) {
hide = true;
} else if (meId === packedNote.userId) {
hide = false;
} else {
// 指定されているかどうか
const specified = packedNote.visibleUserIds!.some((id: any) => meId === id);
if (specified) {
hide = false;
} else {
hide = true;
}
}
}
// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
if (packedNote.visibility === 'followers') {
if (meId == null) {
hide = true;
} else if (meId === packedNote.userId) {
hide = false;
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
// 自分の投稿に対するリプライ
hide = false;
} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
// 自分へのメンション
hide = false;
} else {
// フォロワーかどうか
const following = await Followings.findOne({
followeeId: packedNote.userId,
followerId: meId,
});
if (following == null) {
hide = true;
} else {
hide = false;
}
}
}
if (hide) {
packedNote.visibleUserIds = undefined;
packedNote.fileIds = [];
packedNote.files = [];
packedNote.text = null;
packedNote.poll = undefined;
packedNote.cw = null;
packedNote.isHidden = true;
}
}
public async pack(
async pack(
src: Note['id'] | Note,
me?: { id: User['id'] } | null | undefined,
options?: {
@ -138,68 +201,9 @@ export class NoteRepository extends Repository<Note> {
}, options);
const meId = me ? me.id : null;
const note = typeof src === 'object' ? src : await this.findOneOrFail(src);
const note = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const host = note.userHost;
async function populatePoll() {
const poll = await Polls.findOneOrFail(note.id);
const choices = poll.choices.map(c => ({
text: c,
votes: poll.votes[poll.choices.indexOf(c)],
isVoted: false,
}));
if (poll.multiple) {
const votes = await PollVotes.find({
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.findOne({
userId: meId!,
noteId: note.id,
});
if (vote) {
choices[vote.choice].isVoted = true;
}
}
return {
multiple: poll.multiple,
expiresAt: poll.expiresAt,
choices,
};
}
async function populateMyReaction() {
if (options?._hint_?.myReactions) {
const reaction = options._hint_.myReactions.get(note.id);
if (reaction) {
return convertLegacyReaction(reaction.reaction);
} else if (reaction === null) {
return undefined;
}
// 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない
}
const reaction = await NoteReactions.findOne({
userId: meId!,
noteId: note.id,
});
if (reaction) {
return convertLegacyReaction(reaction.reaction);
}
return undefined;
}
let text = note.text;
if (note.name && (note.url ?? note.uri)) {
@ -209,7 +213,7 @@ export class NoteRepository extends Repository<Note> {
const channel = note.channelId
? note.channel
? note.channel
: await Channels.findOne(note.channelId)
: 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, ''));
@ -255,10 +259,10 @@ export class NoteRepository extends Repository<Note> {
_hint_: options?._hint_,
}) : undefined,
poll: note.hasPoll ? populatePoll() : undefined,
poll: note.hasPoll ? populatePoll(note, meId) : undefined,
...(meId ? {
myReaction: populateMyReaction(),
myReaction: populateMyReaction(note, meId, options?._hint_),
} : {}),
} : {}),
});
@ -275,13 +279,13 @@ export class NoteRepository extends Repository<Note> {
}
if (!opts.skipHide) {
await this.hideNote(packed, meId);
await hideNote(packed, meId);
}
return packed;
}
},
public async packMany(
async packMany(
notes: Note[],
me?: { id: User['id'] } | null | undefined,
options?: {
@ -296,7 +300,7 @@ export class NoteRepository extends Repository<Note> {
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.find({
const myReactions = await NoteReactions.findBy({
userId: meId,
noteId: In(targets),
});
@ -314,5 +318,5 @@ export class NoteRepository extends Repository<Note> {
myReactions: myReactionsMap,
},
})));
}
}
},
});

View File

@ -1,4 +1,4 @@
import { EntityRepository, In, Repository } from 'typeorm';
import { In, Repository } from 'typeorm';
import { Users, Notes, UserGroupInvitations, AccessTokens, NoteReactions } from '../index.js';
import { Notification } from '@/models/entities/notification.js';
import { awaitAll } from '@/prelude/await-all.js';
@ -8,10 +8,10 @@ import { NoteReaction } from '@/models/entities/note-reaction.js';
import { User } from '@/models/entities/user.js';
import { aggregateNoteEmojis, prefetchEmojis } from '@/misc/populate-emojis.js';
import { notificationTypes } from '@/types.js';
import { db } from '@/db/postgre.js';
@EntityRepository(Notification)
export class NotificationRepository extends Repository<Notification> {
public async pack(
export const NotificationRepository = db.getRepository(Notification).extend({
async pack(
src: Notification['id'] | Notification,
options: {
_hintForEachNotes_?: {
@ -19,8 +19,8 @@ export class NotificationRepository extends Repository<Notification> {
};
}
): Promise<Packed<'Notification'>> {
const notification = typeof src === 'object' ? src : await this.findOneOrFail(src);
const token = notification.appAccessTokenId ? await AccessTokens.findOneOrFail(notification.appAccessTokenId) : null;
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,
@ -82,9 +82,9 @@ export class NotificationRepository extends Repository<Notification> {
icon: notification.customIcon || token?.iconUrl,
} : {}),
});
}
},
public async packMany(
async packMany(
notifications: Notification[],
meId: User['id']
) {
@ -95,7 +95,7 @@ export class NotificationRepository extends Repository<Notification> {
const myReactionsMap = new Map<Note['id'], NoteReaction | null>();
const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!);
const targets = [...noteIds, ...renoteIds];
const myReactions = await NoteReactions.find({
const myReactions = await NoteReactions.findBy({
userId: meId,
noteId: In(targets),
});
@ -111,5 +111,5 @@ export class NotificationRepository extends Repository<Notification> {
myReactions: myReactionsMap,
},
})));
}
}
},
});

View File

@ -1,26 +1,25 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { PageLike } from '@/models/entities/page-like.js';
import { Pages } from '../index.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(PageLike)
export class PageLikeRepository extends Repository<PageLike> {
public async pack(
export const PageLikeRepository = db.getRepository(PageLike).extend({
async pack(
src: PageLike['id'] | PageLike,
me?: { id: User['id'] } | null | undefined
) {
const like = typeof src === 'object' ? src : await this.findOneOrFail(src);
const like = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return {
id: like.id,
page: await Pages.pack(like.page || like.pageId, me),
};
}
},
public packMany(
packMany(
likes: any[],
me: { id: User['id'] }
) {
return Promise.all(likes.map(x => this.pack(x, me)));
}
}
},
});

View File

@ -1,4 +1,4 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Page } from '@/models/entities/page.js';
import { Packed } from '@/misc/schema.js';
import { Users, DriveFiles, PageLikes } from '../index.js';
@ -6,20 +6,19 @@ import { awaitAll } from '@/prelude/await-all.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(Page)
export class PageRepository extends Repository<Page> {
public async pack(
export const PageRepository = db.getRepository(Page).extend({
async pack(
src: Page['id'] | Page,
me?: { id: User['id'] } | null | undefined,
): Promise<Packed<'Page'>> {
const meId = me ? me.id : null;
const page = typeof src === 'object' ? src : await this.findOneOrFail(src);
const page = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const attachedFiles: Promise<DriveFile | undefined>[] = [];
const collectFile = (xs: any[]) => {
for (const x of xs) {
if (x.type === 'image') {
attachedFiles.push(DriveFiles.findOne({
attachedFiles.push(DriveFiles.findOneBy({
id: x.fileId,
userId: page.userId,
}));
@ -76,14 +75,14 @@ export class PageRepository extends Repository<Page> {
eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null,
attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles)),
likedCount: page.likedCount,
isLiked: meId ? await PageLikes.findOne({ pageId: page.id, userId: meId }).then(x => x != null) : undefined,
isLiked: meId ? await PageLikes.findOneBy({ pageId: page.id, userId: meId }).then(x => x != null) : undefined,
});
}
},
public packMany(
packMany(
pages: Page[],
me?: { id: User['id'] } | null | undefined,
) {
return Promise.all(pages.map(x => this.pack(x, me)));
}
}
},
});

View File

@ -1,6 +1,5 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Relay } from '@/models/entities/relay.js';
@EntityRepository(Relay)
export class RelayRepository extends Repository<Relay> {
}
export const RelayRepository = db.getRepository(Relay).extend({
});

View File

@ -1,11 +1,10 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Signin } from '@/models/entities/signin.js';
@EntityRepository(Signin)
export class SigninRepository extends Repository<Signin> {
public async pack(
export const SigninRepository = db.getRepository(Signin).extend({
async pack(
src: Signin,
) {
return src;
}
}
},
});

View File

@ -1,23 +1,22 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { UserGroupInvitation } from '@/models/entities/user-group-invitation.js';
import { UserGroups } from '../index.js';
@EntityRepository(UserGroupInvitation)
export class UserGroupInvitationRepository extends Repository<UserGroupInvitation> {
public async pack(
export const UserGroupInvitationRepository = db.getRepository(UserGroupInvitation).extend({
async pack(
src: UserGroupInvitation['id'] | UserGroupInvitation,
) {
const invitation = typeof src === 'object' ? src : await this.findOneOrFail(src);
const invitation = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return {
id: invitation.id,
group: await UserGroups.pack(invitation.userGroup || invitation.userGroupId),
};
}
},
public packMany(
packMany(
invitations: any[],
) {
return Promise.all(invitations.map(x => this.pack(x)));
}
}
},
});

View File

@ -1,16 +1,15 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { UserGroup } from '@/models/entities/user-group.js';
import { UserGroupJoinings } from '../index.js';
import { Packed } from '@/misc/schema.js';
@EntityRepository(UserGroup)
export class UserGroupRepository extends Repository<UserGroup> {
public async pack(
export const UserGroupRepository = db.getRepository(UserGroup).extend({
async pack(
src: UserGroup['id'] | UserGroup,
): Promise<Packed<'UserGroup'>> {
const userGroup = typeof src === 'object' ? src : await this.findOneOrFail(src);
const userGroup = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const users = await UserGroupJoinings.find({
const users = await UserGroupJoinings.findBy({
userGroupId: userGroup.id,
});
@ -21,5 +20,5 @@ export class UserGroupRepository extends Repository<UserGroup> {
ownerId: userGroup.userId,
userIds: users.map(x => x.userId),
};
}
}
},
});

View File

@ -1,16 +1,15 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { UserList } from '@/models/entities/user-list.js';
import { UserListJoinings } from '../index.js';
import { Packed } from '@/misc/schema.js';
@EntityRepository(UserList)
export class UserListRepository extends Repository<UserList> {
public async pack(
export const UserListRepository = db.getRepository(UserList).extend({
async pack(
src: UserList['id'] | UserList,
): Promise<Packed<'UserList'>> {
const userList = typeof src === 'object' ? src : await this.findOneOrFail(src);
const userList = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const users = await UserListJoinings.find({
const users = await UserListJoinings.findBy({
userListId: userList.id,
});
@ -20,5 +19,5 @@ export class UserListRepository extends Repository<UserList> {
name: userList.name,
userIds: users.map(x => x.userId),
};
}
}
},
});

View File

@ -8,6 +8,11 @@ import { awaitAll, Promiseable } from '@/prelude/await-all.js';
import { populateEmojis } from '@/misc/populate-emojis.js';
import { getAntennas } from '@/misc/antenna-cache.js';
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js';
import { Cache } from '@/misc/cache.js';
import { Instance } from '../entities/instance.js';
import { db } from '@/db/postgre.js';
const userInstanceCache = new Cache<Instance | null>(1000 * 60 * 60 * 3);
type IsUserDetailed<Detailed extends boolean> = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>;
type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends boolean> =
@ -19,51 +24,69 @@ type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends bo
const ajv = new Ajv();
@EntityRepository(User)
export class UserRepository extends Repository<User> {
public localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const;
public passwordSchema = { type: 'string', minLength: 1 } as const;
public nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
public descriptionSchema = { type: 'string', minLength: 1, maxLength: 500 } as const;
public locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
public birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const;
const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const;
const passwordSchema = { type: 'string', minLength: 1 } as const;
const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
const descriptionSchema = { type: 'string', minLength: 1, maxLength: 500 } as const;
const locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
const birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const;
function isLocalUser(user: User): user is ILocalUser;
function isLocalUser<T extends { host: User['host'] }>(user: T): user is T & { host: null; };
function isLocalUser(user: User | { host: User['host'] }): boolean {
return user.host == null;
}
function isRemoteUser(user: User): user is IRemoteUser;
function isRemoteUser<T extends { host: User['host'] }>(user: T): user is T & { host: string; };
function isRemoteUser(user: User | { host: User['host'] }): boolean {
return !isLocalUser(user);
}
export const UserRepository = db.getRepository(User).extend({
localUsernameSchema,
passwordSchema,
nameSchema,
descriptionSchema,
locationSchema,
birthdaySchema,
//#region Validators
public validateLocalUsername = ajv.compile(this.localUsernameSchema);
public validatePassword = ajv.compile(this.passwordSchema);
public validateName = ajv.compile(this.nameSchema);
public validateDescription = ajv.compile(this.descriptionSchema);
public validateLocation = ajv.compile(this.locationSchema);
public validateBirthday = ajv.compile(this.birthdaySchema);
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
public async getRelation(me: User['id'], target: User['id']) {
async getRelation(me: User['id'], target: User['id']) {
const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([
Followings.findOne({
Followings.findOneBy({
followerId: me,
followeeId: target,
}),
Followings.findOne({
Followings.findOneBy({
followerId: target,
followeeId: me,
}),
FollowRequests.findOne({
FollowRequests.findOneBy({
followerId: me,
followeeId: target,
}),
FollowRequests.findOne({
FollowRequests.findOneBy({
followerId: target,
followeeId: me,
}),
Blockings.findOne({
Blockings.findOneBy({
blockerId: me,
blockeeId: target,
}),
Blockings.findOne({
Blockings.findOneBy({
blockerId: target,
blockeeId: me,
}),
Mutings.findOne({
Mutings.findOneBy({
muterId: me,
muteeId: target,
}),
@ -79,14 +102,14 @@ export class UserRepository extends Repository<User> {
isBlocked: fromBlocked != null,
isMuted: mute != null,
};
}
},
public async getHasUnreadMessagingMessage(userId: User['id']): Promise<boolean> {
const mute = await Mutings.find({
async getHasUnreadMessagingMessage(userId: User['id']): Promise<boolean> {
const mute = await Mutings.findBy({
muterId: userId,
});
const joinings = await UserGroupJoinings.find({ userId: 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 })
@ -108,44 +131,44 @@ export class UserRepository extends Repository<User> {
]);
return withUser || withGroups.some(x => x);
}
},
public async getHasUnreadAnnouncement(userId: User['id']): Promise<boolean> {
const reads = await AnnouncementReads.find({
async getHasUnreadAnnouncement(userId: User['id']): Promise<boolean> {
const reads = await AnnouncementReads.findBy({
userId: userId,
});
const count = await Announcements.count(reads.length > 0 ? {
const count = await Announcements.countBy(reads.length > 0 ? {
id: Not(In(reads.map(read => read.announcementId))),
} : {});
return count > 0;
}
},
public async getHasUnreadAntenna(userId: User['id']): Promise<boolean> {
async getHasUnreadAntenna(userId: User['id']): Promise<boolean> {
const myAntennas = (await getAntennas()).filter(a => a.userId === userId);
const unread = myAntennas.length > 0 ? await AntennaNotes.findOne({
const unread = myAntennas.length > 0 ? await AntennaNotes.findOneBy({
antennaId: In(myAntennas.map(x => x.id)),
read: false,
}) : null;
return unread != null;
}
},
public async getHasUnreadChannel(userId: User['id']): Promise<boolean> {
const channels = await ChannelFollowings.find({ followerId: userId });
async getHasUnreadChannel(userId: User['id']): Promise<boolean> {
const channels = await ChannelFollowings.findBy({ followerId: userId });
const unread = channels.length > 0 ? await NoteUnreads.findOne({
const unread = channels.length > 0 ? await NoteUnreads.findOneBy({
userId: userId,
noteChannelId: In(channels.map(x => x.followeeId)),
}) : null;
return unread != null;
}
},
public async getHasUnreadNotification(userId: User['id']): Promise<boolean> {
const mute = await Mutings.find({
async getHasUnreadNotification(userId: User['id']): Promise<boolean> {
const mute = await Mutings.findBy({
muterId: userId,
});
const mutedUserIds = mute.map(m => m.muteeId);
@ -160,17 +183,17 @@ export class UserRepository extends Repository<User> {
});
return count > 0;
}
},
public async getHasPendingReceivedFollowRequest(userId: User['id']): Promise<boolean> {
const count = await FollowRequests.count({
async getHasPendingReceivedFollowRequest(userId: User['id']): Promise<boolean> {
const count = await FollowRequests.countBy({
followeeId: userId,
});
return count > 0;
}
},
public getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' {
getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' {
if (user.hideOnlineStatus) return 'unknown';
if (user.lastActiveDate == null) return 'unknown';
const elapsed = Date.now() - user.lastActiveDate.getTime();
@ -179,22 +202,22 @@ export class UserRepository extends Repository<User> {
elapsed < USER_ACTIVE_THRESHOLD ? 'active' :
'offline'
);
}
},
public getAvatarUrl(user: User): string {
getAvatarUrl(user: User): string {
// TODO: avatarIdがあるがavatarがない(JOINされてない)場合のハンドリング
if (user.avatar) {
return DriveFiles.getPublicUrl(user.avatar, true) || this.getIdenticonUrl(user.id);
} else {
return this.getIdenticonUrl(user.id);
}
}
},
public getIdenticonUrl(userId: User['id']): string {
getIdenticonUrl(userId: User['id']): string {
return `${config.url}/identicon/${userId}`;
}
},
public async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>(
async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>(
src: User['id'] | User,
me?: { id: User['id'] } | null | undefined,
options?: {
@ -211,11 +234,15 @@ export class UserRepository extends Repository<User> {
if (typeof src === 'object') {
user = src;
if (src.avatar === undefined && src.avatarId) src.avatar = await DriveFiles.findOne(src.avatarId) ?? null;
if (src.banner === undefined && src.bannerId) src.banner = await DriveFiles.findOne(src.bannerId) ?? null;
if (src.avatar === undefined && src.avatarId) src.avatar = await DriveFiles.findOneBy({ id: src.avatarId }) ?? null;
if (src.banner === undefined && src.bannerId) src.banner = await DriveFiles.findOneBy({ id: src.bannerId }) ?? null;
} else {
user = await this.findOneOrFail(src, {
relations: ['avatar', 'banner'],
user = await this.findOneOrFail({
where: { id: src },
relations: {
avatar: true,
banner: true,
},
});
}
@ -228,7 +255,7 @@ export class UserRepository extends Repository<User> {
.innerJoinAndSelect('pin.note', 'note')
.orderBy('pin.id', 'DESC')
.getMany() : [];
const profile = opts.detail ? await UserProfiles.findOneOrFail(user.id) : null;
const profile = opts.detail ? await UserProfiles.findOneByOrFail({ userId: user.id }) : null;
const followingCount = profile == null ? null :
(profile.ffVisibility === 'public') || isMe ? user.followingCount :
@ -254,8 +281,10 @@ export class UserRepository extends Repository<User> {
isModerator: user.isModerator || falsy,
isBot: user.isBot || falsy,
isCat: user.isCat || falsy,
showTimelineReplies: user.showTimelineReplies || falsy,
instance: user.host ? Instances.findOne({ host: user.host }).then(instance => instance ? {
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,
@ -297,7 +326,7 @@ export class UserRepository extends Repository<User> {
twoFactorEnabled: profile!.twoFactorEnabled,
usePasswordLessLogin: profile!.usePasswordLessLogin,
securityKeys: profile!.twoFactorEnabled
? UserSecurityKeys.count({
? UserSecurityKeys.countBy({
userId: user.id,
}).then(result => result >= 1)
: false,
@ -334,6 +363,7 @@ export class UserRepository extends Repository<User> {
mutedInstances: profile!.mutedInstances,
mutingNotificationTypes: profile!.mutingNotificationTypes,
emailNotificationTypes: profile!.emailNotificationTypes,
showTimelineReplies: user.showTimelineReplies || falsy,
} : {}),
...(opts.includeSecrets ? {
@ -344,7 +374,11 @@ export class UserRepository extends Repository<User> {
where: {
userId: user.id,
},
select: ['id', 'name', 'lastUsed'],
select: {
id: true,
name: true,
lastUsed: true,
},
})
: [],
} : {}),
@ -361,9 +395,9 @@ export class UserRepository extends Repository<User> {
} as Promiseable<Packed<'User'>> as Promiseable<IsMeAndIsUserDetailed<ExpectsMe, D>>;
return await awaitAll(packed);
}
},
public packMany<D extends boolean = false>(
packMany<D extends boolean = false>(
users: (User['id'] | User)[],
me?: { id: User['id'] } | null | undefined,
options?: {
@ -372,17 +406,8 @@ export class UserRepository extends Repository<User> {
}
): Promise<IsUserDetailed<D>[]> {
return Promise.all(users.map(u => this.pack(u, me, options)));
}
},
public isLocalUser(user: User): user is ILocalUser;
public isLocalUser<T extends { host: User['host'] }>(user: T): user is T & { host: null; };
public isLocalUser(user: User | { host: User['host'] }): boolean {
return user.host == null;
}
public isRemoteUser(user: User): user is IRemoteUser;
public isRemoteUser<T extends { host: User['host'] }>(user: T): user is T & { host: string; };
public isRemoteUser(user: User | { host: User['host'] }): boolean {
return !this.isLocalUser(user);
}
}
isLocalUser,
isRemoteUser,
});

View File

@ -8,13 +8,15 @@ import processInbox from './processors/inbox.js';
import processDb from './processors/db/index.js';
import processObjectStorage from './processors/object-storage/index.js';
import processSystemQueue from './processors/system/index.js';
import processWebhookDeliver from './processors/webhook-deliver.js';
import { endedPollNotification } from './processors/ended-poll-notification.js';
import { queueLogger } from './logger.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { getJobInfo } from './get-job-info.js';
import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue, endedPollNotificationQueue } from './queues.js';
import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue, endedPollNotificationQueue, webhookDeliverQueue } from './queues.js';
import { ThinUser } from './types.js';
import { IActivity } from '@/remote/activitypub/type.js';
import { Webhook } from '@/models/entities/webhook.js';
function renderError(e: Error): any {
return {
@ -26,6 +28,7 @@ function renderError(e: Error): any {
const systemLogger = queueLogger.createSubLogger('system');
const deliverLogger = queueLogger.createSubLogger('deliver');
const webhookLogger = queueLogger.createSubLogger('webhook');
const inboxLogger = queueLogger.createSubLogger('inbox');
const dbLogger = queueLogger.createSubLogger('db');
const objectStorageLogger = queueLogger.createSubLogger('objectStorage');
@ -70,6 +73,14 @@ objectStorageQueue
.on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`, { job, e: renderError(err) }))
.on('stalled', (job) => objectStorageLogger.warn(`stalled id=${job.id}`));
webhookDeliverQueue
.on('waiting', (jobId) => webhookLogger.debug(`waiting id=${jobId}`))
.on('active', (job) => webhookLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
.on('completed', (job, result) => webhookLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
.on('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`))
.on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`, { job, e: renderError(err) }))
.on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
export function deliver(user: ThinUser, content: unknown, to: string | null) {
if (content == null) return null;
if (to == null) return null;
@ -251,18 +262,39 @@ export function createCleanRemoteFilesJob() {
});
}
export function webhookDeliver(webhook: Webhook, content: unknown) {
const data = {
content,
webhookId: webhook.id,
to: webhook.url,
secret: webhook.secret,
};
return webhookDeliverQueue.add(data, {
attempts: 4,
timeout: 1 * 60 * 1000, // 1min
backoff: {
type: 'apBackoff',
},
removeOnComplete: true,
removeOnFail: true,
});
}
export default function() {
if (envOption.onlyServer) return;
deliverQueue.process(config.deliverJobConcurrency || 128, processDeliver);
inboxQueue.process(config.inboxJobConcurrency || 16, processInbox);
endedPollNotificationQueue.process(endedPollNotification);
webhookDeliverQueue.process(64, processWebhookDeliver);
processDb(dbQueue);
processObjectStorage(objectStorageQueue);
systemQueue.add('tickCharts', {
}, {
repeat: { cron: '55 * * * *' },
removeOnComplete: true,
});
systemQueue.add('resyncCharts', {
@ -278,6 +310,7 @@ export default function() {
systemQueue.add('checkExpiredMutings', {
}, {
repeat: { cron: '*/5 * * * *' },
removeOnComplete: true,
});
processSystemQueue(systemQueue);

View File

@ -13,7 +13,7 @@ const logger = queueLogger.createSubLogger('delete-account');
export async function deleteAccount(job: Bull.Job<DbUserDeleteJobData>): Promise<string | void> {
logger.info(`Deleting account of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id);
const user = await Users.findOneBy({ id: job.data.user.id });
if (user == null) {
return;
}
@ -75,7 +75,7 @@ export async function deleteAccount(job: Bull.Job<DbUserDeleteJobData>): Promise
}
{ // Send email notification
const profile = await UserProfiles.findOneOrFail(user.id);
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
if (profile.email && profile.emailVerified) {
sendEmail(profile.email, 'Account deleted',
`Your account has been deleted.`,

View File

@ -11,7 +11,7 @@ const logger = queueLogger.createSubLogger('delete-drive-files');
export async function deleteDriveFiles(job: Bull.Job<DbUserJobData>, done: any): Promise<void> {
logger.info(`Deleting drive files of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id);
const user = await Users.findOneBy({ id: job.data.user.id });
if (user == null) {
done();
return;
@ -44,7 +44,7 @@ export async function deleteDriveFiles(job: Bull.Job<DbUserJobData>, done: any):
deletedCount++;
}
const total = await DriveFiles.count({
const total = await DriveFiles.countBy({
userId: user.id,
});

View File

@ -15,7 +15,7 @@ const logger = queueLogger.createSubLogger('export-blocking');
export async function exportBlocking(job: Bull.Job<DbUserJobData>, done: any): Promise<void> {
logger.info(`Exporting blocking of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id);
const user = await Users.findOneBy({ id: job.data.user.id });
if (user == null) {
done();
return;
@ -56,7 +56,7 @@ export async function exportBlocking(job: Bull.Job<DbUserJobData>, done: any): P
cursor = blockings[blockings.length - 1].id;
for (const block of blockings) {
const u = await Users.findOne({ id: block.blockeeId });
const u = await Users.findOneBy({ id: block.blockeeId });
if (u == null) {
exportedCount++; continue;
}
@ -75,7 +75,7 @@ export async function exportBlocking(job: Bull.Job<DbUserJobData>, done: any): P
exportedCount++;
}
const total = await Blockings.count({
const total = await Blockings.countBy({
blockerId: user.id,
});

View File

@ -12,13 +12,14 @@ import { Users, Emojis } from '@/models/index.js';
import { } from '@/queue/types.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: Bull.Job, done: () => void): Promise<void> {
logger.info(`Exporting custom emojis ...`);
const user = await Users.findOne(job.data.user.id);
const user = await Users.findOneBy({ id: job.data.user.id });
if (user == null) {
done();
return;
@ -57,7 +58,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi
const customEmojis = await Emojis.find({
where: {
host: null,
host: IsNull(),
},
order: {
id: 'ASC',

View File

@ -16,7 +16,7 @@ const logger = queueLogger.createSubLogger('export-following');
export async function exportFollowing(job: Bull.Job<DbUserJobData>, done: () => void): Promise<void> {
logger.info(`Exporting following of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id);
const user = await Users.findOneBy({ id: job.data.user.id });
if (user == null) {
done();
return;
@ -36,7 +36,7 @@ export async function exportFollowing(job: Bull.Job<DbUserJobData>, done: () =>
let cursor: Following['id'] | null = null;
const mutings = job.data.excludeMuting ? await Mutings.find({
const mutings = job.data.excludeMuting ? await Mutings.findBy({
muterId: user.id,
}) : [];
@ -60,7 +60,7 @@ export async function exportFollowing(job: Bull.Job<DbUserJobData>, done: () =>
cursor = followings[followings.length - 1].id;
for (const following of followings) {
const u = await Users.findOne({ id: following.followeeId });
const u = await Users.findOneBy({ id: following.followeeId });
if (u == null) {
continue;
}

View File

@ -15,7 +15,7 @@ const logger = queueLogger.createSubLogger('export-mute');
export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promise<void> {
logger.info(`Exporting mute of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id);
const user = await Users.findOneBy({ id: job.data.user.id });
if (user == null) {
done();
return;
@ -57,7 +57,7 @@ export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promi
cursor = mutes[mutes.length - 1].id;
for (const mute of mutes) {
const u = await Users.findOne({ id: mute.muteeId });
const u = await Users.findOneBy({ id: mute.muteeId });
if (u == null) {
exportedCount++; continue;
}
@ -76,7 +76,7 @@ export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promi
exportedCount++;
}
const total = await Mutings.count({
const total = await Mutings.countBy({
muterId: user.id,
});

View File

@ -16,7 +16,7 @@ const logger = queueLogger.createSubLogger('export-notes');
export async function exportNotes(job: Bull.Job<DbUserJobData>, done: any): Promise<void> {
logger.info(`Exporting notes of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id);
const user = await Users.findOneBy({ id: job.data.user.id });
if (user == null) {
done();
return;
@ -74,7 +74,7 @@ export async function exportNotes(job: Bull.Job<DbUserJobData>, done: any): Prom
for (const note of notes) {
let poll: Poll | undefined;
if (note.hasPoll) {
poll = await Polls.findOneOrFail({ noteId: note.id });
poll = await Polls.findOneByOrFail({ noteId: note.id });
}
const content = JSON.stringify(serialize(note, poll));
const isFirst = exportedNotesCount === 0;
@ -82,7 +82,7 @@ export async function exportNotes(job: Bull.Job<DbUserJobData>, done: any): Prom
exportedNotesCount++;
}
const total = await Notes.count({
const total = await Notes.countBy({
userId: user.id,
});

View File

@ -15,13 +15,13 @@ const logger = queueLogger.createSubLogger('export-user-lists');
export async function exportUserLists(job: Bull.Job<DbUserJobData>, done: any): Promise<void> {
logger.info(`Exporting user lists of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id);
const user = await Users.findOneBy({ id: job.data.user.id });
if (user == null) {
done();
return;
}
const lists = await UserLists.find({
const lists = await UserLists.findBy({
userId: user.id,
});
@ -38,8 +38,8 @@ export async function exportUserLists(job: Bull.Job<DbUserJobData>, done: any):
const stream = fs.createWriteStream(path, { flags: 'a' });
for (const list of lists) {
const joinings = await UserListJoinings.find({ userListId: list.id });
const users = await Users.find({
const joinings = await UserListJoinings.findBy({ userListId: list.id });
const users = await Users.findBy({
id: In(joinings.map(j => j.userId)),
});

View File

@ -8,19 +8,20 @@ import { isSelfHost, toPuny } from '@/misc/convert-host.js';
import { Users, DriveFiles, Blockings } from '@/models/index.js';
import { DbUserImportJobData } from '@/queue/types.js';
import block from '@/services/blocking/create.js';
import { IsNull } from 'typeorm';
const logger = queueLogger.createSubLogger('import-blocking');
export async function importBlocking(job: Bull.Job<DbUserImportJobData>, done: any): Promise<void> {
logger.info(`Importing blocking of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id);
const user = await Users.findOneBy({ id: job.data.user.id });
if (user == null) {
done();
return;
}
const file = await DriveFiles.findOne({
const file = await DriveFiles.findOneBy({
id: job.data.fileId,
});
if (file == null) {
@ -39,10 +40,10 @@ export async function importBlocking(job: Bull.Job<DbUserImportJobData>, done: a
const acct = line.split(',')[0].trim();
const { username, host } = Acct.parse(acct);
let target = isSelfHost(host!) ? await Users.findOne({
host: null,
let target = isSelfHost(host!) ? await Users.findOneBy({
host: IsNull(),
usernameLower: username.toLowerCase(),
}) : await Users.findOne({
}) : await Users.findOneBy({
host: toPuny(host!),
usernameLower: username.toLowerCase(),
});

View File

@ -2,7 +2,6 @@ import Bull from 'bull';
import * as tmp from 'tmp';
import * as fs from 'node:fs';
import unzipper from 'unzipper';
import { getConnection } from 'typeorm';
import { queueLogger } from '../../logger.js';
import { downloadUrl } from '@/misc/download-url.js';
@ -10,6 +9,7 @@ import { DriveFiles, Emojis } from '@/models/index.js';
import { DbUserImportJobData } from '@/queue/types.js';
import { addFile } from '@/services/drive/add-file.js';
import { genId } from '@/misc/gen-id.js';
import { db } from '@/db/postgre.js';
const logger = queueLogger.createSubLogger('import-custom-emojis');
@ -17,7 +17,7 @@ const logger = queueLogger.createSubLogger('import-custom-emojis');
export async function importCustomEmojis(job: Bull.Job<DbUserImportJobData>, done: any): Promise<void> {
logger.info(`Importing custom emojis ...`);
const file = await DriveFiles.findOne({
const file = await DriveFiles.findOneBy({
id: job.data.fileId,
});
if (file == null) {
@ -72,10 +72,10 @@ export async function importCustomEmojis(job: Bull.Job<DbUserImportJobData>, don
originalUrl: driveFile.url,
publicUrl: driveFile.webpublicUrl ?? driveFile.url,
type: driveFile.webpublicType ?? driveFile.type,
}).then(x => Emojis.findOneOrFail(x.identifiers[0]));
}).then(x => Emojis.findOneByOrFail(x.identifiers[0]));
}
await getConnection().queryResultCache!.remove(['meta_emojis']);
await db.queryResultCache!.remove(['meta_emojis']);
cleanup();

View File

@ -8,19 +8,20 @@ import { downloadTextFile } from '@/misc/download-text-file.js';
import { isSelfHost, toPuny } from '@/misc/convert-host.js';
import { Users, DriveFiles } from '@/models/index.js';
import { DbUserImportJobData } from '@/queue/types.js';
import { IsNull } from 'typeorm';
const logger = queueLogger.createSubLogger('import-following');
export async function importFollowing(job: Bull.Job<DbUserImportJobData>, done: any): Promise<void> {
logger.info(`Importing following of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id);
const user = await Users.findOneBy({ id: job.data.user.id });
if (user == null) {
done();
return;
}
const file = await DriveFiles.findOne({
const file = await DriveFiles.findOneBy({
id: job.data.fileId,
});
if (file == null) {
@ -39,10 +40,10 @@ export async function importFollowing(job: Bull.Job<DbUserImportJobData>, done:
const acct = line.split(',')[0].trim();
const { username, host } = Acct.parse(acct);
let target = isSelfHost(host!) ? await Users.findOne({
host: null,
let target = isSelfHost(host!) ? await Users.findOneBy({
host: IsNull(),
usernameLower: username.toLowerCase(),
}) : await Users.findOne({
}) : await Users.findOneBy({
host: toPuny(host!),
usernameLower: username.toLowerCase(),
});

View File

@ -9,19 +9,20 @@ import { Users, DriveFiles, Mutings } from '@/models/index.js';
import { DbUserImportJobData } from '@/queue/types.js';
import { User } from '@/models/entities/user.js';
import { genId } from '@/misc/gen-id.js';
import { IsNull } from 'typeorm';
const logger = queueLogger.createSubLogger('import-muting');
export async function importMuting(job: Bull.Job<DbUserImportJobData>, done: any): Promise<void> {
logger.info(`Importing muting of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id);
const user = await Users.findOneBy({ id: job.data.user.id });
if (user == null) {
done();
return;
}
const file = await DriveFiles.findOne({
const file = await DriveFiles.findOneBy({
id: job.data.fileId,
});
if (file == null) {
@ -40,10 +41,10 @@ export async function importMuting(job: Bull.Job<DbUserImportJobData>, done: any
const acct = line.split(',')[0].trim();
const { username, host } = Acct.parse(acct);
let target = isSelfHost(host!) ? await Users.findOne({
host: null,
let target = isSelfHost(host!) ? await Users.findOneBy({
host: IsNull(),
usernameLower: username.toLowerCase(),
}) : await Users.findOne({
}) : await Users.findOneBy({
host: toPuny(host!),
usernameLower: username.toLowerCase(),
});

View File

@ -9,19 +9,20 @@ import { isSelfHost, toPuny } from '@/misc/convert-host.js';
import { DriveFiles, Users, UserLists, UserListJoinings } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js';
import { DbUserImportJobData } from '@/queue/types.js';
import { IsNull } from 'typeorm';
const logger = queueLogger.createSubLogger('import-user-lists');
export async function importUserLists(job: Bull.Job<DbUserImportJobData>, done: any): Promise<void> {
logger.info(`Importing user lists of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id);
const user = await Users.findOneBy({ id: job.data.user.id });
if (user == null) {
done();
return;
}
const file = await DriveFiles.findOne({
const file = await DriveFiles.findOneBy({
id: job.data.fileId,
});
if (file == null) {
@ -40,7 +41,7 @@ export async function importUserLists(job: Bull.Job<DbUserImportJobData>, done:
const listName = line.split(',')[0].trim();
const { username, host } = Acct.parse(line.split(',')[1].trim());
let list = await UserLists.findOne({
let list = await UserLists.findOneBy({
userId: user.id,
name: listName,
});
@ -51,13 +52,13 @@ export async function importUserLists(job: Bull.Job<DbUserImportJobData>, done:
createdAt: new Date(),
userId: user.id,
name: listName,
}).then(x => UserLists.findOneOrFail(x.identifiers[0]));
}).then(x => UserLists.findOneByOrFail(x.identifiers[0]));
}
let target = isSelfHost(host!) ? await Users.findOne({
host: null,
let target = isSelfHost(host!) ? await Users.findOneBy({
host: IsNull(),
usernameLower: username.toLowerCase(),
}) : await Users.findOne({
}) : await Users.findOneBy({
host: toPuny(host!),
usernameLower: username.toLowerCase(),
});
@ -66,7 +67,7 @@ export async function importUserLists(job: Bull.Job<DbUserImportJobData>, done:
target = await resolveUser(username, host);
}
if (await UserListJoinings.findOne({ userListId: list!.id, userId: target.id }) != null) continue;
if (await UserListJoinings.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue;
pushUserToUserList(target, list!);
} catch (e) {

View File

@ -8,7 +8,7 @@ import { createNotification } from '@/services/create-notification.js';
const logger = queueLogger.createSubLogger('ended-poll-notification');
export async function endedPollNotification(job: Bull.Job<EndedPollNotificationJobData>, done: any): Promise<void> {
const note = await Notes.findOne(job.data.noteId);
const note = await Notes.findOneBy({ id: job.data.noteId });
if (note == null || !note.hasPoll) {
done();
return;

View File

@ -15,6 +15,8 @@ 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 { CacheableRemoteUser } from '@/models/entities/user.js';
import { UserPublickey } from '@/models/entities/user-publickey.js';
const logger = new Logger('inbox');
@ -42,11 +44,13 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
return `Old keyId is no longer supported. ${keyIdLower}`;
}
// TDOO: キャッシュ
const dbResolver = new DbResolver();
// HTTP-Signature keyIdを元にDBから取得
let authUser = await dbResolver.getAuthUserFromKeyId(signature.keyId);
let authUser: {
user: CacheableRemoteUser;
key: UserPublickey | null;
} | null = await dbResolver.getAuthUserFromKeyId(signature.keyId);
// keyIdでわからなければ、activity.actorを元にDBから取得 || activity.actorを元にリモートから取得
if (authUser == null) {

View File

@ -37,7 +37,7 @@ export default async function cleanRemoteFiles(job: Bull.Job<Record<string, unkn
deletedCount += 8;
const total = await DriveFiles.count({
const total = await DriveFiles.countBy({
userHost: Not(IsNull()),
isLink: false,
});

View File

@ -0,0 +1,56 @@
import { URL } from 'node:url';
import Bull from 'bull';
import Logger from '@/services/logger.js';
import { WebhookDeliverJobData } from '../types.js';
import { getResponse, StatusError } from '@/misc/fetch.js';
import { Webhooks } from '@/models/index.js';
import config from '@/config/index.js';
const logger = new Logger('webhook');
let latest: string | null = null;
export default async (job: Bull.Job<WebhookDeliverJobData>) => {
try {
if (latest !== (latest = JSON.stringify(job.data.content, null, 2))) {
logger.debug(`delivering ${latest}`);
}
const res = await getResponse({
url: job.data.to,
method: 'POST',
headers: {
'User-Agent': 'Misskey-Hooks',
'X-Misskey-Host': config.host,
'X-Misskey-Hook-Id': job.data.webhookId,
'X-Misskey-Hook-Secret': job.data.secret,
},
body: JSON.stringify(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.isClientError) {
return `${res.statusCode} ${res.statusMessage}`;
}
// 5xx etc.
throw `${res.statusCode} ${res.statusMessage}`;
} else {
// DNS error, socket error, timeout ...
throw res;
}
}
};

View File

@ -1,6 +1,6 @@
import config from '@/config/index.js';
import { initialize as initializeQueue } from './initialize.js';
import { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData } from './types.js';
import { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData, WebhookDeliverJobData } from './types.js';
export const systemQueue = initializeQueue<Record<string, unknown>>('system');
export const endedPollNotificationQueue = initializeQueue<EndedPollNotificationJobData>('endedPollNotification');
@ -8,3 +8,14 @@ export const deliverQueue = initializeQueue<DeliverJobData>('deliver', config.de
export const inboxQueue = initializeQueue<InboxJobData>('inbox', config.inboxJobPerSec || 16);
export const dbQueue = initializeQueue<DbJobData>('db');
export const objectStorageQueue = initializeQueue<ObjectStorageJobData>('objectStorage');
export const webhookDeliverQueue = initializeQueue<WebhookDeliverJobData>('webhookDeliver', 64);
export const queues = [
systemQueue,
endedPollNotificationQueue,
deliverQueue,
inboxQueue,
dbQueue,
objectStorageQueue,
webhookDeliverQueue,
];

View File

@ -1,6 +1,7 @@
import { DriveFile } from '@/models/entities/drive-file.js';
import { Note } from '@/models/entities/note';
import { User } from '@/models/entities/user.js';
import { Webhook } from '@/models/entities/webhook';
import { IActivity } from '@/remote/activitypub/type.js';
import httpSignature from 'http-signature';
@ -46,6 +47,13 @@ export type EndedPollNotificationJobData = {
noteId: Note['id'];
};
export type WebhookDeliverJobData = {
content: unknown;
webhookId: Webhook['id'];
to: string;
secret: string;
};
export type ThinUser = {
id: User['id'];
};

View File

@ -3,26 +3,26 @@ import Resolver from './resolver.js';
import { resolvePerson } from './models/person.js';
import { unique, concat } from '@/prelude/array.js';
import promiseLimit from 'promise-limit';
import { User, IRemoteUser } from '@/models/entities/user.js';
import { User, CacheableRemoteUser, CacheableUser } from '@/models/entities/user.js';
type Visibility = 'public' | 'home' | 'followers' | 'specified';
type AudienceInfo = {
visibility: Visibility,
mentionedUsers: User[],
visibleUsers: User[],
mentionedUsers: CacheableUser[],
visibleUsers: CacheableUser[],
};
export async function parseAudience(actor: IRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise<AudienceInfo> {
export async function parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise<AudienceInfo> {
const toGroups = groupingAudience(getApIds(to), actor);
const ccGroups = groupingAudience(getApIds(cc), actor);
const others = unique(concat([toGroups.other, ccGroups.other]));
const limit = promiseLimit<User | null>(2);
const limit = promiseLimit<CacheableUser | null>(2);
const mentionedUsers = (await Promise.all(
others.map(id => limit(() => resolvePerson(id, resolver).catch(() => null)))
)).filter((x): x is User => x != null);
)).filter((x): x is CacheableUser => x != null);
if (toGroups.public.length > 0) {
return {
@ -55,7 +55,7 @@ export async function parseAudience(actor: IRemoteUser, to?: ApObject, cc?: ApOb
};
}
function groupingAudience(ids: string[], actor: IRemoteUser) {
function groupingAudience(ids: string[], actor: CacheableRemoteUser) {
const groups = {
public: [] as string[],
followers: [] as string[],
@ -85,7 +85,7 @@ function isPublic(id: string) {
].includes(id);
}
function isFollowers(id: string, actor: IRemoteUser) {
function isFollowers(id: string, actor: CacheableRemoteUser) {
return (
id === (actor.followersUri || `${actor.uri}/followers`)
);

View File

@ -1,12 +1,17 @@
import escapeRegexp from 'escape-regexp';
import config from '@/config/index.js';
import { Note } from '@/models/entities/note.js';
import { User, IRemoteUser } from '@/models/entities/user.js';
import { User, IRemoteUser, CacheableRemoteUser, CacheableUser } from '@/models/entities/user.js';
import { UserPublickey } from '@/models/entities/user-publickey.js';
import { MessagingMessage } from '@/models/entities/messaging-message.js';
import { Notes, Users, UserPublickeys, MessagingMessages } from '@/models/index.js';
import { IObject, getApId } from './type.js';
import { resolvePerson } from './models/person.js';
import escapeRegexp from 'escape-regexp';
import { Cache } from '@/misc/cache.js';
import { uriPersonCache, userByIdCache } from '@/services/user-cache.js';
const publicKeyCache = new Cache<UserPublickey | null>(Infinity);
const publicKeyByUserIdCache = new Cache<UserPublickey | null>(Infinity);
export default class DbResolver {
constructor() {
@ -19,15 +24,15 @@ export default class DbResolver {
const parsed = this.parseUri(value);
if (parsed.id) {
return (await Notes.findOne({
return await Notes.findOneBy({
id: parsed.id,
})) || null;
});
}
if (parsed.uri) {
return (await Notes.findOne({
return await Notes.findOneBy({
uri: parsed.uri,
})) || null;
});
}
return null;
@ -37,15 +42,15 @@ export default class DbResolver {
const parsed = this.parseUri(value);
if (parsed.id) {
return (await MessagingMessages.findOne({
return await MessagingMessages.findOneBy({
id: parsed.id,
})) || null;
});
}
if (parsed.uri) {
return (await MessagingMessages.findOne({
return await MessagingMessages.findOneBy({
uri: parsed.uri,
})) || null;
});
}
return null;
@ -54,19 +59,19 @@ export default class DbResolver {
/**
* AP Person => Misskey User in DB
*/
public async getUserFromApId(value: string | IObject): Promise<User | null> {
public async getUserFromApId(value: string | IObject): Promise<CacheableUser | null> {
const parsed = this.parseUri(value);
if (parsed.id) {
return (await Users.findOne({
return await userByIdCache.fetchMaybe(parsed.id, () => Users.findOneBy({
id: parsed.id,
})) || null;
}).then(x => x ?? undefined)) ?? null;
}
if (parsed.uri) {
return (await Users.findOne({
return await uriPersonCache.fetch(parsed.uri, () => Users.findOneBy({
uri: parsed.uri,
})) || null;
}));
}
return null;
@ -75,17 +80,24 @@ export default class DbResolver {
/**
* AP KeyId => Misskey User and Key
*/
public async getAuthUserFromKeyId(keyId: string): Promise<AuthUser | null> {
const key = await UserPublickeys.findOne({
keyId,
});
public async getAuthUserFromKeyId(keyId: string): Promise<{
user: CacheableRemoteUser;
key: UserPublickey;
} | null> {
const key = await publicKeyCache.fetch(keyId, async () => {
const key = await UserPublickeys.findOneBy({
keyId,
});
if (key == null) return null;
return key;
}, key => key != null);
if (key == null) return null;
const user = await Users.findOne(key.userId) as IRemoteUser;
return {
user,
user: await userByIdCache.fetch(key.userId, () => Users.findOneByOrFail({ id: key.userId })) as CacheableRemoteUser,
key,
};
}
@ -93,12 +105,15 @@ export default class DbResolver {
/**
* AP Actor id => Misskey User and Key
*/
public async getAuthUserFromApId(uri: string): Promise<AuthUser | null> {
const user = await resolvePerson(uri) as IRemoteUser;
public async getAuthUserFromApId(uri: string): Promise<{
user: CacheableRemoteUser;
key: UserPublickey | null;
} | null> {
const user = await resolvePerson(uri) as CacheableRemoteUser;
if (user == null) return null;
const key = await UserPublickeys.findOne(user.id);
const key = await publicKeyByUserIdCache.fetch(user.id, () => UserPublickeys.findOneBy({ userId: user.id }), v => v != null);
return {
user,
@ -125,11 +140,6 @@ export default class DbResolver {
}
}
export type AuthUser = {
user: IRemoteUser;
key?: UserPublickey;
};
type UriParseResult = {
/** id in DB (local object only) */
id?: string;

View File

@ -1,6 +1,7 @@
import { Users, Followings } from '@/models/index.js';
import { ILocalUser, IRemoteUser, User } from '@/models/entities/user.js';
import { deliver } from '@/queue/index.js';
import { IsNull, Not } from 'typeorm';
//#region types
interface IRecipe {
@ -78,27 +79,46 @@ export default class DeliverManager {
const inboxes = new Set<string>();
// build inbox list
for (const recipe of this.recipes) {
if (isFollowers(recipe)) {
// followers deliver
const followers = await Followings.find({
followeeId: this.actor.id,
});
/*
build inbox list
for (const following of followers) {
if (Followings.isRemoteFollower(following)) {
const inbox = following.followerSharedInbox || following.followerInbox;
inboxes.add(inbox);
}
}
} else if (isDirect(recipe)) {
// direct deliver
const inbox = recipe.to.inbox;
if (inbox) inboxes.add(inbox);
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,
},
}) as {
followerSharedInbox: string | null;
followerInbox: string;
}[];
for (const following of followers) {
const inbox = following.followerSharedInbox || following.followerInbox;
inboxes.add(inbox);
}
}
this.recipes.filter((recipe): recipe is IDirectRecipe => {
// 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
})
.forEach(recipe => inboxes.add(recipe.to.inbox));
// deliver
for (const inbox of inboxes) {
deliver(this.actor, this.activity, inbox);
@ -112,7 +132,7 @@ export default class DeliverManager {
* @param activity Activity
* @param from Followee
*/
export async function deliverToFollowers(actor: ILocalUser, activity: any) {
export async function deliverToFollowers(actor: { id: ILocalUser['id']; host: null; }, activity: any) {
const manager = new DeliverManager(actor, activity);
manager.addFollowersRecipe();
await manager.execute();
@ -123,7 +143,7 @@ export async function deliverToFollowers(actor: ILocalUser, activity: any) {
* @param activity Activity
* @param to Target user
*/
export async function deliverToUser(actor: ILocalUser, activity: any, to: IRemoteUser) {
export async function deliverToUser(actor: { id: ILocalUser['id']; host: null; }, activity: any, to: IRemoteUser) {
const manager = new DeliverManager(actor, activity);
manager.addDirectRecipe(to);
await manager.execute();

View File

@ -1,10 +1,10 @@
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import accept from '@/services/following/requests/accept.js';
import { IFollow } from '../../type.js';
import DbResolver from '../../db-resolver.js';
import { relayAccepted } from '@/services/relay.js';
export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => {
// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
const dbResolver = new DbResolver();

View File

@ -1,12 +1,12 @@
import Resolver from '../../resolver.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import acceptFollow from './follow.js';
import { IAccept, isFollow, getApType } from '../../type.js';
import { apLogger } from '../../logger.js';
const logger = apLogger;
export default async (actor: IRemoteUser, activity: IAccept): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IAccept): Promise<string> => {
const uri = activity.id || activity;
logger.info(`Accept: ${uri}`);

View File

@ -1,9 +1,9 @@
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { IAdd } from '../../type.js';
import { resolveNote } from '../../models/note.js';
import { addPinned } from '@/services/i/pin.js';
export default async (actor: IRemoteUser, activity: IAdd): Promise<void> => {
export default async (actor: CacheableRemoteUser, activity: IAdd): Promise<void> => {
if ('actor' in activity && actor.uri !== activity.actor) {
throw new Error('invalid actor');
}

View File

@ -1,12 +1,12 @@
import Resolver from '../../resolver.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import announceNote from './note.js';
import { IAnnounce, getApId } from '../../type.js';
import { apLogger } from '../../logger.js';
const logger = apLogger;
export default async (actor: IRemoteUser, activity: IAnnounce): Promise<void> => {
export default async (actor: CacheableRemoteUser, activity: IAnnounce): Promise<void> => {
const uri = getApId(activity);
logger.info(`Announce: ${uri}`);

View File

@ -1,6 +1,6 @@
import Resolver from '../../resolver.js';
import post from '@/services/note/create.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { IAnnounce, getApId } from '../../type.js';
import { fetchNote, resolveNote } from '../../models/note.js';
import { apLogger } from '../../logger.js';
@ -15,10 +15,9 @@ const logger = apLogger;
/**
* アナウンスアクティビティを捌きます
*/
export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
export default async function(resolver: Resolver, actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
const uri = getApId(activity);
// アナウンサーが凍結されていたらスキップ
if (actor.isSuspended) {
return;
}

View File

@ -1,9 +1,10 @@
import { IBlock } from '../../type.js';
import block from '@/services/blocking/create.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import DbResolver from '../../db-resolver.js';
import { Users } from '@/models/index.js';
export default async (actor: IRemoteUser, activity: IBlock): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IBlock): Promise<string> => {
// ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず
const dbResolver = new DbResolver();
@ -17,6 +18,6 @@ export default async (actor: IRemoteUser, activity: IBlock): Promise<string> =>
return `skip: ブロックしようとしているユーザーはローカルユーザーではありません`;
}
await block(actor, blockee);
await block(await Users.findOneByOrFail({ id: actor.id }), await Users.findOneByOrFail({ id: blockee.id }));
return `ok`;
};

View File

@ -1,5 +1,5 @@
import Resolver from '../../resolver.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import createNote from './note.js';
import { ICreate, getApId, isPost, getApType } from '../../type.js';
import { apLogger } from '../../logger.js';
@ -7,7 +7,7 @@ import { toArray, concat, unique } from '@/prelude/array.js';
const logger = apLogger;
export default async (actor: IRemoteUser, activity: ICreate): Promise<void> => {
export default async (actor: CacheableRemoteUser, activity: ICreate): Promise<void> => {
const uri = getApId(activity);
logger.info(`Create: ${uri}`);

View File

@ -1,5 +1,5 @@
import Resolver from '../../resolver.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { createNote, fetchNote } from '../../models/note.js';
import { getApId, IObject, ICreate } from '../../type.js';
import { getApLock } from '@/misc/app-lock.js';
@ -9,7 +9,7 @@ import { StatusError } from '@/misc/fetch.js';
/**
* 投稿作成アクティビティを捌きます
*/
export default async function(resolver: Resolver, actor: IRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<string> {
export default async function(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<string> {
const uri = getApId(note);
if (typeof note === 'object') {

View File

@ -1,18 +1,19 @@
import { apLogger } from '../../logger.js';
import { createDeleteAccountJob } from '@/queue/index.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { Users } from '@/models/index.js';
const logger = apLogger;
export async function deleteActor(actor: IRemoteUser, uri: string): Promise<string> {
export async function deleteActor(actor: CacheableRemoteUser, uri: string): Promise<string> {
logger.info(`Deleting the Actor: ${uri}`);
if (actor.uri !== uri) {
return `skip: delete actor ${actor.uri} !== ${uri}`;
}
if (actor.isDeleted) {
const user = await Users.findOneByOrFail({ id: actor.id });
if (user.isDeleted) {
logger.info(`skip: already deleted`);
}

View File

@ -1,5 +1,5 @@
import deleteNote from './note.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { IDelete, getApId, isTombstone, IObject, validPost, validActor } from '../../type.js';
import { toSingle } from '@/prelude/array.js';
import { deleteActor } from './actor.js';
@ -7,7 +7,7 @@ import { deleteActor } from './actor.js';
/**
* 削除アクティビティを捌きます
*/
export default async (actor: IRemoteUser, activity: IDelete): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IDelete): Promise<string> => {
if ('actor' in activity && actor.uri !== activity.actor) {
throw new Error('invalid actor');
}

View File

@ -1,4 +1,4 @@
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import deleteNode from '@/services/note/delete.js';
import { apLogger } from '../../logger.js';
import DbResolver from '../../db-resolver.js';
@ -7,7 +7,7 @@ import { deleteMessage } from '@/services/messages/delete.js';
const logger = apLogger;
export default async function(actor: IRemoteUser, uri: string): Promise<string> {
export default async function(actor: CacheableRemoteUser, uri: string): Promise<string> {
logger.info(`Deleting the Note: ${uri}`);
const unlock = await getApLock(uri);

View File

@ -1,17 +1,17 @@
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import config from '@/config/index.js';
import { IFlag, 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: IRemoteUser, activity: IFlag): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IFlag): Promise<string> => {
// objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので
// 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する
const uris = getApIds(activity.object);
const userIds = uris.filter(uri => uri.startsWith(config.url + '/users/')).map(uri => uri.split('/').pop()!);
const users = await Users.find({
const users = await Users.findBy({
id: In(userIds),
});
if (users.length < 1) return `skip`;

View File

@ -1,9 +1,9 @@
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import follow from '@/services/following/create.js';
import { IFollow } from '../type.js';
import DbResolver from '../db-resolver.js';
export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => {
const dbResolver = new DbResolver();
const followee = await dbResolver.getUserFromApId(activity.object);

View File

@ -1,5 +1,5 @@
import { IObject, isCreate, isDelete, isUpdate, isRead, isFollow, isAccept, isReject, isAdd, isRemove, isAnnounce, isLike, isUndo, isBlock, isCollectionOrOrderedCollection, isCollection, isFlag } from '../type.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import create from './create/index.js';
import performDeleteActivity from './delete/index.js';
import performUpdateActivity from './update/index.js';
@ -17,8 +17,9 @@ import flag from './flag/index.js';
import { apLogger } from '../logger.js';
import Resolver from '../resolver.js';
import { toArray } from '@/prelude/array.js';
import { Users } from '@/models/index.js';
export async function performActivity(actor: IRemoteUser, activity: IObject) {
export async function performActivity(actor: CacheableRemoteUser, activity: IObject) {
if (isCollectionOrOrderedCollection(activity)) {
const resolver = new Resolver();
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) {
@ -36,7 +37,7 @@ export async function performActivity(actor: IRemoteUser, activity: IObject) {
}
}
async function performOneActivity(actor: IRemoteUser, activity: IObject): Promise<void> {
async function performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise<void> {
if (actor.isSuspended) return;
if (isCreate(activity)) {

View File

@ -1,9 +1,9 @@
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { ILike, getApId } from '../type.js';
import create from '@/services/note/reaction/create.js';
import { fetchNote, extractEmojis } from '../models/note.js';
export default async (actor: IRemoteUser, activity: ILike) => {
export default async (actor: CacheableRemoteUser, activity: ILike) => {
const targetUri = getApId(activity.object);
const note = await fetchNote(targetUri);

View File

@ -1,10 +1,10 @@
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { IRead, 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: IRemoteUser, activity: IRead): Promise<string> => {
export const performReadActivity = async (actor: CacheableRemoteUser, activity: IRead): Promise<string> => {
const id = await getApId(activity.object);
if (!isSelfHost(extractDbHost(id))) {
@ -13,7 +13,7 @@ export const performReadActivity = async (actor: IRemoteUser, activity: IRead):
const messageId = id.split('/').pop();
const message = await MessagingMessages.findOne(messageId);
const message = await MessagingMessages.findOneBy({ id: messageId });
if (message == null) {
return `skip: message not found`;
}

View File

@ -1,11 +1,11 @@
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { remoteReject } from '@/services/following/reject.js';
import { IFollow } from '../../type.js';
import DbResolver from '../../db-resolver.js';
import { relayRejected } from '@/services/relay.js';
import { Users } from '@/models/index.js';
export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => {
// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
const dbResolver = new DbResolver();

View File

@ -1,12 +1,12 @@
import Resolver from '../../resolver.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import rejectFollow from './follow.js';
import { IReject, isFollow, getApType } from '../../type.js';
import { apLogger } from '../../logger.js';
const logger = apLogger;
export default async (actor: IRemoteUser, activity: IReject): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IReject): Promise<string> => {
const uri = activity.id || activity;
logger.info(`Reject: ${uri}`);

View File

@ -1,9 +1,9 @@
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { IRemove } from '../../type.js';
import { resolveNote } from '../../models/note.js';
import { removePinned } from '@/services/i/pin.js';
export default async (actor: IRemoteUser, activity: IRemove): Promise<void> => {
export default async (actor: CacheableRemoteUser, activity: IRemove): Promise<void> => {
if ('actor' in activity && actor.uri !== activity.actor) {
throw new Error('invalid actor');
}

View File

@ -1,11 +1,11 @@
import unfollow from '@/services/following/delete.js';
import cancelRequest from '@/services/following/requests/cancel.js';
import {IAccept} from '../../type.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { Followings } from '@/models/index.js';
import DbResolver from '../../db-resolver.js';
export default async (actor: IRemoteUser, activity: IAccept): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IAccept): Promise<string> => {
const dbResolver = new DbResolver();
const follower = await dbResolver.getUserFromApId(activity.object);
@ -13,7 +13,7 @@ export default async (actor: IRemoteUser, activity: IAccept): Promise<string> =>
return `skip: follower not found`;
}
const following = await Followings.findOne({
const following = await Followings.findOneBy({
followerId: follower.id,
followeeId: actor.id,
});

View File

@ -1,12 +1,12 @@
import { Notes } from '@/models/index.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { IAnnounce, getApId } from '../../type.js';
import deleteNote from '@/services/note/delete.js';
export const undoAnnounce = async (actor: IRemoteUser, activity: IAnnounce): Promise<string> => {
export const undoAnnounce = async (actor: CacheableRemoteUser, activity: IAnnounce): Promise<string> => {
const uri = getApId(activity);
const note = await Notes.findOne({
const note = await Notes.findOneBy({
uri,
});

View File

@ -1,9 +1,10 @@
import { IBlock } from '../../type.js';
import unblock from '@/services/blocking/delete.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import DbResolver from '../../db-resolver.js';
import { Users } from '@/models/index.js';
export default async (actor: IRemoteUser, activity: IBlock): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IBlock): Promise<string> => {
const dbResolver = new DbResolver();
const blockee = await dbResolver.getUserFromApId(activity.object);
@ -15,6 +16,6 @@ export default async (actor: IRemoteUser, activity: IBlock): Promise<string> =>
return `skip: ブロック解除しようとしているユーザーはローカルユーザーではありません`;
}
await unblock(actor, blockee);
await unblock(await Users.findOneByOrFail({ id: actor.id }), blockee);
return `ok`;
};

View File

@ -1,11 +1,11 @@
import unfollow from '@/services/following/delete.js';
import cancelRequest from '@/services/following/requests/cancel.js';
import { IFollow } from '../../type.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { FollowRequests, Followings } from '@/models/index.js';
import DbResolver from '../../db-resolver.js';
export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => {
const dbResolver = new DbResolver();
const followee = await dbResolver.getUserFromApId(activity.object);
@ -17,12 +17,12 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise<string> =>
return `skip: フォロー解除しようとしているユーザーはローカルユーザーではありません`;
}
const req = await FollowRequests.findOne({
const req = await FollowRequests.findOneBy({
followerId: actor.id,
followeeId: followee.id,
});
const following = await Followings.findOne({
const following = await Followings.findOneBy({
followerId: actor.id,
followeeId: followee.id,
});

View File

@ -1,5 +1,5 @@
import { IRemoteUser } from '@/models/entities/user.js';
import {IUndo, isFollow, isBlock, isLike, isAnnounce, getApType, isAccept} from '../../type.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { IUndo, isFollow, isBlock, isLike, isAnnounce, getApType, isAccept } from '../../type.js';
import unfollow from './follow.js';
import unblock from './block.js';
import undoLike from './like.js';
@ -10,7 +10,7 @@ import { apLogger } from '../../logger.js';
const logger = apLogger;
export default async (actor: IRemoteUser, activity: IUndo): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IUndo): Promise<string> => {
if ('actor' in activity && actor.uri !== activity.actor) {
throw new Error('invalid actor');
}

View File

@ -1,4 +1,4 @@
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { ILike, getApId } from '../../type.js';
import deleteReaction from '@/services/note/reaction/delete.js';
import { fetchNote } from '../../models/note.js';
@ -6,7 +6,7 @@ import { fetchNote } from '../../models/note.js';
/**
* Process Undo.Like activity
*/
export default async (actor: IRemoteUser, activity: ILike) => {
export default async (actor: CacheableRemoteUser, activity: ILike) => {
const targetUri = getApId(activity.object);
const note = await fetchNote(targetUri);

View File

@ -1,4 +1,4 @@
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { getApType, IUpdate, isActor } from '../../type.js';
import { apLogger } from '../../logger.js';
import { updateQuestion } from '../../models/question.js';
@ -8,7 +8,7 @@ import { updatePerson } from '../../models/person.js';
/**
* Updateアクティビティを捌きます
*/
export default async (actor: IRemoteUser, activity: IUpdate): Promise<string> => {
export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise<string> => {
if ('actor' in activity && actor.uri !== activity.actor) {
return `skip: invalid actor`;
}

View File

@ -1,10 +1,10 @@
import { uploadFromUrl } from '@/services/drive/upload-from-url.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser, IRemoteUser } from '@/models/entities/user.js';
import Resolver from '../resolver.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { apLogger } from '../logger.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { DriveFiles } from '@/models/index.js';
import { DriveFiles, Users } from '@/models/index.js';
import { truncate } from '@/misc/truncate.js';
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js';
@ -13,7 +13,7 @@ const logger = apLogger;
/**
* Imageを作成します。
*/
export async function createImage(actor: IRemoteUser, value: any): Promise<DriveFile> {
export async function createImage(actor: CacheableRemoteUser, value: any): Promise<DriveFile> {
// 投稿者が凍結されていたらスキップ
if (actor.isSuspended) {
throw new Error('actor has been suspended');
@ -47,7 +47,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<Drive
uri: image.url,
});
file = await DriveFiles.findOneOrFail(file.id);
file = await DriveFiles.findOneByOrFail({ id: file.id });
}
}
@ -60,7 +60,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<Drive
* Misskeyに対象のImageが登録されていればそれを返し、そうでなければ
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
*/
export async function resolveImage(actor: IRemoteUser, value: any): Promise<DriveFile> {
export async function resolveImage(actor: CacheableRemoteUser, value: any): Promise<DriveFile> {
// TODO
// リモートサーバーからフェッチしてきて登録

View File

@ -3,17 +3,17 @@ import { IObject, isMention, IApMention } from '../type.js';
import { resolvePerson } from './person.js';
import promiseLimit from 'promise-limit';
import Resolver from '../resolver.js';
import { User } from '@/models/entities/user.js';
import { CacheableUser, User } from '@/models/entities/user.js';
export async function extractApMentions(tags: IObject | IObject[] | null | undefined) {
const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string));
const resolver = new Resolver();
const limit = promiseLimit<User | null>(2);
const limit = promiseLimit<CacheableUser | null>(2);
const mentionedUsers = (await Promise.all(
hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null)))
)).filter((x): x is User => x != null);
)).filter((x): x is CacheableUser => x != null);
return mentionedUsers;
}

View File

@ -5,7 +5,7 @@ import Resolver from '../resolver.js';
import post from '@/services/note/create.js';
import { resolvePerson, updatePerson } from './person.js';
import { resolveImage } from './image.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser, IRemoteUser } from '@/models/entities/user.js';
import { htmlToMfm } from '../misc/html-to-mfm.js';
import { extractApHashtags } from './tag.js';
import { unique, toArray, toSingle } from '@/prelude/array.js';
@ -15,7 +15,7 @@ import { apLogger } from '../logger.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { deliverQuestionUpdate } from '@/services/note/polls/update.js';
import { extractDbHost, toPuny } from '@/misc/convert-host.js';
import { Emojis, Polls, MessagingMessages } from '@/models/index.js';
import { Emojis, Polls, MessagingMessages, Users } from '@/models/index.js';
import { Note } from '@/models/entities/note.js';
import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji, getApType } from '../type.js';
import { Emoji } from '@/models/entities/emoji.js';
@ -90,7 +90,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
logger.info(`Creating the Note: ${note.id}`);
// 投稿者をフェッチ
const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as IRemoteUser;
const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser;
// 投稿者が凍結されていたらスキップ
if (actor.isSuspended) {
@ -141,7 +141,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
const uri = getApId(note.inReplyTo);
if (uri.startsWith(config.url + '/')) {
const id = uri.split('/').pop();
const talk = await MessagingMessages.findOne(id);
const talk = await MessagingMessages.findOneBy({ id });
if (talk) {
isTalk = true;
return null;
@ -197,11 +197,11 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
const cw = note.summary === '' ? null : note.summary;
// テキストのパース
const text = note._misskey_content || (note.content ? htmlToMfm(note.content, note.tag) : null);
const text = typeof note._misskey_content !== 'undefined' ? note._misskey_content : (note.content ? htmlToMfm(note.content, note.tag) : null);
// vote
if (reply && reply.hasPoll) {
const poll = await Polls.findOneOrFail(reply.id);
const poll = await Polls.findOneByOrFail({ noteId: reply.id });
const tryCreateVote = async (name: string, index: number): Promise<null> => {
if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) {
@ -230,11 +230,6 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
const poll = await extractPollFromQuestion(note, resolver).catch(() => undefined);
// ユーザーの情報が古かったらついでに更新しておく
if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) {
if (actor.uri) updatePerson(actor.uri);
}
if (isTalk) {
for (const recipient of visibleUsers) {
await createMessage(actor, recipient, undefined, text || undefined, (files && files.length > 0) ? files[0] : null, object.id);
@ -311,7 +306,7 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr
const name = tag.name!.replace(/^:/, '').replace(/:$/, '');
tag.icon = toSingle(tag.icon);
const exists = await Emojis.findOne({
const exists = await Emojis.findOneBy({
host,
name,
});
@ -332,7 +327,7 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr
updatedAt: new Date(),
});
return await Emojis.findOne({
return await Emojis.findOneBy({
host,
name,
}) as Emoji;
@ -352,6 +347,6 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr
publicUrl: tag.icon!.url,
updatedAt: new Date(),
aliases: [],
} as Partial<Emoji>).then(x => Emojis.findOneOrFail(x.identifiers[0]));
} as Partial<Emoji>).then(x => Emojis.findOneByOrFail(x.identifiers[0]));
}));
}

View File

@ -15,7 +15,7 @@ import { apLogger } from '../logger.js';
import { Note } from '@/models/entities/note.js';
import { updateUsertags } from '@/services/update-hashtag.js';
import { Users, Instances, DriveFiles, Followings, UserProfiles, UserPublickeys } from '@/models/index.js';
import { User, IRemoteUser } from '@/models/entities/user.js';
import { User, IRemoteUser, CacheableUser } from '@/models/entities/user.js';
import { Emoji } from '@/models/entities/emoji.js';
import { UserNotePining } from '@/models/entities/user-note-pining.js';
import { genId } from '@/misc/gen-id.js';
@ -24,12 +24,14 @@ import { UserPublickey } from '@/models/entities/user-publickey.js';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
import { toPuny } from '@/misc/convert-host.js';
import { UserProfile } from '@/models/entities/user-profile.js';
import { getConnection } from 'typeorm';
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';
const logger = apLogger;
@ -91,19 +93,25 @@ function validateActor(x: IObject, uri: string): IActor {
*
* Misskeyに対象のPersonが登録されていればそれを返します。
*/
export async function fetchPerson(uri: string, resolver?: Resolver): Promise<User | null> {
export async function fetchPerson(uri: string, resolver?: Resolver): Promise<CacheableUser | null> {
if (typeof uri !== 'string') throw new Error('uri is not string');
const cached = uriPersonCache.get(uri);
if (cached) return cached;
// URIがこのサーバーを指しているならデータベースからフェッチ
if (uri.startsWith(config.url + '/')) {
const id = uri.split('/').pop();
return await Users.findOne(id).then(x => x || null);
const u = await Users.findOneBy({ id });
if (u) uriPersonCache.set(uri, u);
return u;
}
//#region このサーバーに既に登録されていたらそれを返す
const exist = await Users.findOne({ uri });
const exist = await Users.findOneBy({ uri });
if (exist) {
uriPersonCache.set(uri, exist);
return exist;
}
//#endregion
@ -143,7 +151,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
let user: IRemoteUser;
try {
// Start transaction
await getConnection().transaction(async transactionalEntityManager => {
await db.transaction(async transactionalEntityManager => {
user = await transactionalEntityManager.save(new User({
id: genId(),
avatarId: null,
@ -189,7 +197,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
// duplicate key error
if (isDuplicateKeyValueError(e)) {
// /users/@a => /users/:id のように入力がaliasなときにエラーになることがあるのを対応
const u = await Users.findOne({
const u = await Users.findOneBy({
uri: person.id,
});
@ -272,7 +280,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
}
//#region このサーバーに既に登録されているか
const exist = await Users.findOne({ uri }) as IRemoteUser;
const exist = await Users.findOneBy({ uri }) as IRemoteUser;
if (exist == null) {
return;
@ -352,6 +360,8 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
location: person['vcard:Address'] || null,
});
publishInternalEvent('remoteUserUpdated', { id: exist.id });
// ハッシュタグ更新
updateUsertags(exist, tags);
@ -371,7 +381,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
* Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
*/
export async function resolvePerson(uri: string, resolver?: Resolver): Promise<User> {
export async function resolvePerson(uri: string, resolver?: Resolver): Promise<CacheableUser> {
if (typeof uri !== 'string') throw new Error('uri is not string');
//#region このサーバーに既に登録されていたらそれを返す
@ -441,7 +451,7 @@ export function analyzeAttachments(attachments: IObject | IObject[] | undefined)
}
export async function updateFeatured(userId: User['id']) {
const user = await Users.findOneOrFail(userId);
const user = await Users.findOneByOrFail({ id: userId });
if (!Users.isRemoteUser(user)) return;
if (!user.featured) return;
@ -464,7 +474,7 @@ export async function updateFeatured(userId: User['id']) {
.slice(0, 5)
.map(item => limit(() => resolveNote(item, resolver))));
await getConnection().transaction(async transactionalEntityManager => {
await db.transaction(async transactionalEntityManager => {
await transactionalEntityManager.delete(UserNotePining, { userId: user.id });
// とりあえずidを別の時間で生成して順番を維持

View File

@ -47,10 +47,10 @@ export async function updateQuestion(value: any) {
if (uri.startsWith(config.url + '/')) throw new Error('uri points local');
//#region このサーバーに既に登録されているか
const note = await Notes.findOne({ uri });
const note = await Notes.findOneBy({ uri });
if (note == null) throw new Error('Question is not registed');
const poll = await Polls.findOne({ noteId: note.id });
const poll = await Polls.findOneBy({ noteId: note.id });
if (poll == null) throw new Error('Question is not registed');
//#endregion

View File

@ -1,7 +1,17 @@
import { IObject } from './type.js';
import { IRemoteUser } from '@/models/entities/user.js';
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { performActivity } from './kernel/index.js';
import { updatePerson } from './models/person.js';
export default async (actor: IRemoteUser, activity: IObject): Promise<void> => {
export default async (actor: CacheableRemoteUser, activity: IObject): Promise<void> => {
await performActivity(actor, activity);
// ついでにリモートユーザーの情報が古かったら更新しておく
if (actor.uri) {
if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) {
setImmediate(() => {
updatePerson(actor.uri!);
});
}
}
};

View File

@ -7,6 +7,6 @@ import { User } from '@/models/entities/user.js';
* @param id Follower|Followee ID
*/
export default async function renderFollowUser(id: User['id']): Promise<any> {
const user = await Users.findOneOrFail(id);
const user = await Users.findOneByOrFail({ id: id });
return Users.isLocalUser(user) ? `${config.url}/users/${user.id}` : user.uri;
}

Some files were not shown because too many files have changed in this diff Show More