parent
							
								
									82f9d5501b
								
							
						
					
					
						commit
						e68278f93e
					
				
					 12 changed files with 116 additions and 6 deletions
				
			
		|  | @ -20,6 +20,7 @@ You should also include the user name that made the change. | ||||||
| 
 | 
 | ||||||
| ### Improvements | ### Improvements | ||||||
| - インスタンスデフォルトテーマを設定できるように @syuilo | - インスタンスデフォルトテーマを設定できるように @syuilo | ||||||
|  | - ミュートに期限を設定できるように @syuilo | ||||||
| - プロフィールの追加情報を最大16まで保存できるように @syuilo | - プロフィールの追加情報を最大16まで保存できるように @syuilo | ||||||
| - 連合チャートにPub&Subを追加 @syuilo | - 連合チャートにPub&Subを追加 @syuilo | ||||||
| - デフォルトで10秒以上時間がかかるデータベースへのクエリは中断されるように @syuilo | - デフォルトで10秒以上時間がかかるデータベースへのクエリは中断されるように @syuilo | ||||||
|  |  | ||||||
|  | @ -834,6 +834,12 @@ searchByGoogle: "ググる" | ||||||
| instanceDefaultLightTheme: "インスタンスデフォルトのライトテーマ" | instanceDefaultLightTheme: "インスタンスデフォルトのライトテーマ" | ||||||
| instanceDefaultDarkTheme: "インスタンスデフォルトのダークテーマ" | instanceDefaultDarkTheme: "インスタンスデフォルトのダークテーマ" | ||||||
| instanceDefaultThemeDescription: "オブジェクト形式のテーマコードを記入します。" | instanceDefaultThemeDescription: "オブジェクト形式のテーマコードを記入します。" | ||||||
|  | mutePeriod: "ミュートする期限" | ||||||
|  | indefinitely: "無期限" | ||||||
|  | tenMinutes: "10分" | ||||||
|  | oneHour: "1時間" | ||||||
|  | oneDay: "1日" | ||||||
|  | oneWeek: "1週間" | ||||||
| 
 | 
 | ||||||
