perf(server): cache user info as much as possible
This commit is contained in:
parent
aebd77ad38
commit
1589e9c9b8
@ -840,6 +840,8 @@ tenMinutes: "10分"
|
||||
oneHour: "1時間"
|
||||
oneDay: "1日"
|
||||
oneWeek: "1週間"
|
||||
reflectMayTakeTime: "反映されるまで時間がかかる場合があります。"
|
||||
failedToFetchAccountInformation: "アカウント情報の取得に失敗しました"
|
||||
|
||||
_emailUnavailable:
|
||||
used: "既に使用されています"
|
||||
|
17
packages/backend/migration/1647777116829-user-indexes.js
Normal file
17
packages/backend/migration/1647777116829-user-indexes.js
Normal file
@ -0,0 +1,17 @@
|
||||
export class userIndexes1647777116829 {
|
||||
name = 'userIndexes1647777116829'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`CREATE INDEX "IDX_8977c6037a7bc2cb0c84b6d4db" ON "user" ("isSuspended") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_391e03c755cc3dffa85d85536f" ON "user" ("isSilenced") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_b2033a3235871353c93700a0b6" ON "user" ("isAdmin") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_dfb7b092897e7b354f73e7ae25" ON "user" ("isModerator") `);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_dfb7b092897e7b354f73e7ae25"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_b2033a3235871353c93700a0b6"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_391e03c755cc3dffa85d85536f"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_8977c6037a7bc2cb0c84b6d4db"`);
|
||||
}
|
||||
}
|
@ -28,12 +28,23 @@ 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) {
|
||||
if (validator) {
|
||||
if (validator(cachedValue)) {
|
||||
// Cache HIT
|
||||
return cachedValue;
|
||||
}
|
||||
} else {
|
||||
// Cache HIT
|
||||
return cachedValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache MISS
|
||||
const value = await fetcher();
|
||||
|
@ -106,12 +106,14 @@ export class User {
|
||||
})
|
||||
public tags: string[];
|
||||
|
||||
@Index()
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
comment: 'Whether the User is suspended.',
|
||||
})
|
||||
public isSuspended: boolean;
|
||||
|
||||
@Index()
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
comment: 'Whether the User is silenced.',
|
||||
@ -136,12 +138,14 @@ export class User {
|
||||
})
|
||||
public isCat: boolean;
|
||||
|
||||
@Index()
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
comment: 'Whether the User is the admin.',
|
||||
})
|
||||
public isAdmin: boolean;
|
||||
|
||||
@Index()
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
comment: 'Whether the User is a moderator.',
|
||||
@ -234,3 +238,27 @@ export interface ILocalUser extends User {
|
||||
export interface IRemoteUser extends User {
|
||||
host: string;
|
||||
}
|
||||
|
||||
export type CacheableLocalUser = {
|
||||
id: ILocalUser['id'];
|
||||
createdAt: ILocalUser['createdAt'];
|
||||
host: ILocalUser['host'];
|
||||
username: ILocalUser['username'];
|
||||
uri: ILocalUser['uri'];
|
||||
inbox: ILocalUser['inbox'];
|
||||
sharedInbox: ILocalUser['sharedInbox'];
|
||||
|
||||
showTimelineReplies: ILocalUser['showTimelineReplies'];
|
||||
};
|
||||
|
||||
export type CacheableRemoteUser = {
|
||||
id: IRemoteUser['id'];
|
||||
createdAt: IRemoteUser['createdAt'];
|
||||
host: IRemoteUser['host'];
|
||||
username: IRemoteUser['username'];
|
||||
uri: IRemoteUser['uri'];
|
||||
inbox: IRemoteUser['inbox'];
|
||||
sharedInbox: IRemoteUser['sharedInbox'];
|
||||
featured: IRemoteUser['featured'];
|
||||
followersUri: IRemoteUser['followersUri'];
|
||||
};
|
||||
|
@ -37,6 +37,65 @@ export class UserRepository extends Repository<User> {
|
||||
public validateBirthday = ajv.compile(this.birthdaySchema);
|
||||
//#endregion
|
||||
|
||||
private suspendedUsersCache: Set<User['id']> = new Set();
|
||||
private silencedUsersCache: Set<User['id']> = new Set();
|
||||
private moderatorsCache: Set<User['id']> = new Set();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const fetchCache = () => {
|
||||
this.find({
|
||||
where: {
|
||||
isSuspended: true,
|
||||
},
|
||||
select: ['id'],
|
||||
}).then(users => {
|
||||
this.suspendedUsersCache = new Set(users.map(user => user.id));
|
||||
});
|
||||
|
||||
this.find({
|
||||
where: {
|
||||
isSilenced: true,
|
||||
},
|
||||
select: ['id'],
|
||||
}).then(users => {
|
||||
this.silencedUsersCache = new Set(users.map(user => user.id));
|
||||
});
|
||||
|
||||
this.find({
|
||||
where: [{
|
||||
isAdmin: true,
|
||||
}, {
|
||||
isModerator: true,
|
||||
}],
|
||||
select: ['id'],
|
||||
}).then(users => {
|
||||
this.moderatorsCache = new Set(users.map(user => user.id));
|
||||
});
|
||||
};
|
||||
|
||||
setImmediate(() => {
|
||||
fetchCache();
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
fetchCache();
|
||||
}, 1000 * 60 * 5);
|
||||
}
|
||||
|
||||
public checkSuspended(userId: User['id']): boolean {
|
||||
return this.suspendedUsersCache.has(userId);
|
||||
}
|
||||
|
||||
public checkSilenced(userId: User['id']): boolean {
|
||||
return this.silencedUsersCache.has(userId);
|
||||
}
|
||||
|
||||
public checkModerator(userId: User['id']): boolean {
|
||||
return this.moderatorsCache.has(userId);
|
||||
}
|
||||
|
||||
public async getRelation(me: User['id'], target: User['id']) {
|
||||
const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([
|
||||
Followings.findOne({
|
||||
|
@ -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 = await dbResolver.getAuthUserFromKeyId(signature.keyId);
|
||||
|
||||
// keyIdでわからなければ、activity.actorを元にDBから取得 || activity.actorを元にリモートから取得
|
||||
if (authUser == null) {
|
||||
|
@ -3,7 +3,7 @@ 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 } from '@/models/entities/user.js';
|
||||
|
||||
type Visibility = 'public' | 'home' | 'followers' | 'specified';
|
||||
|
||||
@ -13,7 +13,7 @@ type AudienceInfo = {
|
||||
visibleUsers: User[],
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
@ -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`)
|
||||
);
|
||||
|
@ -1,12 +1,15 @@
|
||||
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 } 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';
|
||||
|
||||
const publicKeyCache = new Cache<(UserPublickey & { user: User }) | null>(Infinity);
|
||||
|
||||
export default class DbResolver {
|
||||
constructor() {
|
||||
@ -75,7 +78,11 @@ export default class DbResolver {
|
||||
/**
|
||||
* AP KeyId => Misskey User and Key
|
||||
*/
|
||||
public async getAuthUserFromKeyId(keyId: string): Promise<AuthUser | null> {
|
||||
public async getAuthUserFromKeyId(keyId: string): Promise<{
|
||||
user: CacheableRemoteUser;
|
||||
key: UserPublickey;
|
||||
} | null> {
|
||||
const key = await publicKeyCache.fetch(keyId, async () => {
|
||||
const key = await UserPublickeys.findOne({
|
||||
keyId,
|
||||
}, {
|
||||
@ -84,6 +91,11 @@ export default class DbResolver {
|
||||
|
||||
if (key == null) return null;
|
||||
|
||||
return key as UserPublickey & { user: User };
|
||||
}, key => key != null);
|
||||
|
||||
if (key == null) return null;
|
||||
|
||||
return {
|
||||
user: key.user as IRemoteUser,
|
||||
key,
|
||||
@ -93,7 +105,10 @@ export default class DbResolver {
|
||||
/**
|
||||
* AP Actor id => Misskey User and Key
|
||||
*/
|
||||
public async getAuthUserFromApId(uri: string): Promise<AuthUser | null> {
|
||||
public async getAuthUserFromApId(uri: string): Promise<{
|
||||
user: CacheableRemoteUser;
|
||||
key?: UserPublickey;
|
||||
} | null> {
|
||||
const user = await resolvePerson(uri) as IRemoteUser;
|
||||
|
||||
if (user == null) return null;
|
||||
@ -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;
|
||||
|
@ -112,7 +112,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 +123,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();
|
||||
|
@ -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();
|
||||
|
@ -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}`);
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -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}`);
|
||||
|
@ -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,14 +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;
|
||||
}
|
||||
|
||||
// アナウンス先をブロックしてたら中断
|
||||
const meta = await fetchMeta();
|
||||
if (meta.blockedHosts.includes(extractDbHost(uri))) return;
|
||||
|
@ -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.findOneOrFail(actor.id), blockee);
|
||||
return `ok`;
|
||||
};
|
||||
|
@ -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}`);
|
||||
|
@ -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') {
|
||||
|
@ -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.findOneOrFail(actor.id);
|
||||
if (user.isDeleted) {
|
||||
logger.info(`skip: already deleted`);
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -1,11 +1,11 @@
|
||||
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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,8 +37,8 @@ export async function performActivity(actor: IRemoteUser, activity: IObject) {
|
||||
}
|
||||
}
|
||||
|
||||
async function performOneActivity(actor: IRemoteUser, activity: IObject): Promise<void> {
|
||||
if (actor.isSuspended) return;
|
||||
async function performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise<void> {
|
||||
if (Users.checkSuspended(actor.id)) return;
|
||||
|
||||
if (isCreate(activity)) {
|
||||
await create(actor, activity);
|
||||
|
@ -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);
|
||||
|
@ -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))) {
|
||||
|
@ -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();
|
||||
|
@ -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}`);
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -1,9 +1,9 @@
|
||||
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({
|
||||
|
@ -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.findOneOrFail(actor.id), blockee);
|
||||
return `ok`;
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { IRemoteUser } from '@/models/entities/user.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';
|
||||
@ -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');
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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`;
|
||||
}
|
||||
|
@ -30,6 +30,9 @@ 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 { Cache } from '@/misc/cache.js';
|
||||
|
||||
const uriPersonCache = new Cache<User | null>(Infinity);
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
@ -90,6 +93,8 @@ function validateActor(x: IObject, uri: string): IActor {
|
||||
* Personをフェッチします。
|
||||
*
|
||||
* Misskeyに対象のPersonが登録されていればそれを返します。
|
||||
*
|
||||
* TODO: cache
|
||||
*/
|
||||
export async function fetchPerson(uri: string, resolver?: Resolver): Promise<User | null> {
|
||||
if (typeof uri !== 'string') throw new Error('uri is not string');
|
||||
|
@ -1,7 +1,7 @@
|
||||
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';
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IObject): Promise<void> => {
|
||||
export default async (actor: CacheableRemoteUser, activity: IObject): Promise<void> => {
|
||||
await performActivity(actor, activity);
|
||||
};
|
||||
|
@ -1,7 +1,13 @@
|
||||
import isNativeToken from './common/is-native-token.js';
|
||||
import { User } from '@/models/entities/user.js';
|
||||
import { CacheableLocalUser, ILocalUser, User } from '@/models/entities/user.js';
|
||||
import { Users, AccessTokens, Apps } from '@/models/index.js';
|
||||
import { AccessToken } from '@/models/entities/access-token.js';
|
||||
import { Cache } from '@/misc/cache.js';
|
||||
import { App } from '@/models/entities/app.js';
|
||||
|
||||
const userByNativeTokenCache = new Cache<CacheableLocalUser | null>(1000 * 60 * 5);
|
||||
const userByIdCache = new Cache<CacheableLocalUser>(1000 * 60 * 5);
|
||||
const appCache = new Cache<App>(Infinity);
|
||||
|
||||
export class AuthenticationError extends Error {
|
||||
constructor(message: string) {
|
||||
@ -10,15 +16,15 @@ export class AuthenticationError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export default async (token: string | null): Promise<[User | null | undefined, AccessToken | null | undefined]> => {
|
||||
export default async (token: string | null): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> => {
|
||||
if (token == null) {
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
if (isNativeToken(token)) {
|
||||
// Fetch user
|
||||
const user = await Users
|
||||
.findOne({ token });
|
||||
// TODO: typeorm 3.0にしたら .then(x => x || null) は消せる
|
||||
const user = await userByNativeTokenCache.fetch(token,
|
||||
() => Users.findOne({ token }).then(x => x || null) as Promise<ILocalUser | null>);
|
||||
|
||||
if (user == null) {
|
||||
throw new AuthenticationError('user not found');
|
||||
@ -42,14 +48,14 @@ export default async (token: string | null): Promise<[User | null | undefined, A
|
||||
lastUsedAt: new Date(),
|
||||
});
|
||||
|
||||
const user = await Users
|
||||
.findOne({
|
||||
const user = await userByIdCache.fetch(accessToken.userId,
|
||||
() => Users.findOne({
|
||||
id: accessToken.userId, // findOne(accessToken.userId) のように書かないのは後方互換性のため
|
||||
});
|
||||
}) as Promise<ILocalUser>);
|
||||
|
||||
if (accessToken.appId) {
|
||||
const app = await Apps
|
||||
.findOneOrFail(accessToken.appId);
|
||||
const app = await appCache.fetch(accessToken.appId,
|
||||
() => Apps.findOneOrFail(accessToken.appId!));
|
||||
|
||||
return [user, {
|
||||
id: accessToken.id,
|
||||
|
@ -1,11 +1,12 @@
|
||||
import Koa from 'koa';
|
||||
import { performance } from 'perf_hooks';
|
||||
import { limiter } from './limiter.js';
|
||||
import { User } from '@/models/entities/user.js';
|
||||
import { CacheableLocalUser, User } from '@/models/entities/user.js';
|
||||
import endpoints, { IEndpoint } from './endpoints.js';
|
||||
import { ApiError } from './error.js';
|
||||
import { apiLogger } from './logger.js';
|
||||
import { AccessToken } from '@/models/entities/access-token.js';
|
||||
import { Users } from '@/models/index.js';
|
||||
|
||||
const accessDenied = {
|
||||
message: 'Access denied.',
|
||||
@ -13,7 +14,7 @@ const accessDenied = {
|
||||
id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e',
|
||||
};
|
||||
|
||||
export default async (endpoint: string, user: User | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => {
|
||||
export default async (endpoint: string, user: CacheableLocalUser | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => {
|
||||
const isSecure = user != null && token == null;
|
||||
|
||||
const ep = endpoints.find(e => e.name === endpoint);
|
||||
@ -40,7 +41,7 @@ export default async (endpoint: string, user: User | null | undefined, token: Ac
|
||||
});
|
||||
}
|
||||
|
||||
if (ep.meta.requireCredential && user!.isSuspended) {
|
||||
if (ep.meta.requireCredential && Users.checkSuspended(user!.id)) {
|
||||
throw new ApiError({
|
||||
message: 'Your account has been suspended.',
|
||||
code: 'YOUR_ACCOUNT_SUSPENDED',
|
||||
@ -49,13 +50,17 @@ export default async (endpoint: string, user: User | null | undefined, token: Ac
|
||||
});
|
||||
}
|
||||
|
||||
if (ep.meta.requireAdmin && !user!.isAdmin) {
|
||||
if (ep.meta.requireAdmin || ep.meta.requireModerator) {
|
||||
const fullUser = await Users.findOneOrFail(user!.id);
|
||||
|
||||
if (ep.meta.requireAdmin && !fullUser.isAdmin) {
|
||||
throw new ApiError(accessDenied, { reason: 'You are not the admin.' });
|
||||
}
|
||||
|
||||
if (ep.meta.requireModerator && !user!.isAdmin && !user!.isModerator) {
|
||||
if (ep.meta.requireModerator && !fullUser.isAdmin && !fullUser.isModerator) {
|
||||
throw new ApiError(accessDenied, { reason: 'You are not a moderator.' });
|
||||
}
|
||||
}
|
||||
|
||||
if (token && ep.meta.kind && !token.permission.some(p => p === ep.meta.kind)) {
|
||||
throw new ApiError({
|
||||
@ -65,7 +70,7 @@ export default async (endpoint: string, user: User | null | undefined, token: Ac
|
||||
});
|
||||
}
|
||||
|
||||
if (ep.meta.requireCredential && ep.meta.limit && !user!.isAdmin && !user!.isModerator) {
|
||||
if (ep.meta.requireCredential && ep.meta.limit) {
|
||||
// Rate limit
|
||||
await limiter(ep as IEndpoint & { meta: { limit: NonNullable<IEndpoint['meta']['limit']> } }, user!).catch(e => {
|
||||
throw new ApiError({
|
||||
|
@ -1,30 +1,16 @@
|
||||
import * as fs from 'node:fs';
|
||||
import Ajv from 'ajv';
|
||||
import { ILocalUser } from '@/models/entities/user.js';
|
||||
import { CacheableLocalUser, ILocalUser } from '@/models/entities/user.js';
|
||||
import { IEndpointMeta } from './endpoints.js';
|
||||
import { ApiError } from './error.js';
|
||||
import { Schema, SchemaType } from '@/misc/schema.js';
|
||||
import { AccessToken } from '@/models/entities/access-token.js';
|
||||
|
||||
type SimpleUserInfo = {
|
||||
id: ILocalUser['id'];
|
||||
createdAt: ILocalUser['createdAt'];
|
||||
host: ILocalUser['host'];
|
||||
username: ILocalUser['username'];
|
||||
uri: ILocalUser['uri'];
|
||||
inbox: ILocalUser['inbox'];
|
||||
sharedInbox: ILocalUser['sharedInbox'];
|
||||
isAdmin: ILocalUser['isAdmin'];
|
||||
isModerator: ILocalUser['isModerator'];
|
||||
isSilenced: ILocalUser['isSilenced'];
|
||||
showTimelineReplies: ILocalUser['showTimelineReplies'];
|
||||
};
|
||||
|
||||
export type Response = Record<string, any> | void;
|
||||
|
||||
// TODO: paramsの型をT['params']のスキーマ定義から推論する
|
||||
type executor<T extends IEndpointMeta, Ps extends Schema> =
|
||||
(params: SchemaType<Ps>, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any, cleanup?: () => any) =>
|
||||
(params: SchemaType<Ps>, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, cleanup?: () => any) =>
|
||||
Promise<T['res'] extends undefined ? Response : SchemaType<NonNullable<T['res']>>>;
|
||||
|
||||
const ajv = new Ajv({
|
||||
@ -34,11 +20,11 @@ const ajv = new Ajv({
|
||||
ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/);
|
||||
|
||||
export default function <T extends IEndpointMeta, Ps extends Schema>(meta: T, paramDef: Ps, cb: executor<T, Ps>)
|
||||
: (params: any, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any) => Promise<any> {
|
||||
: (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any) => Promise<any> {
|
||||
|
||||
const validate = ajv.compile(paramDef);
|
||||
|
||||
return (params: any, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any) => {
|
||||
return (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any) => {
|
||||
function cleanup() {
|
||||
fs.unlink(file.path, () => {});
|
||||
}
|
||||
|
401
packages/backend/src/server/api/endpoints/admin/meta.ts
Normal file
401
packages/backend/src/server/api/endpoints/admin/meta.ts
Normal file
@ -0,0 +1,401 @@
|
||||
import config from '@/config/index.js';
|
||||
import define from '../../define.js';
|
||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['meta'],
|
||||
|
||||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
driveCapacityPerLocalUserMb: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
driveCapacityPerRemoteUserMb: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
cacheRemoteFiles: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
emailRequiredForSignup: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
enableHcaptcha: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
hcaptchaSiteKey: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
enableRecaptcha: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
recaptchaSiteKey: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
swPublickey: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
mascotImageUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
default: '/assets/ai.png',
|
||||
},
|
||||
bannerUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
errorImageUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
default: 'https://xn--931a.moe/aiart/yubitun.png',
|
||||
},
|
||||
iconUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
maxNoteTextLength: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
emojis: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
aliases: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
category: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
host: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'url',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ads: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
place: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'url',
|
||||
},
|
||||
imageUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'url',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
enableEmail: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
enableTwitterIntegration: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
enableGithubIntegration: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
enableDiscordIntegration: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
enableServiceWorker: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
translatorAvailable: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
proxyAccountName: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
userStarForReactionFallback: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
pinnedUsers: {
|
||||
type: 'array',
|
||||
optional: true, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
hiddenTags: {
|
||||
type: 'array',
|
||||
optional: true, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
blockedHosts: {
|
||||
type: 'array',
|
||||
optional: true, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
hcaptchaSecretKey: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
recaptchaSecretKey: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
proxyAccountId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
format: 'id',
|
||||
},
|
||||
twitterConsumerKey: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
twitterConsumerSecret: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
githubClientId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
githubClientSecret: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
discordClientId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
discordClientSecret: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
summaryProxy: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
smtpSecure: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
smtpHost: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
smtpPort: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
smtpUser: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
smtpPass: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
swPrivateKey: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
useObjectStorage: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
objectStorageBaseUrl: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStorageBucket: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStoragePrefix: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStorageEndpoint: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStorageRegion: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStoragePort: {
|
||||
type: 'number',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStorageAccessKey: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStorageSecretKey: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStorageUseSSL: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
objectStorageUseProxy: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
objectStorageSetPublicRead: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps, me) => {
|
||||
const instance = await fetchMeta(true);
|
||||
|
||||
return {
|
||||
maintainerName: instance.maintainerName,
|
||||
maintainerEmail: instance.maintainerEmail,
|
||||
version: config.version,
|
||||
name: instance.name,
|
||||
uri: config.url,
|
||||
description: instance.description,
|
||||
langs: instance.langs,
|
||||
tosUrl: instance.ToSUrl,
|
||||
repositoryUrl: instance.repositoryUrl,
|
||||
feedbackUrl: instance.feedbackUrl,
|
||||
disableRegistration: instance.disableRegistration,
|
||||
disableLocalTimeline: instance.disableLocalTimeline,
|
||||
disableGlobalTimeline: instance.disableGlobalTimeline,
|
||||
driveCapacityPerLocalUserMb: instance.localDriveCapacityMb,
|
||||
driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb,
|
||||
emailRequiredForSignup: instance.emailRequiredForSignup,
|
||||
enableHcaptcha: instance.enableHcaptcha,
|
||||
hcaptchaSiteKey: instance.hcaptchaSiteKey,
|
||||
enableRecaptcha: instance.enableRecaptcha,
|
||||
recaptchaSiteKey: instance.recaptchaSiteKey,
|
||||
swPublickey: instance.swPublicKey,
|
||||
themeColor: instance.themeColor,
|
||||
mascotImageUrl: instance.mascotImageUrl,
|
||||
bannerUrl: instance.bannerUrl,
|
||||
errorImageUrl: instance.errorImageUrl,
|
||||
iconUrl: instance.iconUrl,
|
||||
backgroundImageUrl: instance.backgroundImageUrl,
|
||||
logoImageUrl: instance.logoImageUrl,
|
||||
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため
|
||||
defaultLightTheme: instance.defaultLightTheme,
|
||||
defaultDarkTheme: instance.defaultDarkTheme,
|
||||
enableEmail: instance.enableEmail,
|
||||
enableTwitterIntegration: instance.enableTwitterIntegration,
|
||||
enableGithubIntegration: instance.enableGithubIntegration,
|
||||
enableDiscordIntegration: instance.enableDiscordIntegration,
|
||||
enableServiceWorker: instance.enableServiceWorker,
|
||||
translatorAvailable: instance.deeplAuthKey != null,
|
||||
pinnedPages: instance.pinnedPages,
|
||||
pinnedClipId: instance.pinnedClipId,
|
||||
cacheRemoteFiles: instance.cacheRemoteFiles,
|
||||
|
||||
useStarForReactionFallback: instance.useStarForReactionFallback,
|
||||
pinnedUsers: instance.pinnedUsers,
|
||||
hiddenTags: instance.hiddenTags,
|
||||
blockedHosts: instance.blockedHosts,
|
||||
hcaptchaSecretKey: instance.hcaptchaSecretKey,
|
||||
recaptchaSecretKey: instance.recaptchaSecretKey,
|
||||
proxyAccountId: instance.proxyAccountId,
|
||||
twitterConsumerKey: instance.twitterConsumerKey,
|
||||
twitterConsumerSecret: instance.twitterConsumerSecret,
|
||||
githubClientId: instance.githubClientId,
|
||||
githubClientSecret: instance.githubClientSecret,
|
||||
discordClientId: instance.discordClientId,
|
||||
discordClientSecret: instance.discordClientSecret,
|
||||
summalyProxy: instance.summalyProxy,
|
||||
email: instance.email,
|
||||
smtpSecure: instance.smtpSecure,
|
||||
smtpHost: instance.smtpHost,
|
||||
smtpPort: instance.smtpPort,
|
||||
smtpUser: instance.smtpUser,
|
||||
smtpPass: instance.smtpPass,
|
||||
swPrivateKey: instance.swPrivateKey,
|
||||
useObjectStorage: instance.useObjectStorage,
|
||||
objectStorageBaseUrl: instance.objectStorageBaseUrl,
|
||||
objectStorageBucket: instance.objectStorageBucket,
|
||||
objectStoragePrefix: instance.objectStoragePrefix,
|
||||
objectStorageEndpoint: instance.objectStorageEndpoint,
|
||||
objectStorageRegion: instance.objectStorageRegion,
|
||||
objectStoragePort: instance.objectStoragePort,
|
||||
objectStorageAccessKey: instance.objectStorageAccessKey,
|
||||
objectStorageSecretKey: instance.objectStorageSecretKey,
|
||||
objectStorageUseSSL: instance.objectStorageUseSSL,
|
||||
objectStorageUseProxy: instance.objectStorageUseProxy,
|
||||
objectStorageSetPublicRead: instance.objectStorageSetPublicRead,
|
||||
objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle,
|
||||
deeplAuthKey: instance.deeplAuthKey,
|
||||
deeplIsPro: instance.deeplIsPro,
|
||||
};
|
||||
});
|
@ -29,7 +29,8 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||
throw new Error('user not found');
|
||||
}
|
||||
|
||||
if ((me.isModerator && !me.isAdmin) && user.isAdmin) {
|
||||
const _me = await Users.findOneOrFail(me.id);
|
||||
if ((_me.isModerator && !_me.isAdmin) && user.isAdmin) {
|
||||
throw new Error('cannot show info of admin');
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { deleteFile } from '@/services/drive/delete-file.js';
|
||||
import { publishDriveStream } from '@/services/stream.js';
|
||||
import define from '../../../define.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import { DriveFiles } from '@/models/index.js';
|
||||
import { DriveFiles, Users } from '@/models/index.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['drive'],
|
||||
@ -42,7 +42,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
throw new ApiError(meta.errors.noSuchFile);
|
||||
}
|
||||
|
||||
if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) {
|
||||
if (!Users.checkModerator(user.id) && (file.userId !== user.id)) {
|
||||
throw new ApiError(meta.errors.accessDenied);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import define from '../../../define.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import { DriveFile } from '@/models/entities/drive-file.js';
|
||||
import { DriveFiles } from '@/models/index.js';
|
||||
import { DriveFiles, Users } from '@/models/index.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['drive'],
|
||||
@ -70,7 +70,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
throw new ApiError(meta.errors.noSuchFile);
|
||||
}
|
||||
|
||||
if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) {
|
||||
if (!Users.checkModerator(user.id) && (file.userId !== user.id)) {
|
||||
throw new ApiError(meta.errors.accessDenied);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { publishDriveStream } from '@/services/stream.js';
|
||||
import define from '../../../define.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import { DriveFiles, DriveFolders } from '@/models/index.js';
|
||||
import { DriveFiles, DriveFolders, Users } from '@/models/index.js';
|
||||
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js';
|
||||
|
||||
export const meta = {
|
||||
@ -64,7 +64,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
throw new ApiError(meta.errors.noSuchFile);
|
||||
}
|
||||
|
||||
if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) {
|
||||
if (!Users.checkModerator(user.id) && (file.userId !== user.id)) {
|
||||
throw new ApiError(meta.errors.accessDenied);
|
||||
}
|
||||
|
||||
|
@ -290,151 +290,6 @@ export const meta = {
|
||||
},
|
||||
},
|
||||
},
|
||||
userStarForReactionFallback: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
pinnedUsers: {
|
||||
type: 'array',
|
||||
optional: true, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
hiddenTags: {
|
||||
type: 'array',
|
||||
optional: true, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
blockedHosts: {
|
||||
type: 'array',
|
||||
optional: true, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
hcaptchaSecretKey: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
recaptchaSecretKey: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
proxyAccountId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
format: 'id',
|
||||
},
|
||||
twitterConsumerKey: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
twitterConsumerSecret: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
githubClientId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
githubClientSecret: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
discordClientId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
discordClientSecret: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
summaryProxy: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
smtpSecure: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
smtpHost: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
smtpPort: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
smtpUser: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
smtpPass: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
swPrivateKey: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
useObjectStorage: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
objectStorageBaseUrl: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStorageBucket: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStoragePrefix: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStorageEndpoint: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStorageRegion: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStoragePort: {
|
||||
type: 'number',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStorageAccessKey: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStorageSecretKey: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStorageUseSSL: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
objectStorageUseProxy: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
objectStorageSetPublicRead: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@ -552,45 +407,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||
serviceWorker: instance.enableServiceWorker,
|
||||
miauth: true,
|
||||
};
|
||||
|
||||
if (me && me.isAdmin) {
|
||||
response.useStarForReactionFallback = instance.useStarForReactionFallback;
|
||||
response.pinnedUsers = instance.pinnedUsers;
|
||||
response.hiddenTags = instance.hiddenTags;
|
||||
response.blockedHosts = instance.blockedHosts;
|
||||
response.hcaptchaSecretKey = instance.hcaptchaSecretKey;
|
||||
response.recaptchaSecretKey = instance.recaptchaSecretKey;
|
||||
response.proxyAccountId = instance.proxyAccountId;
|
||||
response.twitterConsumerKey = instance.twitterConsumerKey;
|
||||
response.twitterConsumerSecret = instance.twitterConsumerSecret;
|
||||
response.githubClientId = instance.githubClientId;
|
||||
response.githubClientSecret = instance.githubClientSecret;
|
||||
response.discordClientId = instance.discordClientId;
|
||||
response.discordClientSecret = instance.discordClientSecret;
|
||||
response.summalyProxy = instance.summalyProxy;
|
||||
response.email = instance.email;
|
||||
response.smtpSecure = instance.smtpSecure;
|
||||
response.smtpHost = instance.smtpHost;
|
||||
response.smtpPort = instance.smtpPort;
|
||||
response.smtpUser = instance.smtpUser;
|
||||
response.smtpPass = instance.smtpPass;
|
||||
response.swPrivateKey = instance.swPrivateKey;
|
||||
response.useObjectStorage = instance.useObjectStorage;
|
||||
response.objectStorageBaseUrl = instance.objectStorageBaseUrl;
|
||||
response.objectStorageBucket = instance.objectStorageBucket;
|
||||
response.objectStoragePrefix = instance.objectStoragePrefix;
|
||||
response.objectStorageEndpoint = instance.objectStorageEndpoint;
|
||||
response.objectStorageRegion = instance.objectStorageRegion;
|
||||
response.objectStoragePort = instance.objectStoragePort;
|
||||
response.objectStorageAccessKey = instance.objectStorageAccessKey;
|
||||
response.objectStorageSecretKey = instance.objectStorageSecretKey;
|
||||
response.objectStorageUseSSL = instance.objectStorageUseSSL;
|
||||
response.objectStorageUseProxy = instance.objectStorageUseProxy;
|
||||
response.objectStorageSetPublicRead = instance.objectStorageSetPublicRead;
|
||||
response.objectStorageS3ForcePathStyle = instance.objectStorageS3ForcePathStyle;
|
||||
response.deeplAuthKey = instance.deeplAuthKey;
|
||||
response.deeplIsPro = instance.deeplIsPro;
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
|
@ -48,7 +48,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (!user.isAdmin && !user.isModerator && (note.userId !== user.id)) {
|
||||
if (!Users.checkModerator(user.id) && (note.userId !== user.id)) {
|
||||
throw new ApiError(meta.errors.accessDenied);
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ import define from '../../define.js';
|
||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||
import { Notes } from '@/models/index.js';
|
||||
import { Notes, Users } from '@/models/index.js';
|
||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
||||
import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query.js';
|
||||
import { activeUsersChart } from '@/services/chart/index.js';
|
||||
@ -49,7 +49,7 @@ export const paramDef = {
|
||||
export default define(meta, paramDef, async (ps, user) => {
|
||||
const m = await fetchMeta();
|
||||
if (m.disableGlobalTimeline) {
|
||||
if (user == null || (!user.isAdmin && !user.isModerator)) {
|
||||
if (user == null || !Users.checkModerator(user.id)) {
|
||||
throw new ApiError(meta.errors.gtlDisabled);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import define from '../../define.js';
|
||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||
import { Followings, Notes } from '@/models/index.js';
|
||||
import { Followings, Notes, Users } from '@/models/index.js';
|
||||
import { Brackets } from 'typeorm';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
||||
@ -56,7 +56,7 @@ export const paramDef = {
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps, user) => {
|
||||
const m = await fetchMeta();
|
||||
if (m.disableLocalTimeline && !user.isAdmin && !user.isModerator) {
|
||||
if (m.disableLocalTimeline && !Users.checkModerator(user.id)) {
|
||||
throw new ApiError(meta.errors.stlDisabled);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import define from '../../define.js';
|
||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { Notes } from '@/models/index.js';
|
||||
import { Notes, Users } from '@/models/index.js';
|
||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
||||
@ -55,7 +55,7 @@ export const paramDef = {
|
||||
export default define(meta, paramDef, async (ps, user) => {
|
||||
const m = await fetchMeta();
|
||||
if (m.disableLocalTimeline) {
|
||||
if (user == null || (!user.isAdmin && !user.isModerator)) {
|
||||
if (user == null || !Users.checkModerator(user.id)) {
|
||||
throw new ApiError(meta.errors.ltlDisabled);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import define from '../../define.js';
|
||||
import { getNote } from '../../common/getters.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { NoteReactions } from '@/models/index.js';
|
||||
import { DeepPartial } from 'typeorm';
|
||||
@ -44,13 +43,8 @@ export const paramDef = {
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps, user) => {
|
||||
const note = await getNote(ps.noteId).catch(e => {
|
||||
if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
|
||||
throw e;
|
||||
});
|
||||
|
||||
const query = {
|
||||
noteId: note.id,
|
||||
noteId: ps.noteId,
|
||||
} as DeepPartial<NoteReaction>;
|
||||
|
||||
if (ps.type) {
|
||||
|
@ -70,7 +70,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||
}).then(x => AbuseUserReports.findOneOrFail(x.identifiers[0]));
|
||||
|
||||
// Publish event to moderators
|
||||
setTimeout(async () => {
|
||||
setImmediate(async () => {
|
||||
const moderators = await Users.find({
|
||||
where: [{
|
||||
isAdmin: true,
|
||||
@ -94,5 +94,5 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||
sanitizeHtml(ps.comment),
|
||||
sanitizeHtml(ps.comment));
|
||||
}
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
|
@ -61,7 +61,7 @@ export const paramDef = {
|
||||
export default define(meta, paramDef, async (ps, me) => {
|
||||
let user;
|
||||
|
||||
const isAdminOrModerator = me && (me.isAdmin || me.isModerator);
|
||||
const isAdminOrModerator = me && Users.checkModerator(me.id);
|
||||
|
||||
if (ps.userIds) {
|
||||
if (ps.userIds.length === 0) {
|
||||
|
@ -2,12 +2,12 @@ import Limiter from 'ratelimiter';
|
||||
import { redisClient } from '../../db/redis.js';
|
||||
import { IEndpoint } from './endpoints.js';
|
||||
import * as Acct from '@/misc/acct.js';
|
||||
import { User } from '@/models/entities/user.js';
|
||||
import { CacheableLocalUser, User } from '@/models/entities/user.js';
|
||||
import Logger from '@/services/logger.js';
|
||||
|
||||
const logger = new Logger('limiter');
|
||||
|
||||
export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndpoint['meta']['limit']> } }, user: User) => new Promise<void>((ok, reject) => {
|
||||
export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndpoint['meta']['limit']> } }, user: CacheableLocalUser) => new Promise<void>((ok, reject) => {
|
||||
const limitation = endpoint.meta.limit;
|
||||
|
||||
const key = Object.prototype.hasOwnProperty.call(limitation, 'key')
|
||||
|
@ -7,8 +7,17 @@ import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js';
|
||||
import { Users, FollowRequests, Followings } from '@/models/index.js';
|
||||
import { decrementFollowing } from './delete.js';
|
||||
|
||||
type Local = ILocalUser | { id: User['id']; host: User['host']; uri: User['host'] };
|
||||
type Remote = IRemoteUser;
|
||||
type Local = ILocalUser | {
|
||||
id: ILocalUser['id'];
|
||||
host: ILocalUser['host'];
|
||||
uri: ILocalUser['uri']
|
||||
};
|
||||
type Remote = IRemoteUser | {
|
||||
id: IRemoteUser['id'];
|
||||
host: IRemoteUser['host'];
|
||||
uri: IRemoteUser['uri'];
|
||||
inbox: IRemoteUser['inbox'];
|
||||
};
|
||||
type Both = Local | Remote;
|
||||
|
||||
/**
|
||||
|
@ -112,7 +112,7 @@ type Option = {
|
||||
app?: App | null;
|
||||
};
|
||||
|
||||
export default async (user: { id: User['id']; username: User['username']; host: User['host']; isSilenced: User['isSilenced']; createdAt: User['createdAt']; }, data: Option, silent = false) => new Promise<Note>(async (res, rej) => {
|
||||
export default async (user: { id: User['id']; username: User['username']; host: User['host']; createdAt: User['createdAt']; }, data: Option, silent = false) => new Promise<Note>(async (res, rej) => {
|
||||
// チャンネル外にリプライしたら対象のスコープに合わせる
|
||||
// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
|
||||
if (data.reply && data.channel && data.reply.channelId !== data.channel.id) {
|
||||
@ -137,7 +137,7 @@ export default async (user: { id: User['id']; username: User['username']; host:
|
||||
if (data.channel != null) data.localOnly = true;
|
||||
|
||||
// サイレンス
|
||||
if (user.isSilenced && data.visibility === 'public' && data.channel == null) {
|
||||
if (Users.checkSilenced(user.id) && data.visibility === 'public' && data.channel == null) {
|
||||
data.visibility = 'home';
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ import { Brackets, In } from 'typeorm';
|
||||
* @param user 投稿者
|
||||
* @param note 投稿
|
||||
*/
|
||||
export default async function(user: User, note: Note, quiet = false) {
|
||||
export default async function(user: { id: User['id']; uri: User['uri']; host: User['host']; }, note: Note, quiet = false) {
|
||||
const deletedAt = new Date();
|
||||
|
||||
// この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき
|
||||
@ -127,7 +127,7 @@ async function getMentionedRemoteUsers(note: Note) {
|
||||
}) as IRemoteUser[];
|
||||
}
|
||||
|
||||
async function deliverToConcerned(user: ILocalUser, note: Note, content: any) {
|
||||
async function deliverToConcerned(user: { id: ILocalUser['id']; host: null; }, note: Note, content: any) {
|
||||
deliverToFollowers(user, content);
|
||||
deliverToRelays(user, content);
|
||||
const remoteUsers = await getMentionedRemoteUsers(note);
|
||||
|
@ -2,7 +2,7 @@ import { del, get, set } from '@/scripts/idb-proxy';
|
||||
import { reactive } from 'vue';
|
||||
import * as misskey from 'misskey-js';
|
||||
import { apiUrl } from '@/config';
|
||||
import { waiting, api, popup, popupMenu, success } from '@/os';
|
||||
import { waiting, api, popup, popupMenu, success, alert } from '@/os';
|
||||
import { unisonReload, reloadChannel } from '@/scripts/unison-reload';
|
||||
import { showSuspendedDialog } from './scripts/show-suspended-dialog';
|
||||
import { i18n } from './i18n';
|
||||
@ -89,7 +89,11 @@ function fetchAccount(token): Promise<Account> {
|
||||
signout();
|
||||
});
|
||||
} else {
|
||||
signout();
|
||||
alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.failedToFetchAccountInformation,
|
||||
text: JSON.stringify(res.error),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.token = token;
|
||||
|
@ -84,7 +84,7 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('meta', { detail: true });
|
||||
const meta = await os.api('admin/meta');
|
||||
this.enableHcaptcha = meta.enableHcaptcha;
|
||||
this.hcaptchaSiteKey = meta.hcaptchaSiteKey;
|
||||
this.hcaptchaSecretKey = meta.hcaptchaSecretKey;
|
||||
|
@ -95,7 +95,7 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('meta', { detail: true });
|
||||
const meta = await os.api('admin/meta');
|
||||
this.enableEmail = meta.enableEmail;
|
||||
this.email = meta.email;
|
||||
this.smtpSecure = meta.smtpSecure;
|
||||
|
@ -42,7 +42,7 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('meta', { detail: true });
|
||||
const meta = await os.api('admin/meta');
|
||||
this.blockedHosts = meta.blockedHosts.join('\n');
|
||||
},
|
||||
|
||||
|
@ -60,7 +60,7 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('meta', { detail: true });
|
||||
const meta = await os.api('admin/meta');
|
||||
this.uri = meta.uri;
|
||||
this.enableDiscordIntegration = meta.enableDiscordIntegration;
|
||||
this.discordClientId = meta.discordClientId;
|
||||
|
@ -60,7 +60,7 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('meta', { detail: true });
|
||||
const meta = await os.api('admin/meta');
|
||||
this.uri = meta.uri;
|
||||
this.enableGithubIntegration = meta.enableGithubIntegration;
|
||||
this.githubClientId = meta.githubClientId;
|
||||
|
@ -60,7 +60,7 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('meta', { detail: true });
|
||||
const meta = await os.api('admin/meta');
|
||||
this.uri = meta.uri;
|
||||
this.enableTwitterIntegration = meta.enableTwitterIntegration;
|
||||
this.twitterConsumerKey = meta.twitterConsumerKey;
|
||||
|
@ -62,7 +62,7 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('meta', { detail: true });
|
||||
const meta = await os.api('admin/meta');
|
||||
this.enableTwitterIntegration = meta.enableTwitterIntegration;
|
||||
this.enableGithubIntegration = meta.enableGithubIntegration;
|
||||
this.enableDiscordIntegration = meta.enableDiscordIntegration;
|
||||
|
@ -120,7 +120,7 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('meta', { detail: true });
|
||||
const meta = await os.api('admin/meta');
|
||||
this.useObjectStorage = meta.useObjectStorage;
|
||||
this.objectStorageBaseUrl = meta.objectStorageBaseUrl;
|
||||
this.objectStorageBucket = meta.objectStorageBucket;
|
||||
|
@ -44,7 +44,7 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('meta', { detail: true });
|
||||
const meta = await os.api('admin/meta');
|
||||
},
|
||||
save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
|
@ -46,7 +46,7 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('meta', { detail: true });
|
||||
const meta = await os.api('admin/meta');
|
||||
this.proxyAccountId = meta.proxyAccountId;
|
||||
if (this.proxyAccountId) {
|
||||
this.proxyAccount = await os.api('users/show', { userId: this.proxyAccountId });
|
||||
|
@ -72,7 +72,7 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('meta', { detail: true });
|
||||
const meta = await os.api('admin/meta');
|
||||
this.summalyProxy = meta.summalyProxy;
|
||||
this.enableHcaptcha = meta.enableHcaptcha;
|
||||
this.enableRecaptcha = meta.enableRecaptcha;
|
||||
|
@ -210,7 +210,7 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('meta', { detail: true });
|
||||
const meta = await os.api('admin/meta');
|
||||
this.name = meta.name;
|
||||
this.description = meta.description;
|
||||
this.tosUrl = meta.tosUrl;
|
||||
|
@ -54,7 +54,7 @@
|
||||
</FormSlot>
|
||||
|
||||
<FormSwitch v-model="profile.isCat" class="_formBlock">{{ i18n.ts.flagAsCat }}<template #caption>{{ i18n.ts.flagAsCatDescription }}</template></FormSwitch>
|
||||
<FormSwitch v-model="profile.showTimelineReplies" class="_formBlock">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }}</template></FormSwitch>
|
||||
<FormSwitch v-model="profile.showTimelineReplies" class="_formBlock">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }} {{ i18n.ts.reflectMayTakeTime }}</template></FormSwitch>
|
||||
<FormSwitch v-model="profile.isBot" class="_formBlock">{{ i18n.ts.flagAsBot }}<template #caption>{{ i18n.ts.flagAsBotDescription }}</template></FormSwitch>
|
||||
|
||||
<FormSwitch v-model="profile.alwaysMarkNsfw" class="_formBlock">{{ i18n.ts.alwaysMarkSensitive }}</FormSwitch>
|
||||
|
@ -25,6 +25,7 @@
|
||||
<FormSwitch v-if="user.host == null && $i.isAdmin && (moderator || !user.isAdmin)" v-model="moderator" class="_formBlock" @update:modelValue="toggleModerator">{{ $ts.moderator }}</FormSwitch>
|
||||
<FormSwitch v-model="silenced" class="_formBlock" @update:modelValue="toggleSilence">{{ $ts.silence }}</FormSwitch>
|
||||
<FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ $ts.suspend }}</FormSwitch>
|
||||
{{ $ts.reflectMayTakeTime }}
|
||||
<FormButton v-if="user.host == null && iAmModerator" class="_formBlock" @click="resetPassword"><i class="fas fa-key"></i> {{ $ts.resetPassword }}</FormButton>
|
||||
</FormSection>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user