feat(dashboard): dashboard_layout table + repo
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
10
lib/db/migrations/012_dashboard_layout.sql
Normal file
10
lib/db/migrations/012_dashboard_layout.sql
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
-- 012_dashboard_layout.sql
|
||||||
|
-- Single global, owner-scoped dashboard layout. One logical row keyed by a
|
||||||
|
-- stable owner key (v2 is single-owner; 'owner' is the only key for now).
|
||||||
|
CREATE TABLE dashboard_layout (
|
||||||
|
owner_key text PRIMARY KEY DEFAULT 'owner',
|
||||||
|
card_order jsonb NOT NULL DEFAULT '[]'::jsonb, -- ["clock","weather",...]
|
||||||
|
hidden jsonb NOT NULL DEFAULT '[]'::jsonb, -- ["speedtest"]
|
||||||
|
sizes jsonb NOT NULL DEFAULT '{}'::jsonb, -- {"weather":"s"}
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
24
lib/db/repos/dashboard_layout.js
Normal file
24
lib/db/repos/dashboard_layout.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { pool } from '../pool.js';
|
||||||
|
|
||||||
|
const DEFAULTS = { card_order: [], hidden: [], sizes: {} };
|
||||||
|
|
||||||
|
export async function get() {
|
||||||
|
const { rows } = await pool.query(
|
||||||
|
`SELECT card_order, hidden, sizes FROM dashboard_layout WHERE owner_key = 'owner'`
|
||||||
|
);
|
||||||
|
return rows[0] || { ...DEFAULTS };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function put({ card_order = [], hidden = [], sizes = {} }) {
|
||||||
|
await pool.query(
|
||||||
|
`INSERT INTO dashboard_layout (owner_key, card_order, hidden, sizes, updated_at)
|
||||||
|
VALUES ('owner', $1::jsonb, $2::jsonb, $3::jsonb, now())
|
||||||
|
ON CONFLICT (owner_key) DO UPDATE
|
||||||
|
SET card_order = EXCLUDED.card_order,
|
||||||
|
hidden = EXCLUDED.hidden,
|
||||||
|
sizes = EXCLUDED.sizes,
|
||||||
|
updated_at = now()`,
|
||||||
|
[JSON.stringify(card_order), JSON.stringify(hidden), JSON.stringify(sizes)]
|
||||||
|
);
|
||||||
|
return get();
|
||||||
|
}
|
||||||
27
tests/repos/dashboard_layout.test.js
Normal file
27
tests/repos/dashboard_layout.test.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { describe, it, expect, beforeAll } from 'vitest';
|
||||||
|
import { resetDb } from '../helpers/db.js';
|
||||||
|
import { migrateUp } from '../../lib/db/migrate.js';
|
||||||
|
import * as repo from '../../lib/db/repos/dashboard_layout.js';
|
||||||
|
|
||||||
|
beforeAll(async () => { await resetDb(); await migrateUp(); });
|
||||||
|
|
||||||
|
describe('dashboard_layout repo', () => {
|
||||||
|
it('returns defaults when unset', async () => {
|
||||||
|
const l = await repo.get();
|
||||||
|
expect(l).toEqual({ card_order: [], hidden: [], sizes: {} });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('upserts and reads back', async () => {
|
||||||
|
await repo.put({ card_order: ['clock', 'weather'], hidden: ['jobs'], sizes: { weather: 's' } });
|
||||||
|
const l = await repo.get();
|
||||||
|
expect(l.card_order).toEqual(['clock', 'weather']);
|
||||||
|
expect(l.hidden).toEqual(['jobs']);
|
||||||
|
expect(l.sizes).toEqual({ weather: 's' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('second put overwrites the same single row', async () => {
|
||||||
|
await repo.put({ card_order: ['host-perf'], hidden: [], sizes: {} });
|
||||||
|
const l = await repo.get();
|
||||||
|
expect(l.card_order).toEqual(['host-perf']);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user