| _emailUnavailable: | _emailUnavailable: | ||||||
|   used: "既に使用されています" |   used: "既に使用されています" | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								packages/backend/migration/1646387162108-mute-expires-at.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								packages/backend/migration/1646387162108-mute-expires-at.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | ||||||
|  | export class muteExpiresAt1646387162108 { | ||||||
|  |     name = 'muteExpiresAt1646387162108' | ||||||
|  | 
 | ||||||
|  |     async up(queryRunner) { | ||||||
|  |         await queryRunner.query(`ALTER TABLE "muting" ADD "expiresAt" TIMESTAMP WITH TIME ZONE`); | ||||||
|  |         await queryRunner.query(`CREATE INDEX "IDX_c1fd1c3dfb0627aa36c253fd14" ON "muting" ("expiresAt") `); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async down(queryRunner) { | ||||||
|  |         await queryRunner.query(`DROP INDEX "public"."IDX_c1fd1c3dfb0627aa36c253fd14"`); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "muting" DROP COLUMN "expiresAt"`); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -14,6 +14,13 @@ export class Muting { | ||||||
| 	}) | 	}) | ||||||
| 	public createdAt: Date; | 	public createdAt: Date; | ||||||
| 
 | 
 | ||||||
|  | 	@Index() | ||||||
|  | 	@Column('timestamp with time zone', { | ||||||
|  | 		nullable: true, | ||||||
|  | 		default: null, | ||||||
|  | 	}) | ||||||
|  | 	public expiresAt: Date | null; | ||||||
|  | 
 | ||||||
| 	@Index() | 	@Index() | ||||||
| 	@Column({ | 	@Column({ | ||||||
| 		...id(), | 		...id(), | ||||||
|  |  | ||||||
|  | @ -16,6 +16,7 @@ export class MutingRepository extends Repository<Muting> { | ||||||
| 		return await awaitAll({ | 		return await awaitAll({ | ||||||
| 			id: muting.id, | 			id: muting.id, | ||||||
| 			createdAt: muting.createdAt.toISOString(), | 			createdAt: muting.createdAt.toISOString(), | ||||||
|  | 			expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null, | ||||||
| 			muteeId: muting.muteeId, | 			muteeId: muting.muteeId, | ||||||
| 			mutee: Users.pack(muting.muteeId, me, { | 			mutee: Users.pack(muting.muteeId, me, { | ||||||
| 				detail: true, | 				detail: true, | ||||||
|  |  | ||||||
|  | @ -12,6 +12,11 @@ export const packedMutingSchema = { | ||||||
| 			optional: false, nullable: false, | 			optional: false, nullable: false, | ||||||
| 			format: 'date-time', | 			format: 'date-time', | ||||||
| 		}, | 		}, | ||||||
|  | 		expiresAt: { | ||||||
|  | 			type: 'string', | ||||||
|  | 			optional: false, nullable: true, | ||||||
|  | 			format: 'date-time', | ||||||
|  | 		}, | ||||||
| 		muteeId: { | 		muteeId: { | ||||||
| 			type: 'string', | 			type: 'string', | ||||||
| 			optional: false, nullable: false, | 			optional: false, nullable: false, | ||||||
|  |  | ||||||
|  | @ -273,6 +273,11 @@ export default function() { | ||||||
| 		repeat: { cron: '0 0 * * *' }, | 		repeat: { cron: '0 0 * * *' }, | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
|  | 	systemQueue.add('checkExpiredMutings', { | ||||||
|  | 	}, { | ||||||
|  | 		repeat: { cron: '*/5 * * * *' }, | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
| 	processSystemQueue(systemQueue); | 	processSystemQueue(systemQueue); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ import { addFile } from '@/services/drive/add-file.js'; | ||||||
| import { format as dateFormat } from 'date-fns'; | import { format as dateFormat } from 'date-fns'; | ||||||
| import { getFullApAccount } from '@/misc/convert-host.js'; | import { getFullApAccount } from '@/misc/convert-host.js'; | ||||||
| import { Users, Mutings } from '@/models/index.js'; | import { Users, Mutings } from '@/models/index.js'; | ||||||
| import { MoreThan } from 'typeorm'; | import { IsNull, MoreThan } from 'typeorm'; | ||||||
| import { DbUserJobData } from '@/queue/types.js'; | import { DbUserJobData } from '@/queue/types.js'; | ||||||
| 
 | 
 | ||||||
| const logger = queueLogger.createSubLogger('export-mute'); | const logger = queueLogger.createSubLogger('export-mute'); | ||||||
|  | @ -40,6 +40,7 @@ export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promi | ||||||
| 		const mutes = await Mutings.find({ | 		const mutes = await Mutings.find({ | ||||||
| 			where: { | 			where: { | ||||||
| 				muterId: user.id, | 				muterId: user.id, | ||||||
|  | 				expiresAt: IsNull(), | ||||||
| 				...(cursor ? { id: MoreThan(cursor) } : {}), | 				...(cursor ? { id: MoreThan(cursor) } : {}), | ||||||
| 			}, | 			}, | ||||||
| 			take: 100, | 			take: 100, | ||||||
|  |  | ||||||
|  | @ -0,0 +1,30 @@ | ||||||
|  | import Bull from 'bull'; | ||||||
|  | import { In } from 'typeorm'; | ||||||
|  | import { Mutings } from '@/models/index.js'; | ||||||
|  | import { queueLogger } from '../../logger.js'; | ||||||
|  | import { publishUserEvent } from '@/services/stream.js'; | ||||||
|  | 
 | ||||||
|  | const logger = queueLogger.createSubLogger('check-expired-mutings'); | ||||||
|  | 
 | ||||||
|  | export async function checkExpiredMutings(job: Bull.Job<Record<string, unknown>>, done: any): Promise<void> { | ||||||
|  | 	logger.info(`Checking expired mutings...`); | ||||||
|  | 
 | ||||||
|  | 	const expired = await Mutings.createQueryBuilder('muting') | ||||||
|  | 		.where('muting.expiresAt IS NOT NULL') | ||||||
|  | 		.andWhere('muting.expiresAt < :now', { now: new Date() }) | ||||||
|  | 		.innerJoinAndSelect('muting.mutee', 'mutee') | ||||||
|  | 		.getMany(); | ||||||
|  | 
 | ||||||
|  | 	if (expired.length > 0) { | ||||||
|  | 		await Mutings.delete({ | ||||||
|  | 			id: In(expired.map(m => m.id)), | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		for (const m of expired) { | ||||||
|  | 			publishUserEvent(m.muterId, 'unmute', m.mutee!); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	logger.succ(`All expired mutings checked.`); | ||||||
|  | 	done(); | ||||||
|  | } | ||||||
|  | @ -2,11 +2,13 @@ import Bull from 'bull'; | ||||||
| import { tickCharts } from './tick-charts.js'; | import { tickCharts } from './tick-charts.js'; | ||||||
| import { resyncCharts } from './resync-charts.js'; | import { resyncCharts } from './resync-charts.js'; | ||||||
| import { cleanCharts } from './clean-charts.js'; | import { cleanCharts } from './clean-charts.js'; | ||||||
|  | import { checkExpiredMutings } from './check-expired-mutings.js'; | ||||||
| 
 | 
 | ||||||
| const jobs = { | const jobs = { | ||||||
| 	tickCharts, | 	tickCharts, | ||||||
| 	resyncCharts, | 	resyncCharts, | ||||||
| 	cleanCharts, | 	cleanCharts, | ||||||
|  | 	checkExpiredMutings, | ||||||
| } as Record<string, Bull.ProcessCallbackFunction<Record<string, unknown>> | Bull.ProcessPromiseFunction<Record<string, unknown>>>; | } as Record<string, Bull.ProcessCallbackFunction<Record<string, unknown>> | Bull.ProcessPromiseFunction<Record<string, unknown>>>; | ||||||
| 
 | 
 | ||||||
| export default function(dbQueue: Bull.Queue<Record<string, unknown>>) { | export default function(dbQueue: Bull.Queue<Record<string, unknown>>) { | ||||||
|  |  | ||||||
|  | @ -38,6 +38,7 @@ export const paramDef = { | ||||||
| 	type: 'object', | 	type: 'object', | ||||||
| 	properties: { | 	properties: { | ||||||
| 		userId: { type: 'string', format: 'misskey:id' }, | 		userId: { type: 'string', format: 'misskey:id' }, | ||||||
|  | 		expiresAt: { type: 'integer', nullable: true }, | ||||||
| 	}, | 	}, | ||||||
| 	required: ['userId'], | 	required: ['userId'], | ||||||
| } as const; | } as const; | ||||||
|  | @ -67,10 +68,15 @@ export default define(meta, paramDef, async (ps, user) => { | ||||||
| 		throw new ApiError(meta.errors.alreadyMuting); | 		throw new ApiError(meta.errors.alreadyMuting); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if (ps.expiresAt && ps.expiresAt <= Date.now()) { | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// Create mute
 | 	// Create mute
 | ||||||
| 	await Mutings.insert({ | 	await Mutings.insert({ | ||||||
| 		id: genId(), | 		id: genId(), | ||||||
| 		createdAt: new Date(), | 		createdAt: new Date(), | ||||||
|  | 		expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null, | ||||||
| 		muterId: muter.id, | 		muterId: muter.id, | ||||||
| 		muteeId: mutee.id, | 		muteeId: mutee.id, | ||||||
| 	} as Muting); | 	} as Muting); | ||||||
|  |  | ||||||
|  | @ -56,11 +56,44 @@ export function getUserMenu(user) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	async function toggleMute() { | 	async function toggleMute() { | ||||||
| 		os.apiWithDialog(user.isMuted ? 'mute/delete' : 'mute/create', { | 		if (user.isMuted) { | ||||||
| 			userId: user.id | 			os.apiWithDialog('mute/delete', { | ||||||
|  | 				userId: user.id, | ||||||
| 			}).then(() => { | 			}).then(() => { | ||||||
| 			user.isMuted = !user.isMuted; | 				user.isMuted = false; | ||||||
| 			}); | 			}); | ||||||
|  | 		} else { | ||||||
|  | 			const { canceled, result: period } = await os.select({ | ||||||
|  | 				title: i18n.ts.mutePeriod, | ||||||
|  | 				items: [{ | ||||||
|  | 					value: 'indefinitely', text: i18n.ts.indefinitely, | ||||||
|  | 				}, { | ||||||
|  | 					value: 'tenMinutes', text: i18n.ts.tenMinutes, | ||||||
|  | 				}, { | ||||||
|  | 					value: 'oneHour', text: i18n.ts.oneHour, | ||||||
|  | 				}, { | ||||||
|  | 					value: 'oneDay', text: i18n.ts.oneDay, | ||||||
|  | 				}, { | ||||||
|  | 					value: 'oneWeek', text: i18n.ts.oneWeek, | ||||||
|  | 				}], | ||||||
|  | 				default: 'indefinitely', | ||||||
|  | 			}); | ||||||
|  | 			if (canceled) return; | ||||||
|  | 
 | ||||||
|  | 			const expiresAt = period === 'indefinitely' ? null | ||||||
|  | 				: period === 'tenMinutes' ? Date.now() + (1000 * 60 * 10) | ||||||
|  | 				: period === 'oneHour' ? Date.now() + (1000 * 60 * 60) | ||||||
|  | 				: period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24) | ||||||
|  | 				: period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7) | ||||||
|  | 				: null; | ||||||
|  | 
 | ||||||
|  | 			os.apiWithDialog('mute/create', { | ||||||
|  | 				userId: user.id, | ||||||
|  | 				expiresAt, | ||||||
|  | 			}).then(() => { | ||||||
|  | 				user.isMuted = true; | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	async function toggleBlock() { | 	async function toggleBlock() { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue