mirror of
https://github.com/Xevion/bus-reminder.git
synced 2025-12-06 01:14:30 -06:00
Reformat project with Prettier
This commit is contained in:
7
.prettierignore
Normal file
7
.prettierignore
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.next/
|
||||||
|
.vercel
|
||||||
|
.env
|
||||||
|
.gitignore
|
||||||
|
vercel.json
|
||||||
|
*.log
|
||||||
|
*.lock
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"useTabs": false
|
"useTabs": false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# bus-reminder
|
# bus-reminder
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
A cron-job based personal notification system for myself.
|
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
|
[cronitor]: https://cronitor.io
|
||||||
[cron-jobs]: https://cron-jobs.org
|
[cron-jobs]: https://cron-jobs.org
|
||||||
|
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- Integrate Discord notifications
|
- Integrate Discord notifications
|
||||||
- Create system for dynamically disabling the check for the rest of the day ([Upstash](upstash.com) for Redis)
|
- Create system for dynamically disabling the check for the rest of the day ([Upstash](upstash.com) for Redis)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
}
|
};
|
||||||
|
|
||||||
module.exports = nextConfig
|
module.exports = nextConfig;
|
||||||
|
|||||||
18
src/env/util.mjs
vendored
18
src/env/util.mjs
vendored
@@ -1,10 +1,10 @@
|
|||||||
export const formatErrors = (
|
export const formatErrors = (
|
||||||
/** @type {import('zod').ZodFormattedError<Map<string,string>,string>} */
|
/** @type {import('zod').ZodFormattedError<Map<string,string>,string>} */
|
||||||
errors,
|
errors
|
||||||
) =>
|
) =>
|
||||||
Object.entries(errors)
|
Object.entries(errors)
|
||||||
.map(([name, value]) => {
|
.map(([name, value]) => {
|
||||||
if (value && "_errors" in value)
|
if (value && "_errors" in value)
|
||||||
return `${name}: ${value._errors.join(", ")}\n`;
|
return `${name}: ${value._errors.join(", ")}\n`;
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import { env } from "@/env/server.mjs";
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as life360 from "life360-node-api";
|
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 MILES_PER_NAUTICAL_MILE = 1.15078;
|
||||||
const KILOMETERS_PER_MILE = 1.60934;
|
const KILOMETERS_PER_MILE = 1.60934;
|
||||||
|
|
||||||
@@ -24,24 +27,25 @@ export function distance(
|
|||||||
const radlat2 = (Math.PI * b.latitude) / 180;
|
const radlat2 = (Math.PI * b.latitude) / 180;
|
||||||
const theta = a.longitude - b.longitude;
|
const theta = a.longitude - b.longitude;
|
||||||
const radtheta = (Math.PI * theta) / 180;
|
const radtheta = (Math.PI * theta) / 180;
|
||||||
let dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
|
let dist =
|
||||||
|
Math.sin(radlat1) * Math.sin(radlat2) +
|
||||||
if (dist > 1)
|
Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
|
||||||
dist = 1;
|
|
||||||
|
if (dist > 1) dist = 1;
|
||||||
|
|
||||||
dist = Math.acos(dist);
|
dist = Math.acos(dist);
|
||||||
dist = (dist * 180) / Math.PI;
|
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
|
// Convert to the specified unit
|
||||||
switch (unit) {
|
switch (unit) {
|
||||||
case "K":
|
case "K":
|
||||||
return dist * KILOMETERS_PER_MILE;
|
return dist * KILOMETERS_PER_MILE;
|
||||||
case "N":
|
case "N":
|
||||||
return dist / MILES_PER_NAUTICAL_MILE;
|
return dist / MILES_PER_NAUTICAL_MILE;
|
||||||
case "M":
|
case "M":
|
||||||
default:
|
default:
|
||||||
return dist;
|
return dist;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,7 +54,7 @@ export function distance(
|
|||||||
* @returns The distance in meters between me and the center
|
* @returns The distance in meters between me and the center
|
||||||
*/
|
*/
|
||||||
export async function getDistance(): Promise<number> {
|
export async function getDistance(): Promise<number> {
|
||||||
// Setup the Life360 API client
|
// Setup the Life360 API client
|
||||||
const client = await life360.login(
|
const client = await life360.login(
|
||||||
env.LIFE360_USERNAME,
|
env.LIFE360_USERNAME,
|
||||||
env.LIFE360_PASSWORD
|
env.LIFE360_PASSWORD
|
||||||
|
|||||||
127
src/monitor.ts
127
src/monitor.ts
@@ -1,77 +1,98 @@
|
|||||||
import {env} from "@/env/server.mjs";
|
import { env } from "@/env/server.mjs";
|
||||||
import {z} from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const BASE_URL = `https://cronitor.link/p/${env.CRONITOR_ACCOUNT_ID}/${env.CRONITOR_JOB_ID}`;
|
const BASE_URL = `https://cronitor.link/p/${env.CRONITOR_ACCOUNT_ID}/${env.CRONITOR_JOB_ID}`;
|
||||||
const parameterSchema = z.object({
|
const parameterSchema = z.object({
|
||||||
env: z.string().optional(),
|
env: z.string().optional(),
|
||||||
host: z.string().optional(),
|
host: z.string().optional(),
|
||||||
message: z.string().optional(),
|
message: z.string().optional(),
|
||||||
metric: z.union([
|
metric: z
|
||||||
z.object({count: z.number()}),
|
.union([
|
||||||
z.object({duration: z.number()}),
|
z.object({ count: z.number() }),
|
||||||
z.object({error_count: z.number()}),
|
z.object({ duration: z.number() }),
|
||||||
]).optional(),
|
z.object({ error_count: z.number() }),
|
||||||
series: z.string().optional(),
|
])
|
||||||
state: z.enum(["run", "complete", "fail", "ok"]).optional(),
|
.optional(),
|
||||||
status_code: z.number().int().optional(),
|
series: z.string().optional(),
|
||||||
})
|
state: z.enum(["run", "complete", "fail", "ok"]).optional(),
|
||||||
|
status_code: z.number().int().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
function buildURL(parameters: z.infer<typeof parameterSchema>) {
|
function buildURL(parameters: z.infer<typeof parameterSchema>) {
|
||||||
const url = new URL(BASE_URL);
|
const url = new URL(BASE_URL);
|
||||||
|
|
||||||
// Definite string properties
|
// Definite string properties
|
||||||
const {env, host, message, series, state} = parameters;
|
const { env, host, message, series, state } = parameters;
|
||||||
for (const [key, value] of Object.entries({env, host, message, series, state}).filter(([, value]) => value != undefined))
|
for (const [key, value] of Object.entries({
|
||||||
url.searchParams.append(key, value!);
|
env,
|
||||||
|
host,
|
||||||
|
message,
|
||||||
|
series,
|
||||||
|
state,
|
||||||
|
}).filter(([, value]) => value != undefined))
|
||||||
|
url.searchParams.append(key, value!);
|
||||||
|
|
||||||
// Extract potentially undefined specified properties
|
// Extract potentially undefined specified properties
|
||||||
if (parameters.metric != undefined) {
|
if (parameters.metric != undefined) {
|
||||||
const {count, duration, error_count} = parameters.metric as {count?: number, duration?: number, error_count?: number};
|
const { count, duration, error_count } = parameters.metric as {
|
||||||
if (count != undefined)
|
count?: number;
|
||||||
url.searchParams.append("metric", `count:${count}`);
|
duration?: number;
|
||||||
else if (duration != undefined)
|
error_count?: number;
|
||||||
url.searchParams.append("metric", `duration:${duration}`);
|
};
|
||||||
else if (error_count != undefined)
|
if (count != undefined) url.searchParams.append("metric", `count:${count}`);
|
||||||
url.searchParams.append("metric", `error_count:${error_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
|
* @returns The duration in seconds between the start and end dates
|
||||||
*/
|
*/
|
||||||
function getDuration(start: Date, end: Date) {
|
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
|
* Executes the given function while providing telemetry to Cronitor
|
||||||
* @param execute The function to execute
|
* @param execute The function to execute
|
||||||
* @throws Any error thrown by the function will be rethrown. Cronitor will be notified of the failure.
|
* @throws Any error thrown by the function will be rethrown. Cronitor will be notified of the failure.
|
||||||
*/
|
*/
|
||||||
export default async function monitorAsync<T = any>(execute: () => Promise<T>): Promise<T> {
|
export default async function monitorAsync<T = any>(
|
||||||
// Tell Cronitor that the job is running
|
execute: () => Promise<T>
|
||||||
const start = new Date();
|
): Promise<T> {
|
||||||
const series = start.getTime().toString();
|
// Tell Cronitor that the job is running
|
||||||
await fetch(buildURL({state: "run", series}).toString());
|
const start = new Date();
|
||||||
console.log("Cronitor: Job started")
|
const series = start.getTime().toString();
|
||||||
|
await fetch(buildURL({ state: "run", series }).toString());
|
||||||
|
console.log("Cronitor: Job started");
|
||||||
|
|
||||||
// Execute the function, provide try/catch
|
// Execute the function, provide try/catch
|
||||||
let result: T | undefined = undefined;
|
let result: T | undefined = undefined;
|
||||||
try {
|
try {
|
||||||
result = await execute();
|
result = await execute();
|
||||||
} catch (error) {
|
} 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")
|
|
||||||
const duration = getDuration(start, new Date());
|
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;
|
// 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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const center = {
|
|||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse<ResponseData & StatusData | StatusData>
|
res: NextApiResponse<(ResponseData & StatusData) | StatusData>
|
||||||
) {
|
) {
|
||||||
if (req.query.key != env.API_KEY) {
|
if (req.query.key != env.API_KEY) {
|
||||||
// auth failed
|
// auth failed
|
||||||
|
|||||||
Reference in New Issue
Block a user