WIP api?
This commit is contained in:
parent
178e3f582c
commit
3ddb34bee5
49
.helix/languages.toml
Normal file
49
.helix/languages.toml
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
[language-server.deno]
|
||||||
|
command = "deno"
|
||||||
|
args = ["lsp", "--unstable-kv"]
|
||||||
|
config.hostInfo = "helix"
|
||||||
|
|
||||||
|
[[language]]
|
||||||
|
name = "javascript"
|
||||||
|
scope = "source.js"
|
||||||
|
injection-regex = "(js|javascript)"
|
||||||
|
language-id = "javascript"
|
||||||
|
file-types = ["js", "mjs", "cjs", "rules", "es6", "pac", "jakefile"]
|
||||||
|
shebangs = ["node"]
|
||||||
|
comment-token = "//"
|
||||||
|
language-servers = [ "deno" ]
|
||||||
|
indent = { tab-width = 2, unit = "\t" }
|
||||||
|
auto-format = true
|
||||||
|
|
||||||
|
[[language]]
|
||||||
|
name = "jsx"
|
||||||
|
scope = "source.jsx"
|
||||||
|
injection-regex = "jsx"
|
||||||
|
language-id = "javascriptreact"
|
||||||
|
file-types = ["jsx"]
|
||||||
|
comment-token = "//"
|
||||||
|
language-servers = [ "deno" ]
|
||||||
|
indent = { tab-width = 2, unit = "\t" }
|
||||||
|
grammar = "javascript"
|
||||||
|
auto-format = true
|
||||||
|
|
||||||
|
[[language]]
|
||||||
|
name = "typescript"
|
||||||
|
scope = "source.ts"
|
||||||
|
injection-regex = "(ts|typescript)"
|
||||||
|
file-types = ["ts", "mts", "cts"]
|
||||||
|
language-id = "typescript"
|
||||||
|
shebangs = ["deno", "ts-node"]
|
||||||
|
language-servers = [ "deno" ]
|
||||||
|
indent = { tab-width = 2, unit = "\t" }
|
||||||
|
auto-format = true
|
||||||
|
|
||||||
|
[[language]]
|
||||||
|
name = "tsx"
|
||||||
|
scope = "source.tsx"
|
||||||
|
injection-regex = "(tsx)"
|
||||||
|
language-id = "typescriptreact"
|
||||||
|
file-types = ["tsx"]
|
||||||
|
language-servers = [ "deno" ]
|
||||||
|
indent = { tab-width = 2, unit = "\t" }
|
||||||
|
auto-format = true
|
17
api.ts
Normal file
17
api.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { FreshContext, Handler } from '$fresh/server.ts'
|
||||||
|
import { TablesForModels } from '@lyrics/db.ts'
|
||||||
|
|
||||||
|
export function crudHandlerFor<
|
||||||
|
T extends { id: string },
|
||||||
|
M extends { parse: (a: T) => T },
|
||||||
|
P extends keyof typeof TablesForModels,
|
||||||
|
>(m: M, p: P): Handler {
|
||||||
|
return {
|
||||||
|
async GET(req: Request, ctx: FreshContext) {
|
||||||
|
if (req.
|
||||||
|
const resp = await ctx.render();
|
||||||
|
resp.headers.set("X-Custom-Header", "Hello");
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ export function Button(props: JSX.HTMLAttributes<HTMLButtonElement>) {
|
||||||
<button
|
<button
|
||||||
{...props}
|
{...props}
|
||||||
disabled={!IS_BROWSER || props.disabled}
|
disabled={!IS_BROWSER || props.disabled}
|
||||||
class="px-2 py-1 border-gray-500 border-2 rounded bg-white hover:bg-gray-200 transition-colors"
|
class="px-2 py-1 border-gray-500 border-2 rounded bg-white hover:bg-gray-200 dark:bg-zinc-900 dark:text-zinc-100 dark:hover:bg-zinc-800 transition-colors"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
42
db.ts
Normal file
42
db.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import { Display, Playlist, Song } from '@lyrics/models.ts'
|
||||||
|
|
||||||
|
export const kv = await Deno.openKv('lyrics')
|
||||||
|
|
||||||
|
export const TablesForModels = {
|
||||||
|
'song': Song,
|
||||||
|
'playlist': Playlist,
|
||||||
|
'display': Display,
|
||||||
|
}
|
||||||
|
|
||||||
|
function crudFor<
|
||||||
|
P extends keyof typeof TablesForModels,
|
||||||
|
T extends { id: string },
|
||||||
|
M extends { parse: (a: T) => T },
|
||||||
|
>(m: M, p: P) {
|
||||||
|
return {
|
||||||
|
async all(): Promise<T[]> {
|
||||||
|
const result: T[] = []
|
||||||
|
for await (const v of kv.list<T>({ prefix: [p] })) {
|
||||||
|
result.push(m.parse(v.value))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
async get(id: string): Promise<T | null> {
|
||||||
|
const e = await kv.get<T>([p, id])
|
||||||
|
if (e.value == null) return null
|
||||||
|
return m.parse(e.value)
|
||||||
|
},
|
||||||
|
async save(t: T) {
|
||||||
|
return await kv.set([p, t.id], t)
|
||||||
|
},
|
||||||
|
async delete(id: string) {
|
||||||
|
return await kv.delete([p, id])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const db = {
|
||||||
|
song: crudFor(Song, 'song'),
|
||||||
|
playlist: crudFor(Playlist, 'playlist'),
|
||||||
|
display: crudFor(Display, 'display'),
|
||||||
|
}
|
17
deno.json
17
deno.json
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"lock": false,
|
"lock": false,
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx",
|
"check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx",
|
||||||
"cli": "echo \"import '\\$fresh/src/dev/cli.ts'\" | deno run --unstable -A -",
|
"cli": "echo \"import '\\$fresh/src/dev/cli.ts'\" | deno run -A -",
|
||||||
"manifest": "deno task cli manifest $(pwd)",
|
"manifest": "deno task cli manifest $(pwd)",
|
||||||
"start": "deno run -A --watch=static/,routes/ dev.ts",
|
"start": "deno run -A --watch=static/,routes/ dev.ts",
|
||||||
"build": "deno run -A dev.ts build",
|
"build": "deno run -A dev.ts build",
|
||||||
"preview": "deno run -A main.ts",
|
"preview": "deno run -A main.ts",
|
||||||
"update": "deno run -A -r https://fresh.deno.dev/update ."
|
"update": "deno run -A -r https://fresh.deno.dev/update ."
|
||||||
},
|
},
|
||||||
"lint": {
|
"lint": {
|
||||||
"rules": {
|
"rules": {
|
||||||
|
@ -29,11 +29,14 @@
|
||||||
"tailwindcss": "npm:tailwindcss@3.4.1",
|
"tailwindcss": "npm:tailwindcss@3.4.1",
|
||||||
"tailwindcss/": "npm:/tailwindcss@3.4.1/",
|
"tailwindcss/": "npm:/tailwindcss@3.4.1/",
|
||||||
"tailwindcss/plugin": "npm:/tailwindcss@3.4.1/plugin.js",
|
"tailwindcss/plugin": "npm:/tailwindcss@3.4.1/plugin.js",
|
||||||
|
"@lyrics/": "./",
|
||||||
"$std/": "https://deno.land/std@0.211.0/"
|
"$std/": "https://deno.land/std@0.211.0/"
|
||||||
},
|
},
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"jsxImportSource": "preact"
|
"jsxImportSource": "preact"
|
||||||
},
|
},
|
||||||
|
"fmt": { "useTabs": true, "semiColons": false, "singleQuote": true },
|
||||||
|
"unstable": ["kv", "cron"],
|
||||||
"nodeModulesDir": true
|
"nodeModulesDir": true
|
||||||
}
|
}
|
||||||
|
|
42
fresh.gen.ts
42
fresh.gen.ts
|
@ -2,26 +2,28 @@
|
||||||
// This file SHOULD be checked into source version control.
|
// This file SHOULD be checked into source version control.
|
||||||
// This file is automatically updated during development when running `dev.ts`.
|
// This file is automatically updated during development when running `dev.ts`.
|
||||||
|
|
||||||
import * as $_404 from "./routes/_404.tsx";
|
import * as $_404 from './routes/_404.tsx'
|
||||||
import * as $_app from "./routes/_app.tsx";
|
import * as $_app from './routes/_app.tsx'
|
||||||
import * as $api_joke from "./routes/api/joke.ts";
|
import * as $api_db from './routes/api/db.ts'
|
||||||
import * as $greet_name_ from "./routes/greet/[name].tsx";
|
import * as $api_joke from './routes/api/joke.ts'
|
||||||
import * as $index from "./routes/index.tsx";
|
import * as $greet_name_ from './routes/greet/[name].tsx'
|
||||||
import * as $Counter from "./islands/Counter.tsx";
|
import * as $index from './routes/index.tsx'
|
||||||
import { type Manifest } from "$fresh/server.ts";
|
import * as $Counter from './islands/Counter.tsx'
|
||||||
|
import { type Manifest } from '$fresh/server.ts'
|
||||||
|
|
||||||
const manifest = {
|
const manifest = {
|
||||||
routes: {
|
routes: {
|
||||||
"./routes/_404.tsx": $_404,
|
'./routes/_404.tsx': $_404,
|
||||||
"./routes/_app.tsx": $_app,
|
'./routes/_app.tsx': $_app,
|
||||||
"./routes/api/joke.ts": $api_joke,
|
'./routes/api/db.ts': $api_db,
|
||||||
"./routes/greet/[name].tsx": $greet_name_,
|
'./routes/api/joke.ts': $api_joke,
|
||||||
"./routes/index.tsx": $index,
|
'./routes/greet/[name].tsx': $greet_name_,
|
||||||
},
|
'./routes/index.tsx': $index,
|
||||||
islands: {
|
},
|
||||||
"./islands/Counter.tsx": $Counter,
|
islands: {
|
||||||
},
|
'./islands/Counter.tsx': $Counter,
|
||||||
baseUrl: import.meta.url,
|
},
|
||||||
} satisfies Manifest;
|
baseUrl: import.meta.url,
|
||||||
|
} satisfies Manifest
|
||||||
|
|
||||||
export default manifest;
|
export default manifest
|
||||||
|
|
11
main.ts
11
main.ts
|
@ -3,11 +3,12 @@
|
||||||
/// <reference lib="dom.iterable" />
|
/// <reference lib="dom.iterable" />
|
||||||
/// <reference lib="dom.asynciterable" />
|
/// <reference lib="dom.asynciterable" />
|
||||||
/// <reference lib="deno.ns" />
|
/// <reference lib="deno.ns" />
|
||||||
|
/// <reference lib="deno.unstable" />
|
||||||
|
|
||||||
import "$std/dotenv/load.ts";
|
import '$std/dotenv/load.ts'
|
||||||
|
|
||||||
import { start } from "$fresh/server.ts";
|
import { start } from '$fresh/server.ts'
|
||||||
import manifest from "./fresh.gen.ts";
|
import manifest from '@lyrics/fresh.gen.ts'
|
||||||
import config from "./fresh.config.ts";
|
import config from '@lyrics/fresh.config.ts'
|
||||||
|
|
||||||
await start(manifest, config);
|
await start(manifest, config)
|
||||||
|
|
40
models.ts
Normal file
40
models.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { z } from 'https://deno.land/x/zod@v3.22.4/mod.ts'
|
||||||
|
|
||||||
|
export const Identifiable = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
})
|
||||||
|
export type TIdentifiable = z.infer<typeof Identifiable>
|
||||||
|
|
||||||
|
export const Verse = z.object({
|
||||||
|
content: z.string(),
|
||||||
|
})
|
||||||
|
export type TVerse = z.infer<typeof Verse>
|
||||||
|
|
||||||
|
export const Map = z.object({
|
||||||
|
verseKeys: z.array(z.string()),
|
||||||
|
})
|
||||||
|
export type TMap = z.infer<typeof Map>
|
||||||
|
|
||||||
|
export const Song = Identifiable.merge(z.object({
|
||||||
|
title: z.string(),
|
||||||
|
verses: z.record(z.string(), Verse),
|
||||||
|
maps: z.record(z.string(), Map),
|
||||||
|
}))
|
||||||
|
export type TSong = z.infer<typeof Song>
|
||||||
|
|
||||||
|
export const PlaylistEntry = z.object({
|
||||||
|
songKey: z.string().uuid(),
|
||||||
|
mapKey: z.string(),
|
||||||
|
})
|
||||||
|
export type TPlaylistEntry = z.infer<typeof PlaylistEntry>
|
||||||
|
|
||||||
|
export const Playlist = Identifiable.merge(z.object({
|
||||||
|
entries: z.array(PlaylistEntry),
|
||||||
|
}))
|
||||||
|
export type TPlaylist = z.infer<typeof Playlist>
|
||||||
|
|
||||||
|
export const Display = Identifiable.merge(z.object({
|
||||||
|
playlistKey: z.string().uuid(),
|
||||||
|
songKeyIndex: z.number(),
|
||||||
|
}))
|
||||||
|
export type TDisplay = z.infer<typeof Display>
|
|
@ -8,7 +8,7 @@ export default function App({ Component }: PageProps) {
|
||||||
<title>lyricscreen</title>
|
<title>lyricscreen</title>
|
||||||
<link rel="stylesheet" href="/styles.css" />
|
<link rel="stylesheet" href="/styles.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="dark:bg-zinc-950">
|
||||||
<Component />
|
<Component />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
5
routes/api/db.ts
Normal file
5
routes/api/db.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { FreshContext } from '$fresh/server.ts'
|
||||||
|
|
||||||
|
export const handler = (_req: Request, _ctx: FreshContext): Response => {
|
||||||
|
return new Response(';)')
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import Counter from "../islands/Counter.tsx";
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const count = useSignal(3);
|
const count = useSignal(3);
|
||||||
return (
|
return (
|
||||||
<div class="px-4 py-8 mx-auto bg-[#86efac]">
|
<div class="px-4 py-8 mx-auto bg-[#86efac] dark:bg-emerald-950 dark:text-zinc-100">
|
||||||
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
|
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
|
||||||
<img
|
<img
|
||||||
class="my-6"
|
class="my-6"
|
||||||
|
|
Loading…
Reference in a new issue