Files
bus-reminder/src/pages/api/cron.ts

110 lines
2.7 KiB
TypeScript

import { getMatchingTime } from '@/timing';
import type { NextApiRequest, NextApiResponse } from 'next';
import { getDistance } from '@/location';
import monitorAsync from '@/monitor';
import { sendNotification } from '@/notify';
import {
checkIdentifier,
fetchConfiguration,
getKey,
markIdentifier
} from '@/db';
import { localNow } from '@/utils/timezone';
import logger from '@/logger';
import { parseBoolean } from '@/utils/client';
import { unauthorized } from '@/utils/server';
type ResponseData = {
diff: number;
inRange: boolean;
};
type StatusData = {
status: ResponseStatus;
identifier?: Identifier;
};
type Identifier = {
name: string;
key?: string;
};
type ResponseStatus =
| 'unauthorized'
| 'out-of-range'
| 'no-matching-time'
| 'already-notified'
| 'notified'
| 'error';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<(ResponseData & StatusData) | StatusData>
) {
if (unauthorized(req, res)) return;
async function innerFunction(): Promise<{
status: ResponseStatus;
identifier?: Identifier;
}> {
const now = localNow();
const config = await fetchConfiguration();
const matching = await getMatchingTime(config, now);
// No matching time - no notification to send.
if (matching == null) return { status: 'no-matching-time' };
// Get the key for this notification (name + time)
const key = getKey(matching.name, now);
const identifier = { name: matching.name, key };
// Check if I am in range of the center
const distanceResult = await getDistance();
if (distanceResult.isErr) {
logger.error(distanceResult.error);
return { status: 'error', identifier };
}
// TODO: Properly draw from environment MAX_DISTANCE
const distance = distanceResult.value;
if (distance > 280)
return {
status: 'out-of-range',
identifier
};
// Check if I have already been notified
const marked = await checkIdentifier(key);
if (marked)
return {
status: 'already-notified',
identifier
};
// Send notification, mark (expire in 1 month)
await sendNotification(`${matching.message} (${matching.name})`);
await markIdentifier(key, true, 60 * 60 * 24 * 31);
return { status: 'notified', identifier };
}
try {
let result;
if (process.env.NODE_ENV === 'production' && parseBoolean(req.query.report))
result = await monitorAsync(innerFunction);
else result = await innerFunction();
logger.info('Cron evaluated.', { result });
if (result.status === 'error') res.status(500).json({ status: 'error' });
else
res
.status(200)
.json({ status: result.status, identifier: result.identifier });
} catch (e) {
logger.error(e);
res.status(500).json({ status: 'error' });
}
}