広告の曜日を設定できるように (#10095)
* 曜日選択できるように * ラベル選択でもチェックが変更されるように * adを参照しないといけないかも * smallint -> integer * 異物混入だったので取りだし * タイムゾーン指定(Date2つ使うのなんか違和感 * 未テスト * これにすると出てこないかも * UIチョット変更 * UI変更 fix bug * 畳むように修正 * dayofweek->dayOfWeek * マイグレ時にnot null,default設定してるのでnullable:falseでよさそう * コメントの記載 * Update packages/backend/src/server/api/endpoints/meta.ts Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com> --------- Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
This commit is contained in:
		
							parent
							
								
									1f181536ae
								
							
						
					
					
						commit
						3c6175d959
					
				|  | @ -1451,6 +1451,7 @@ _ad: | |||
|   back: "戻る" | ||||
|   reduceFrequencyOfThisAd: "この広告の表示頻度を下げる" | ||||
|   hide: "表示しない" | ||||
|   timezoneinfo: "曜日はサーバーのタイムゾーンを元に指定されます。" | ||||
| 
 | ||||
| _forgotPassword: | ||||
|   enterEmail: "アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。" | ||||
|  |  | |||
|  | @ -0,0 +1,9 @@ | |||
| export class ad1677054292210 { | ||||
| 	name = 'ad1677054292210'; | ||||
| 	async up(queryRunner) { | ||||
| 			await queryRunner.query(`ALTER TABLE "ad" ADD "dayOfWeek" integer NOT NULL Default 0`); | ||||
| 	} | ||||
| 	async down(queryRunner) { | ||||
| 		await queryRunner.query(`ALTER TABLE "ad" DROP COLUMN "dayOfWeek"`); | ||||
| 	} | ||||
| } | ||||
|  | @ -55,7 +55,10 @@ export class Ad { | |||
| 		length: 8192, nullable: false, | ||||
| 	}) | ||||
| 	public memo: string; | ||||
| 
 | ||||
| 	@Column('integer', { | ||||
| 		default: 0, nullable: false, | ||||
| 	}) | ||||
| 	public dayOfWeek: number; | ||||
| 	constructor(data: Partial<Ad>) { | ||||
| 		if (data == null) return; | ||||
| 
 | ||||
|  |  | |||
|  | @ -22,8 +22,9 @@ export const paramDef = { | |||
| 		expiresAt: { type: 'integer' }, | ||||
| 		startsAt: { type: 'integer' }, | ||||
| 		imageUrl: { type: 'string', minLength: 1 }, | ||||
| 		dayOfWeek: { type: 'integer' }, | ||||
| 	}, | ||||
| 	required: ['url', 'memo', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'imageUrl'], | ||||
| 	required: ['url', 'memo', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'imageUrl', 'dayOfWeek'], | ||||
| } as const; | ||||
| 
 | ||||
| // eslint-disable-next-line import/no-default-export
 | ||||
|  | @ -41,6 +42,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 				createdAt: new Date(), | ||||
| 				expiresAt: new Date(ps.expiresAt), | ||||
| 				startsAt: new Date(ps.startsAt), | ||||
| 				dayOfWeek: ps.dayOfWeek, | ||||
| 				url: ps.url, | ||||
| 				imageUrl: ps.imageUrl, | ||||
| 				priority: ps.priority, | ||||
|  |  | |||
|  | @ -31,8 +31,9 @@ export const paramDef = { | |||
| 		ratio: { type: 'integer' }, | ||||
| 		expiresAt: { type: 'integer' }, | ||||
| 		startsAt: { type: 'integer' }, | ||||
| 		dayOfWeek: { type: 'integer' }, | ||||
| 	}, | ||||
| 	required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt'], | ||||
| 	required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'dayOfWeek'], | ||||
| } as const; | ||||
| 
 | ||||
| // eslint-disable-next-line import/no-default-export
 | ||||
