広告の曜日を設定できるように (#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: "戻る" |   back: "戻る" | ||||||
|   reduceFrequencyOfThisAd: "この広告の表示頻度を下げる" |   reduceFrequencyOfThisAd: "この広告の表示頻度を下げる" | ||||||
|   hide: "表示しない" |   hide: "表示しない" | ||||||
|  |   timezoneinfo: "曜日はサーバーのタイムゾーンを元に指定されます。" | ||||||
| 
 | 
 | ||||||
| _forgotPassword: | _forgotPassword: | ||||||
|   enterEmail: "アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。" |   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, | 		length: 8192, nullable: false, | ||||||
| 	}) | 	}) | ||||||
| 	public memo: string; | 	public memo: string; | ||||||
| 
 | 	@Column('integer', { | ||||||
|  | 		default: 0, nullable: false, | ||||||
|  | 	}) | ||||||
|  | 	public dayOfWeek: number; | ||||||
| 	constructor(data: Partial<Ad>) { | 	constructor(data: Partial<Ad>) { | ||||||
| 		if (data == null) return; | 		if (data == null) return; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -22,8 +22,9 @@ export const paramDef = { | ||||||
| 		expiresAt: { type: 'integer' }, | 		expiresAt: { type: 'integer' }, | ||||||
| 		startsAt: { type: 'integer' }, | 		startsAt: { type: 'integer' }, | ||||||
| 		imageUrl: { type: 'string', minLength: 1 }, | 		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; | } as const; | ||||||
| 
 | 
 | ||||||
| // eslint-disable-next-line import/no-default-export
 | // eslint-disable-next-line import/no-default-export
 | ||||||
|  | @ -41,6 +42,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | ||||||
| 				createdAt: new Date(), | 				createdAt: new Date(), | ||||||
| 				expiresAt: new Date(ps.expiresAt), | 				expiresAt: new Date(ps.expiresAt), | ||||||
| 				startsAt: new Date(ps.startsAt), | 				startsAt: new Date(ps.startsAt), | ||||||
|  | 				dayOfWeek: ps.dayOfWeek, | ||||||
| 				url: ps.url, | 				url: ps.url, | ||||||
| 				imageUrl: ps.imageUrl, | 				imageUrl: ps.imageUrl, | ||||||
| 				priority: ps.priority, | 				priority: ps.priority, | ||||||
|  |  | ||||||
|  | @ -31,8 +31,9 @@ export const paramDef = { | ||||||
| 		ratio: { type: 'integer' }, | 		ratio: { type: 'integer' }, | ||||||
| 		expiresAt: { type: 'integer' }, | 		expiresAt: { type: 'integer' }, | ||||||
| 		startsAt: { 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; | } as const; | ||||||
| 
 | 
 | ||||||
| // eslint-disable-next-line import/no-default-export
 | // eslint-disable-next-line import/no-default-export
 | ||||||
|  | @ -56,6 +57,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | ||||||
| 				imageUrl: ps.imageUrl, | 				imageUrl: ps.imageUrl, | ||||||
| 				expiresAt: new Date(ps.expiresAt), | 				expiresAt: new Date(ps.expiresAt), | ||||||
| 				startsAt: new Date(ps.startsAt), | 				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 { Inject, Injectable } from '@nestjs/common'; | ||||||
| import JSON5 from 'json5'; | import JSON5 from 'json5'; | ||||||
| import type { AdsRepository, UsersRepository } from '@/models/index.js'; | import type { AdsRepository, UsersRepository } from '@/models/index.js'; | ||||||
|  | @ -263,12 +263,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		super(meta, paramDef, async (ps, me) => { | ||||||
| 			const instance = await this.metaService.fetch(true); | 			const instance = await this.metaService.fetch(true); | ||||||
| 
 | 
 | ||||||
| 			const ads = await this.adsRepository.find({ | 			const ads = await this.adsRepository.createQueryBuilder("ads") | ||||||
| 				where: { | 				.where('ads.expiresAt > :now', { now: new Date() }) | ||||||
| 					expiresAt: MoreThan(new Date()), | 				.andWhere('ads.startsAt <= :now', { now: new Date() }) | ||||||
| 					startsAt: LessThanOrEqual(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 = { | 			const response: any = { | ||||||
| 				maintainerName: instance.maintainerName, | 				maintainerName: instance.maintainerName, | ||||||
|  | @ -311,6 +314,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | ||||||
| 					place: ad.place, | 					place: ad.place, | ||||||
| 					ratio: ad.ratio, | 					ratio: ad.ratio, | ||||||
| 					imageUrl: ad.imageUrl, | 					imageUrl: ad.imageUrl, | ||||||
|  | 					dayOfWeek: ad.dayOfWeek, | ||||||
| 				})), | 				})), | ||||||
| 				enableEmail: instance.enableEmail, | 				enableEmail: instance.enableEmail, | ||||||
| 				enableServiceWorker: instance.enableServiceWorker, | 				enableServiceWorker: instance.enableServiceWorker, | ||||||
|  |  | ||||||
|  | @ -36,6 +36,16 @@ | ||||||
| 						<template #label>{{ i18n.ts.expiration }}</template> | 						<template #label>{{ i18n.ts.expiration }}</template> | ||||||
| 					</MkInput> | 					</MkInput> | ||||||
| 				</FormSplit> | 				</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"> | 				<MkTextarea v-model="ad.memo"> | ||||||
| 					<template #label>{{ i18n.ts.memo }}</template> | 					<template #label>{{ i18n.ts.memo }}</template> | ||||||
| 				</MkTextarea> | 				</MkTextarea> | ||||||
|  | @ -59,6 +69,7 @@ import MkButton from '@/components/MkButton.vue'; | ||||||
| import MkInput from '@/components/MkInput.vue'; | import MkInput from '@/components/MkInput.vue'; | ||||||
| import MkTextarea from '@/components/MkTextarea.vue'; | import MkTextarea from '@/components/MkTextarea.vue'; | ||||||
| import MkRadios from '@/components/MkRadios.vue'; | import MkRadios from '@/components/MkRadios.vue'; | ||||||
|  | import MkFolder from '@/components/MkFolder.vue'; | ||||||
| import FormSplit from '@/components/form/split.vue'; | import FormSplit from '@/components/form/split.vue'; | ||||||
| import * as os from '@/os'; | import * as os from '@/os'; | ||||||
| import { i18n } from '@/i18n'; | import { i18n } from '@/i18n'; | ||||||
|  | @ -69,6 +80,7 @@ let ads: any[] = $ref([]); | ||||||
| // ISO形式はTZがUTCになってしまうので、TZ分ずらして時間を初期化 | // ISO形式はTZがUTCになってしまうので、TZ分ずらして時間を初期化 | ||||||
| const localTime = new Date(); | const localTime = new Date(); | ||||||
| const localTimeDiff = localTime.getTimezoneOffset() * 60 * 1000; | 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 => { | os.api('admin/ad/list').then(adsResponse => { | ||||||
| 	ads = adsResponse.map(r => { | 	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() { | function add() { | ||||||
| 	ads.unshift({ | 	ads.unshift({ | ||||||
| 		id: null, | 		id: null, | ||||||
|  | @ -95,6 +112,7 @@ function add() { | ||||||
| 		imageUrl: null, | 		imageUrl: null, | ||||||
| 		expiresAt: null, | 		expiresAt: null, | ||||||
| 		startsAt: null, | 		startsAt: null, | ||||||
|  | 		dayOfWeek: 0, | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -105,6 +123,7 @@ function remove(ad) { | ||||||
| 	}).then(({ canceled }) => { | 	}).then(({ canceled }) => { | ||||||
| 		if (canceled) return; | 		if (canceled) return; | ||||||
| 		ads = ads.filter(x => x !== ad); | 		ads = ads.filter(x => x !== ad); | ||||||
|  | 		if (ad.id == null) return; | ||||||
| 		os.apiWithDialog('admin/ad/delete', { | 		os.apiWithDialog('admin/ad/delete', { | ||||||
| 			id: ad.id, | 			id: ad.id, | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue