Compare commits
	
		
			13 Commits
		
	
	
		
			2024.2.0-b
			...
			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>; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user