|  | @ -56,6 +57,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 				imageUrl: ps.imageUrl, | ||||
| 				expiresAt: new Date(ps.expiresAt), | ||||
| 				startsAt: new Date(ps.startsAt), | ||||
| 				dayOfWeek: ps.dayOfWeek, | ||||
| 			}); | ||||
| 		}); | ||||
| 	} | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { IsNull, LessThanOrEqual, MoreThan } from 'typeorm'; | ||||
| import { IsNull, LessThanOrEqual, MoreThan, Brackets } from 'typeorm'; | ||||
| import { Inject, Injectable } from '@nestjs/common'; | ||||
| import JSON5 from 'json5'; | ||||
| import type { AdsRepository, UsersRepository } from '@/models/index.js'; | ||||
|  | @ -263,13 +263,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 		super(meta, paramDef, async (ps, me) => { | ||||
| 			const instance = await this.metaService.fetch(true); | ||||
| 
 | ||||
| 			const ads = await this.adsRepository.find({ | ||||
| 				where: { | ||||
| 					expiresAt: MoreThan(new Date()), | ||||
| 					startsAt: LessThanOrEqual(new Date()), | ||||
| 				}, | ||||
| 			}); | ||||
| 
 | ||||
| 			const ads = await this.adsRepository.createQueryBuilder("ads") | ||||
| 				.where('ads.expiresAt > :now', { now: new Date() }) | ||||
| 				.andWhere('ads.startsAt <= :now', { now: new Date() }) | ||||
| 				.andWhere(new Brackets(qb => { | ||||
| 					// 曜日のビットフラグを確認する
 | ||||
| 					qb.where('ads.dayOfWeek & :dayOfWeek > 0', { dayOfWeek: 1 << new Date().getDay() }) | ||||
| 						.orWhere('ads.dayOfWeek = 0'); | ||||
| 				})) | ||||
| 				.getMany(); | ||||
| 		 | ||||
| 			const response: any = { | ||||
| 				maintainerName: instance.maintainerName, | ||||
| 				maintainerEmail: instance.maintainerEmail, | ||||
|  | @ -311,6 +314,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 					place: ad.place, | ||||
| 					ratio: ad.ratio, | ||||
| 					imageUrl: ad.imageUrl, | ||||
| 					dayOfWeek: ad.dayOfWeek, | ||||
| 				})), | ||||
| 				enableEmail: instance.enableEmail, | ||||
| 				enableServiceWorker: instance.enableServiceWorker, | ||||
|  |  | |||
|  | @ -36,6 +36,16 @@ | |||
| 						<template #label>{{ i18n.ts.expiration }}</template> | ||||
| 					</MkInput> | ||||
| 				</FormSplit> | ||||
| 				<MkFolder> | ||||
| 					<template #label>{{ i18n.ts.advancedSettings }}</template> | ||||
| 					<span> | ||||
| 						{{ i18n.ts._ad.timezoneinfo }} | ||||
| 						<div v-for="(day, index) in daysOfWeek" :key="index"> | ||||
| 							<input :id="`ad${ad.id}-${index}`" type="checkbox" :checked="(ad.dayOfWeek & (1 << index)) !== 0" @change="toggleDayOfWeek(ad, index)"> | ||||
| 							<label :for="`ad${ad.id}-${index}`">{{ day }}</label> | ||||
| 						</div> | ||||
| 					</span> | ||||
| 				</MkFolder> | ||||
| 				<MkTextarea v-model="ad.memo"> | ||||
| 					<template #label>{{ i18n.ts.memo }}</template> | ||||
| 				</MkTextarea> | ||||
|  | @ -59,6 +69,7 @@ import MkButton from '@/components/MkButton.vue'; | |||
| import MkInput from '@/components/MkInput.vue'; | ||||
| import MkTextarea from '@/components/MkTextarea.vue'; | ||||
| import MkRadios from '@/components/MkRadios.vue'; | ||||
| import MkFolder from '@/components/MkFolder.vue'; | ||||
| import FormSplit from '@/components/form/split.vue'; | ||||
| import * as os from '@/os'; | ||||
| import { i18n } from '@/i18n'; | ||||
|  | @ -69,6 +80,7 @@ let ads: any[] = $ref([]); | |||
| // ISO形式はTZがUTCになってしまうので、TZ分ずらして時間を初期化 | ||||
| const localTime = new Date(); | ||||
| const localTimeDiff = localTime.getTimezoneOffset() * 60 * 1000; | ||||
| const daysOfWeek: string[] = [i18n.ts._weekday.sunday, i18n.ts._weekday.monday, i18n.ts._weekday.tuesday, i18n.ts._weekday.wednesday, i18n.ts._weekday.thursday, i18n.ts._weekday.friday, i18n.ts._weekday.saturday]; | ||||
| 
 | ||||
| os.api('admin/ad/list').then(adsResponse => { | ||||
| 	ads = adsResponse.map(r => { | ||||
|  | @ -84,6 +96,11 @@ os.api('admin/ad/list').then(adsResponse => { | |||
| 	}); | ||||
| }); | ||||
| 
 | ||||
| // 選択された曜日(index)のビットフラグを操作する | ||||
| function toggleDayOfWeek(ad, index) { | ||||
| 	ad.dayOfWeek ^= 1 << index; | ||||
| } | ||||
| 
 | ||||
| function add() { | ||||
| 	ads.unshift({ | ||||
| 		id: null, | ||||
|  | @ -95,6 +112,7 @@ function add() { | |||
| 		imageUrl: null, | ||||
| 		expiresAt: null, | ||||
| 		startsAt: null, | ||||
| 		dayOfWeek: 0, | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
|  | @ -105,6 +123,7 @@ function remove(ad) { | |||
| 	}).then(({ canceled }) => { | ||||
| 		if (canceled) return; | ||||
| 		ads = ads.filter(x => x !== ad); | ||||
| 		if (ad.id == null) return; | ||||
| 		os.apiWithDialog('admin/ad/delete', { | ||||
| 			id: ad.id, | ||||
| 		}); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue