Compare commits
13 commits
develop
...
fix-storyb
Author | SHA1 | Date | |
---|---|---|---|
5d4ab609f5 | |||
0370b4dfab | |||
a516557bb6 | |||
7c4e2b658d | |||
bb2eb57a35 | |||
698fa84b5a | |||
2184323ae7 | |||
0ba5edc6aa | |||
3fde3fbc4d | |||
24a0063af0 | |||
74e565b71c | |||
6ed7d1a76e | |||
c277934328 |
|
@ -46,6 +46,7 @@
|
||||||
- 連合の配送ジョブのパフォーマンスを向上(ロック機構の見直し、Redisキャッシュの活用)
|
- 連合の配送ジョブのパフォーマンスを向上(ロック機構の見直し、Redisキャッシュの活用)
|
||||||
- 全体的なDBクエリのパフォーマンスを向上
|
- 全体的なDBクエリのパフォーマンスを向上
|
||||||
- featuredノートのsignedGet回数を減らしました
|
- featuredノートのsignedGet回数を減らしました
|
||||||
|
- リモートサーバーからのNSFW映像のキャッシュだけを無効化できるオプションを追加しました
|
||||||
|
|
||||||
## 13.13.2
|
## 13.13.2
|
||||||
|
|
||||||
|
|
2
locales/index.d.ts
vendored
2
locales/index.d.ts
vendored
|
@ -159,6 +159,8 @@ export interface Locale {
|
||||||
"settingGuide": string;
|
"settingGuide": string;
|
||||||
"cacheRemoteFiles": string;
|
"cacheRemoteFiles": string;
|
||||||
"cacheRemoteFilesDescription": string;
|
"cacheRemoteFilesDescription": string;
|
||||||
|
"cacheRemoteSensitiveFiles": string;
|
||||||
|
"cacheRemoteSensitiveFilesDescription": string;
|
||||||
"flagAsBot": string;
|
"flagAsBot": string;
|
||||||
"flagAsBotDescription": string;
|
"flagAsBotDescription": string;
|
||||||
"flagAsCat": string;
|
"flagAsCat": string;
|
||||||
|
|
|
@ -156,6 +156,8 @@ addEmoji: "絵文字を追加"
|
||||||
settingGuide: "おすすめ設定"
|
settingGuide: "おすすめ設定"
|
||||||
cacheRemoteFiles: "リモートのファイルをキャッシュする"
|
cacheRemoteFiles: "リモートのファイルをキャッシュする"
|
||||||
cacheRemoteFilesDescription: "この設定を無効にすると、リモートファイルをキャッシュせず直リンクするようになります。サーバーのストレージを節約できますが、サムネイルが生成されないので通信量が増加します。"
|
cacheRemoteFilesDescription: "この設定を無効にすると、リモートファイルをキャッシュせず直リンクするようになります。サーバーのストレージを節約できますが、サムネイルが生成されないので通信量が増加します。"
|
||||||
|
cacheRemoteSensitiveFiles: "リモートのNSFWファイルをキャッシュする"
|
||||||
|
cacheRemoteSensitiveFilesDescription: "この設定を無効にすると、リモートのNSFWファイルだけはキャッシュせず直リンクするようになります。"
|
||||||
flagAsBot: "Botとして設定"
|
flagAsBot: "Botとして設定"
|
||||||
flagAsBotDescription: "このアカウントがプログラムによって運用される場合は、このフラグをオンにします。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Misskeyのシステム上での扱いがBotに合ったものになります。"
|
flagAsBotDescription: "このアカウントがプログラムによって運用される場合は、このフラグをオンにします。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Misskeyのシステム上での扱いがBotに合ったものになります。"
|
||||||
flagAsCat: "にゃああああああああああああああ!!!!!!!!!!!!"
|
flagAsCat: "にゃああああああああああああああ!!!!!!!!!!!!"
|
||||||
|
|
11
packages/backend/migration/1689102832143-nsfw-cache.js
Normal file
11
packages/backend/migration/1689102832143-nsfw-cache.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
export class NsfwCache1689102832143 {
|
||||||
|
name = 'NsfwCache1689102832143'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "cacheRemoteSensitiveFiles" boolean NOT NULL DEFAULT true`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "cacheRemoteSensitiveFiles"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { DriveFilesRepository } from '@/models/index.js';
|
import type { DriveFilesRepository } from '@/models/index.js';
|
||||||
import type { Config } from '@/config.js';
|
|
||||||
import type { RemoteUser } from '@/models/entities/User.js';
|
import type { RemoteUser } from '@/models/entities/User.js';
|
||||||
import type { DriveFile } from '@/models/entities/DriveFile.js';
|
import type { DriveFile } from '@/models/entities/DriveFile.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
@ -20,9 +19,6 @@ export class ApImageService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
|
||||||
private config: Config,
|
|
||||||
|
|
||||||
@Inject(DI.driveFilesRepository)
|
@Inject(DI.driveFilesRepository)
|
||||||
private driveFilesRepository: DriveFilesRepository,
|
private driveFilesRepository: DriveFilesRepository,
|
||||||
|
|
||||||
|
@ -47,7 +43,7 @@ export class ApImageService {
|
||||||
const image = await this.apResolverService.createResolver().resolve(value);
|
const image = await this.apResolverService.createResolver().resolve(value);
|
||||||
|
|
||||||
if (image.url == null) {
|
if (image.url == null) {
|
||||||
throw new Error('invalid image: url not privided');
|
throw new Error('invalid image: url not provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof image.url !== 'string') {
|
if (typeof image.url !== 'string') {
|
||||||
|
@ -62,12 +58,17 @@ export class ApImageService {
|
||||||
|
|
||||||
const instance = await this.metaService.fetch();
|
const instance = await this.metaService.fetch();
|
||||||
|
|
||||||
|
// Cache if remote file cache is on AND either
|
||||||
|
// 1. remote sensitive file is also on
|
||||||
|
// 2. or the image is not sensitive
|
||||||
|
const shouldBeCached = instance.cacheRemoteFiles && (instance.cacheRemoteSensitiveFiles || !image.sensitive);
|
||||||
|
|
||||||
const file = await this.driveService.uploadFromUrl({
|
const file = await this.driveService.uploadFromUrl({
|
||||||
url: image.url,
|
url: image.url,
|
||||||
user: actor,
|
user: actor,
|
||||||
uri: image.url,
|
uri: image.url,
|
||||||
sensitive: image.sensitive,
|
sensitive: image.sensitive,
|
||||||
isLink: !instance.cacheRemoteFiles,
|
isLink: !shouldBeCached,
|
||||||
comment: truncate(image.name ?? undefined, DB_MAX_IMAGE_COMMENT_LENGTH),
|
comment: truncate(image.name ?? undefined, DB_MAX_IMAGE_COMMENT_LENGTH),
|
||||||
});
|
});
|
||||||
if (!file.isLink || file.url === image.url) return file;
|
if (!file.isLink || file.url === image.url) return file;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { Entity, Column, PrimaryColumn, ManyToOne, JoinColumn } from 'typeorm';
|
import { Entity, Column, PrimaryColumn, ManyToOne, JoinColumn } from 'typeorm';
|
||||||
import { id } from '../id.js';
|
import { id } from '../id.js';
|
||||||
import { User } from './User.js';
|
import { User } from './User.js';
|
||||||
import type { Clip } from './Clip.js';
|
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Meta {
|
export class Meta {
|
||||||
|
@ -126,6 +125,11 @@ export class Meta {
|
||||||
})
|
})
|
||||||
public cacheRemoteFiles: boolean;
|
public cacheRemoteFiles: boolean;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: true,
|
||||||
|
})
|
||||||
|
public cacheRemoteSensitiveFiles: boolean;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
...id(),
|
...id(),
|
||||||
nullable: true,
|
nullable: true,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
|
@ -20,6 +19,10 @@ export const meta = {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
cacheRemoteSensitiveFiles: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
emailRequiredForSignup: {
|
emailRequiredForSignup: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
@ -332,6 +335,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
enableServiceWorker: instance.enableServiceWorker,
|
enableServiceWorker: instance.enableServiceWorker,
|
||||||
translatorAvailable: instance.deeplAuthKey != null,
|
translatorAvailable: instance.deeplAuthKey != null,
|
||||||
cacheRemoteFiles: instance.cacheRemoteFiles,
|
cacheRemoteFiles: instance.cacheRemoteFiles,
|
||||||
|
cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles,
|
||||||
pinnedUsers: instance.pinnedUsers,
|
pinnedUsers: instance.pinnedUsers,
|
||||||
hiddenTags: instance.hiddenTags,
|
hiddenTags: instance.hiddenTags,
|
||||||
blockedHosts: instance.blockedHosts,
|
blockedHosts: instance.blockedHosts,
|
||||||
|
|
|
@ -43,6 +43,7 @@ export const paramDef = {
|
||||||
defaultLightTheme: { type: 'string', nullable: true },
|
defaultLightTheme: { type: 'string', nullable: true },
|
||||||
defaultDarkTheme: { type: 'string', nullable: true },
|
defaultDarkTheme: { type: 'string', nullable: true },
|
||||||
cacheRemoteFiles: { type: 'boolean' },
|
cacheRemoteFiles: { type: 'boolean' },
|
||||||
|
cacheRemoteSensitiveFiles: { type: 'boolean' },
|
||||||
emailRequiredForSignup: { type: 'boolean' },
|
emailRequiredForSignup: { type: 'boolean' },
|
||||||
enableHcaptcha: { type: 'boolean' },
|
enableHcaptcha: { type: 'boolean' },
|
||||||
hcaptchaSiteKey: { type: 'string', nullable: true },
|
hcaptchaSiteKey: { type: 'string', nullable: true },
|
||||||
|
@ -193,6 +194,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
set.cacheRemoteFiles = ps.cacheRemoteFiles;
|
set.cacheRemoteFiles = ps.cacheRemoteFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.cacheRemoteSensitiveFiles !== undefined) {
|
||||||
|
set.cacheRemoteSensitiveFiles = ps.cacheRemoteSensitiveFiles;
|
||||||
|
}
|
||||||
|
|
||||||
if (ps.emailRequiredForSignup !== undefined) {
|
if (ps.emailRequiredForSignup !== undefined) {
|
||||||
set.emailRequiredForSignup = ps.emailRequiredForSignup;
|
set.emailRequiredForSignup = ps.emailRequiredForSignup;
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,6 +83,10 @@ export const meta = {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
cacheRemoteSensitiveFiles: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
emailRequiredForSignup: {
|
emailRequiredForSignup: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
@ -329,6 +333,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
|
|
||||||
...(ps.detail ? {
|
...(ps.detail ? {
|
||||||
cacheRemoteFiles: instance.cacheRemoteFiles,
|
cacheRemoteFiles: instance.cacheRemoteFiles,
|
||||||
|
cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles,
|
||||||
requireSetup: (await this.usersRepository.countBy({
|
requireSetup: (await this.usersRepository.countBy({
|
||||||
host: IsNull(),
|
host: IsNull(),
|
||||||
})) === 0,
|
})) === 0,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import * as assert from 'assert';
|
||||||
import { Test } from '@nestjs/testing';
|
import { Test } from '@nestjs/testing';
|
||||||
import { jest } from '@jest/globals';
|
import { jest } from '@jest/globals';
|
||||||
|
|
||||||
|
import { ApImageService } from '@/core/activitypub/models/ApImageService.js';
|
||||||
import { ApNoteService } from '@/core/activitypub/models/ApNoteService.js';
|
import { ApNoteService } from '@/core/activitypub/models/ApNoteService.js';
|
||||||
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
||||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
|
@ -11,9 +12,12 @@ import { GlobalModule } from '@/GlobalModule.js';
|
||||||
import { CoreModule } from '@/core/CoreModule.js';
|
import { CoreModule } from '@/core/CoreModule.js';
|
||||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import type { IActor, ICollection, IPost } from '@/core/activitypub/type.js';
|
import type { IActor, IApDocument, ICollection, IPost } from '@/core/activitypub/type.js';
|
||||||
import { Note } from '@/models/index.js';
|
import { Meta, Note } from '@/models/index.js';
|
||||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||||
|
import { DownloadService } from '@/core/DownloadService.js';
|
||||||
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
import type { RemoteUser } from '@/models/entities/User.js';
|
||||||
import { MockResolver } from '../misc/mock-resolver.js';
|
import { MockResolver } from '../misc/mock-resolver.js';
|
||||||
|
|
||||||
const host = 'https://host1.test';
|
const host = 'https://host1.test';
|
||||||
|
@ -63,15 +67,46 @@ function createRandomFeaturedCollection(actor: NonTransientIActor, length: numbe
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createRandomRemoteUser(
|
||||||
|
resolver: MockResolver,
|
||||||
|
personService: ApPersonService,
|
||||||
|
): Promise<RemoteUser> {
|
||||||
|
const actor = createRandomActor();
|
||||||
|
resolver.register(actor.id, actor);
|
||||||
|
|
||||||
|
return await personService.createPerson(actor.id, resolver);
|
||||||
|
}
|
||||||
|
|
||||||
describe('ActivityPub', () => {
|
describe('ActivityPub', () => {
|
||||||
|
let imageService: ApImageService;
|
||||||
let noteService: ApNoteService;
|
let noteService: ApNoteService;
|
||||||
let personService: ApPersonService;
|
let personService: ApPersonService;
|
||||||
let rendererService: ApRendererService;
|
let rendererService: ApRendererService;
|
||||||
let resolver: MockResolver;
|
let resolver: MockResolver;
|
||||||
|
|
||||||
|
const metaInitial = {
|
||||||
|
cacheRemoteFiles: true,
|
||||||
|
cacheRemoteSensitiveFiles: true,
|
||||||
|
blockedHosts: [] as string[],
|
||||||
|
sensitiveWords: [] as string[],
|
||||||
|
} as Meta;
|
||||||
|
let meta = metaInitial;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const app = await Test.createTestingModule({
|
const app = await Test.createTestingModule({
|
||||||
imports: [GlobalModule, CoreModule],
|
imports: [GlobalModule, CoreModule],
|
||||||
|
})
|
||||||
|
.overrideProvider(DownloadService).useValue({
|
||||||
|
async downloadUrl(): Promise<{ filename: string }> {
|
||||||
|
return {
|
||||||
|
filename: 'dummy.tmp',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.overrideProvider(MetaService).useValue({
|
||||||
|
async fetch(): Promise<Meta> {
|
||||||
|
return meta;
|
||||||
|
},
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
await app.init();
|
await app.init();
|
||||||
|
@ -80,6 +115,7 @@ describe('ActivityPub', () => {
|
||||||
noteService = app.get<ApNoteService>(ApNoteService);
|
noteService = app.get<ApNoteService>(ApNoteService);
|
||||||
personService = app.get<ApPersonService>(ApPersonService);
|
personService = app.get<ApPersonService>(ApPersonService);
|
||||||
rendererService = app.get<ApRendererService>(ApRendererService);
|
rendererService = app.get<ApRendererService>(ApRendererService);
|
||||||
|
imageService = app.get<ApImageService>(ApImageService);
|
||||||
resolver = new MockResolver(await app.resolve<LoggerService>(LoggerService));
|
resolver = new MockResolver(await app.resolve<LoggerService>(LoggerService));
|
||||||
|
|
||||||
// Prevent ApPersonService from fetching instance, as it causes Jest import-after-test error
|
// Prevent ApPersonService from fetching instance, as it causes Jest import-after-test error
|
||||||
|
@ -219,4 +255,91 @@ describe('ActivityPub', () => {
|
||||||
assert.strictEqual(note.uri, actor2Note.id);
|
assert.strictEqual(note.uri, actor2Note.id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Images', () => {
|
||||||
|
test('Create images', async () => {
|
||||||
|
const imageObject: IApDocument = {
|
||||||
|
type: 'Document',
|
||||||
|
mediaType: 'image/png',
|
||||||
|
url: 'http://host1.test/foo.png',
|
||||||
|
name: '',
|
||||||
|
};
|
||||||
|
const driveFile = await imageService.createImage(
|
||||||
|
await createRandomRemoteUser(resolver, personService),
|
||||||
|
imageObject,
|
||||||
|
);
|
||||||
|
assert.ok(!driveFile.isLink);
|
||||||
|
|
||||||
|
const sensitiveImageObject: IApDocument = {
|
||||||
|
type: 'Document',
|
||||||
|
mediaType: 'image/png',
|
||||||
|
url: 'http://host1.test/bar.png',
|
||||||
|
name: '',
|
||||||
|
sensitive: true,
|
||||||
|
};
|
||||||
|
const sensitiveDriveFile = await imageService.createImage(
|
||||||
|
await createRandomRemoteUser(resolver, personService),
|
||||||
|
sensitiveImageObject,
|
||||||
|
);
|
||||||
|
assert.ok(!sensitiveDriveFile.isLink);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cacheRemoteFiles=false disables caching', async () => {
|
||||||
|
meta = { ...metaInitial, cacheRemoteFiles: false };
|
||||||
|
|
||||||
|
const imageObject: IApDocument = {
|
||||||
|
type: 'Document',
|
||||||
|
mediaType: 'image/png',
|
||||||
|
url: 'http://host1.test/foo.png',
|
||||||
|
name: '',
|
||||||
|
};
|
||||||
|
const driveFile = await imageService.createImage(
|
||||||
|
await createRandomRemoteUser(resolver, personService),
|
||||||
|
imageObject,
|
||||||
|
);
|
||||||
|
assert.ok(driveFile.isLink);
|
||||||
|
|
||||||
|
const sensitiveImageObject: IApDocument = {
|
||||||
|
type: 'Document',
|
||||||
|
mediaType: 'image/png',
|
||||||
|
url: 'http://host1.test/bar.png',
|
||||||
|
name: '',
|
||||||
|
sensitive: true,
|
||||||
|
};
|
||||||
|
const sensitiveDriveFile = await imageService.createImage(
|
||||||
|
await createRandomRemoteUser(resolver, personService),
|
||||||
|
sensitiveImageObject,
|
||||||
|
);
|
||||||
|
assert.ok(sensitiveDriveFile.isLink);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cacheRemoteSensitiveFiles=false only affects sensitive files', async () => {
|
||||||
|
meta = { ...metaInitial, cacheRemoteSensitiveFiles: false };
|
||||||
|
|
||||||
|
const imageObject: IApDocument = {
|
||||||
|
type: 'Document',
|
||||||
|
mediaType: 'image/png',
|
||||||
|
url: 'http://host1.test/foo.png',
|
||||||
|
name: '',
|
||||||
|
};
|
||||||
|
const driveFile = await imageService.createImage(
|
||||||
|
await createRandomRemoteUser(resolver, personService),
|
||||||
|
imageObject,
|
||||||
|
);
|
||||||
|
assert.ok(!driveFile.isLink);
|
||||||
|
|
||||||
|
const sensitiveImageObject: IApDocument = {
|
||||||
|
type: 'Document',
|
||||||
|
mediaType: 'image/png',
|
||||||
|
url: 'http://host1.test/bar.png',
|
||||||
|
name: '',
|
||||||
|
sensitive: true,
|
||||||
|
};
|
||||||
|
const sensitiveDriveFile = await imageService.createImage(
|
||||||
|
await createRandomRemoteUser(resolver, personService),
|
||||||
|
sensitiveImageObject,
|
||||||
|
);
|
||||||
|
assert.ok(sensitiveDriveFile.isLink);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { userEvent, waitFor, within } from '@storybook/testing-library';
|
||||||
import { StoryObj } from '@storybook/vue3';
|
import { StoryObj } from '@storybook/vue3';
|
||||||
import MkAd from './MkAd.vue';
|
import MkAd from './MkAd.vue';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
|
import { tick } from '@/scripts/test-utils';
|
||||||
const common = {
|
const common = {
|
||||||
render(args) {
|
render(args) {
|
||||||
return {
|
return {
|
||||||
|
@ -37,6 +38,7 @@ const common = {
|
||||||
await expect(i).toBeInTheDocument();
|
await expect(i).toBeInTheDocument();
|
||||||
await userEvent.click(i);
|
await userEvent.click(i);
|
||||||
await waitFor(() => expect(canvasElement).toHaveTextContent(i18n.ts._ad.back));
|
await waitFor(() => expect(canvasElement).toHaveTextContent(i18n.ts._ad.back));
|
||||||
|
await tick();
|
||||||
await expect(a).not.toBeInTheDocument();
|
await expect(a).not.toBeInTheDocument();
|
||||||
await expect(i).not.toBeInTheDocument();
|
await expect(i).not.toBeInTheDocument();
|
||||||
buttons = canvas.getAllByRole<HTMLButtonElement>('button');
|
buttons = canvas.getAllByRole<HTMLButtonElement>('button');
|
||||||
|
@ -51,6 +53,7 @@ const common = {
|
||||||
await expect(back).toHaveTextContent(i18n.ts._ad.back);
|
await expect(back).toHaveTextContent(i18n.ts._ad.back);
|
||||||
await userEvent.click(back);
|
await userEvent.click(back);
|
||||||
await waitFor(() => expect(canvas.queryByRole('img')).toBeTruthy());
|
await waitFor(() => expect(canvas.queryByRole('img')).toBeTruthy());
|
||||||
|
await tick();
|
||||||
if (reduce) {
|
if (reduce) {
|
||||||
await expect(reduce).not.toBeInTheDocument();
|
await expect(reduce).not.toBeInTheDocument();
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,13 @@
|
||||||
<template #label>{{ i18n.ts.cacheRemoteFiles }}</template>
|
<template #label>{{ i18n.ts.cacheRemoteFiles }}</template>
|
||||||
<template #caption>{{ i18n.ts.cacheRemoteFilesDescription }}</template>
|
<template #caption>{{ i18n.ts.cacheRemoteFilesDescription }}</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
|
|
||||||
|
<template v-if="cacheRemoteFiles">
|
||||||
|
<MkSwitch v-model="cacheRemoteSensitiveFiles">
|
||||||
|
<template #label>{{ i18n.ts.cacheRemoteSensitiveFiles }}</template>
|
||||||
|
<template #caption>{{ i18n.ts.cacheRemoteSensitiveFilesDescription }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
|
@ -104,7 +111,6 @@ import { fetchInstance } from '@/instance';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkColorInput from '@/components/MkColorInput.vue';
|
|
||||||
|
|
||||||
let name: string | null = $ref(null);
|
let name: string | null = $ref(null);
|
||||||
let description: string | null = $ref(null);
|
let description: string | null = $ref(null);
|
||||||
|
@ -112,13 +118,14 @@ let maintainerName: string | null = $ref(null);
|
||||||
let maintainerEmail: string | null = $ref(null);
|
let maintainerEmail: string | null = $ref(null);
|
||||||
let pinnedUsers: string = $ref('');
|
let pinnedUsers: string = $ref('');
|
||||||
let cacheRemoteFiles: boolean = $ref(false);
|
let cacheRemoteFiles: boolean = $ref(false);
|
||||||
|
let cacheRemoteSensitiveFiles: boolean = $ref(false);
|
||||||
let enableServiceWorker: boolean = $ref(false);
|
let enableServiceWorker: boolean = $ref(false);
|
||||||
let swPublicKey: any = $ref(null);
|
let swPublicKey: any = $ref(null);
|
||||||
let swPrivateKey: any = $ref(null);
|
let swPrivateKey: any = $ref(null);
|
||||||
let deeplAuthKey: string = $ref('');
|
let deeplAuthKey: string = $ref('');
|
||||||
let deeplIsPro: boolean = $ref(false);
|
let deeplIsPro: boolean = $ref(false);
|
||||||
|
|
||||||
async function init() {
|
async function init(): Promise<void> {
|
||||||
const meta = await os.api('admin/meta');
|
const meta = await os.api('admin/meta');
|
||||||
name = meta.name;
|
name = meta.name;
|
||||||
description = meta.description;
|
description = meta.description;
|
||||||
|
@ -126,6 +133,7 @@ async function init() {
|
||||||
maintainerEmail = meta.maintainerEmail;
|
maintainerEmail = meta.maintainerEmail;
|
||||||
pinnedUsers = meta.pinnedUsers.join('\n');
|
pinnedUsers = meta.pinnedUsers.join('\n');
|
||||||
cacheRemoteFiles = meta.cacheRemoteFiles;
|
cacheRemoteFiles = meta.cacheRemoteFiles;
|
||||||
|
cacheRemoteSensitiveFiles = meta.cacheRemoteSensitiveFiles;
|
||||||
enableServiceWorker = meta.enableServiceWorker;
|
enableServiceWorker = meta.enableServiceWorker;
|
||||||
swPublicKey = meta.swPublickey;
|
swPublicKey = meta.swPublickey;
|
||||||
swPrivateKey = meta.swPrivateKey;
|
swPrivateKey = meta.swPrivateKey;
|
||||||
|
@ -133,7 +141,7 @@ async function init() {
|
||||||
deeplIsPro = meta.deeplIsPro;
|
deeplIsPro = meta.deeplIsPro;
|
||||||
}
|
}
|
||||||
|
|
||||||
function save() {
|
function save(): void {
|
||||||
os.apiWithDialog('admin/update-meta', {
|
os.apiWithDialog('admin/update-meta', {
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
|
@ -141,6 +149,7 @@ function save() {
|
||||||
maintainerEmail,
|
maintainerEmail,
|
||||||
pinnedUsers: pinnedUsers.split('\n'),
|
pinnedUsers: pinnedUsers.split('\n'),
|
||||||
cacheRemoteFiles,
|
cacheRemoteFiles,
|
||||||
|
cacheRemoteSensitiveFiles,
|
||||||
enableServiceWorker,
|
enableServiceWorker,
|
||||||
swPublicKey,
|
swPublicKey,
|
||||||
swPrivateKey,
|
swPrivateKey,
|
||||||
|
|
|
@ -271,6 +271,7 @@ type DetailedInstanceMetadata = LiteInstanceMetadata & {
|
||||||
pinnedPages: string[];
|
pinnedPages: string[];
|
||||||
pinnedClipId: string | null;
|
pinnedClipId: string | null;
|
||||||
cacheRemoteFiles: boolean;
|
cacheRemoteFiles: boolean;
|
||||||
|
cacheRemoteSensitiveFiles: boolean;
|
||||||
requireSetup: boolean;
|
requireSetup: boolean;
|
||||||
proxyAccountName: string | null;
|
proxyAccountName: string | null;
|
||||||
features: Record<string, any>;
|
features: Record<string, any>;
|
||||||
|
@ -327,6 +328,10 @@ export type Endpoints = {
|
||||||
req: TODO;
|
req: TODO;
|
||||||
res: TODO;
|
res: TODO;
|
||||||
};
|
};
|
||||||
|
'admin/meta': {
|
||||||
|
req: TODO;
|
||||||
|
res: TODO;
|
||||||
|
};
|
||||||
'admin/reset-password': {
|
'admin/reset-password': {
|
||||||
req: TODO;
|
req: TODO;
|
||||||
res: TODO;
|
res: TODO;
|
||||||
|
@ -2756,7 +2761,7 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u
|
||||||
//
|
//
|
||||||
// src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts
|
// src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts
|
||||||
// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
|
// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
|
||||||
// src/api.types.ts:620:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
// src/api.types.ts:621:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
||||||
// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
|
// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
|
||||||
|
|
||||||
// (No @packageDocumentation comment for this package)
|
// (No @packageDocumentation comment for this package)
|
||||||
|
|
|
@ -20,6 +20,7 @@ export type Endpoints = {
|
||||||
'admin/get-table-stats': { req: TODO; res: TODO; };
|
'admin/get-table-stats': { req: TODO; res: TODO; };
|
||||||
'admin/invite': { req: TODO; res: TODO; };
|
'admin/invite': { req: TODO; res: TODO; };
|
||||||
'admin/logs': { req: TODO; res: TODO; };
|
'admin/logs': { req: TODO; res: TODO; };
|
||||||
|
'admin/meta': { req: TODO; res: TODO; };
|
||||||
'admin/reset-password': { req: TODO; res: TODO; };
|
'admin/reset-password': { req: TODO; res: TODO; };
|
||||||
'admin/resolve-abuse-user-report': { req: TODO; res: TODO; };
|
'admin/resolve-abuse-user-report': { req: TODO; res: TODO; };
|
||||||
'admin/resync-chart': { req: TODO; res: TODO; };
|
'admin/resync-chart': { req: TODO; res: TODO; };
|
||||||
|
|
|
@ -338,6 +338,7 @@ export type DetailedInstanceMetadata = LiteInstanceMetadata & {
|
||||||
pinnedPages: string[];
|
pinnedPages: string[];
|
||||||
pinnedClipId: string | null;
|
pinnedClipId: string | null;
|
||||||
cacheRemoteFiles: boolean;
|
cacheRemoteFiles: boolean;
|
||||||
|
cacheRemoteSensitiveFiles: boolean;
|
||||||
requireSetup: boolean;
|
requireSetup: boolean;
|
||||||
proxyAccountName: string | null;
|
proxyAccountName: string | null;
|
||||||
features: Record<string, any>;
|
features: Record<string, any>;
|
||||||
|
|
Loading…
Reference in a new issue