mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-23 05:23:09 +02:00
feat(backend/ApiCallService): allow limited access for suspend accounts
This commit is contained in:
parent
ab58b651f7
commit
c28e0abb75
49 changed files with 109 additions and 55 deletions
|
@ -276,17 +276,10 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
id: '1384574d-a912-4b81-8601-c7b1c4085df1',
|
id: '1384574d-a912-4b81-8601-c7b1c4085df1',
|
||||||
httpStatusCode: 401,
|
httpStatusCode: 401,
|
||||||
});
|
});
|
||||||
} else if (user!.isSuspended) {
|
|
||||||
throw new ApiError({
|
|
||||||
message: 'Your account has been suspended.',
|
|
||||||
code: 'YOUR_ACCOUNT_SUSPENDED',
|
|
||||||
kind: 'permission',
|
|
||||||
id: 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ep.meta.prohibitMoved) {
|
if (ep.meta.prohibitDeactivated) {
|
||||||
if (user?.movedToUri) {
|
if (user?.movedToUri) {
|
||||||
throw new ApiError({
|
throw new ApiError({
|
||||||
message: 'You have moved your account.',
|
message: 'You have moved your account.',
|
||||||
|
@ -295,6 +288,14 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
id: '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31',
|
id: '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (user?.isSuspended) {
|
||||||
|
throw new ApiError({
|
||||||
|
message: 'Your account has been suspended.',
|
||||||
|
code: 'YOUR_ACCOUNT_SUSPENDED',
|
||||||
|
kind: 'permission',
|
||||||
|
id: 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370',
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((ep.meta.requireModerator || ep.meta.requireAdmin) && !user!.isRoot) {
|
if ((ep.meta.requireModerator || ep.meta.requireAdmin) && !user!.isRoot) {
|
||||||
|
|
|
@ -729,7 +729,7 @@ export interface IEndpointMeta {
|
||||||
* 引っ越し済みのユーザーによるリクエストを禁止するか
|
* 引っ越し済みのユーザーによるリクエストを禁止するか
|
||||||
* 省略した場合は false として解釈されます。
|
* 省略した場合は false として解釈されます。
|
||||||
*/
|
*/
|
||||||
readonly prohibitMoved?: boolean;
|
readonly prohibitDeactivated?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* エンドポイントのリミテーションに関するやつ
|
* エンドポイントのリミテーションに関するやつ
|
||||||
|
|
|
@ -18,7 +18,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:account',
|
kind: 'write:account',
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:account',
|
kind: 'write:account',
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:channels',
|
kind: 'write:channels',
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:channels',
|
kind: 'write:channels',
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:channels',
|
kind: 'write:channels',
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:channels',
|
kind: 'write:channels',
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:channels',
|
kind: 'write:channels',
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:account',
|
kind: 'write:account',
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:account',
|
kind: 'write:account',
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:clip-favorite',
|
kind: 'write:clip-favorite',
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:account',
|
kind: 'write:account',
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:clip-favorite',
|
kind: 'write:clip-favorite',
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:account',
|
kind: 'write:account',
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
limit: {
|
limit: {
|
||||||
duration: ms('1hour'),
|
duration: ms('1hour'),
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:drive',
|
kind: 'write:drive',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -16,7 +16,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:flash',
|
kind: 'write:flash',
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:flash-likes',
|
kind: 'write:flash-likes',
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:flash-likes',
|
kind: 'write:flash-likes',
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:flash',
|
kind: 'write:flash',
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:following',
|
kind: 'write:following',
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:gallery',
|
kind: 'write:gallery',
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:gallery-likes',
|
kind: 'write:gallery-likes',
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:gallery-likes',
|
kind: 'write:gallery-likes',
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:gallery',
|
kind: 'write:gallery',
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { AchievementService, ACHIEVEMENT_TYPES } from '@/core/AchievementService
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { ApiError } from '../../error.js';
|
||||||
export const meta = {
|
export const meta = {
|
||||||
secure: true,
|
secure: true,
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
limit: {
|
limit: {
|
||||||
duration: ms('1hour'),
|
duration: ms('1hour'),
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { ApiError } from '../../error.js';
|
||||||
export const meta = {
|
export const meta = {
|
||||||
secure: true,
|
secure: true,
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
limit: {
|
limit: {
|
||||||
duration: ms('1hour'),
|
duration: ms('1hour'),
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { ApiError } from '../../error.js';
|
||||||
export const meta = {
|
export const meta = {
|
||||||
secure: true,
|
secure: true,
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
limit: {
|
limit: {
|
||||||
duration: ms('1hour'),
|
duration: ms('1hour'),
|
||||||
max: 1,
|
max: 1,
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { ApiError } from '../../error.js';
|
||||||
export const meta = {
|
export const meta = {
|
||||||
secure: true,
|
secure: true,
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
limit: {
|
limit: {
|
||||||
duration: ms('1hour'),
|
duration: ms('1hour'),
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { ApiError } from '../../error.js';
|
||||||
export const meta = {
|
export const meta = {
|
||||||
secure: true,
|
secure: true,
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
limit: {
|
limit: {
|
||||||
duration: ms('1hour'),
|
duration: ms('1hour'),
|
||||||
max: 1,
|
max: 1,
|
||||||
|
|
|
@ -25,7 +25,7 @@ export const meta = {
|
||||||
|
|
||||||
secure: true,
|
secure: true,
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
limit: {
|
limit: {
|
||||||
duration: ms('1day'),
|
duration: ms('1day'),
|
||||||
max: 5,
|
max: 5,
|
||||||
|
|
|
@ -13,7 +13,7 @@ export const meta = {
|
||||||
tags: ['account', 'notes'],
|
tags: ['account', 'notes'],
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:account',
|
kind: 'write:account',
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ export const meta = {
|
||||||
tags: ['account'],
|
tags: ['account'],
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:mutes',
|
kind: 'write:mutes',
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
limit: {
|
limit: {
|
||||||
duration: ms('1hour'),
|
duration: ms('1hour'),
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const meta = {
|
||||||
tags: ['notes', 'favorites'],
|
tags: ['notes', 'favorites'],
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:favorites',
|
kind: 'write:favorites',
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:votes',
|
kind: 'write:votes',
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:reactions',
|
kind: 'write:reactions',
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:pages',
|
kind: 'write:pages',
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:page-likes',
|
kind: 'write:page-likes',
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:page-likes',
|
kind: 'write:page-likes',
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:pages',
|
kind: 'write:pages',
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const meta = {
|
||||||
tags: ['account'],
|
tags: ['account'],
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:mutes',
|
kind: 'write:mutes',
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { UserListService } from '@/core/UserListService.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
res: {
|
res: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
|
|
@ -18,7 +18,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:account',
|
kind: 'write:account',
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:account',
|
kind: 'write:account',
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const meta = {
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
|
||||||
prohibitMoved: true,
|
prohibitDeactivated: true,
|
||||||
|
|
||||||
kind: 'write:account',
|
kind: 'write:account',
|
||||||
|
|
||||||
|
|
53
packages/backend/test/e2e/suspend.ts
Normal file
53
packages/backend/test/e2e/suspend.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
|
import * as assert from 'assert';
|
||||||
|
import { loadConfig } from '@/config.js';
|
||||||
|
import { User, UsersRepository } from '@/models/index.js';
|
||||||
|
import { jobQueue } from '@/boot/common.js';
|
||||||
|
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||||
|
import { uploadFile, signup, startServer, initTestDb, api, sleep, successfulApiCall } from '../utils.js';
|
||||||
|
import type { INestApplicationContext } from '@nestjs/common';
|
||||||
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
|
describe('Account Suspension', () => {
|
||||||
|
let app: INestApplicationContext;
|
||||||
|
|
||||||
|
let root: misskey.entities.MeSignup;
|
||||||
|
let alice: misskey.entities.MeSignup;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
app = await startServer();
|
||||||
|
root = await signup({ username: 'root' });
|
||||||
|
alice = await signup({ username: 'alice' });
|
||||||
|
|
||||||
|
await api('admin/suspend-user', { userId: alice.id }, root);
|
||||||
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await app.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Cannot create notes', async () => {
|
||||||
|
const res = await api('notes/create', { text: 'foo' }, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(res.status, 403);
|
||||||
|
assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_SUSPENDED');
|
||||||
|
assert.strictEqual(res.body.error.id, 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Can see notes', async () => {
|
||||||
|
const createRes = await api('notes/create', { text: 'bar' }, root);
|
||||||
|
assert.strictEqual(createRes.status, 200);
|
||||||
|
assert.strictEqual(createRes.body.createdNote.text, 'bar');
|
||||||
|
|
||||||
|
const showRes = await api('notes/show', { noteId: createRes.body.createdNote.id }, alice);
|
||||||
|
assert.strictEqual(showRes.status, 200);
|
||||||
|
assert.strictEqual(showRes.body.text, 'bar');
|
||||||
|
assert.strictEqual(showRes.body.id, createRes.body.createdNote.id);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue