diff --git a/server/cron.ts b/server/cron.ts index 3bf10ea..6a4da96 100644 --- a/server/cron.ts +++ b/server/cron.ts @@ -1,191 +1,3 @@ -import fetch from 'node-fetch'; -import { createTransport } from 'nodemailer'; -// .envファイルを読み込む(データベースの接続情報等が記載されている) -import * as dotenv from 'dotenv'; -dotenv.config({ path: `${__dirname}/.env` }); -import { Train } from './src/interfaces/train.interface'; - -import { DataSource, Repository } from 'typeorm'; -import { Notice } from './src/entities/notice.entity'; - export class Cron { - static async execute(NoticeRepository: Repository) { - // メールを送信するためのインスタンスを初期化 - let mailTransporter = await Cron.getMailTransporter(); - - // 検索する日付を取得 - let findDate = new Date(); - if (0 <= findDate.getHours() && findDate.getHours() <= 1) { - // 深夜 00:00〜01:59ならば、前日扱いとする - findDate.setDate(findDate.getDate() - 1); - } - let findDateString = Cron.getDateString(findDate); - - // 今日の日付の通知登録を取得 - let notices = await NoticeRepository.find({ - where: { - noticeDate: findDateString, - notified: false, - }, - }); - - // 通知登録配列を反復 - for (let notice of notices) { - // 当該通知登録の路線の在線情報を取得 - let trains = await Cron.getTrains(notice.lineName); - // 当該通知登録の列車番号を検索 - let targetTrain = undefined; - for (let train of trains) { - if (train.no == notice.trainNumber) { - targetTrain = train; - } - } - - // 設定された運休判断時刻の取得 - let today = new Date(); - let cancelDecisionDate = notice.cancelDecisionTime - ? new Date( - `${Cron.getDateString(today)} ${notice.cancelDecisionTime}:00` - ) - : null; - - let isSuspended = false; - let isDeley = false; - - if ( - targetTrain == undefined && - cancelDecisionDate && - cancelDecisionDate.getTime() < Date.now() - ) { - // 列車番号が見つからず、運休判断時刻が登録されており、運休判断時刻が過ぎていれば運休とする - isSuspended = true; - } else if (targetTrain && targetTrain.delayMinutes >= 15) { - // また列車番号が見つかり、15分以上の遅延になっていたら遅延とする - isDeley = true; - } - - // 運休 or 遅延ならばメールを送信し、通知済みフラグをtrueにする - if (isSuspended || isDeley) { - await this.sendNoticeEmail(notice, isSuspended, isDeley, targetTrain); - } - } - - // スリープ防止 - await fetch('https://jr-trainformation.onrender.com/'); - } - - /** - * メールの送信 - * @param notice 通知の登録状況 - * @param isSuspended 列車が運休になっているかのフラグ - * @param isDeley 列車が遅延しているかのフラグ - * @param train 取得した列車情報 - */ - static async sendNoticeEmail( - notice: Notice, - isSuspended: boolean, - isDeley: boolean, - train?: Train - ) { - let sendResult; - let mailTransporter = Cron.getMailTransporter(); - - // メールの送信日時の取得 - const sentDateString = Cron.getDateTimeString(new Date()); - - // 送信するメールの定義 - let detailText = ''; - if (train) { - detailText = `種別:${train.displayType} -列車名:${train.nickname} -行先:${train.dest} -遅れ:${train.delayMinutes}分`; - } - let mail = { - from: process.env['EMAIL_FROM'], - to: notice.noticeEmail, - subject: `${notice.trainNumber} 運行情報`, - text: `darinono.info 運行情報通知サービスです。 -登録されている以下の列車が${isDeley ? '遅延' : '運休'}している可能性があります。 - -列車番号:${notice.trainNumber} -${detailText} - -※${sentDateString}時点の情報です -※最新の情報は https://jr-trainformation.herokuapp.com/ にてご確認ください -※遅延証明書は https://delay.trafficinfo.westjr.co.jp/ からご利用ください - -ご利用ありがとうございます。`, - }; - - // メールの送信処理 - try { - sendResult = await mailTransporter.sendMail(mail); - console.log(`メールを送信しました ${notice.noticeEmail}`, notice); - } catch (e: any) { - console.error('メールが送信できませんでした', e); - return; - } - if (sendResult?.rejected && sendResult.rejected.length >= 1) { - console.error('メールが拒否されました', sendResult?.rejected); - } - - // 送信済みとしてマークする - notice.notified = true; - await notice.save(); - } - - /** - * 日時文字列の取得 - * @param date Date オブジェクト - * @return 日時文字列 (例: '2022/01/01 00:00') - */ - static getDateTimeString(date: Date) { - const hourString = new String(date.getHours()).padStart(2, '0'); - const minuteString = new String(date.getMinutes()).padStart(2, '0'); - return `${Cron.getDateString(date)} ${hourString}:${minuteString}`; - } - - /** - * 日付文字列の取得 - * @param date Date オブジェクト - * @return 日付文字列 (例: '2022/01/01') - */ - static getDateString(date: Date) { - return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`; - } - - /** - * メール送信に必要な列車情報の取得 - * @param lineName 路線名 - * @returns 列車情報の配列 - */ - static async getTrains(lineName: string): Promise { - const response = await fetch( - `https://www.train-guide.westjr.co.jp/api/v3/${lineName}.json` - ); - const object = await response.json(); - return object.trains; - } - - /** - * nodemailerのインスタンス生成 - * @returns メールを送信するためのインスタンス - */ - static getMailTransporter() { - return createTransport({ - host: process.env['EMAIL_SMTP_HOST'], - port: parseInt(process.env['EMAIL_SMTP_PORT'] || '587', 10), - auth: { - user: process.env['EMAIL_SMTP_USERNAME'], - pass: process.env['EMAIL_SMTP_PW'], - }, - }); - } + static async execute() {} } - -/* -(async () => { - await Cron.execute(); -})(); -*/ diff --git a/server/index.ts b/server/index.ts index 83ed915..3a7de09 100644 --- a/server/index.ts +++ b/server/index.ts @@ -10,9 +10,6 @@ import * as express from 'express'; const app = express(); app.use(express.json()); -// データベース接続を初期化 -import { AppDataSource, NoticeRepository } from './src/database'; - // Angularアプリケーションを静的ファイルとしてServe app.use(express.static(path.join(__dirname, '../dist/jr-trainformation/'))); @@ -31,7 +28,7 @@ app.get('*', (req, res) => { // 非同期処理を実行 (async () => { // データベースの接続完了まで待機 - await AppDataSource.initialize(); + // await AppDataSource.initialize(); // サーバを開始 const server = app.listen(process.env['PORT'] || 8080, () => { @@ -47,6 +44,6 @@ var node_cron = require('node-cron'); node_cron.schedule('* */10 * * *', () => { //console.log('running a task every 10 minutes'); (async () => { - await Cron.execute(NoticeRepository); + await Cron.execute(); })(); }); diff --git a/server/src/database.ts b/server/src/database.ts deleted file mode 100644 index 9c92c01..0000000 --- a/server/src/database.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { DataSource } from 'typeorm'; -import { Notice } from './entities/notice.entity'; - -// 環境変数 DATABASE_URL の読み込み -const databaseUrl: string = process.env['DATABASE_URL'] as string; -if (!databaseUrl) { - throw '環境変数 DATABASE_URL が未指定です'; -} - -// データベース接続の初期化 -export const AppDataSource = new DataSource({ - type: 'postgres', - url: databaseUrl, - logging: !process.env['NODE_ENV'] || process.env['NODE_ENV'] != 'production', - entities: [Notice], - synchronize: true, - ssl: { - rejectUnauthorized: false, - }, -}); - -// 各エンティティをリポジトリとして取得してエクスポート -export const NoticeRepository = AppDataSource.getRepository(Notice); diff --git a/server/src/entities/notice.entity.ts b/server/src/entities/notice.entity.ts index baf8621..d6ba43f 100644 --- a/server/src/entities/notice.entity.ts +++ b/server/src/entities/notice.entity.ts @@ -1,30 +1,17 @@ -import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from 'typeorm'; - -@Entity() -export class Notice extends BaseEntity { - @PrimaryGeneratedColumn() +export interface NotifyEntity { id: number; - @Column() noticeEmail: string; - @Column() lineName: string; - @Column() trainNumber: string; - @Column() noticeDate: string; - @Column() cancelDecisionTime: string; - @Column({ - default: false, - }) notified: boolean; - @Column() ipAddress: string; } diff --git a/server/src/routes/notice.ts b/server/src/routes/notice.ts index 4a93b9b..26c484b 100644 --- a/server/src/routes/notice.ts +++ b/server/src/routes/notice.ts @@ -1,6 +1,5 @@ import { Router } from 'express'; // データベース接続を初期化 -import { AppDataSource, NoticeRepository } from '../database'; const noticeRouter = Router(); @@ -9,6 +8,7 @@ const noticeRouter = Router(); * 通知を登録するためのAPIですけど… */ noticeRouter.post('/register', async (req, res) => { + /* try { let item = await NoticeRepository.save({ noticeEmail: req.body.noticeEmail || null, @@ -23,7 +23,7 @@ noticeRouter.post('/register', async (req, res) => { console.log(item); } catch (e: any) { res.status(400).send(e.toString()); - } + }*/ }); export default noticeRouter; diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 7ef1844..bf92c94 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -3,6 +3,7 @@ import { RouterModule, Routes } from '@angular/router'; import { LineDetailComponent } from './line-detail/line-detail.component'; import { LineSelectComponent } from './line-select/line-select.component'; import { NoticeRegisterComponent } from './notice-register/notice-register.component'; +import { StationListComponent } from './station-list/station-list.component'; const routes: Routes = [ { @@ -17,6 +18,10 @@ const routes: Routes = [ path: 'notice', component: NoticeRegisterComponent, }, + { + path: 'stations/:lineName', + component: StationListComponent, + }, ]; @NgModule({ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index f2cc39c..d5ad131 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -14,6 +14,7 @@ import { FormsModule } from '@angular/forms'; import { TermsOfServiceDialogComponent } from './terms-of-service-dialog/terms-of-service-dialog.component'; import { TrainService } from './train.service'; import trainRouter from 'server/src/routes/train'; +import { StationListComponent } from './station-list/station-list.component'; @NgModule({ declarations: [ @@ -22,6 +23,7 @@ import trainRouter from 'server/src/routes/train'; LineSelectComponent, NoticeRegisterComponent, TermsOfServiceDialogComponent, + StationListComponent, ], imports: [ BrowserModule, diff --git a/src/app/line-detail/line-detail.component.html b/src/app/line-detail/line-detail.component.html index 68aad17..b20d6af 100644 --- a/src/app/line-detail/line-detail.component.html +++ b/src/app/line-detail/line-detail.component.html @@ -136,6 +136,15 @@ notifications + + + diff --git a/src/app/station-list/station-list.component.scss b/src/app/station-list/station-list.component.scss new file mode 100644 index 0000000..785d37d --- /dev/null +++ b/src/app/station-list/station-list.component.scss @@ -0,0 +1,11 @@ +.fabs { + z-index: 1000; + position: fixed; + bottom: 5%; + right: 2.5%; +} + +.fabs button { + display: block; + margin-top: 1rem; +} diff --git a/src/app/station-list/station-list.component.spec.ts b/src/app/station-list/station-list.component.spec.ts new file mode 100644 index 0000000..3c9b612 --- /dev/null +++ b/src/app/station-list/station-list.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StationListComponent } from './station-list.component'; + +describe('StationListComponent', () => { + let component: StationListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ StationListComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(StationListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/station-list/station-list.component.ts b/src/app/station-list/station-list.component.ts new file mode 100644 index 0000000..6c3793e --- /dev/null +++ b/src/app/station-list/station-list.component.ts @@ -0,0 +1,42 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { TrainService } from '../train.service'; + +@Component({ + selector: 'app-station-list', + templateUrl: './station-list.component.html', + styleUrls: ['./station-list.component.scss'], +}) +export class StationListComponent implements OnInit { + stations!: any[]; + lineName!: string | null; + line!: any; + + constructor( + public activatedRoute: ActivatedRoute, + public trainService: TrainService + ) {} + + async ngOnInit() { + // URLの変更を監視する + this.activatedRoute.paramMap.subscribe((params) => { + this.lineName = params.get('lineName'); + + if (!this.lineName) { + return; + } + this.line = this.trainService.getLine(this.lineName); + this.loadTrains(); + }); + } + + // リロードボタンのための実装 + async loadTrains() { + // lineNameが空だったら何もしない + if (!this.lineName) { + return; + } + + this.stations = await this.trainService.getTrains(this.lineName); + } +} diff --git a/src/app/train.service.ts b/src/app/train.service.ts index c154d8b..8d6ceda 100644 --- a/src/app/train.service.ts +++ b/src/app/train.service.ts @@ -403,6 +403,10 @@ export class TrainService { return undefined; } + getStaions(lineName: string) { + // TODO? + } + async getTrains(lineName: string) { const companyName = this.getCompanyName(lineName); if (companyName === undefined) {