12.109.1-nca10.net-v1
This commit is contained in:
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 }));
|
||||
}
|
||||
|
@ -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({
|
||||
|
@ -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,
|
||||
});
|
||||
|
||||
|
49
packages/backend/src/misc/webhook-cache.ts
Normal file
49
packages/backend/src/misc/webhook-cache.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
});
|
Reference in New Issue
Block a user