[WIP] A simple wake-on-lan service
at main 123 lines 2.9 kB view raw
1import z from "zod"; 2 3function route<T, I>(opts: { 4 route: `/${string}`; 5 output: z.ZodType<T>; 6 method: "GET"; 7 input: z.ZodType<I>; 8}): (data: I) => Promise<T>; 9function route<T, I>(opts: { 10 route: `/${string}`; 11 output: z.ZodType<T>; 12 method: "POST"; 13 input: z.ZodType<I>; 14}): (data: I) => Promise<T>; 15function route<T, I>(opts: { 16 route: `/${string}`; 17 output: z.ZodType<T>; 18 method: "GET"; 19}): () => Promise<T>; 20function route<T, I>(opts: { 21 route: `/${string}`; 22 output: z.ZodType<T>; 23 method: "POST"; 24}): () => Promise<T>; 25function route<T, I>({ 26 route, 27 output, 28 method, 29 input, 30}: { 31 route: `/${string}`; 32 output: z.ZodType<T>; 33 method: "GET" | "POST"; 34 input?: z.ZodType<I>; 35}): ((data: I) => Promise<T>) | (() => Promise<T>) { 36 const stringify = 37 method == "GET" 38 ? (obj: I) => 39 new URLSearchParams( 40 Object.fromEntries( 41 Object.entries(obj as Object).map(([k, v]) => [k, "" + v]), 42 ), 43 ).toString() 44 : (obj: I) => JSON.stringify(obj); 45 46 const then = (req: Response) => req.json().then(output.parseAsync); 47 48 return input 49 ? method == "GET" 50 ? (data: I) => fetch(route + "?" + stringify(data), { method }).then(then) 51 : (data: I) => 52 fetch(route, { 53 method, 54 body: stringify(data), 55 headers: { "Content-Type": "application/json" }, 56 }).then(then) 57 : () => fetch(route, { method }).then(then); 58} 59 60const u8 = z.int().min(0).max(255); 61const colour = z.tuple([u8, u8, u8]); 62 63const Api = { 64 config: route({ 65 route: "/config", 66 method: "GET", 67 output: z.object({ 68 binding: z.string(), 69 theme: z.object({ 70 background: colour, 71 background_main: colour, 72 background_server: colour, 73 background_button: colour, 74 text: colour, 75 text_secondary: colour, 76 accent_success: colour, 77 accent_fail: colour, 78 link: colour, 79 link_visited: colour, 80 highlight: colour, 81 highlight_opacity: u8, 82 fonts: z.array(z.string()), 83 }), 84 info: z.object({ 85 title: z.string(), 86 links: z.array(z.tuple([z.string(), z.string()])), 87 icon: z.string(), 88 }), 89 pinned: z.array(z.string()), 90 targets: z.record( 91 z.string(), 92 z.object({ 93 mac: z.string(), 94 ip: z.string().nullable(), 95 // should be a url but we allow any string in rust 96 url: z.string().nullable(), 97 }), 98 ), 99 }), 100 }), 101 102 wake: route({ 103 route: "/wake", 104 method: "POST", 105 input: z.object({ 106 mac: z.string(), 107 }), 108 output: z.boolean(), 109 }), 110 111 ping: route({ 112 route: "/ping", 113 method: "GET", 114 input: z.object({ 115 ip: z.string(), 116 }), 117 output: z.boolean(), 118 }), 119} as const; 120 121export const config = await Api.config(); 122 123export default Api;