app: lists on meta tables, schema-qualified SQL, typed columns

- lists read/write real lists.* tables via meta.tables/meta.columns and the
  SECURITY DEFINER meta functions; typed extra columns in the item forms
- old numeric list URLs 308-redirect to the slug
- include_recurring is boolean, osu accuracy/pp/rate numeric
- raw SQL qualified with the new schemas
This commit is contained in:
2026-10-23 22:03:42 +00:00
parent 64fb8df262
commit 4cfe6bad6c
25 changed files with 529 additions and 218 deletions

View File

@@ -0,0 +1,59 @@
<script lang="ts">
// Form inputs for a list's own typed columns (meta.columns), posted as `extra:<column>`.
import { Input } from '#lib/components/ui/input/index.js';
import { Textarea } from '#lib/components/ui/textarea/index.js';
import { NativeSelect } from '#lib/components/ui/native-select/index.js';
import { Checkbox } from '#lib/components/ui/checkbox/index.js';
import type { ExtraValue, ListColumn } from '#lib/server/lists.js';
let { columns, values = {} }: { columns: ListColumn[]; values?: Record<string, ExtraValue> } =
$props();
const str = (v: ExtraValue | undefined) => (v === null || v === undefined ? '' : String(v));
</script>
{#each columns as column (column.name)}
{@const name = `extra:${column.name}`}
{#if column.uiType === 'bool'}
<label class="text-sm"
><Checkbox {name} value="on" checked={values[column.name] === true} /> {column.label}</label
>
{:else if column.uiType === 'select' && column.options.length}
<label
>{column.label}<NativeSelect
{name}
class="block w-full rounded border p-2"
value={str(values[column.name])}
>
<option value="">—</option>
{#each column.options as option (option)}<option value={option}>{option}</option>{/each}
</NativeSelect></label
>
{:else if column.uiType === 'longtext'}
<label
>{column.label}<Textarea
{name}
rows={3}
maxlength={2000}
class="block w-full rounded border p-2"
value={str(values[column.name])}
/></label
>
{:else if ['text', 'url', 'image', 'number', 'cents', 'date', 'select'].includes(column.uiType)}
<label
>{column.label}<Input
{name}
type={column.uiType === 'number' || column.uiType === 'cents'
? 'number'
: column.uiType === 'date'
? 'date'
: column.uiType === 'url' || column.uiType === 'image'
? 'url'
: 'text'}
step={column.uiType === 'number' ? 'any' : undefined}
maxlength={column.uiType === 'text' ? 2000 : undefined}
class="block w-full rounded border p-2"
value={str(values[column.name])}
/></label
>
{/if}
{/each}