diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..1c5d3ff --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +.next/ +.vercel +.env +.gitignore +vercel.json +*.log +*.lock \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index 7a28161..222861c 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,4 @@ { "tabWidth": 2, "useTabs": false -} \ No newline at end of file +} diff --git a/README.md b/README.md index 0c97429..4c4c873 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # bus-reminder + ![Location Check](https://cronitor.io/badges/8frC2k/production/XcmWhvdYm0OyCRuinS1IP6MEUiE.svg) A cron-job based personal notification system for myself. @@ -31,7 +32,7 @@ Next.js was complete overkill for this, and in retrospect, using something like [cronitor]: https://cronitor.io [cron-jobs]: https://cron-jobs.org - ## TODO + - Integrate Discord notifications -- Create system for dynamically disabling the check for the rest of the day ([Upstash](upstash.com) for Redis) \ No newline at end of file +- Create system for dynamically disabling the check for the rest of the day ([Upstash](upstash.com) for Redis) diff --git a/next.config.js b/next.config.js index a843cbe..91ef62f 100644 --- a/next.config.js +++ b/next.config.js @@ -1,6 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, -} +}; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/src/env/util.mjs b/src/env/util.mjs index d3238a5..982a01c 100644 --- a/src/env/util.mjs +++ b/src/env/util.mjs @@ -1,10 +1,10 @@ export const formatErrors = ( - /** @type {import('zod').ZodFormattedError,string>} */ - errors, - ) => - Object.entries(errors) - .map(([name, value]) => { - if (value && "_errors" in value) - return `${name}: ${value._errors.join(", ")}\n`; - }) - .filter(Boolean); \ No newline at end of file + /** @type {import('zod').ZodFormattedError,string>} */ + errors +) => + Object.entries(errors) + .map(([name, value]) => { + if (value && "_errors" in value) + return `${name}: ${value._errors.join(", ")}\n`; + }) + .filter(Boolean); diff --git a/src/location.ts b/src/location.ts index 14ee3d1..9883fb2 100644 --- a/src/location.ts +++ b/src/location.ts @@ -2,7 +2,10 @@ import { env } from "@/env/server.mjs"; // @ts-ignore import * as life360 from "life360-node-api"; -const center = {longitude: env.CENTER_LONGITUDE, latitude: env.CENTER_LATITUDE}; +const center = { + longitude: env.CENTER_LONGITUDE, + latitude: env.CENTER_LATITUDE, +}; const MILES_PER_NAUTICAL_MILE = 1.15078; const KILOMETERS_PER_MILE = 1.60934; @@ -24,24 +27,25 @@ export function distance( const radlat2 = (Math.PI * b.latitude) / 180; const theta = a.longitude - b.longitude; const radtheta = (Math.PI * theta) / 180; - let dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta); - - if (dist > 1) - dist = 1; + let dist = + Math.sin(radlat1) * Math.sin(radlat2) + + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta); + + if (dist > 1) dist = 1; dist = Math.acos(dist); dist = (dist * 180) / Math.PI; - dist = dist * 60 * MILES_PER_NAUTICAL_MILE; // Convert to miles + dist = dist * 60 * MILES_PER_NAUTICAL_MILE; // Convert to miles // Convert to the specified unit switch (unit) { - case "K": - return dist * KILOMETERS_PER_MILE; - case "N": - return dist / MILES_PER_NAUTICAL_MILE; - case "M": - default: - return dist; + case "K": + return dist * KILOMETERS_PER_MILE; + case "N": + return dist / MILES_PER_NAUTICAL_MILE; + case "M": + default: + return dist; } } } @@ -50,7 +54,7 @@ export function distance( * @returns The distance in meters between me and the center */ export async function getDistance(): Promise { - // Setup the Life360 API client + // Setup the Life360 API client const client = await life360.login( env.LIFE360_USERNAME, env.LIFE360_PASSWORD diff --git a/src/monitor.ts b/src/monitor.ts index 43200a8..ad68c8d 100644 --- a/src/monitor.ts +++ b/src/monitor.ts @@ -1,77 +1,98 @@ -import {env} from "@/env/server.mjs"; -import {z} from "zod"; +import { env } from "@/env/server.mjs"; +import { z } from "zod"; const BASE_URL = `https://cronitor.link/p/${env.CRONITOR_ACCOUNT_ID}/${env.CRONITOR_JOB_ID}`; const parameterSchema = z.object({ - env: z.string().optional(), - host: z.string().optional(), - message: z.string().optional(), - metric: z.union([ - z.object({count: z.number()}), - z.object({duration: z.number()}), - z.object({error_count: z.number()}), - ]).optional(), - series: z.string().optional(), - state: z.enum(["run", "complete", "fail", "ok"]).optional(), - status_code: z.number().int().optional(), -}) + env: z.string().optional(), + host: z.string().optional(), + message: z.string().optional(), + metric: z + .union([ + z.object({ count: z.number() }), + z.object({ duration: z.number() }), + z.object({ error_count: z.number() }), + ]) + .optional(), + series: z.string().optional(), + state: z.enum(["run", "complete", "fail", "ok"]).optional(), + status_code: z.number().int().optional(), +}); function buildURL(parameters: z.infer) { - const url = new URL(BASE_URL); + const url = new URL(BASE_URL); - // Definite string properties - const {env, host, message, series, state} = parameters; - for (const [key, value] of Object.entries({env, host, message, series, state}).filter(([, value]) => value != undefined)) - url.searchParams.append(key, value!); + // Definite string properties + const { env, host, message, series, state } = parameters; + for (const [key, value] of Object.entries({ + env, + host, + message, + series, + state, + }).filter(([, value]) => value != undefined)) + url.searchParams.append(key, value!); - // Extract potentially undefined specified properties - if (parameters.metric != undefined) { - const {count, duration, error_count} = parameters.metric as {count?: number, duration?: number, error_count?: number}; - if (count != undefined) - url.searchParams.append("metric", `count:${count}`); - else if (duration != undefined) - url.searchParams.append("metric", `duration:${duration}`); - else if (error_count != undefined) - url.searchParams.append("metric", `error_count:${error_count}`); - } + // Extract potentially undefined specified properties + if (parameters.metric != undefined) { + const { count, duration, error_count } = parameters.metric as { + count?: number; + duration?: number; + error_count?: number; + }; + if (count != undefined) url.searchParams.append("metric", `count:${count}`); + else if (duration != undefined) + url.searchParams.append("metric", `duration:${duration}`); + else if (error_count != undefined) + url.searchParams.append("metric", `error_count:${error_count}`); + } - return url; + return url; } /** * @returns The duration in seconds between the start and end dates */ function getDuration(start: Date, end: Date) { - return (end.getTime() - start.getTime()) / 1000; + return (end.getTime() - start.getTime()) / 1000; } - /** * Executes the given function while providing telemetry to Cronitor * @param execute The function to execute * @throws Any error thrown by the function will be rethrown. Cronitor will be notified of the failure. */ -export default async function monitorAsync(execute: () => Promise): Promise { - // Tell Cronitor that the job is running - const start = new Date(); - const series = start.getTime().toString(); - await fetch(buildURL({state: "run", series}).toString()); - console.log("Cronitor: Job started") +export default async function monitorAsync( + execute: () => Promise +): Promise { + // Tell Cronitor that the job is running + const start = new Date(); + const series = start.getTime().toString(); + await fetch(buildURL({ state: "run", series }).toString()); + console.log("Cronitor: Job started"); - // Execute the function, provide try/catch - let result: T | undefined = undefined; - try { - result = await execute(); - } catch (error) { - const duration = getDuration(start, new Date()); - await fetch(buildURL({state: "fail", series, message: error instanceof Error ? error.message : undefined, metric: {duration}}).toString()); - throw error; - } - - // Tell Cronitor that the job is complete (success) - console.log("Cronitor: Job completed") + // Execute the function, provide try/catch + let result: T | undefined = undefined; + try { + result = await execute(); + } catch (error) { const duration = getDuration(start, new Date()); - await fetch(buildURL({state: "complete", series, metric: {duration}}).toString()); + await fetch( + buildURL({ + state: "fail", + series, + message: error instanceof Error ? error.message : undefined, + metric: { duration }, + }).toString() + ); + throw error; + } - return result as T; -} \ No newline at end of file + // Tell Cronitor that the job is complete (success) + console.log("Cronitor: Job completed"); + const duration = getDuration(start, new Date()); + await fetch( + buildURL({ state: "complete", series, metric: { duration } }).toString() + ); + + return result as T; +} diff --git a/src/pages/api/cron.ts b/src/pages/api/cron.ts index 6dc9c60..aeb0cad 100644 --- a/src/pages/api/cron.ts +++ b/src/pages/api/cron.ts @@ -18,7 +18,7 @@ const center = { export default async function handler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse<(ResponseData & StatusData) | StatusData> ) { if (req.query.key != env.API_KEY) { // auth failed