forked from FoundKeyGang/FoundKey
Merge branch 'main' into mk.absturztau.be
This commit is contained in:
commit
7e808d663a
117 changed files with 748 additions and 887 deletions
|
@ -108,11 +108,6 @@ redis:
|
|||
#deliverJobMaxAttempts: 12
|
||||
#inboxJobMaxAttempts: 8
|
||||
|
||||
# Syslog option
|
||||
#syslog:
|
||||
# host: localhost
|
||||
# port: 514
|
||||
|
||||
# Proxy for HTTP/HTTPS outgoing connections
|
||||
#proxy: http://127.0.0.1:3128
|
||||
|
||||
|
|
|
@ -8,15 +8,15 @@ clone:
|
|||
pipeline:
|
||||
install:
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
branch: main
|
||||
event: push
|
||||
image: node:18.6.0
|
||||
commands:
|
||||
- yarn install
|
||||
build:
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
branch: main
|
||||
event: push
|
||||
image: node:18.6.0
|
||||
commands:
|
||||
- yarn build
|
||||
|
|
|
@ -8,15 +8,15 @@ clone:
|
|||
pipeline:
|
||||
install:
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
branch: main
|
||||
event: push
|
||||
image: node:18.6.0
|
||||
commands:
|
||||
- yarn install
|
||||
lint:
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
branch: main
|
||||
event: push
|
||||
image: node:18.6.0
|
||||
commands:
|
||||
- yarn workspace backend run lint
|
||||
|
|
|
@ -8,15 +8,15 @@ clone:
|
|||
pipeline:
|
||||
install:
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
branch: main
|
||||
event: push
|
||||
image: node:18.6.0
|
||||
commands:
|
||||
- yarn install
|
||||
lint:
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
branch: main
|
||||
event: push
|
||||
image: node:18.6.0
|
||||
commands:
|
||||
- yarn workspace client run lint
|
||||
|
|
|
@ -8,15 +8,15 @@ clone:
|
|||
pipeline:
|
||||
install:
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
branch: main
|
||||
event: push
|
||||
image: node:18.6.0
|
||||
commands:
|
||||
- yarn install
|
||||
lint:
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
branch: main
|
||||
event: push
|
||||
image: node:18.6.0
|
||||
commands:
|
||||
- yarn workspace foundkey-js run lint
|
||||
|
|
|
@ -8,15 +8,15 @@ clone:
|
|||
pipeline:
|
||||
install:
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
branch: main
|
||||
event: push
|
||||
image: node:18.6.0
|
||||
commands:
|
||||
- yarn install
|
||||
lint:
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
branch: main
|
||||
event: push
|
||||
image: node:18.6.0
|
||||
commands:
|
||||
- yarn workspace sw run lint
|
||||
|
|
|
@ -5,11 +5,14 @@ clone:
|
|||
depth: 1 # CI does not need commit history
|
||||
recursive: true
|
||||
|
||||
depends_on:
|
||||
- build
|
||||
|
||||
pipeline:
|
||||
build:
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
branch: main
|
||||
event: push
|
||||
image: node:18.6.0
|
||||
commands:
|
||||
- yarn install
|
||||
|
@ -18,15 +21,15 @@ pipeline:
|
|||
- yarn build
|
||||
mocha:
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
branch: main
|
||||
event: push
|
||||
image: node:18.6.0
|
||||
commands:
|
||||
- yarn mocha
|
||||
e2e:
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
branch: main
|
||||
event: push
|
||||
image: cypress/included:10.3.0
|
||||
commands:
|
||||
- npm run start:test &
|
||||
|
|
|
@ -18,12 +18,11 @@ Please note that Emoji may be subject to copyright and you are responsible for c
|
|||
|
||||
If you have an image file that you would like to turn into a custom emoji you can import the image as an emoji.
|
||||
This works just like attaching files to a note:
|
||||
You can choose to upload a new file, pick a file from your Misskey drive or upload a file from another URL.
|
||||
You can choose to upload a new file, pick a file from your Foundkey drive or upload a file from another URL.
|
||||
|
||||
::: danger
|
||||
**Warning:**
|
||||
When you import emoji from your drive, the file will remain inside your drive.
|
||||
Misskey does not make a copy of this file so if you delete it, the emoji will be broken.
|
||||
:::
|
||||
Foundkey does not make a copy of this file so if you delete it, the emoji will be broken.
|
||||
|
||||
The emoji will be added to the instance and you will then be able to edit or delete it as usual.
|
||||
|
||||
|
@ -32,10 +31,9 @@ The emoji will be added to the instance and you will then be able to edit or del
|
|||
Emojis can be imported in bulk as packed ZIP files with a special format.
|
||||
This ability can be found in the three dots menu in the top right corner of the custom emoji menu.
|
||||
|
||||
::: warning
|
||||
**Warning:**
|
||||
Bulk emoji import may overwrite existing emoji or otherwise mess up your instance.
|
||||
Be sure to only import emoji from trusted sources, ideally only ones you exported yourself.
|
||||
:::
|
||||
|
||||
### Packed emoji format
|
||||
|
||||
|
@ -89,10 +87,9 @@ The properties of an emoji can be edited by clicking it in the list of local emo
|
|||
When you click on a custom emoji, a dialog for editing the properties will open.
|
||||
This dialog will also allow you to delete an emoji.
|
||||
|
||||
::: danger
|
||||
**Warning:**
|
||||
When you delete a custom emoji, old notes that contain it will still have the text name of the emoji in it.
|
||||
The emoji will no longer be rendered correctly.
|
||||
:::
|
||||
|
||||
Note that remote emoji can not be edited or deleted.
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ cd packages/backend
|
|||
LINE_NUM="$(npx typeorm migration:show -d ormconfig.js | grep -n nsfwDetection1655368940105 | cut -d ':' -f 1)"
|
||||
NUM_MIGRATIONS="$(npx typeorm migration:show -d ormconfig.js | tail -n+"$LINE_NUM" | grep '\[X\]' | nl)"
|
||||
|
||||
for i in $(seq 1 $NUM_MIGRAIONS); do
|
||||
for i in $(seq 1 $NUM_MIGRATIONS); do
|
||||
npx typeorm migration:revert -d ormconfig.js
|
||||
done
|
||||
```
|
||||
|
|
|
@ -844,6 +844,7 @@ _ffVisibility:
|
|||
public: "Public"
|
||||
followers: "Visible to followers only"
|
||||
private: "Private"
|
||||
nobody: "Nobody (not even you)"
|
||||
_signup:
|
||||
almostThere: "Almost there"
|
||||
emailAddressInfo: "Please enter your email address. It will not be made public."
|
||||
|
|
|
@ -854,6 +854,18 @@ _mfm:
|
|||
spin: Animación (Spin)
|
||||
shakeDescription: Brinda al contenido una animación temblorosa.
|
||||
inlineMath: Función matemática (Inline)
|
||||
rainbow: Arcoíris
|
||||
x4Description: Muestra el contenido de la manera más grandemente posible.
|
||||
blurDescription: Muestra borroso el contenido. Se mostrará con claridad cuando se
|
||||
cubra.
|
||||
spinDescription: Da al contenido una animación de girar.
|
||||
x2: Grande
|
||||
x2Description: Muestra en grande el contenido.
|
||||
x3Description: Muestra más grande el contenido.
|
||||
x4: Increíblemente grande
|
||||
blur: Borroso
|
||||
fontDescription: Agrega la fuente para mostrar contenido.
|
||||
x3: Muy grande
|
||||
_instanceTicker:
|
||||
none: "No mostrar"
|
||||
remote: "Mostrar a usuarios remotos"
|
||||
|
@ -1318,3 +1330,9 @@ unlikeConfirm: ¿En verdad quieres remover tu like?
|
|||
breakFollow: Quitar seguidor
|
||||
reporter: Reportero
|
||||
continueThread: Ver la continuación del hilo
|
||||
uploadFailedSize: El archivo es muy grande para subirse.
|
||||
uploadFailed: Subida fallida
|
||||
uploadFailedDescription: No se pudo subir el archivo.
|
||||
movedTo: Este usuario se ha movido a {handle}.
|
||||
attachedToNotes: Notas del archivo
|
||||
showAttachedNotes: Mostrar notas del archivo
|
||||
|
|
|
@ -842,8 +842,8 @@ _ffVisibility:
|
|||
private: "비공개"
|
||||
_signup:
|
||||
almostThere: "거의 다 끝났습니다"
|
||||
emailAddressInfo: "당신이 사용하고 있는 이메일 주소를 입력해 주세요. 이메일 주소는 다른 유저에게 공개되지 않습니다."
|
||||
emailSent: "입력하신 메일 주소({email})로 확인 메일을 보내드렸습니다. 가입을 완료하시려면 보내드린 메일에 있는 링크로 접속해 주세요."
|
||||
emailAddressInfo: "당신이 사용하고 있는 이메일 주소를 입력해 주세요. 이메일 주소는 다른 유저에게 공개되지 않습니다."
|
||||
emailSent: "입력하신 메일 주소({email})로 확인 메일을 보내드렸습니다. 가입을 완료하시려면 보내드린 메일에 있는 링크로 접속해 주세요."
|
||||
_accountDelete:
|
||||
accountDelete: "계정 삭제"
|
||||
mayTakeTime: "계정 삭제는 서버에 부하를 가하기 때문에, 작성한 콘텐츠나 업로드한 파일의 수가 많으면 완료까지 시간이 걸릴 수 있습니다."
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
export class ffVisibilityNobody1684536337602 {
|
||||
name = 'ffVisibilityNobody1684536337602';
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TYPE "public"."user_profile_ffvisibility_enum" RENAME TO "user_profile_ffvisibility_enum_old"`);
|
||||
await queryRunner.query(`CREATE TYPE "public"."user_profile_ffvisibility_enum" AS ENUM('public', 'followers', 'private', 'nobody')`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "ffVisibility" DROP DEFAULT`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "ffVisibility" TYPE "public"."user_profile_ffvisibility_enum" USING "ffVisibility"::"text"::"public"."user_profile_ffvisibility_enum"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "ffVisibility" SET DEFAULT 'public'`);
|
||||
await queryRunner.query(`DROP TYPE "public"."user_profile_ffvisibility_enum_old"`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`CREATE TYPE "public"."user_profile_ffvisibility_enum_old" AS ENUM('public', 'followers', 'private')`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "ffVisibility" DROP DEFAULT`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "ffVisibility" TYPE "public"."user_profile_ffvisibility_enum_old" USING "ffVisibility"::"text"::"public"."user_profile_ffvisibility_enum_old"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "ffVisibility" SET DEFAULT 'public'`);
|
||||
await queryRunner.query(`DROP TYPE "public"."user_profile_ffvisibility_enum"`);
|
||||
await queryRunner.query(`ALTER TYPE "public"."user_profile_ffvisibility_enum_old" RENAME TO "user_profile_ffvisibility_enum"`);
|
||||
}
|
||||
}
|
|
@ -69,7 +69,7 @@
|
|||
"koa-views": "7.0.2",
|
||||
"mfm-js": "0.22.1",
|
||||
"mime-types": "2.1.35",
|
||||
"mocha": "10.0.0",
|
||||
"mocha": "10.2.0",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"nested-property": "4.0.0",
|
||||
"node-fetch": "3.2.6",
|
||||
|
@ -100,7 +100,6 @@
|
|||
"stringz": "2.1.0",
|
||||
"style-loader": "3.3.1",
|
||||
"summaly": "2.7.0",
|
||||
"syslog-pro": "1.0.0",
|
||||
"systeminformation": "5.11.22",
|
||||
"tinycolor2": "1.4.2",
|
||||
"tmp": "0.2.1",
|
||||
|
@ -158,7 +157,6 @@
|
|||
"@types/sinon": "^10.0.13",
|
||||
"@types/sinonjs__fake-timers": "8.1.2",
|
||||
"@types/speakeasy": "2.0.7",
|
||||
"@types/syslog-pro": "^1.0.0",
|
||||
"@types/tinycolor2": "1.4.3",
|
||||
"@types/tmp": "0.2.3",
|
||||
"@types/uuid": "8.3.4",
|
||||
|
|
|
@ -8,7 +8,7 @@ import chalkTemplate from 'chalk-template';
|
|||
import semver from 'semver';
|
||||
|
||||
import Logger from '@/services/logger.js';
|
||||
import loadConfig from '@/config/load.js';
|
||||
import { loadConfig } from '@/config/load.js';
|
||||
import { Config } from '@/config/types.js';
|
||||
import { showMachineInfo } from '@/misc/show-machine-info.js';
|
||||
import { envOption } from '@/env.js';
|
||||
|
@ -41,7 +41,7 @@ function greet(): void {
|
|||
}
|
||||
|
||||
bootLogger.info('Welcome to FoundKey!');
|
||||
bootLogger.info(`FoundKey v${meta.version}`, null, true);
|
||||
bootLogger.info(`FoundKey v${meta.version}`, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,7 +59,7 @@ export async function masterMain(): Promise<void> {
|
|||
config = loadConfigBoot();
|
||||
await connectDb();
|
||||
} catch (e) {
|
||||
bootLogger.error('Fatal error occurred during initialization', {}, true);
|
||||
bootLogger.error('Fatal error occurred during initialization', true);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ export async function masterMain(): Promise<void> {
|
|||
await spawnWorkers(config.clusterLimits);
|
||||
}
|
||||
|
||||
bootLogger.succ(`Now listening on port ${config.port} on ${config.url}`, null, true);
|
||||
bootLogger.succ(`Now listening on port ${config.port} on ${config.url}`, true);
|
||||
|
||||
if (!envOption.noDaemons) {
|
||||
import('../daemons/server-stats.js').then(x => x.serverStats());
|
||||
|
@ -84,7 +84,7 @@ function showEnvironment(): void {
|
|||
|
||||
if (env !== 'production') {
|
||||
logger.warn('The environment is not in production mode.');
|
||||
logger.warn('DO NOT USE FOR PRODUCTION PURPOSE!', {}, true);
|
||||
logger.warn('DO NOT USE FOR PRODUCTION PURPOSE!', true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,7 +109,7 @@ function loadConfigBoot(): Config {
|
|||
} catch (exception) {
|
||||
const e = exception as Partial<NodeJS.ErrnoException> | Error;
|
||||
if ('code' in e && e.code === 'ENOENT') {
|
||||
configLogger.error('Configuration file not found', {}, true);
|
||||
configLogger.error('Configuration file not found', true);
|
||||
process.exit(1);
|
||||
} else if (e instanceof Error) {
|
||||
configLogger.error(e.message);
|
||||
|
@ -133,7 +133,7 @@ async function connectDb(): Promise<void> {
|
|||
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', {}, true);
|
||||
dbLogger.error('Cannot connect', true);
|
||||
dbLogger.error(e as Error | string);
|
||||
process.exit(1);
|
||||
}
|
||||
|
@ -160,12 +160,24 @@ function spawnWorker(mode: 'web' | 'queue'): Promise<void> {
|
|||
return new Promise(res => {
|
||||
const worker = cluster.fork({ mode });
|
||||
worker.on('message', message => {
|
||||
if (message === 'listenFailed') {
|
||||
bootLogger.error('The server Listen failed due to the previous error.');
|
||||
process.exit(1);
|
||||
switch (message) {
|
||||
case 'listenFailed':
|
||||
bootLogger.error('The server Listen failed due to the previous error.');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'ready':
|
||||
res();
|
||||
break;
|
||||
case 'metaUpdate':
|
||||
// forward new instance metadata to all workers
|
||||
for (const otherWorker of Object.values(cluster.workers)) {
|
||||
// don't forward the message to the worker that sent it
|
||||
if (worker.id === otherWorker.id) continue;
|
||||
|
||||
otherWorker.send(message);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (message !== 'ready') return;
|
||||
res();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import load from './load.js';
|
||||
import { loadConfig } from './load.js';
|
||||
|
||||
export default load();
|
||||
export default loadConfig();
|
||||
|
|
|
@ -23,7 +23,7 @@ const path = process.env.NODE_ENV === 'test'
|
|||
? `${dir}/test.yml`
|
||||
: `${dir}/default.yml`;
|
||||
|
||||
export default function load(): Config {
|
||||
export function loadConfig(): Config {
|
||||
const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, 'utf-8'));
|
||||
const clientManifest = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/_client_dist_/manifest.json`, 'utf-8'));
|
||||
let config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
|
||||
|
|
|
@ -59,11 +59,6 @@ export type Source = {
|
|||
deliverJobMaxAttempts?: number;
|
||||
inboxJobMaxAttempts?: number;
|
||||
|
||||
syslog?: {
|
||||
host: string;
|
||||
port: number;
|
||||
};
|
||||
|
||||
mediaProxy?: string;
|
||||
proxyRemoteFiles?: boolean;
|
||||
internalStoragePath?: string;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import process from 'node:process';
|
||||
import push from 'web-push';
|
||||
import { db } from '@/db/postgre.js';
|
||||
import { Meta } from '@/models/entities/meta.js';
|
||||
|
@ -17,9 +18,20 @@ export async function setMeta(meta: Meta): Promise<void> {
|
|||
|
||||
cache = meta;
|
||||
|
||||
/*
|
||||
The meta is not included here because another process may have updated
|
||||
the content before the other process receives it.
|
||||
*/
|
||||
process.send!('metaUpdated');
|
||||
|
||||
unlock();
|
||||
}
|
||||
|
||||
// the primary will forward this message
|
||||
process.on('message', async message => {
|
||||
if (message === 'metaUpdated') await getMeta();
|
||||
});
|
||||
|
||||
/**
|
||||
* Performs the primitive database operation to fetch server configuration.
|
||||
* If there is no entry yet, inserts a new one.
|
||||
|
|
|
@ -260,9 +260,3 @@ export interface IRemoteUser extends User {
|
|||
host: string;
|
||||
token: null;
|
||||
}
|
||||
|
||||
export type CacheableLocalUser = ILocalUser;
|
||||
|
||||
export type CacheableRemoteUser = IRemoteUser;
|
||||
|
||||
export type CacheableUser = CacheableLocalUser | CacheableRemoteUser;
|
||||
|
|
|
@ -230,6 +230,36 @@ export const UserRepository = db.getRepository(User).extend({
|
|||
return `${config.url}/identicon/${userId}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines whether the followers/following of user `user` are visibile to user `me`.
|
||||
*/
|
||||
async areFollowersVisibleTo(user: User, me: { id: User['id'] } | null | undefined): Promise<boolean> {
|
||||
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
|
||||
|
||||
switch (profile.ffVisibility) {
|
||||
case 'public':
|
||||
return true;
|
||||
case 'followers':
|
||||
if (me == null) {
|
||||
return false;
|
||||
} else if (me.id === user.id) {
|
||||
return true;
|
||||
} else {
|
||||
return await Followings.count({
|
||||
where: {
|
||||
followerId: me.id,
|
||||
followeeId: user.id,
|
||||
},
|
||||
take: 1,
|
||||
}).then(n => n > 0);
|
||||
}
|
||||
case 'private':
|
||||
return me?.id === user.id;
|
||||
case 'nobody':
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>(
|
||||
src: User['id'] | User,
|
||||
me?: { id: User['id'] } | null | undefined,
|
||||
|
@ -270,15 +300,13 @@ export const UserRepository = db.getRepository(User).extend({
|
|||
.getMany() : [];
|
||||
const profile = opts.detail ? await UserProfiles.findOneByOrFail({ userId: user.id }) : null;
|
||||
|
||||
const followingCount = profile == null ? null :
|
||||
(profile.ffVisibility === 'public') || isMe ? user.followingCount :
|
||||
(profile.ffVisibility === 'followers') && relation?.isFollowing ? user.followingCount :
|
||||
null;
|
||||
const ffVisible = await this.areFollowersVisibleTo(user, me);
|
||||
|
||||
const followersCount = profile == null ? null :
|
||||
(profile.ffVisibility === 'public') || isMe ? user.followersCount :
|
||||
(profile.ffVisibility === 'followers') && relation?.isFollowing ? user.followersCount :
|
||||
null;
|
||||
const followingCount = opts.detail ? null :
|
||||
ffVisible ? user.followingCount : null;
|
||||
|
||||
const followersCount = opts.detail ? null :
|
||||
ffVisible ? user.followersCount : null;
|
||||
|
||||
const packed = {
|
||||
id: user.id,
|
||||
|
|
|
@ -40,8 +40,8 @@ systemQueue
|
|||
.on('waiting', (jobId) => systemLogger.debug(`waiting id=${jobId}`))
|
||||
.on('active', (job) => systemLogger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => systemLogger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => systemLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) }))
|
||||
.on('error', (job: any, err: Error) => systemLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
||||
.on('failed', (job, err) => systemLogger.warn(`failed(${err}) id=${job.id}`))
|
||||
.on('error', (job: any, err: Error) => systemLogger.error(`error ${err}`))
|
||||
.on('stalled', (job) => systemLogger.warn(`stalled id=${job.id}`));
|
||||
|
||||
deliverQueue
|
||||
|
@ -49,31 +49,31 @@ deliverQueue
|
|||
.on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('failed', (job, err) => deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`))
|
||||
.on('error', (job: any, err: Error) => deliverLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
||||
.on('error', (job: any, err: Error) => deliverLogger.error(`error ${err}`))
|
||||
.on('stalled', (job) => deliverLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
|
||||
|
||||
inboxQueue
|
||||
.on('waiting', (jobId) => inboxLogger.debug(`waiting id=${jobId}`))
|
||||
.on('active', (job) => inboxLogger.debug(`active ${getJobInfo(job, true)}`))
|
||||
.on('completed', (job, result) => inboxLogger.debug(`completed(${result}) ${getJobInfo(job, true)}`))
|
||||
.on('failed', (job, err) => inboxLogger.warn(`failed(${err}) ${getJobInfo(job)} activity=${job.data.activity ? job.data.activity.id : 'none'}`, { job, e: renderError(err) }))
|
||||
.on('error', (job: any, err: Error) => inboxLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
||||
.on('failed', (job, err) => inboxLogger.warn(`failed(${err}) ${getJobInfo(job)} activity=${job.data.activity ? job.data.activity.id : 'none'}`))
|
||||
.on('error', (job: any, err: Error) => inboxLogger.error(`error ${err}`))
|
||||
.on('stalled', (job) => inboxLogger.warn(`stalled ${getJobInfo(job)} activity=${job.data.activity ? job.data.activity.id : 'none'}`));
|
||||
|
||||
dbQueue
|
||||
.on('waiting', (jobId) => dbLogger.debug(`waiting id=${jobId}`))
|
||||
.on('active', (job) => dbLogger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => dbLogger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => dbLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) }))
|
||||
.on('error', (job: any, err: Error) => dbLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
||||
.on('failed', (job, err) => dbLogger.warn(`failed(${err}) id=${job.id}`))
|
||||
.on('error', (job: any, err: Error) => dbLogger.error(`error ${err}`))
|
||||
.on('stalled', (job) => dbLogger.warn(`stalled id=${job.id}`));
|
||||
|
||||
objectStorageQueue
|
||||
.on('waiting', (jobId) => objectStorageLogger.debug(`waiting id=${jobId}`))
|
||||
.on('active', (job) => objectStorageLogger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => objectStorageLogger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => objectStorageLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) }))
|
||||
.on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
||||
.on('failed', (job, err) => objectStorageLogger.warn(`failed(${err}) id=${job.id}`))
|
||||
.on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`))
|
||||
.on('stalled', (job) => objectStorageLogger.warn(`stalled id=${job.id}`));
|
||||
|
||||
webhookDeliverQueue
|
||||
|
@ -81,7 +81,7 @@ webhookDeliverQueue
|
|||
.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('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`))
|
||||
.on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
|
||||
|
||||
export async function deliver(content: IActivity|IActivity[], to: string | null) {
|
||||
|
|
|
@ -71,7 +71,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi
|
|||
try {
|
||||
await downloadUrl(emoji.originalUrl, emojiPath);
|
||||
downloaded = true;
|
||||
} catch (e) { // TODO: 何度か再試行
|
||||
} catch (e) { // TODO: retry
|
||||
logger.error(e instanceof Error ? e : new Error(e as string));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import promiseLimit from 'promise-limit';
|
||||
import { CacheableRemoteUser, CacheableUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser, User } from '@/models/entities/user.js';
|
||||
import { unique, concat } from '@/prelude/array.js';
|
||||
import { resolvePerson } from './models/person.js';
|
||||
import { Resolver } from './resolver.js';
|
||||
|
@ -9,20 +9,20 @@ type Visibility = 'public' | 'home' | 'followers' | 'specified';
|
|||
|
||||
type AudienceInfo = {
|
||||
visibility: Visibility,
|
||||
mentionedUsers: CacheableUser[],
|
||||
visibleUsers: CacheableUser[],
|
||||
mentionedUsers: User[],
|
||||
visibleUsers: User[],
|
||||
};
|
||||
|
||||
export async function parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise<AudienceInfo> {
|
||||
export async function parseAudience(actor: IRemoteUser, 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<CacheableUser | null>(2);
|
||||
const limit = promiseLimit<User | null>(2);
|
||||
const mentionedUsers = (await Promise.all(
|
||||
others.map(id => limit(() => resolvePerson(id, resolver).catch(() => null))),
|
||||
)).filter((x): x is CacheableUser => x != null);
|
||||
)).filter((x): x is User => x != null);
|
||||
|
||||
if (toGroups.public.length > 0) {
|
||||
return {
|
||||
|
@ -55,7 +55,7 @@ export async function parseAudience(actor: CacheableRemoteUser, to?: ApObject, c
|
|||
};
|
||||
}
|
||||
|
||||
function groupingAudience(ids: string[], actor: CacheableRemoteUser) {
|
||||
function groupingAudience(ids: string[], actor: IRemoteUser) {
|
||||
const groups = {
|
||||
public: [] as string[],
|
||||
followers: [] as string[],
|
||||
|
@ -85,7 +85,7 @@ function isPublic(id: string) {
|
|||
].includes(id);
|
||||
}
|
||||
|
||||
function isFollowers(id: string, actor: CacheableRemoteUser) {
|
||||
function isFollowers(id: string, actor: IRemoteUser) {
|
||||
return (
|
||||
id === (actor.followersUri || `${actor.uri}/followers`)
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import escapeRegexp from 'escape-regexp';
|
||||
import config from '@/config/index.js';
|
||||
import { Note } from '@/models/entities/note.js';
|
||||
import { CacheableUser } from '@/models/entities/user.js';
|
||||
import { User } from '@/models/entities/user.js';
|
||||
import { MessagingMessage } from '@/models/entities/messaging-message.js';
|
||||
import { Notes, MessagingMessages } from '@/models/index.js';
|
||||
import { uriPersonCache, userByIdCache } from '@/services/user-cache.js';
|
||||
|
@ -89,7 +89,7 @@ export class DbResolver {
|
|||
/**
|
||||
* AP Person => FoundKey User in DB
|
||||
*/
|
||||
public async getUserFromApId(value: string | IObject): Promise<CacheableUser | null> {
|
||||
public async getUserFromApId(value: string | IObject): Promise<User | null> {
|
||||
const parsed = parseUri(value);
|
||||
|
||||
if (parsed.local) {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { acceptFollowRequest } from '@/services/following/requests/accept.js';
|
||||
import { relayAccepted } from '@/services/relay.js';
|
||||
import { IFollow } from '@/remote/activitypub/type.js';
|
||||
import { DbResolver } from '@/remote/activitypub/db-resolver.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => {
|
||||
// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
|
||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
|
||||
// activity is a follow request started by this server, so activity.actor must be an existing local user.
|
||||
|
||||
const dbResolver = new DbResolver();
|
||||
const follower = await dbResolver.getUserFromApId(activity.actor);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { apLogger } from '@/remote/activitypub/logger.js';
|
||||
import { Resolver } from '@/remote/activitypub/resolver.js';
|
||||
import { IAccept, isFollow, getApType } from '@/remote/activitypub/type.js';
|
||||
import acceptFollow from './follow.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: IAccept, resolver: Resolver): Promise<string> => {
|
||||
export default async (actor: IRemoteUser, activity: IAccept, resolver: Resolver): Promise<string> => {
|
||||
const uri = activity.id || activity;
|
||||
|
||||
apLogger.info(`Accept: ${uri}`);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { addPinned } from '@/services/i/pin.js';
|
||||
import { resolveNote } from '@/remote/activitypub/models/note.js';
|
||||
import { IAdd } from '@/remote/activitypub/type.js';
|
||||
import { Resolver } from '@/remote/activitypub/resolver.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: IAdd, resolver: Resolver): Promise<void> => {
|
||||
export default async (actor: IRemoteUser, activity: IAdd, resolver: Resolver): Promise<void> => {
|
||||
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||
throw new Error('invalid actor');
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { apLogger } from '@/remote/activitypub/logger.js';
|
||||
import { Resolver } from '@/remote/activitypub/resolver.js';
|
||||
import { IAnnounce, getApId } from '@/remote/activitypub/type.js';
|
||||
import announceNote from './note.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: IAnnounce, resolver: Resolver): Promise<void> => {
|
||||
export default async (actor: IRemoteUser, activity: IAnnounce, resolver: Resolver): Promise<void> => {
|
||||
const uri = getApId(activity);
|
||||
|
||||
apLogger.info(`Announce: ${uri}`);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import post from '@/services/note/create.js';
|
||||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { extractDbHost } from '@/misc/convert-host.js';
|
||||
import { getApLock } from '@/misc/app-lock.js';
|
||||
import { StatusError } from '@/misc/fetch.js';
|
||||
|
@ -11,7 +11,7 @@ import { Resolver } from '@/remote/activitypub/resolver.js';
|
|||
import { IAnnounce, getApId } from '@/remote/activitypub/type.js';
|
||||
import { shouldBlockInstance } from '@/misc/should-block-instance.js';
|
||||
|
||||
export default async function(resolver: Resolver, actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
|
||||
export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
|
||||
const uri = getApId(activity);
|
||||
|
||||
if (actor.isSuspended) {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import block from '@/services/blocking/create.js';
|
||||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { Users } from '@/models/index.js';
|
||||
import { DbResolver } from '@/remote/activitypub/db-resolver.js';
|
||||
import { IBlock } from '@/remote/activitypub/type.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: IBlock): Promise<string> => {
|
||||
// ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず
|
||||
export default async (actor: IRemoteUser, activity: IBlock): Promise<string> => {
|
||||
// There is a block target in activity.object, which should be a local user that exists.
|
||||
|
||||
const dbResolver = new DbResolver();
|
||||
const blockee = await dbResolver.getUserFromApId(activity.object);
|
||||
|
@ -15,7 +15,7 @@ export default async (actor: CacheableRemoteUser, activity: IBlock): Promise<str
|
|||
}
|
||||
|
||||
if (blockee.host != null) {
|
||||
return 'skip: ブロックしようとしているユーザーはローカルユーザーではありません';
|
||||
return 'skip: blockee is not local';
|
||||
}
|
||||
|
||||
await block(await Users.findOneByOrFail({ id: actor.id }), await Users.findOneByOrFail({ id: blockee.id }));
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { toArray, concat, unique } from '@/prelude/array.js';
|
||||
import { Resolver } from '@/remote/activitypub/resolver.js';
|
||||
import { ICreate, getApId, isPost, getApType } from '../../type.js';
|
||||
import { apLogger } from '../../logger.js';
|
||||
import createNote from './note.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: ICreate, resolver: Resolver): Promise<void> => {
|
||||
export default async (actor: IRemoteUser, activity: ICreate, resolver: Resolver): Promise<void> => {
|
||||
const uri = getApId(activity);
|
||||
|
||||
apLogger.info(`Create: ${uri}`);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { getApLock } from '@/misc/app-lock.js';
|
||||
import { extractDbHost } from '@/misc/convert-host.js';
|
||||
import { StatusError } from '@/misc/fetch.js';
|
||||
|
@ -9,7 +9,7 @@ import { getApId, IObject } from '@/remote/activitypub/type.js';
|
|||
/**
|
||||
* 投稿作成アクティビティを捌きます
|
||||
*/
|
||||
export default async function(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false): Promise<string> {
|
||||
export default async function(resolver: Resolver, actor: IRemoteUser, note: IObject, silent = false): Promise<string> {
|
||||
const uri = getApId(note);
|
||||
|
||||
if (typeof note === 'object') {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { Users } from '@/models/index.js';
|
||||
import { apLogger } from '@/remote/activitypub/logger.js';
|
||||
import { deleteAccount } from '@/services/delete-account.js';
|
||||
|
||||
export async function deleteActor(actor: CacheableRemoteUser, uri: string): Promise<string> {
|
||||
export async function deleteActor(actor: IRemoteUser, uri: string): Promise<string> {
|
||||
apLogger.info(`Deleting the Actor: ${uri}`);
|
||||
|
||||
if (actor.uri !== uri) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { toSingle } from '@/prelude/array.js';
|
||||
import { IDelete, getApId, isTombstone, IObject, validPost, validActor } from '@/remote/activitypub/type.js';
|
||||
import { deleteActor } from './actor.js';
|
||||
|
@ -7,7 +7,7 @@ import deleteNote from './note.js';
|
|||
/**
|
||||
* 削除アクティビティを捌きます
|
||||
*/
|
||||
export default async (actor: CacheableRemoteUser, activity: IDelete): Promise<string> => {
|
||||
export default async (actor: IRemoteUser, activity: IDelete): Promise<string> => {
|
||||
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||
throw new Error('invalid actor');
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { deleteNotes } from '@/services/note/delete.js';
|
||||
import { getApLock } from '@/misc/app-lock.js';
|
||||
import { deleteMessage } from '@/services/messages/delete.js';
|
||||
import { DbResolver } from '@/remote/activitypub/db-resolver.js';
|
||||
import { apLogger } from '@/remote/activitypub/logger.js';
|
||||
|
||||
export default async function(actor: CacheableRemoteUser, uri: string): Promise<string> {
|
||||
export default async function(actor: IRemoteUser, uri: string): Promise<string> {
|
||||
apLogger.info(`Deleting the Note: ${uri}`);
|
||||
|
||||
const unlock = await getApLock(uri);
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { In } from 'typeorm';
|
||||
import config from '@/config/index.js';
|
||||
import { genId } from '@/misc/gen-id.js';
|
||||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { AbuseUserReports, Users } from '@/models/index.js';
|
||||
import { IFlag, getApIds } from '@/remote/activitypub/type.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: IFlag): Promise<string> => {
|
||||
// objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので
|
||||
// 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する
|
||||
export default async (actor: IRemoteUser, activity: IFlag): Promise<string> => {
|
||||
// The object is `(User|Note) | (User|Note)[]`, but since the database schema
|
||||
// cannot be made to handle every possible case, the target user is the first user
|
||||
// and everything else is stored by URL.
|
||||
const uris = getApIds(activity.object);
|
||||
|
||||
const userIds = uris.filter(uri => uri.startsWith(config.url + '/users/')).map(uri => uri.split('/').pop()!);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } 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: CacheableRemoteUser, activity: IFollow): Promise<string> => {
|
||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
|
||||
const dbResolver = new DbResolver();
|
||||
const followee = await dbResolver.getUserFromApId(activity.object);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { toArray } from '@/prelude/array.js';
|
||||
import { Resolver } from '@/remote/activitypub/resolver.js';
|
||||
import { extractDbHost } from '@/misc/convert-host.js';
|
||||
|
@ -21,7 +21,7 @@ import block from './block/index.js';
|
|||
import flag from './flag/index.js';
|
||||
import { move } from './move/index.js';
|
||||
|
||||
export async function performActivity(actor: CacheableRemoteUser, activity: IObject, resolver: Resolver): Promise<void> {
|
||||
export async function performActivity(actor: IRemoteUser, activity: IObject, resolver: Resolver): Promise<void> {
|
||||
if (isCollectionOrOrderedCollection(activity)) {
|
||||
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) {
|
||||
const act = await resolver.resolve(item);
|
||||
|
@ -38,7 +38,7 @@ export async function performActivity(actor: CacheableRemoteUser, activity: IObj
|
|||
}
|
||||
}
|
||||
|
||||
async function performOneActivity(actor: CacheableRemoteUser, activity: IObject, resolver: Resolver): Promise<void> {
|
||||
async function performOneActivity(actor: IRemoteUser, activity: IObject, resolver: Resolver): Promise<void> {
|
||||
if (actor.isSuspended) return;
|
||||
|
||||
if (typeof activity.id !== 'undefined') {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { createReaction } from '@/services/note/reaction/create.js';
|
||||
import { ILike, getApId } from '../type.js';
|
||||
import { fetchNote, extractEmojis } from '../models/note.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: ILike) => {
|
||||
export default async (actor: IRemoteUser, activity: ILike) => {
|
||||
const targetUri = getApId(activity.object);
|
||||
|
||||
const note = await fetchNote(targetUri);
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { IsNull } from 'typeorm';
|
||||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { resolvePerson } from '@/remote/activitypub/models/person.js';
|
||||
import { Followings, Users } from '@/models/index.js';
|
||||
import { createNotification } from '@/services/create-notification.js';
|
||||
import Resolver from '../../resolver.js';
|
||||
import { IMove, isActor, getApId } from '../../type.js';
|
||||
|
||||
export async function move(actor: CacheableRemoteUser, activity: IMove, resolver: Resolver): Promise<void> {
|
||||
export async function move(actor: IRemoteUser, activity: IMove, resolver: Resolver): Promise<void> {
|
||||
// actor is not move origin
|
||||
if (activity.object == null || getApId(activity.object) !== actor.uri) return;
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.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';
|
||||
import { IRead, getApId } from '../type.js';
|
||||
|
||||
export const performReadActivity = async (actor: CacheableRemoteUser, activity: IRead): Promise<string> => {
|
||||
export const performReadActivity = async (actor: IRemoteUser, activity: IRead): Promise<string> => {
|
||||
const id = await getApId(activity.object);
|
||||
|
||||
if (!isSelfHost(extractDbHost(id))) {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { remoteReject } from '@/services/following/reject.js';
|
||||
import { relayRejected } from '@/services/relay.js';
|
||||
import { Users } from '@/models/index.js';
|
||||
import { IFollow } from '../../type.js';
|
||||
import { DbResolver } from '../../db-resolver.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => {
|
||||
// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
|
||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
|
||||
// activity is a follow request started by this server, so activity.actor must be an existing local user.
|
||||
|
||||
const dbResolver = new DbResolver();
|
||||
const follower = await dbResolver.getUserFromApId(activity.actor);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { Resolver } from '@/remote/activitypub/resolver.js';
|
||||
import { apLogger } from '../../logger.js';
|
||||
import { IReject, isFollow, getApType } from '../../type.js';
|
||||
import rejectFollow from './follow.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: IReject, resolver: Resolver): Promise<string> => {
|
||||
export default async (actor: IRemoteUser, activity: IReject, resolver: Resolver): Promise<string> => {
|
||||
const uri = activity.id || activity;
|
||||
|
||||
apLogger.info(`Reject: ${uri}`);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { removePinned } from '@/services/i/pin.js';
|
||||
import { Resolver } from '@/remote/activitypub/resolver.js';
|
||||
import { IRemove } from '../../type.js';
|
||||
import { resolveNote } from '../../models/note.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: IRemove, resolver: Resolver): Promise<void> => {
|
||||
export default async (actor: IRemoteUser, activity: IRemove, resolver: Resolver): Promise<void> => {
|
||||
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||
throw new Error('invalid actor');
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import unfollow from '@/services/following/delete.js';
|
||||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { Followings } from '@/models/index.js';
|
||||
import { DbResolver } from '@/remote/activitypub/db-resolver.js';
|
||||
import { IAccept } from '@/remote/activitypub/type.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: IAccept): Promise<string> => {
|
||||
export default async (actor: IRemoteUser, 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 { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { deleteNotes } from '@/services/note/delete.js';
|
||||
import { IAnnounce, getApId } from '@/remote/activitypub/type.js';
|
||||
|
||||
export const undoAnnounce = async (actor: CacheableRemoteUser, activity: IAnnounce): Promise<string> => {
|
||||
export const undoAnnounce = async (actor: IRemoteUser, activity: IAnnounce): Promise<string> => {
|
||||
const uri = getApId(activity);
|
||||
|
||||
const note = await Notes.findOneBy({
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import unblock from '@/services/blocking/delete.js';
|
||||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { Users } from '@/models/index.js';
|
||||
import { IBlock } from '@/remote/activitypub/type.js';
|
||||
import { DbResolver } from '@/remote/activitypub/db-resolver.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: IBlock): Promise<string> => {
|
||||
export default async (actor: IRemoteUser, activity: IBlock): Promise<string> => {
|
||||
const dbResolver = new DbResolver();
|
||||
const blockee = await dbResolver.getUserFromApId(activity.object);
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import unfollow from '@/services/following/delete.js';
|
||||
import { cancelFollowRequest } from '@/services/following/requests/cancel.js';
|
||||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { FollowRequests, Followings } from '@/models/index.js';
|
||||
import { IFollow } from '@/remote/activitypub/type.js';
|
||||
import { DbResolver } from '@/remote/activitypub/db-resolver.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => {
|
||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
|
||||
const dbResolver = new DbResolver();
|
||||
|
||||
const followee = await dbResolver.getUserFromApId(activity.object);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { apLogger } from '@/remote/activitypub/logger.js';
|
||||
import { Resolver } from '@/remote/activitypub/resolver.js';
|
||||
import { IUndo, isFollow, isBlock, isLike, isAnnounce, getApType, isAccept } from '@/remote/activitypub/type.js';
|
||||
|
@ -8,7 +8,7 @@ import undoLike from './like.js';
|
|||
import undoAccept from './accept.js';
|
||||
import { undoAnnounce } from './announce.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: IUndo, resolver: Resolver): Promise<string> => {
|
||||
export default async (actor: IRemoteUser, activity: IUndo, resolver: Resolver): Promise<string> => {
|
||||
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||
throw new Error('invalid actor');
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { deleteReaction } from '@/services/note/reaction/delete.js';
|
||||
import { ILike, getApId } from '@/remote/activitypub/type.js';
|
||||
import { fetchNote } from '@/remote/activitypub/models/note.js';
|
||||
|
@ -6,7 +6,7 @@ import { fetchNote } from '@/remote/activitypub/models/note.js';
|
|||
/**
|
||||
* Process Undo.Like activity
|
||||
*/
|
||||
export default async (actor: CacheableRemoteUser, activity: ILike) => {
|
||||
export default async (actor: IRemoteUser, activity: ILike) => {
|
||||
const targetUri = getApId(activity.object);
|
||||
|
||||
const note = await fetchNote(targetUri);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { getApId, getApType, IUpdate, isActor } from '@/remote/activitypub/type.js';
|
||||
import { apLogger } from '@/remote/activitypub/logger.js';
|
||||
import { updateQuestion } from '@/remote/activitypub/models/question.js';
|
||||
|
@ -8,7 +8,7 @@ import { updatePerson } from '@/remote/activitypub/models/person.js';
|
|||
/**
|
||||
* Updateアクティビティを捌きます
|
||||
*/
|
||||
export default async (actor: CacheableRemoteUser, activity: IUpdate, resolver: Resolver): Promise<string> => {
|
||||
export default async (actor: IRemoteUser, activity: IUpdate, resolver: Resolver): Promise<string> => {
|
||||
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||
return 'skip: invalid actor';
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Cache } from '@/misc/cache.js';
|
||||
import { UserPublickeys } from '@/models/index.js';
|
||||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { UserPublickey } from '@/models/entities/user-publickey.js';
|
||||
import { uriPersonCache, userByIdCache } from '@/services/user-cache.js';
|
||||
import { createPerson } from '@/remote/activitypub/models/person.js';
|
||||
|
||||
export type AuthUser = {
|
||||
user: CacheableRemoteUser;
|
||||
user: IRemoteUser;
|
||||
key: UserPublickey;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { uploadFromUrl } from '@/services/drive/upload-from-url.js';
|
||||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||
import { DriveFile } from '@/models/entities/drive-file.js';
|
||||
import { DriveFiles } from '@/models/index.js';
|
||||
|
@ -11,7 +11,7 @@ import { apLogger } from '../logger.js';
|
|||
/**
|
||||
* Imageを作成します。
|
||||
*/
|
||||
export async function createImage(actor: CacheableRemoteUser, value: any, resolver: Resolver): Promise<DriveFile> {
|
||||
export async function createImage(actor: IRemoteUser, value: any, resolver: Resolver): Promise<DriveFile> {
|
||||
// 投稿者が凍結されていたらスキップ
|
||||
if (actor.isSuspended) {
|
||||
throw new Error('actor has been suspended');
|
||||
|
@ -58,7 +58,7 @@ export async function createImage(actor: CacheableRemoteUser, value: any, resolv
|
|||
* If the target Image is registered in FoundKey, return it; otherwise, fetch it from the remote server and return it.
|
||||
* Fetch the image from the remote server, register it in FoundKey and return it.
|
||||
*/
|
||||
export async function resolveImage(actor: CacheableRemoteUser, value: any, resolver: Resolver): Promise<DriveFile> {
|
||||
export async function resolveImage(actor: IRemoteUser, value: any, resolver: Resolver): Promise<DriveFile> {
|
||||
// TODO
|
||||
|
||||
// Fetch from remote server and register it.
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import promiseLimit from 'promise-limit';
|
||||
import { toArray, unique } from '@/prelude/array.js';
|
||||
import { CacheableUser } from '@/models/entities/user.js';
|
||||
import { User } from '@/models/entities/user.js';
|
||||
import { Resolver } from '@/remote/activitypub/resolver.js';
|
||||
import { IObject, isMention, IApMention } from '../type.js';
|
||||
import { resolvePerson } from './person.js';
|
||||
|
||||
export async function extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver): Promise<CacheableUser[]> {
|
||||
export async function extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver): Promise<User[]> {
|
||||
const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string));
|
||||
|
||||
const limit = promiseLimit<CacheableUser | null>(2);
|
||||
const limit = promiseLimit<User | null>(2);
|
||||
const mentionedUsers = (await Promise.all(
|
||||
hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null))),
|
||||
)).filter((x): x is CacheableUser => x != null);
|
||||
)).filter((x): x is User => x != null);
|
||||
|
||||
return mentionedUsers;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import promiseLimit from 'promise-limit';
|
|||
|
||||
import config from '@/config/index.js';
|
||||
import post from '@/services/note/create.js';
|
||||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { unique, toArray, toSingle } from '@/prelude/array.js';
|
||||
import { vote } from '@/services/note/polls/vote.js';
|
||||
import { DriveFile } from '@/models/entities/drive-file.js';
|
||||
|
@ -74,13 +74,7 @@ export async function createNote(value: string | IObject, resolver: Resolver, si
|
|||
|
||||
const err = validateNote(object);
|
||||
if (err) {
|
||||
apLogger.error(`${err.message}`, {
|
||||
resolver: {
|
||||
history: resolver.getHistory(),
|
||||
},
|
||||
value,
|
||||
object,
|
||||
});
|
||||
apLogger.error(`${err.message}`);
|
||||
throw new Error('invalid note');
|
||||
}
|
||||
|
||||
|
@ -91,7 +85,7 @@ export async function createNote(value: string | IObject, resolver: Resolver, si
|
|||
apLogger.info(`Creating the Note: ${note.id}`);
|
||||
|
||||
// 投稿者をフェッチ
|
||||
const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser;
|
||||
const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as IRemoteUser;
|
||||
|
||||
// 投稿者が凍結されていたらスキップ
|
||||
if (actor.isSuspended) {
|
||||
|
|
|
@ -6,7 +6,7 @@ import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instanc
|
|||
import { Note } from '@/models/entities/note.js';
|
||||
import { updateUsertags } from '@/services/update-hashtag.js';
|
||||
import { Users, Instances, Followings, UserProfiles, UserPublickeys } from '@/models/index.js';
|
||||
import { User, IRemoteUser, CacheableUser } from '@/models/entities/user.js';
|
||||
import { User, IRemoteUser, User } 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';
|
||||
|
@ -121,7 +121,7 @@ async function validateActor(x: IObject, resolver: Resolver): Promise<IActor> {
|
|||
*
|
||||
* If the target Person is registered in FoundKey, it is returned.
|
||||
*/
|
||||
export async function fetchPerson(uri: string): Promise<CacheableUser | null> {
|
||||
export async function fetchPerson(uri: string): Promise<User | null> {
|
||||
if (typeof uri !== 'string') throw new Error('uri is not string');
|
||||
|
||||
const cached = uriPersonCache.get(uri);
|
||||
|
@ -217,7 +217,7 @@ export async function createPerson(value: string | IObject, resolver: Resolver):
|
|||
} catch (e) {
|
||||
// duplicate key error
|
||||
if (isDuplicateKeyValueError(e)) {
|
||||
// /users/@a => /users/:id のように入力がaliasなときにエラーになることがあるのを対応
|
||||
// Fix an error when the input is an alias like /users/@a -> /users/:id
|
||||
const u = await Users.findOneBy({
|
||||
uri: person.id,
|
||||
});
|
||||
|
@ -394,7 +394,7 @@ export async function updatePerson(value: IObject | string, resolver: Resolver):
|
|||
* If the target Person is registered in FoundKey, return it; otherwise, fetch it from a remote server and return it.
|
||||
* Fetch the person from the remote server, register it in FoundKey, and return it.
|
||||
*/
|
||||
export async function resolvePerson(uri: string, resolver: Resolver, hint?: IObject): Promise<CacheableUser> {
|
||||
export async function resolvePerson(uri: string, resolver: Resolver, hint?: IObject): Promise<User> {
|
||||
if (typeof uri !== 'string') throw new Error('uri is not string');
|
||||
|
||||
//#region このサーバーに既に登録されていたらそれを返す
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { DAY } from '@/const.js';
|
||||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { IRemoteUser } from '@/models/entities/user.js';
|
||||
import { Resolver } from '@/remote/activitypub/resolver.js';
|
||||
import { IObject } from './type.js';
|
||||
import { performActivity } from './kernel/index.js';
|
||||
import { updatePerson } from './models/person.js';
|
||||
|
||||
export async function perform(actor: CacheableRemoteUser, activity: IObject, resolver: Resolver): Promise<void> {
|
||||
export async function perform(actor: IRemoteUser, activity: IObject, resolver: Resolver): Promise<void> {
|
||||
await performActivity(actor, activity, resolver);
|
||||
|
||||
// And while I'm at it, I'll update the remote user information if it's out of date.
|
||||
|
|
|
@ -6,7 +6,7 @@ import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
|||
import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js';
|
||||
import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js';
|
||||
import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js';
|
||||
import { Users, Followings, UserProfiles } from '@/models/index.js';
|
||||
import { Users, Followings } from '@/models/index.js';
|
||||
import { Following } from '@/models/entities/following.js';
|
||||
import { setResponseType } from '../activitypub.js';
|
||||
|
||||
|
@ -31,19 +31,12 @@ export default async (ctx: Router.RouterContext) => {
|
|||
return;
|
||||
}
|
||||
|
||||
//#region Check ff visibility
|
||||
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
|
||||
|
||||
if (profile.ffVisibility === 'private') {
|
||||
ctx.status = 403;
|
||||
ctx.set('Cache-Control', 'public, max-age=30');
|
||||
return;
|
||||
} else if (profile.ffVisibility === 'followers') {
|
||||
const ffVisible = await Users.areFollowersVisibleTo(user, null);
|
||||
if (!ffVisible) {
|
||||
ctx.status = 403;
|
||||
ctx.set('Cache-Control', 'public, max-age=30');
|
||||
return;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
const limit = 10;
|
||||
const partOf = `${config.url}/users/${userId}/followers`;
|
||||
|
|
|
@ -6,7 +6,7 @@ import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
|||
import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js';
|
||||
import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js';
|
||||
import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js';
|
||||
import { Users, Followings, UserProfiles } from '@/models/index.js';
|
||||
import { Users, Followings } from '@/models/index.js';
|
||||
import { Following } from '@/models/entities/following.js';
|
||||
import { setResponseType } from '../activitypub.js';
|
||||
|
||||
|
@ -31,19 +31,12 @@ export default async (ctx: Router.RouterContext) => {
|
|||
return;
|
||||
}
|
||||
|
||||
//#region Check ff visibility
|
||||
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
|
||||
|
||||
if (profile.ffVisibility === 'private') {
|
||||
ctx.status = 403;
|
||||
ctx.set('Cache-Control', 'public, max-age=30');
|
||||
return;
|
||||
} else if (profile.ffVisibility === 'followers') {
|
||||
const ffVisible = await Users.areFollowersVisibleTo(user, null);
|
||||
if (!ffVisible) {
|
||||
ctx.status = 403;
|
||||
ctx.set('Cache-Control', 'public, max-age=30');
|
||||
return;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
const limit = 10;
|
||||
const partOf = `${config.url}/users/${userId}/following`;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Koa from 'koa';
|
||||
|
||||
import { IEndpoint } from './endpoints.js';
|
||||
import authenticate, { AuthenticationError } from './authenticate.js';
|
||||
import { authenticate, AuthenticationError } from './authenticate.js';
|
||||
import call from './call.js';
|
||||
import { ApiError } from './error.js';
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { CacheableLocalUser } from '@/models/entities/user.js';
|
||||
import { ILocalUser } from '@/models/entities/user.js';
|
||||
import { Users, AccessTokens } from '@/models/index.js';
|
||||
import { AccessToken } from '@/models/entities/access-token.js';
|
||||
import { userByIdCache, localUserByNativeTokenCache } from '@/services/user-cache.js';
|
||||
|
@ -11,7 +11,7 @@ export class AuthenticationError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
export default async (authorization: string | null | undefined, bodyToken: string | null | undefined): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> => {
|
||||
export async function authenticate(authorization: string | null | undefined, bodyToken: string | null | undefined): Promise<[ILocalUser | null | undefined, AccessToken | null | undefined]> {
|
||||
let maybeToken: string | null = null;
|
||||
|
||||
// check if there is an authorization header set
|
||||
|
@ -66,4 +66,4 @@ export default async (authorization: string | null | undefined, bodyToken: strin
|
|||
|
||||
return [user, accessToken];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { performance } from 'perf_hooks';
|
||||
import Koa from 'koa';
|
||||
import { CacheableLocalUser } from '@/models/entities/user.js';
|
||||
import { ILocalUser } from '@/models/entities/user.js';
|
||||
import { AccessToken } from '@/models/entities/access-token.js';
|
||||
import { getIpHash } from '@/misc/get-ip-hash.js';
|
||||
import { limiter } from './limiter.js';
|
||||
import endpoints, { IEndpointMeta } from './endpoints.js';
|
||||
import { endpoints, IEndpointMeta } from './endpoints.js';
|
||||
import { ApiError } from './error.js';
|
||||
import { apiLogger } from './logger.js';
|
||||
|
||||
export default async (endpoint: string, user: CacheableLocalUser | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => {
|
||||
export default async (endpoint: string, user: ILocalUser | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => {
|
||||
const isSecure = user != null && token == null;
|
||||
const isModerator = user != null && (user.isModerator || user.isAdmin);
|
||||
|
||||
|
@ -82,15 +82,7 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
|
|||
if (e instanceof ApiError) {
|
||||
throw e;
|
||||
} else {
|
||||
apiLogger.error(`Internal error occurred in ${ep.name}: ${e.message}`, {
|
||||
ep: ep.name,
|
||||
ps: data,
|
||||
e: {
|
||||
message: e.message,
|
||||
code: e.name,
|
||||
stack: e.stack,
|
||||
},
|
||||
});
|
||||
apiLogger.error(`Internal error occurred in ${ep.name}: ${e.message}`);
|
||||
throw new ApiError('INTERNAL_ERROR', {
|
||||
e: {
|
||||
message: e.message,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as fs from 'node:fs';
|
||||
import Ajv from 'ajv';
|
||||
import { CacheableLocalUser } from '@/models/entities/user.js';
|
||||
import { ILocalUser } from '@/models/entities/user.js';
|
||||
import { Schema, SchemaType } from '@/misc/schema.js';
|
||||
import { AccessToken } from '@/models/entities/access-token.js';
|
||||
import { IEndpointMeta } from './endpoints.js';
|
||||
|
@ -10,7 +10,7 @@ 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 ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, cleanup?: () => any) =>
|
||||
(params: SchemaType<Ps>, user: T['requireCredential'] extends true ? ILocalUser : ILocalUser | null, token: AccessToken | null, file?: any, cleanup?: () => any) =>
|
||||
Promise<T['res'] extends undefined ? Response : SchemaType<NonNullable<T['res']>>>;
|
||||
|
||||
const ajv = new Ajv({
|
||||
|
@ -20,10 +20,10 @@ 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 ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any) => Promise<any> {
|
||||
: (params: any, user: T['requireCredential'] extends true ? ILocalUser : ILocalUser | null, token: AccessToken | null, file?: any) => Promise<any> {
|
||||
const validate = ajv.compile(paramDef);
|
||||
|
||||
return (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any) => {
|
||||
return (params: any, user: T['requireCredential'] extends true ? ILocalUser : ILocalUser | null, token: AccessToken | null, file?: any) => {
|
||||
function cleanup() {
|
||||
fs.unlink(file.path, () => {});
|
||||
}
|
||||
|
|
|
@ -713,7 +713,7 @@ export interface IEndpoint {
|
|||
params: Schema;
|
||||
}
|
||||
|
||||
const endpoints: IEndpoint[] = eps.map(([name, ep]) => {
|
||||
export const endpoints: IEndpoint[] = eps.map(([name, ep]) => {
|
||||
return {
|
||||
name,
|
||||
exec: ep.default,
|
||||
|
@ -721,5 +721,3 @@ const endpoints: IEndpoint[] = eps.map(([name, ep]) => {
|
|||
params: ep.paramDef,
|
||||
};
|
||||
});
|
||||
|
||||
export default endpoints;
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Resolver } from '@/remote/activitypub/resolver.js';
|
|||
import { extractDbHost } from '@/misc/convert-host.js';
|
||||
import { Users, Notes } from '@/models/index.js';
|
||||
import { Note } from '@/models/entities/note.js';
|
||||
import { CacheableLocalUser, User } from '@/models/entities/user.js';
|
||||
import { ILocalUser, User } from '@/models/entities/user.js';
|
||||
import { isActor, isPost } from '@/remote/activitypub/type.js';
|
||||
import { SchemaType } from '@/misc/schema.js';
|
||||
import { HOUR } from '@/const.js';
|
||||
|
@ -85,7 +85,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
/***
|
||||
* URIからUserかNoteを解決する
|
||||
*/
|
||||
async function fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
|
||||
async function fetchAny(uri: string, me: ILocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
|
||||
// Stop if the host is blocked.
|
||||
const host = extractDbHost(uri);
|
||||
if (await shouldBlockInstance(host)) {
|
||||
|
@ -122,7 +122,7 @@ async function fetchAny(uri: string, me: CacheableLocalUser | null | undefined):
|
|||
);
|
||||
}
|
||||
|
||||
async function mergePack(me: CacheableLocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise<SchemaType<typeof meta.res> | null> {
|
||||
async function mergePack(me: ILocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise<SchemaType<typeof meta.res> | null> {
|
||||
if (user != null) {
|
||||
return {
|
||||
type: 'User',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import define from '@/server/api/define.js';
|
||||
import endpoints from '@/server/api/endpoints.js';
|
||||
import { endpoints } from '@/server/api/endpoints.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: false,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import define from '@/server/api/define.js';
|
||||
import endpoints from '@/server/api/endpoints.js';
|
||||
import { endpoints } from '@/server/api/endpoints.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: false,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import RE2 from 're2';
|
||||
import * as mfm from 'mfm-js';
|
||||
import { notificationTypes } from 'foundkey-js';
|
||||
import { ffVisibility, notificationTypes } from 'foundkey-js';
|
||||
import { publishMainStream, publishUserEvent } from '@/services/stream.js';
|
||||
import { acceptAllFollowRequests } from '@/services/following/requests/accept-all.js';
|
||||
import { publishToFollowers } from '@/services/i/update.js';
|
||||
|
@ -67,7 +67,7 @@ export const paramDef = {
|
|||
injectFeaturedNote: { type: 'boolean' },
|
||||
receiveAnnouncementEmail: { type: 'boolean' },
|
||||
alwaysMarkNsfw: { type: 'boolean' },
|
||||
ffVisibility: { type: 'string', enum: ['public', 'followers', 'private'] },
|
||||
ffVisibility: { type: 'string', enum: ffVisibility },
|
||||
pinnedPageId: { type: 'array', items: {
|
||||
type: 'string', format: 'misskey:id',
|
||||
} },
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { IsNull } from 'typeorm';
|
||||
import { Users, Followings, UserProfiles } from '@/models/index.js';
|
||||
import { Users, Followings } from '@/models/index.js';
|
||||
import { toPunyNullable } from '@/misc/convert-host.js';
|
||||
import define from '@/server/api/define.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
@ -61,25 +61,8 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
|
||||
if (user == null) throw new ApiError('NO_SUCH_USER');
|
||||
|
||||
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
|
||||
|
||||
if (profile.ffVisibility === 'private') {
|
||||
if (me == null || (me.id !== user.id)) {
|
||||
throw new ApiError('ACCESS_DENIED');
|
||||
}
|
||||
} else if (profile.ffVisibility === 'followers') {
|
||||
if (me == null) {
|
||||
throw new ApiError('ACCESS_DENIED');
|
||||
} else if (me.id !== user.id) {
|
||||
const following = await Followings.countBy({
|
||||
followeeId: user.id,
|
||||
followerId: me.id,
|
||||
});
|
||||
if (!following) {
|
||||
throw new ApiError('ACCESS_DENIED');
|
||||
}
|
||||
}
|
||||
}
|
||||
const ffVisible = await Users.areFollowersVisibleTo(user, me);
|
||||
if (!ffVisible) throw new ApiError('ACCESS_DENIED');
|
||||
|
||||
const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId)
|
||||
.andWhere('following.followeeId = :userId', { userId: user.id })
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { IsNull } from 'typeorm';
|
||||
import { Users, Followings, UserProfiles } from '@/models/index.js';
|
||||
import { Users, Followings } from '@/models/index.js';
|
||||
import { toPunyNullable } from '@/misc/convert-host.js';
|
||||
import define from '@/server/api/define.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
@ -61,25 +61,8 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
|
||||
if (user == null) throw new ApiError('NO_SUCH_USER');
|
||||
|
||||
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
|
||||
|
||||
if (profile.ffVisibility === 'private') {
|
||||
if (me == null || (me.id !== user.id)) {
|
||||
throw new ApiError('ACCESS_DENIED');
|
||||
}
|
||||
} else if (profile.ffVisibility === 'followers') {
|
||||
if (me == null) {
|
||||
throw new ApiError('ACCESS_DENIED');
|
||||
} else if (me.id !== user.id) {
|
||||
const following = await Followings.countBy({
|
||||
followeeId: user.id,
|
||||
followerId: me.id,
|
||||
});
|
||||
if (!following) {
|
||||
throw new ApiError('ACCESS_DENIED');
|
||||
}
|
||||
}
|
||||
}
|
||||
const ffVisible = await Users.areFollowersVisibleTo(user, me);
|
||||
if (!ffVisible) throw new ApiError('ACCESS_DENIED');
|
||||
|
||||
const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId)
|
||||
.andWhere('following.followerId = :userId', { userId: user.id })
|
||||
|
|
|
@ -46,27 +46,27 @@ export const meta = {
|
|||
},
|
||||
localFollowingCount: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
remoteFollowingCount: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
localFollowersCount: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
remoteFollowersCount: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
followingCount: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
followersCount: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
sentReactionsCount: {
|
||||
type: 'integer',
|
||||
|
@ -110,7 +110,7 @@ export const paramDef = {
|
|||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps) => {
|
||||
export default define(meta, paramDef, async (ps, me) => {
|
||||
const user = await Users.findOneBy({ id: ps.userId });
|
||||
if (user == null) {
|
||||
throw new ApiError('NO_SUCH_USER');
|
||||
|
@ -141,22 +141,6 @@ export default define(meta, paramDef, async (ps) => {
|
|||
.innerJoin('vote.note', 'note')
|
||||
.where('note.userId = :userId', { userId: user.id })
|
||||
.getCount(),
|
||||
localFollowingCount: Followings.createQueryBuilder('following')
|
||||
.where('following.followerId = :userId', { userId: user.id })
|
||||
.andWhere('following.followeeHost IS NULL')
|
||||
.getCount(),
|
||||
remoteFollowingCount: Followings.createQueryBuilder('following')
|
||||
.where('following.followerId = :userId', { userId: user.id })
|
||||
.andWhere('following.followeeHost IS NOT NULL')
|
||||
.getCount(),
|
||||
localFollowersCount: Followings.createQueryBuilder('following')
|
||||
.where('following.followeeId = :userId', { userId: user.id })
|
||||
.andWhere('following.followerHost IS NULL')
|
||||
.getCount(),
|
||||
remoteFollowersCount: Followings.createQueryBuilder('following')
|
||||
.where('following.followeeId = :userId', { userId: user.id })
|
||||
.andWhere('following.followerHost IS NOT NULL')
|
||||
.getCount(),
|
||||
sentReactionsCount: NoteReactions.createQueryBuilder('reaction')
|
||||
.where('reaction.userId = :userId', { userId: user.id })
|
||||
.getCount(),
|
||||
|
@ -180,8 +164,32 @@ export default define(meta, paramDef, async (ps) => {
|
|||
driveUsage: DriveFiles.calcDriveUsageOf(user.id),
|
||||
});
|
||||
|
||||
result.followingCount = result.localFollowingCount + result.remoteFollowingCount;
|
||||
result.followersCount = result.localFollowersCount + result.remoteFollowersCount;
|
||||
const ffVisible = await Users.areFollowersVisibleTo(user, me);
|
||||
if (ffVisible) {
|
||||
const follows = await awaitAll({
|
||||
localFollowingCount: Followings.createQueryBuilder('following')
|
||||
.where('following.followerId = :userId', { userId: user.id })
|
||||
.andWhere('following.followeeHost IS NULL')
|
||||
.getCount(),
|
||||
remoteFollowingCount: Followings.createQueryBuilder('following')
|
||||
.where('following.followerId = :userId', { userId: user.id })
|
||||
.andWhere('following.followeeHost IS NOT NULL')
|
||||
.getCount(),
|
||||
localFollowersCount: Followings.createQueryBuilder('following')
|
||||
.where('following.followeeId = :userId', { userId: user.id })
|
||||
.andWhere('following.followerHost IS NULL')
|
||||
.getCount(),
|
||||
remoteFollowersCount: Followings.createQueryBuilder('following')
|
||||
.where('following.followeeId = :userId', { userId: user.id })
|
||||
.andWhere('following.followerHost IS NOT NULL')
|
||||
.getCount(),
|
||||
});
|
||||
|
||||
Object.assign(result, follows);
|
||||
|
||||
result.followingCount = result.localFollowingCount + result.remoteFollowingCount;
|
||||
result.followersCount = result.localFollowersCount + result.remoteFollowersCount;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@ import cors from '@koa/cors';
|
|||
|
||||
import { Instances, AccessTokens, Users } from '@/models/index.js';
|
||||
import config from '@/config/index.js';
|
||||
import endpoints from './endpoints.js';
|
||||
import { endpoints } from './endpoints.js';
|
||||
import { handler } from './api-handler.js';
|
||||
import signup from './private/signup.js';
|
||||
import signin from './private/signin.js';
|
||||
|
|
|
@ -2,7 +2,7 @@ import config from '@/config/index.js';
|
|||
import { kinds } from '@/misc/api-permissions.js';
|
||||
import { I18n } from '@/misc/i18n.js';
|
||||
import { errors as errorDefinitions } from '@/server/api/error.js';
|
||||
import endpoints from '@/server/api/endpoints.js';
|
||||
import { endpoints } from '@/server/api/endpoints.js';
|
||||
import { schemas, convertSchemaToOpenApiSchema } from './schemas.js';
|
||||
import { httpCodes } from './http-codes.js';
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import { SECOND, MINUTE } from '@/const.js';
|
|||
import { subscriber as redisClient } from '@/db/redis.js';
|
||||
import { Users } from '@/models/index.js';
|
||||
import { Connection } from './stream/index.js';
|
||||
import authenticate from './authenticate.js';
|
||||
import { authenticate } from './authenticate.js';
|
||||
|
||||
export const initializeStreamingServer = (server: http.Server): void => {
|
||||
// Init websocket server
|
||||
|
|
|
@ -8,7 +8,7 @@ import { dirname } from 'node:path';
|
|||
import Koa from 'koa';
|
||||
import cors from '@koa/cors';
|
||||
import Router from '@koa/router';
|
||||
import sendDriveFile from './send-drive-file.js';
|
||||
import { sendDriveFile } from './send-drive-file.js';
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
|
|
|
@ -27,8 +27,7 @@ const commonReadableHandlerGenerator = (ctx: Koa.Context) => (e: Error): void =>
|
|||
ctx.set('Cache-Control', 'max-age=300');
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default async function(ctx: Koa.Context) {
|
||||
export async function sendDriveFile(ctx: Koa.Context) {
|
||||
const key = ctx.params.key;
|
||||
|
||||
// Fetch drive file
|
||||
|
@ -49,7 +48,7 @@ export default async function(ctx: Koa.Context) {
|
|||
const isWebpublic = file.webpublicAccessKey === key;
|
||||
|
||||
if (!file.storedInternal) {
|
||||
if (file.isLink && file.uri) { // 期限切れリモートファイル
|
||||
if (file.isLink && file.uri) { // expired remote file
|
||||
const [path, cleanup] = await createTemp();
|
||||
|
||||
try {
|
||||
|
|
|
@ -4,7 +4,7 @@ import config from '@/config/index.js';
|
|||
import { User } from '@/models/entities/user.js';
|
||||
import { Notes, DriveFiles, UserProfiles, Users } from '@/models/index.js';
|
||||
|
||||
export default async function(user: User) {
|
||||
export async function packFeed(user: User) {
|
||||
const author = {
|
||||
link: `${config.url}/@${user.username}`,
|
||||
name: user.name || user.username,
|
||||
|
|
|
@ -26,7 +26,7 @@ import { MINUTE, DAY } from '@/const.js';
|
|||
import { genOpenapiSpec } from '../api/openapi/gen-spec.js';
|
||||
import { urlPreviewHandler } from './url-preview.js';
|
||||
import { manifestHandler } from './manifest.js';
|
||||
import packFeed from './feed.js';
|
||||
import { packFeed } from './feed.js';
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
|
|
|
@ -2,13 +2,13 @@ import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
|||
import { renderBlock } from '@/remote/activitypub/renderer/block.js';
|
||||
import renderUndo from '@/remote/activitypub/renderer/undo.js';
|
||||
import { deliver } from '@/queue/index.js';
|
||||
import { CacheableUser } from '@/models/entities/user.js';
|
||||
import { User } from '@/models/entities/user.js';
|
||||
import { Blockings, Users } from '@/models/index.js';
|
||||
import Logger from '../logger.js';
|
||||
|
||||
const logger = new Logger('blocking/delete');
|
||||
|
||||
export default async function(blocker: CacheableUser, blockee: CacheableUser) {
|
||||
export default async function(blocker: User, blockee: User) {
|
||||
const blocking = await Blockings.findOneBy({
|
||||
blockerId: blocker.id,
|
||||
blockeeId: blockee.id,
|
||||
|
|
|
@ -60,10 +60,7 @@ export async function uploadFromUrl({
|
|||
logger.succ(`Got: ${driveFile.id}`);
|
||||
return driveFile;
|
||||
} catch (e) {
|
||||
logger.error(`Failed to create drive file: ${e}`, {
|
||||
url,
|
||||
e,
|
||||
});
|
||||
logger.error(`Failed to create drive file: ${e}`);
|
||||
throw e;
|
||||
} finally {
|
||||
cleanup();
|
||||
|
|
|
@ -2,7 +2,6 @@ import cluster from 'node:cluster';
|
|||
import chalk from 'chalk';
|
||||
import convertColor from 'color-convert';
|
||||
import { format as dateFormat } from 'date-fns';
|
||||
import * as SyslogPro from 'syslog-pro';
|
||||
import config from '@/config/index.js';
|
||||
import { envOption } from '@/env.js';
|
||||
import type { KEYWORD } from 'color-convert/conversions.js';
|
||||
|
@ -12,16 +11,26 @@ type Domain = {
|
|||
color?: KEYWORD;
|
||||
};
|
||||
|
||||
type Level = 'error' | 'success' | 'warning' | 'debug' | 'info';
|
||||
export const LEVELS = {
|
||||
error: 0,
|
||||
warning: 1,
|
||||
success: 2,
|
||||
info: 3,
|
||||
debug: 4,
|
||||
};
|
||||
export type Level = LEVELS[keyof LEVELS];
|
||||
|
||||
/**
|
||||
* Class that facilitates recording log messages to the console and optionally a syslog server.
|
||||
* Class that facilitates recording log messages to the console.
|
||||
*/
|
||||
export default class Logger {
|
||||
private domain: Domain;
|
||||
private parentLogger: Logger | null = null;
|
||||
private store: boolean;
|
||||
private syslogClient: SyslogPro.RFC5424 | null = null;
|
||||
/**
|
||||
* Messages below this level will be discarded.
|
||||
*/
|
||||
private minLevel: Level;
|
||||
|
||||
/**
|
||||
* Create a logger instance.
|
||||
|
@ -29,26 +38,13 @@ export default class Logger {
|
|||
* @param color Log message color
|
||||
* @param store Whether to store messages
|
||||
*/
|
||||
constructor(domain: string, color?: KEYWORD, store = true) {
|
||||
constructor(domain: string, color?: KEYWORD, store = true, minLevel: Level = LEVELS.info) {
|
||||
this.domain = {
|
||||
name: domain,
|
||||
color,
|
||||
};
|
||||
this.store = store;
|
||||
|
||||
if (config.syslog) {
|
||||
this.syslogClient = new SyslogPro.RFC5424({
|
||||
applicationName: 'FoundKey',
|
||||
timestamp: true,
|
||||
includeStructuredData: true,
|
||||
color: true,
|
||||
extendedColor: true,
|
||||
server: {
|
||||
target: config.syslog.host,
|
||||
port: config.syslog.port,
|
||||
},
|
||||
});
|
||||
}
|
||||
this.minLevel = minLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,69 +54,90 @@ export default class Logger {
|
|||
* @param store Whether to store messages
|
||||
* @returns A Logger instance whose parent logger is this instance.
|
||||
*/
|
||||
public createSubLogger(domain: string, color?: KEYWORD, store = true): Logger {
|
||||
const logger = new Logger(domain, color, store);
|
||||
public createSubLogger(domain: string, color?: KEYWORD, store = true, minLevel: Level = LEVELS.info): Logger {
|
||||
const logger = new Logger(domain, color, store, minLevel);
|
||||
logger.parentLogger = this;
|
||||
return logger;
|
||||
}
|
||||
|
||||
private log(level: Level, message: string, data?: Record<string, any> | null, important = false, subDomains: Domain[] = [], _store = true): void {
|
||||
/**
|
||||
* Log a message.
|
||||
* @param level Indicates the level of this particular message. If it is
|
||||
* less than the minimum level configured, the message will be discarded.
|
||||
* @param message The message to be logged.
|
||||
* @param important Whether to highlight this message as especially important.
|
||||
* @param subDomains Names of sub-loggers to be added.
|
||||
*/
|
||||
private log(level: Level, message: string, important = false, subDomains: Domain[] = [], _store = true): void {
|
||||
if (envOption.quiet) return;
|
||||
const store = _store && this.store && (level !== 'debug');
|
||||
const store = _store && this.store;
|
||||
|
||||
// Check against the configured log level.
|
||||
if (level < this.minLevel) return;
|
||||
|
||||
// If this logger has a parent logger, delegate the actual logging to it,
|
||||
// so the parent domain(s) will be logged properly.
|
||||
if (this.parentLogger) {
|
||||
this.parentLogger.log(level, message, data, important, [this.domain].concat(subDomains), store);
|
||||
this.parentLogger.log(level, message, important, [this.domain].concat(subDomains), store);
|
||||
return;
|
||||
}
|
||||
|
||||
const time = dateFormat(new Date(), 'HH:mm:ss');
|
||||
const worker = cluster.isPrimary ? '*' : cluster.worker?.id;
|
||||
const l =
|
||||
level === 'error' ? important ? chalk.bgRed.white('ERR ') : chalk.red('ERR ') :
|
||||
level === 'warning' ? chalk.yellow('WARN') :
|
||||
level === 'success' ? important ? chalk.bgGreen.white('DONE') : chalk.green('DONE') :
|
||||
level === 'debug' ? chalk.gray('VERB') :
|
||||
chalk.blue('INFO');
|
||||
const domains = [this.domain].concat(subDomains).map(d => d.color ? chalk.rgb(...convertColor.keyword.rgb(d.color))(d.name) : chalk.white(d.name));
|
||||
const m =
|
||||
level === 'error' ? chalk.red(message) :
|
||||
level === 'warning' ? chalk.yellow(message) :
|
||||
level === 'success' ? chalk.green(message) :
|
||||
level === 'debug' ? chalk.gray(message) :
|
||||
message;
|
||||
|
||||
let log = `${l} ${worker}\t[${domains.join(' ')}]\t${m}`;
|
||||
let levelDisplay;
|
||||
let messageDisplay;
|
||||
switch (level) {
|
||||
case LEVELS.error:
|
||||
if (important) {
|
||||
levelDisplay = chalk.bgRed.white('ERR ');
|
||||
} else {
|
||||
levelDisplay = chalk.red('ERR ');
|
||||
}
|
||||
messageDisplay = chalk.red(message);
|
||||
break;
|
||||
case LEVELS.warning:
|
||||
levelDisplay = chalk.yellow('WARN');
|
||||
messageDisplay = chalk.yellow(message);
|
||||
break;
|
||||
case LEVELS.success:
|
||||
if (important) {
|
||||
levelDisplay = chalk.bgGreen.white('DONE');
|
||||
} else {
|
||||
levelDisplay = chalk.green('DONE');
|
||||
}
|
||||
messageDisplay = chalk.green(message);
|
||||
break;
|
||||
case LEVELS.info:
|
||||
levelDisplay = chalk.blue('INFO');
|
||||
messageDisplay = message;
|
||||
break;
|
||||
case LEVELS.debug: default:
|
||||
levelDisplay = chalk.gray('VERB');
|
||||
messageDisplay = chalk.gray(message);
|
||||
break;
|
||||
}
|
||||
|
||||
let log = `${levelDisplay} ${worker}\t[${domains.join(' ')}]\t${messageDisplay}`;
|
||||
if (envOption.withLogTime) log = chalk.gray(time) + ' ' + log;
|
||||
|
||||
console.log(important ? chalk.bold(log) : log);
|
||||
|
||||
if (store) {
|
||||
if (this.syslogClient) {
|
||||
const send =
|
||||
level === 'error' ? this.syslogClient.error :
|
||||
level === 'warning' ? this.syslogClient.warning :
|
||||
this.syslogClient.info;
|
||||
|
||||
send.bind(this.syslogClient)(message).catch(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an error message.
|
||||
* Use in situations where execution cannot be continued.
|
||||
* @param err Error or string containing an error message
|
||||
* @param data Data relating to the error
|
||||
* @param important Whether this error is important
|
||||
*/
|
||||
public error(err: string | Error, data: Record<string, any> = {}, important = false): void {
|
||||
public error(err: string | Error, important = false): void {
|
||||
if (err instanceof Error) {
|
||||
data.e = err;
|
||||
this.log('error', err.toString(), data, important);
|
||||
this.log(LEVELS.error, err.toString(), important);
|
||||
} else if (typeof err === 'object') {
|
||||
this.log('error', `${(err as any).message || (err as any).name || err}`, data, important);
|
||||
this.log(LEVELS.error, `${(err as any).message || (err as any).name || err}`, important);
|
||||
} else {
|
||||
this.log('error', `${err}`, data, important);
|
||||
this.log(LEVELS.error, `${err}`, important);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,45 +145,39 @@ export default class Logger {
|
|||
* Log a warning message.
|
||||
* Use in situations where execution can continue but needs to be improved.
|
||||
* @param message Warning message
|
||||
* @param data Data relating to the warning
|
||||
* @param important Whether this warning is important
|
||||
*/
|
||||
public warn(message: string, data?: Record<string, any> | null, important = false): void {
|
||||
this.log('warning', message, data, important);
|
||||
public warn(message: string, important = false): void {
|
||||
this.log(LEVELS.warning, message, important);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a success message.
|
||||
* Use in situations where something has been successfully done.
|
||||
* @param message Success message
|
||||
* @param data Data relating to the success
|
||||
* @param important Whether this success message is important
|
||||
*/
|
||||
public succ(message: string, data?: Record<string, any> | null, important = false): void {
|
||||
this.log('success', message, data, important);
|
||||
public succ(message: string, important = false): void {
|
||||
this.log(LEVELS.success, message, important);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a debug message.
|
||||
* Use for debugging (information needed by developers but not required by users).
|
||||
* @param message Debug message
|
||||
* @param data Data relating to the debug message
|
||||
* @param important Whether this debug message is important
|
||||
*/
|
||||
public debug(message: string, data?: Record<string, any> | null, important = false): void {
|
||||
if (process.env.NODE_ENV !== 'production' || envOption.verbose) {
|
||||
this.log('debug', message, data, important);
|
||||
}
|
||||
public debug(message: string, important = false): void {
|
||||
this.log(LEVELS.debug, message, important);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an informational message.
|
||||
* Use when something needs to be logged but doesn't fit into other levels.
|
||||
* @param message Info message
|
||||
* @param data Data relating to the info message
|
||||
* @param important Whether this info message is important
|
||||
*/
|
||||
public info(message: string, data?: Record<string, any> | null, important = false): void {
|
||||
this.log('info', message, data, important);
|
||||
public info(message: string, important = false): void {
|
||||
this.log(LEVELS.info, message, important);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Not } from 'typeorm';
|
||||
import { CacheableUser, User } from '@/models/entities/user.js';
|
||||
import { User } from '@/models/entities/user.js';
|
||||
import { UserGroup } from '@/models/entities/user-group.js';
|
||||
import { DriveFile } from '@/models/entities/drive-file.js';
|
||||
import { MessagingMessages, UserGroupJoinings, Mutings, Users } from '@/models/index.js';
|
||||
|
@ -13,7 +13,7 @@ import renderCreate from '@/remote/activitypub/renderer/create.js';
|
|||
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
||||
import { deliver } from '@/queue/index.js';
|
||||
|
||||
export async function createMessage(user: { id: User['id']; host: User['host']; }, recipientUser: CacheableUser | undefined, recipientGroup: UserGroup | undefined, text: string | null | undefined, file: DriveFile | null, uri?: string) {
|
||||
export async function createMessage(user: { id: User['id']; host: User['host']; }, recipientUser: User | undefined, recipientGroup: UserGroup | undefined, text: string | null | undefined, file: DriveFile | null, uri?: string) {
|
||||
const message = {
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { ArrayOverlap, Not } from 'typeorm';
|
||||
import { publishNoteStream } from '@/services/stream.js';
|
||||
import { CacheableUser } from '@/models/entities/user.js';
|
||||
import { User } from '@/models/entities/user.js';
|
||||
import { Note } from '@/models/entities/note.js';
|
||||
import { PollVotes, NoteWatchings, Polls, Blockings, NoteThreadMutings } from '@/models/index.js';
|
||||
import { genId } from '@/misc/gen-id.js';
|
||||
import { createNotification } from '@/services/create-notification.js';
|
||||
|
||||
export async function vote(user: CacheableUser, note: Note, choice: number): Promise<void> {
|
||||
export async function vote(user: User, note: Note, choice: number): Promise<void> {
|
||||
const poll = await Polls.findOneBy({ noteId: note.id });
|
||||
|
||||
if (poll == null) throw new Error('poll not found');
|
||||
|
|
|
@ -95,9 +95,7 @@ class Publisher {
|
|||
};
|
||||
}
|
||||
|
||||
const publisher = new Publisher();
|
||||
|
||||
export default publisher;
|
||||
export const publisher = new Publisher();
|
||||
|
||||
export const publishInternalEvent = publisher.publishInternalEvent;
|
||||
export const publishUserEvent = publisher.publishUserEvent;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { IsNull } from 'typeorm';
|
||||
import { CacheableLocalUser, ILocalUser, User } from '@/models/entities/user.js';
|
||||
import { ILocalUser, User } from '@/models/entities/user.js';
|
||||
import { Users } from '@/models/index.js';
|
||||
import { Cache } from '@/misc/cache.js';
|
||||
import { subscriber } from '@/db/redis.js';
|
||||
|
@ -8,7 +8,7 @@ export const userByIdCache = new Cache<User>(
|
|||
Infinity,
|
||||
async (id) => await Users.findOneBy({ id, isDeleted: false }) ?? undefined,
|
||||
);
|
||||
export const localUserByNativeTokenCache = new Cache<CacheableLocalUser>(
|
||||
export const localUserByNativeTokenCache = new Cache<ILocalUser>(
|
||||
Infinity,
|
||||
async (token) => await Users.findOneBy({ token, host: IsNull(), isDeleted: false }) as ILocalUser | null ?? undefined,
|
||||
);
|
||||
|
|
|
@ -8,6 +8,7 @@ describe('API visibility', () => {
|
|||
let p: childProcess.ChildProcess;
|
||||
|
||||
before(async () => {
|
||||
this.timeout(0);
|
||||
p = await startServer();
|
||||
});
|
||||
|
||||
|
@ -17,15 +18,15 @@ describe('API visibility', () => {
|
|||
|
||||
describe('Note visibility', async () => {
|
||||
//#region vars
|
||||
/** ヒロイン */
|
||||
/** protagonist */
|
||||
let alice: any;
|
||||
/** フォロワー */
|
||||
/** follower */
|
||||
let follower: any;
|
||||
/** 非フォロワー */
|
||||
/** non-follower */
|
||||
let other: any;
|
||||
/** 非フォロワーでもリプライやメンションをされた人 */
|
||||
/** non-follower who has been replied to or mentioned */
|
||||
let target: any;
|
||||
/** specified mentionでmentionを飛ばされる人 */
|
||||
/** actor for which a specified visibility was set */
|
||||
let target2: any;
|
||||
|
||||
/** public-post */
|
||||
|
@ -100,90 +101,90 @@ describe('API visibility', () => {
|
|||
|
||||
//#region show post
|
||||
// public
|
||||
it('[show] public-postを自分が見れる', async(async () => {
|
||||
it('[show] public post can be seen by author', async(async () => {
|
||||
const res = await show(pub.id, alice);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] public-postをフォロワーが見れる', async(async () => {
|
||||
it('[show] public post can be seen by follower', async(async () => {
|
||||
const res = await show(pub.id, follower);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] public-postを非フォロワーが見れる', async(async () => {
|
||||
it('[show] public post can be seen by non-follower', async(async () => {
|
||||
const res = await show(pub.id, other);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] public-postを未認証が見れる', async(async () => {
|
||||
it('[show] public post can be seen unauthenticated', async(async () => {
|
||||
const res = await show(pub.id, null);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
// home
|
||||
it('[show] home-postを自分が見れる', async(async () => {
|
||||
it('[show] home post can be seen by author', async(async () => {
|
||||
const res = await show(home.id, alice);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] home-postをフォロワーが見れる', async(async () => {
|
||||
it('[show] home post can be seen by follower', async(async () => {
|
||||
const res = await show(home.id, follower);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] home-postを非フォロワーが見れる', async(async () => {
|
||||
it('[show] home post can be seen by non-follower', async(async () => {
|
||||
const res = await show(home.id, other);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] home-postを未認証が見れる', async(async () => {
|
||||
it('[show] home post can be seen unauthenticated', async(async () => {
|
||||
const res = await show(home.id, null);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
// followers
|
||||
it('[show] followers-postを自分が見れる', async(async () => {
|
||||
it('[show] followers post can be seen by author', async(async () => {
|
||||
const res = await show(fol.id, alice);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] followers-postをフォロワーが見れる', async(async () => {
|
||||
it('[show] followers post can be seen by follower', async(async () => {
|
||||
const res = await show(fol.id, follower);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] followers-postを非フォロワーが見れない', async(async () => {
|
||||
it('[show] followers post is hidden from non-follower', async(async () => {
|
||||
const res = await show(fol.id, other);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] followers-postを未認証が見れない', async(async () => {
|
||||
it('[show] followers post is hidden when unathenticated', async(async () => {
|
||||
const res = await show(fol.id, null);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
// specified
|
||||
it('[show] specified-postを自分が見れる', async(async () => {
|
||||
it('[show] specified post can be seen by author', async(async () => {
|
||||
const res = await show(spe.id, alice);
|
||||
assert.strictEqual(res.status, 404);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] specified-postを指定ユーザーが見れる', async(async () => {
|
||||
it('[show] specified post can be seen by designated user', async(async () => {
|
||||
const res = await show(spe.id, target);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] specified-postをフォロワーが見れない', async(async () => {
|
||||
it('[show] specified post is hidden from non-specified follower', async(async () => {
|
||||
const res = await show(spe.id, follower);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] specified-postを非フォロワーが見れない', async(async () => {
|
||||
it('[show] specified post is hidden from non-follower', async(async () => {
|
||||
const res = await show(spe.id, other);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] specified-postを未認証が見れない', async(async () => {
|
||||
it('[show] specified post is hidden when unauthenticated', async(async () => {
|
||||
const res = await show(spe.id, null);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
@ -191,110 +192,105 @@ describe('API visibility', () => {
|
|||
|
||||
//#region show reply
|
||||
// public
|
||||
it('[show] public-replyを自分が見れる', async(async () => {
|
||||
it('[show] public reply can be seen by author', async(async () => {
|
||||
const res = await show(pubR.id, alice);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] public-replyをされた人が見れる', async(async () => {
|
||||
it('[show] public reply can be seen by replied to author', async(async () => {
|
||||
const res = await show(pubR.id, target);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] public-replyをフォロワーが見れる', async(async () => {
|
||||
it('[show] public reply can be seen by follower', async(async () => {
|
||||
const res = await show(pubR.id, follower);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] public-replyを非フォロワーが見れる', async(async () => {
|
||||
it('[show] public reply can be seen by non-follower', async(async () => {
|
||||
const res = await show(pubR.id, other);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] public-replyを未認証が見れる', async(async () => {
|
||||
it('[show] public reply can be seen unauthenticated', async(async () => {
|
||||
const res = await show(pubR.id, null);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
// home
|
||||
it('[show] home-replyを自分が見れる', async(async () => {
|
||||
it('[show] home reply can be seen by author', async(async () => {
|
||||
const res = await show(homeR.id, alice);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] home-replyをされた人が見れる', async(async () => {
|
||||
it('[show] home reply can be seen by replied to author', async(async () => {
|
||||
const res = await show(homeR.id, target);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] home-replyをフォロワーが見れる', async(async () => {
|
||||
it('[show] home reply can be seen by follower', async(async () => {
|
||||
const res = await show(homeR.id, follower);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] home-replyを非フォロワーが見れる', async(async () => {
|
||||
it('[show] home reply can be seen by non-follower', async(async () => {
|
||||
const res = await show(homeR.id, other);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] home-replyを未認証が見れる', async(async () => {
|
||||
it('[show] home reply can be seen unauthenticated', async(async () => {
|
||||
const res = await show(homeR.id, null);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
// followers
|
||||
it('[show] followers-replyを自分が見れる', async(async () => {
|
||||
it('[show] followers reply can be seen by author', async(async () => {
|
||||
const res = await show(folR.id, alice);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] followers-replyを非フォロワーでもリプライされていれば見れる', async(async () => {
|
||||
it('[show] followers reply can be seen by replied to author', async(async () => {
|
||||
const res = await show(folR.id, target);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] followers-replyをフォロワーが見れる', async(async () => {
|
||||
it('[show] followers reply can be seen by follower', async(async () => {
|
||||
const res = await show(folR.id, follower);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] followers-replyを非フォロワーが見れない', async(async () => {
|
||||
it('[show] followers reply is hidden from non-follower', async(async () => {
|
||||
const res = await show(folR.id, other);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] followers-replyを未認証が見れない', async(async () => {
|
||||
it('[show] followers reply is hidden when unauthenticated', async(async () => {
|
||||
const res = await show(folR.id, null);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
// specified
|
||||
it('[show] specified-replyを自分が見れる', async(async () => {
|
||||
it('[show] specified reply can be seen by author', async(async () => {
|
||||
const res = await show(speR.id, alice);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] specified-replyを指定ユーザーが見れる', async(async () => {
|
||||
it('[show] specified reply can be seen by replied to user', async(async () => {
|
||||
const res = await show(speR.id, target);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] specified-replyをされた人が指定されてなくても見れる', async(async () => {
|
||||
const res = await show(speR.id, target);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] specified-replyをフォロワーが見れない', async(async () => {
|
||||
it('[show] specified reply is hidden from follower', async(async () => {
|
||||
const res = await show(speR.id, follower);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] specified-replyを非フォロワーが見れない', async(async () => {
|
||||
it('[show] specified reply is hidden from non-follower', async(async () => {
|
||||
const res = await show(speR.id, other);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] specified-replyを未認証が見れない', async(async () => {
|
||||
it('[show] specified reply is hidden when unauthenticated', async(async () => {
|
||||
const res = await show(speR.id, null);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
@ -302,131 +298,131 @@ describe('API visibility', () => {
|
|||
|
||||
//#region show mention
|
||||
// public
|
||||
it('[show] public-mentionを自分が見れる', async(async () => {
|
||||
it('[show] public-mention can be seen by author', async(async () => {
|
||||
const res = await show(pubM.id, alice);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] public-mentionをされた人が見れる', async(async () => {
|
||||
it('[show] public mention can be seen by mentioned', async(async () => {
|
||||
const res = await show(pubM.id, target);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] public-mentionをフォロワーが見れる', async(async () => {
|
||||
it('[show] public mention can be seen by follower', async(async () => {
|
||||
const res = await show(pubM.id, follower);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] public-mentionを非フォロワーが見れる', async(async () => {
|
||||
it('[show] public mention can be seen by non-follower', async(async () => {
|
||||
const res = await show(pubM.id, other);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] public-mentionを未認証が見れる', async(async () => {
|
||||
it('[show] public mention can be seen unauthenticated', async(async () => {
|
||||
const res = await show(pubM.id, null);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
// home
|
||||
it('[show] home-mentionを自分が見れる', async(async () => {
|
||||
it('[show] home mention can be seen by author', async(async () => {
|
||||
const res = await show(homeM.id, alice);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] home-mentionをされた人が見れる', async(async () => {
|
||||
it('[show] home mention can be seen by mentioned', async(async () => {
|
||||
const res = await show(homeM.id, target);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] home-mentionをフォロワーが見れる', async(async () => {
|
||||
it('[show] home mention can be seen by follower', async(async () => {
|
||||
const res = await show(homeM.id, follower);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] home-mentionを非フォロワーが見れる', async(async () => {
|
||||
it('[show] home mention can be seen by non-follower', async(async () => {
|
||||
const res = await show(homeM.id, other);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] home-mentionを未認証が見れる', async(async () => {
|
||||
it('[show] home mention can be seen unauthenticated', async(async () => {
|
||||
const res = await show(homeM.id, null);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
// followers
|
||||
it('[show] followers-mentionを自分が見れる', async(async () => {
|
||||
it('[show] followers mention can be seen by author', async(async () => {
|
||||
const res = await show(folM.id, alice);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] followers-mentionをメンションされていれば非フォロワーでも見れる', async(async () => {
|
||||
it('[show] followers mention can be seen by non-follower mentioned', async(async () => {
|
||||
const res = await show(folM.id, target);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] followers-mentionをフォロワーが見れる', async(async () => {
|
||||
it('[show] followers mention can be seen by follower', async(async () => {
|
||||
const res = await show(folM.id, follower);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] followers-mentionを非フォロワーが見れない', async(async () => {
|
||||
it('[show] followers mention is hidden from non-follower', async(async () => {
|
||||
const res = await show(folM.id, other);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] followers-mentionを未認証が見れない', async(async () => {
|
||||
it('[show] followers mention is hidden when unauthenticated', async(async () => {
|
||||
const res = await show(folM.id, null);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
// specified
|
||||
it('[show] specified-mentionを自分が見れる', async(async () => {
|
||||
it('[show] specified mention can be seen by author', async(async () => {
|
||||
const res = await show(speM.id, alice);
|
||||
assert.strictEqual(res.body.text, '@target2 x');
|
||||
}));
|
||||
|
||||
it('[show] specified-mentionを指定ユーザーが見れる', async(async () => {
|
||||
it('[show] specified mention can be seen by specified actor', async(async () => {
|
||||
const res = await show(speM.id, target);
|
||||
assert.strictEqual(res.body.text, '@target2 x');
|
||||
}));
|
||||
|
||||
it('[show] specified-mentionをされた人が指定されてなかったら見れない', async(async () => {
|
||||
it('[show] specified mention is hidden from mentioned but not specified actor', async(async () => {
|
||||
const res = await show(speM.id, target2);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] specified-mentionをフォロワーが見れない', async(async () => {
|
||||
it('[show] specified mention is hidden from follower', async(async () => {
|
||||
const res = await show(speM.id, follower);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] specified-mentionを非フォロワーが見れない', async(async () => {
|
||||
it('[show] specified mention is hidden from non-follower', async(async () => {
|
||||
const res = await show(speM.id, other);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] specified-mentionを未認証が見れない', async(async () => {
|
||||
it('[show] specified mention is hidden when unauthenticated', async(async () => {
|
||||
const res = await show(speM.id, null);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
//#endregion
|
||||
|
||||
//#region HTL
|
||||
it('[HTL] public-post が 自分が見れる', async(async () => {
|
||||
//#region Home Timeline
|
||||
it('[TL] public post on author home TL', async(async () => {
|
||||
const res = await request('/notes/timeline', { limit: 100 }, alice);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n: any) => n.id == pub.id);
|
||||
assert.strictEqual(notes[0].text, 'x');
|
||||
}));
|
||||
|
||||
it('[HTL] public-post が 非フォロワーから見れない', async(async () => {
|
||||
it('[TL] public post absent from non-follower home TL', async(async () => {
|
||||
const res = await request('/notes/timeline', { limit: 100 }, other);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n: any) => n.id == pub.id);
|
||||
assert.strictEqual(notes.length, 0);
|
||||
}));
|
||||
|
||||
it('[HTL] followers-post が フォロワーから見れる', async(async () => {
|
||||
it('[TL] followers post on follower home TL', async(async () => {
|
||||
const res = await request('/notes/timeline', { limit: 100 }, follower);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n: any) => n.id == fol.id);
|
||||
|
@ -434,22 +430,22 @@ describe('API visibility', () => {
|
|||
}));
|
||||
//#endregion
|
||||
|
||||
//#region RTL
|
||||
it('[replies] followers-reply が フォロワーから見れる', async(async () => {
|
||||
//#region replies timeline
|
||||
it('[TL] followers reply on follower reply TL', async(async () => {
|
||||
const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, follower);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n: any) => n.id == folR.id);
|
||||
assert.strictEqual(notes[0].text, 'x');
|
||||
}));
|
||||
|
||||
it('[replies] followers-reply が 非フォロワー (リプライ先ではない) から見れない', async(async () => {
|
||||
it('[TL] followers reply absent from not replied to non-follower reply TL', async(async () => {
|
||||
const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, other);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n: any) => n.id == folR.id);
|
||||
assert.strictEqual(notes.length, 0);
|
||||
}));
|
||||
|
||||
it('[replies] followers-reply が 非フォロワー (リプライ先である) から見れる', async(async () => {
|
||||
it('[TL] followers reply on replied to actor reply TL', async(async () => {
|
||||
const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, target);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n: any) => n.id == folR.id);
|
||||
|
@ -458,14 +454,14 @@ describe('API visibility', () => {
|
|||
//#endregion
|
||||
|
||||
//#region MTL
|
||||
it('[mentions] followers-reply が 非フォロワー (リプライ先である) から見れる', async(async () => {
|
||||
it('[TL] followers reply on replied to non-follower mention TL', async(async () => {
|
||||
const res = await request('/notes/mentions', { limit: 100 }, target);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n: any) => n.id == folR.id);
|
||||
assert.strictEqual(notes[0].text, 'x');
|
||||
}));
|
||||
|
||||
it('[mentions] followers-mention が 非フォロワー (メンション先である) から見れる', async(async () => {
|
||||
it('[TL] followers mention on mentioned non-follower mention TL', async(async () => {
|
||||
const res = await request('/notes/mentions', { limit: 100 }, target);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n: any) => n.id == folM.id);
|
||||
|
|
|
@ -10,7 +10,8 @@ describe('API', () => {
|
|||
let bob: any;
|
||||
let carol: any;
|
||||
|
||||
before(async () => {
|
||||
before(async function() {
|
||||
this.timeout(0);
|
||||
p = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
|
|
|
@ -13,6 +13,8 @@ describe('Block', () => {
|
|||
let carol: any;
|
||||
|
||||
before(async () => {
|
||||
this.timeout(0);
|
||||
|
||||
p = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
|
@ -23,7 +25,7 @@ describe('Block', () => {
|
|||
await shutdownServer(p);
|
||||
});
|
||||
|
||||
it('Block作成', async(async () => {
|
||||
it('can block someone', async(async () => {
|
||||
const res = await request('/blocking/create', {
|
||||
userId: bob.id,
|
||||
}, alice);
|
||||
|
@ -31,45 +33,45 @@ describe('Block', () => {
|
|||
assert.strictEqual(res.status, 200);
|
||||
}));
|
||||
|
||||
it('ブロックされているユーザーをフォローできない', async(async () => {
|
||||
it('cannot follow if blocked', async(async () => {
|
||||
const res = await request('/following/create', { userId: alice.id }, bob);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.id, 'c4ab57cc-4e41-45e9-bfd9-584f61e35ce0');
|
||||
assert.strictEqual(res.body.error.code, 'BLOCKED');
|
||||
}));
|
||||
|
||||
it('ブロックされているユーザーにリアクションできない', async(async () => {
|
||||
it('cannot react to blocking users note', async(async () => {
|
||||
const note = await post(alice, { text: 'hello' });
|
||||
|
||||
const res = await request('/notes/reactions/create', { noteId: note.id, reaction: '👍' }, bob);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.id, '20ef5475-9f38-4e4c-bd33-de6d979498ec');
|
||||
assert.strictEqual(res.body.error.code, 'BLOCKED');
|
||||
}));
|
||||
|
||||
it('ブロックされているユーザーに返信できない', async(async () => {
|
||||
it('cannot reply to blocking users note', async(async () => {
|
||||
const note = await post(alice, { text: 'hello' });
|
||||
|
||||
const res = await request('/notes/create', { replyId: note.id, text: 'yo' }, bob);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.id, 'b390d7e1-8a5e-46ed-b625-06271cafd3d3');
|
||||
assert.strictEqual(res.body.error.code, 'BLOCKED');
|
||||
}));
|
||||
|
||||
it('ブロックされているユーザーのノートをRenoteできない', async(async () => {
|
||||
it('canot renote blocking users note', async(async () => {
|
||||
const note = await post(alice, { text: 'hello' });
|
||||
|
||||
const res = await request('/notes/create', { renoteId: note.id, text: 'yo' }, bob);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.id, 'b390d7e1-8a5e-46ed-b625-06271cafd3d3');
|
||||
assert.strictEqual(res.body.error.code, 'BLOCKED');
|
||||
}));
|
||||
|
||||
// TODO: ユーザーリストに入れられないテスト
|
||||
it('cannot include blocked users in user lists');
|
||||
|
||||
// TODO: ユーザーリストから除外されるテスト
|
||||
it('removes users from user lists');
|
||||
|
||||
it('タイムライン(LTL)にブロックされているユーザーの投稿が含まれない', async(async () => {
|
||||
it('local timeline does not contain blocked users', async(async () => {
|
||||
const aliceNote = await post(alice);
|
||||
const bobNote = await post(bob);
|
||||
const carolNote = await post(carol);
|
||||
|
|
|
@ -23,6 +23,7 @@ describe('Fetch resource', () => {
|
|||
let alicesPost: any;
|
||||
|
||||
before(async () => {
|
||||
this.timeout(0);
|
||||
p = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
alicesPost = await post(alice, {
|
||||
|
|
|
@ -9,159 +9,110 @@ describe('FF visibility', () => {
|
|||
|
||||
let alice: any;
|
||||
let bob: any;
|
||||
let carol: any;
|
||||
let follower: any;
|
||||
|
||||
before(async () => {
|
||||
this.timeout(0);
|
||||
|
||||
p = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
carol = await signup({ username: 'carol' });
|
||||
follower = await signup({ username: 'follower' });
|
||||
|
||||
await request('/following/create', { userId: alice.id }, follower);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await shutdownServer(p);
|
||||
});
|
||||
|
||||
it('ffVisibility が public なユーザーのフォロー/フォロワーを誰でも見れる', async(async () => {
|
||||
await request('/i/update', {
|
||||
ffVisibility: 'public',
|
||||
}, alice);
|
||||
const visible = (user) => {
|
||||
return async () => {
|
||||
const followingRes = await request('/users/following', {
|
||||
userId: alice.id,
|
||||
}, user);
|
||||
const followersRes = await request('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, user);
|
||||
|
||||
const followingRes = await request('/users/following', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
const followersRes = await request('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.ok(Array.isArray(followingRes.body));
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
assert.ok(Array.isArray(followersRes.body));
|
||||
};
|
||||
};
|
||||
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
}));
|
||||
const hidden = (user) => {
|
||||
return async () => {
|
||||
const followingRes = await request('/users/following', {
|
||||
userId: alice.id,
|
||||
}, user);
|
||||
const followersRes = await request('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, user);
|
||||
|
||||
it('ffVisibility が followers なユーザーのフォロー/フォロワーを自分で見れる', async(async () => {
|
||||
await request('/i/update', {
|
||||
ffVisibility: 'followers',
|
||||
}, alice);
|
||||
assert.strictEqual(followingRes.status, 403);
|
||||
assert.strictEqual(followersRes.status, 403);
|
||||
};
|
||||
};
|
||||
|
||||
const followingRes = await request('/users/following', {
|
||||
userId: alice.id,
|
||||
}, alice);
|
||||
const followersRes = await request('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, alice);
|
||||
describe('public visibility', () => {
|
||||
before(async () => {
|
||||
await request('/i/update', {
|
||||
ffVisibility: 'public',
|
||||
}, alice);
|
||||
});
|
||||
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
}));
|
||||
it('shows followers and following to self', visible(alice));
|
||||
it('shows followers and following to a follower', visible(follower));
|
||||
it('shows followers and following to a non-follower', visible(bob));
|
||||
it('shows followers and following when unauthenticated', visible(null));
|
||||
|
||||
it('ffVisibility が followers なユーザーのフォロー/フォロワーを非フォロワーが見れない', async(async () => {
|
||||
await request('/i/update', {
|
||||
ffVisibility: 'followers',
|
||||
}, alice);
|
||||
it('provides followers in ActivityPub representation', async () => {
|
||||
const followingRes = await simpleGet(`/users/${alice.id}/following`, 'application/activity+json');
|
||||
const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json');
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
});
|
||||
});
|
||||
|
||||
const followingRes = await request('/users/following', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
const followersRes = await request('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
describe('followers visibility', () => {
|
||||
before(async () => {
|
||||
await request('/i/update', {
|
||||
ffVisibility: 'followers',
|
||||
}, alice);
|
||||
});
|
||||
|
||||
assert.strictEqual(followingRes.status, 400);
|
||||
assert.strictEqual(followersRes.status, 400);
|
||||
}));
|
||||
it('shows followers and following to self', visible(alice));
|
||||
it('shows followers and following to a follower', visible(follower));
|
||||
it('hides followers and following from a non-follower', hidden(bob));
|
||||
it('hides followers and following when unauthenticated', hidden(null));
|
||||
|
||||
it('ffVisibility が followers なユーザーのフォロー/フォロワーをフォロワーが見れる', async(async () => {
|
||||
await request('/i/update', {
|
||||
ffVisibility: 'followers',
|
||||
}, alice);
|
||||
it('hides followers from ActivityPub representation', async () => {
|
||||
const followingRes = await simpleGet(`/users/${alice.id}/following`, 'application/activity+json').catch(res => ({ status: res.statusCode }));
|
||||
const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json').catch(res => ({ status: res.statusCode }));
|
||||
assert.strictEqual(followingRes.status, 403);
|
||||
assert.strictEqual(followersRes.status, 403);
|
||||
});
|
||||
});
|
||||
|
||||
await request('/following/create', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
describe('private visibility', () => {
|
||||
before(async () => {
|
||||
await request('/i/update', {
|
||||
ffVisibility: 'private',
|
||||
}, alice);
|
||||
});
|
||||
|
||||
const followingRes = await request('/users/following', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
const followersRes = await request('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
it('shows followers and following to self', visible(alice));
|
||||
it('hides followers and following from a follower', hidden(follower));
|
||||
it('hides followers and following from a non-follower', hidden(bob));
|
||||
it('hides followers and following when unauthenticated', hidden(null));
|
||||
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
}));
|
||||
|
||||
it('ffVisibility が private なユーザーのフォロー/フォロワーを自分で見れる', async(async () => {
|
||||
await request('/i/update', {
|
||||
ffVisibility: 'private',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await request('/users/following', {
|
||||
userId: alice.id,
|
||||
}, alice);
|
||||
const followersRes = await request('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
}));
|
||||
|
||||
it('ffVisibility が private なユーザーのフォロー/フォロワーを他人が見れない', async(async () => {
|
||||
await request('/i/update', {
|
||||
ffVisibility: 'private',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await request('/users/following', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
const followersRes = await request('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
|
||||
assert.strictEqual(followingRes.status, 400);
|
||||
assert.strictEqual(followersRes.status, 400);
|
||||
}));
|
||||
|
||||
describe('AP', () => {
|
||||
it('ffVisibility が public 以外ならばAPからは取得できない', async(async () => {
|
||||
{
|
||||
await request('/i/update', {
|
||||
ffVisibility: 'public',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await simpleGet(`/users/${alice.id}/following`, 'application/activity+json');
|
||||
const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json');
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
}
|
||||
{
|
||||
await request('/i/update', {
|
||||
ffVisibility: 'followers',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await simpleGet(`/users/${alice.id}/following`, 'application/activity+json').catch(res => ({ status: res.statusCode }));
|
||||
const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json').catch(res => ({ status: res.statusCode }));
|
||||
assert.strictEqual(followingRes.status, 403);
|
||||
assert.strictEqual(followersRes.status, 403);
|
||||
}
|
||||
{
|
||||
await request('/i/update', {
|
||||
ffVisibility: 'private',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await simpleGet(`/users/${alice.id}/following`, 'application/activity+json').catch(res => ({ status: res.statusCode }));
|
||||
const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json').catch(res => ({ status: res.statusCode }));
|
||||
assert.strictEqual(followingRes.status, 403);
|
||||
assert.strictEqual(followersRes.status, 403);
|
||||
}
|
||||
}));
|
||||
it('hides followers from ActivityPub representation', async () => {
|
||||
const followingRes = await simpleGet(`/users/${alice.id}/following`, 'application/activity+json').catch(res => ({ status: res.statusCode }));
|
||||
const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json').catch(res => ({ status: res.statusCode }));
|
||||
assert.strictEqual(followingRes.status, 403);
|
||||
assert.strictEqual(followersRes.status, 403);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Resolver from '../../src/remote/activitypub/resolver.js';
|
||||
import { Resolver } from '../../src/remote/activitypub/resolver.js';
|
||||
import { IObject } from '../../src/remote/activitypub/type.js';
|
||||
|
||||
type MockResponse = {
|
||||
|
|
|
@ -13,6 +13,8 @@ describe('Mute', () => {
|
|||
let carol: any;
|
||||
|
||||
before(async () => {
|
||||
this.timeout(0);
|
||||
|
||||
p = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
|
|
|
@ -13,6 +13,8 @@ describe('Note', () => {
|
|||
let bob: any;
|
||||
|
||||
before(async () => {
|
||||
this.timeout(0);
|
||||
|
||||
p = await startServer();
|
||||
const connection = await initTestDb(true);
|
||||
Notes = connection.getRepository(Note);
|
||||
|
@ -158,7 +160,7 @@ describe('Note', () => {
|
|||
replyId: '000000000000000000000000',
|
||||
};
|
||||
const res = await request('/notes/create', post, alice);
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('存在しないrenote対象で怒られる', async(async () => {
|
||||
|
@ -166,7 +168,7 @@ describe('Note', () => {
|
|||
renoteId: '000000000000000000000000',
|
||||
};
|
||||
const res = await request('/notes/create', post, alice);
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('不正なリプライ先IDで怒られる', async(async () => {
|
||||
|
@ -175,7 +177,7 @@ describe('Note', () => {
|
|||
replyId: 'foo',
|
||||
};
|
||||
const res = await request('/notes/create', post, alice);
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('不正なrenote対象IDで怒られる', async(async () => {
|
||||
|
@ -183,7 +185,7 @@ describe('Note', () => {
|
|||
renoteId: 'foo',
|
||||
};
|
||||
const res = await request('/notes/create', post, alice);
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('存在しないユーザーにメンションできる', async(async () => {
|
||||
|
@ -286,7 +288,7 @@ describe('Note', () => {
|
|||
choice: 2,
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.status, 409);
|
||||
}));
|
||||
|
||||
it('許可されている場合は複数投票できる', async(async () => {
|
||||
|
|
|
@ -14,6 +14,8 @@ describe('Creating a block activity', () => {
|
|||
let carol: any;
|
||||
|
||||
before(async () => {
|
||||
this.timeout(0);
|
||||
|
||||
await initTestDb();
|
||||
p = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
|
|
|
@ -38,6 +38,8 @@ describe('Streaming', () => {
|
|||
let list: any;
|
||||
|
||||
before(async () => {
|
||||
this.timeout(0);
|
||||
|
||||
p = await startServer();
|
||||
const connection = await initTestDb(true);
|
||||
Followings = connection.getRepository(Following);
|
||||
|
|
|
@ -12,6 +12,8 @@ describe('Note thread mute', () => {
|
|||
let carol: any;
|
||||
|
||||
before(async () => {
|
||||
this.timeout(0);
|
||||
|
||||
p = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
|
|
|
@ -13,6 +13,8 @@ describe('users/notes', () => {
|
|||
let jpgPngNote: any;
|
||||
|
||||
before(async () => {
|
||||
this.timeout(0);
|
||||
|
||||
p = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
const jpg = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg');
|
||||
|
|
|
@ -31,16 +31,15 @@ export const async = (fn: Function) => (done: Function) => {
|
|||
export const api = async (endpoint: string, params: any, me?: any) => {
|
||||
endpoint = endpoint.replace(/^\//, '');
|
||||
|
||||
const auth = me ? {
|
||||
i: me.token
|
||||
} : {};
|
||||
const auth = me ? { authorization: `Bearer ${me.token}` } : {};
|
||||
|
||||
const res = await got<string>(`http://localhost:${port}/api/${endpoint}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
...auth,
|
||||
},
|
||||
body: JSON.stringify(Object.assign(auth, params)),
|
||||
body: JSON.stringify(params),
|
||||
retry: {
|
||||
limit: 0,
|
||||
},
|
||||
|
@ -65,16 +64,15 @@ export const api = async (endpoint: string, params: any, me?: any) => {
|
|||
};
|
||||
|
||||
export const request = async (endpoint: string, params: any, me?: any): Promise<{ body: any, status: number }> => {
|
||||
const auth = me ? {
|
||||
i: me.token,
|
||||
} : {};
|
||||
const auth = me ? { authorization: `Bearer ${me.token}` } : {};
|
||||
|
||||
const res = await fetch(`http://localhost:${port}/api${endpoint}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...auth,
|
||||
},
|
||||
body: JSON.stringify(Object.assign(auth, params)),
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
|
||||
const status = res.status;
|
||||
|
|
|
@ -3,9 +3,9 @@ import * as foundkey from 'foundkey-js';
|
|||
import { showSuspendedDialog } from '@/scripts/show-suspended-dialog';
|
||||
import { i18n } from '@/i18n';
|
||||
import { del, get, set } from '@/scripts/idb-proxy';
|
||||
import { apiUrl } from '@/config';
|
||||
import { waiting, api, popup, popupMenu, success, alert } from '@/os';
|
||||
import { unisonReload, reloadChannel } from '@/scripts/unison-reload';
|
||||
import { MenuItem } from '@/types/menu';
|
||||
|
||||
// TODO: 他のタブと永続化されたstateを同期
|
||||
|
||||
|
@ -22,7 +22,7 @@ export async function signout() {
|
|||
waiting();
|
||||
localStorage.removeItem('account');
|
||||
|
||||
await removeAccount($i.id);
|
||||
if ($i) await removeAccount($i!.id);
|
||||
|
||||
const accounts = await getAccounts();
|
||||
|
||||
|
@ -99,14 +99,18 @@ function fetchAccount(token: string): Promise<Account> {
|
|||
}
|
||||
|
||||
export function updateAccount(accountData) {
|
||||
if (!$i) return;
|
||||
|
||||
for (const [key, value] of Object.entries(accountData)) {
|
||||
$i[key] = value;
|
||||
$i![key] = value;
|
||||
}
|
||||
localStorage.setItem('account', JSON.stringify($i));
|
||||
localStorage.setItem('account', JSON.stringify($i!));
|
||||
}
|
||||
|
||||
export function refreshAccount() {
|
||||
return fetchAccount($i.token).then(updateAccount);
|
||||
if (!$i) return;
|
||||
|
||||
return fetchAccount($i!.token).then(updateAccount);
|
||||
}
|
||||
|
||||
export async function login(token: Account['token'], redirect?: string) {
|
||||
|
@ -134,7 +138,39 @@ export async function openAccountMenu(opts: {
|
|||
active?: foundkey.entities.UserDetailed['id'];
|
||||
onChoose?: (account: foundkey.entities.UserDetailed) => void;
|
||||
}, ev: MouseEvent) {
|
||||
function showSigninDialog() {
|
||||
const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== $i?.id));
|
||||
const accountsPromise = api('users/show', { userIds: storedAccounts.map(x => x.id) });
|
||||
|
||||
const switchAccount = async (account: foundkey.entities.UserDetailed) => {
|
||||
const storedAccounts = await getAccounts();
|
||||
const token = storedAccounts.find(x => x.id === account.id)?.token;
|
||||
if (!token) {
|
||||
// TODO error handling?
|
||||
} else {
|
||||
login(token);
|
||||
}
|
||||
};
|
||||
const createItem = (account: foundkey.entities.UserDetailed): MenuItem => ({
|
||||
type: 'user',
|
||||
user: account,
|
||||
active: opts.active != null ? opts.active === account.id : false,
|
||||
action: () => {
|
||||
if (opts.onChoose) {
|
||||
opts.onChoose(account);
|
||||
} else {
|
||||
switchAccount(account);
|
||||
}
|
||||
},
|
||||
});
|
||||
const accountItemPromises: Promise<MenuItem[]> = storedAccounts.map(a => new Promise(res => {
|
||||
accountsPromise.then(accounts => {
|
||||
const account = accounts.find(x => x.id === a.id);
|
||||
if (account == null) return res(null);
|
||||
res(createItem(account));
|
||||
});
|
||||
}));
|
||||
|
||||
const showSigninDialog = () => {
|
||||
popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), {}, {
|
||||
done: res => {
|
||||
addAccount(res.id, res.i);
|
||||
|
@ -143,50 +179,14 @@ export async function openAccountMenu(opts: {
|
|||
}, 'closed');
|
||||
}
|
||||
|
||||
function createAccount() {
|
||||
const createAccount = () => {
|
||||
popup(defineAsyncComponent(() => import('@/components/signup-dialog.vue')), {}, {
|
||||
done: res => {
|
||||
addAccount(res.id, res.i);
|
||||
switchAccountWithToken(res.i);
|
||||
login(res.i);
|
||||
},
|
||||
}, 'closed');
|
||||
}
|
||||
|
||||
async function switchAccount(account: foundkey.entities.UserDetailed) {
|
||||
const storedAccounts = await getAccounts();
|
||||
const token = storedAccounts.find(x => x.id === account.id).token;
|
||||
switchAccountWithToken(token);
|
||||
}
|
||||
|
||||
function switchAccountWithToken(token: string) {
|
||||
login(token);
|
||||
}
|
||||
|
||||
const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== $i.id));
|
||||
const accountsPromise = api('users/show', { userIds: storedAccounts.map(x => x.id) });
|
||||
|
||||
function createItem(account: foundkey.entities.UserDetailed) {
|
||||
return {
|
||||
type: 'user',
|
||||
user: account,
|
||||
active: opts.active != null ? opts.active === account.id : false,
|
||||
action: () => {
|
||||
if (opts.onChoose) {
|
||||
opts.onChoose(account);
|
||||
} else {
|
||||
switchAccount(account);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const accountItemPromises = storedAccounts.map(a => new Promise(res => {
|
||||
accountsPromise.then(accounts => {
|
||||
const account = accounts.find(x => x.id === a.id);
|
||||
if (account == null) return res(null);
|
||||
res(createItem(account));
|
||||
});
|
||||
}));
|
||||
};
|
||||
|
||||
if (opts.withExtraOperation) {
|
||||
popupMenu([...[{
|
||||
|
@ -194,16 +194,16 @@ export async function openAccountMenu(opts: {
|
|||
text: i18n.ts.profile,
|
||||
to: `/@${ $i.username }`,
|
||||
avatar: $i,
|
||||
}, null, ...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises, {
|
||||
}, null, ...(opts.includeCurrentAccount && $i ? [createItem($i)] : []), ...accountItemPromises, {
|
||||
icon: 'fas fa-plus',
|
||||
text: i18n.ts.addAccount,
|
||||
action: () => {
|
||||
popupMenu([{
|
||||
text: i18n.ts.existingAccount,
|
||||
action: () => { showSigninDialog(); },
|
||||
action: showSigninDialog,
|
||||
}, {
|
||||
text: i18n.ts.createAccount,
|
||||
action: () => { createAccount(); },
|
||||
action: createAccount,
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
},
|
||||
}, {
|
||||
|
@ -211,11 +211,11 @@ export async function openAccountMenu(opts: {
|
|||
icon: 'fas fa-users',
|
||||
text: i18n.ts.manageAccounts,
|
||||
to: '/settings/accounts',
|
||||
}]], ev.currentTarget ?? ev.target, {
|
||||
}]], ev.currentTarget ?? ev.target ?? undefined, {
|
||||
align: 'left',
|
||||
});
|
||||
} else {
|
||||
popupMenu([...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises], ev.currentTarget ?? ev.target, {
|
||||
popupMenu([...(opts.includeCurrentAccount && $i ? [createItem($i)] : []), ...accountItemPromises], ev.currentTarget ?? ev.target ?? undefined, {
|
||||
align: 'left',
|
||||
});
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue