Simple API gateway for webhooks

add country endpoint

finxol.io 569784f6 1b6bfad6

verified
Changed files
+161
src
+104
src/sensors.ts
···
import { Hono } from "hono"
import { validator } from "hono/validator"
import { z } from "zod"
+
import { tryCatch } from "./util.ts"
const SensorsSchema = z.object({
sensorData: z.array(
···
deviceName: z.string(),
})
+
const ReverseGeocodingSchema = z.object({
+
type: z.literal("FeatureCollection"),
+
features: z.array(
+
z.object({
+
type: z.literal("Feature"),
+
properties: z.object({
+
datasource: z.object({
+
sourcename: z.string(),
+
attribution: z.string(),
+
license: z.string(),
+
url: z.string(),
+
}).optional(),
+
name: z.string().optional(),
+
country: z.string(),
+
country_code: z.string(),
+
state: z.string().optional(),
+
city: z.string().optional(),
+
postcode: z.string().optional(),
+
district: z.string().optional(),
+
suburb: z.string().optional(),
+
street: z.string().optional(),
+
housenumber: z.string().optional(),
+
iso3166_2: z.string().optional(),
+
lon: z.number(),
+
lat: z.number(),
+
state_code: z.string().optional(),
+
distance: z.number().optional(),
+
result_type: z.string().optional(),
+
formatted: z.string(),
+
address_line1: z.string().optional(),
+
address_line2: z.string().optional(),
+
category: z.string().optional(),
+
timezone: z.object({
+
name: z.string(),
+
offset_STD: z.string(),
+
offset_STD_seconds: z.number(),
+
offset_DST: z.string(),
+
offset_DST_seconds: z.number(),
+
abbreviation_STD: z.string(),
+
abbreviation_DST: z.string(),
+
}).optional(),
+
plus_code: z.string().optional(),
+
rank: z.object({
+
importance: z.number(),
+
popularity: z.number(),
+
}).optional(),
+
place_id: z.string().optional(),
+
}),
+
geometry: z.object({
+
type: z.literal("Point"),
+
coordinates: z.tuple([z.number(), z.number()]),
+
}),
+
bbox: z.array(z.number()).optional(),
+
}),
+
),
+
query: z.object({
+
lat: z.number(),
+
lon: z.number(),
+
plus_code: z.string().optional(),
+
}).optional(),
+
})
+
type Sensors = z.infer<typeof SensorsSchema>
const kv = await Deno.openKv()
const sensors = new Hono()
+
.get("/country", async (c) => {
+
const data = await kv.get<Sensors>(["sensors", "latest"])
+
if (!data.value) {
+
return c.text("No data found", 404)
+
}
+
+
const location = data.value.sensorData.find((sensor) =>
+
sensor.sensorType === "location"
+
)?.data
+
+
if (!location) {
+
return c.text("No location data found", 404)
+
}
+
+
const geocode = await tryCatch(
+
fetch(
+
`https://api.geoapify.com/v1/geocode/reverse?lat=${location.latitude}&lon=${location.longitude}&apiKey=${
+
Deno.env.get("GEOAPIFY_API_KEY")
+
}`,
+
)
+
.then((res) => res.json())
+
.then((data) => data.features[0].properties.country_code),
+
)
+
+
if (!geocode.success) {
+
console.error(geocode.error)
+
return c.text("Geoapify API error", 404)
+
}
+
+
const country = ReverseGeocodingSchema.safeParse(geocode.value)
+
+
if (!country.success) {
+
console.error(country.error)
+
return c.text("Invalid country data", 400)
+
}
+
+
return c.json({
+
country: country.data.features[0].properties.country,
+
country_code: country.data.features[0].properties.country_code,
+
})
+
})
.get("/get", async (c) => {
const data = await kv.get<Sensors>(["sensors", "latest"])
if (!data.value) {
+57
src/util.ts
···
+
/**
+
* Wraps a promise in a try/catch block and returns a Result object representing
+
* either a successful value or an error.
+
*
+
* This utility function provides a more structured way to handle asynchronous operations
+
* without using try/catch blocks throughout your codebase. It follows a pattern similar
+
* to Rust's Result type, allowing for more predictable error handling.
+
*
+
* @template T - The type of the value returned by the promise on success
+
* @template E - The type of the error object (defaults to Error)
+
*
+
* @param promise - The promise to wrap and execute
+
*
+
* @returns A Promise resolving to a discriminated union object with:
+
* - On success: `{ success: true, value: T, error: null }`
+
* - On failure: `{ success: false, value: null, error: E }`
+
*
+
* @example
+
* // Success case
+
* const successResult = await tryCatch(Promise.resolve('data'));
+
* if (successResult.success) {
+
* console.log(successResult.value); // 'data'
+
* }
+
*
+
* @example
+
* // Error case
+
* const errorResult = await tryCatch(Promise.reject(new Error('Failed')));
+
* if (!errorResult.success) {
+
* console.error(errorResult.error.message); // 'Failed'
+
* }
+
*
+
* @example
+
* // Using with custom error type
+
* interface ApiError { code: number; message: string }
+
* const apiCall = tryCatch<UserData, ApiError>(fetchUserData());
+
*/
+
export async function tryCatch<T, E = Error>(
+
promise: Promise<T>,
+
): Promise<
+
| {
+
success: true
+
value: T
+
error: null
+
}
+
| {
+
success: false
+
value: null
+
error: E
+
}
+
> {
+
try {
+
const value = await promise
+
return { success: true, value, error: null }
+
} catch (error) {
+
return { success: false, value: null, error: error as E }
+
}
+
}