Files
Tomas Dvorak b17a06fbba 🚀 Dash - Homelab Dashboard
A clean, customizable homelab dashboard inspired by CasaOS.

Features:
- Empty-first dashboard (no demo data)
- 3 themes: Light, Dark, CasaOS glassmorphism
- Widgets: Clock (multi-timezone), Pi-hole, Memos, Immich, Image
- Drag & drop app organization
- Grid + list view for apps
- Groups with collapse/expand
- Proper widget refresh handling
- Visual timezone picker
- Square app cards with hover effects

Stack: Go + Gin + PostgreSQL + Next.js 15 + React 19 + Tailwind CSS + shadcn/ui
2026-05-03 16:13:46 +02:00

112 lines
3.9 KiB
PL/PgSQL

-- +goose Up
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE groups (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
sort_order integer NOT NULL,
collapsed boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE asset_files (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
original_name text NOT NULL,
stored_name text NOT NULL,
mime_type text NOT NULL,
size_bytes integer NOT NULL,
public_path text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE services (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
group_id uuid NULL REFERENCES groups(id) ON DELETE SET NULL,
name text NOT NULL,
icon_url text NULL,
icon_asset_id uuid NULL REFERENCES asset_files(id) ON DELETE SET NULL,
sort_order integer NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE service_urls (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
service_id uuid NOT NULL REFERENCES services(id) ON DELETE CASCADE,
label text NOT NULL,
kind text NOT NULL CHECK (kind IN ('local', 'external', 'custom')),
url text NOT NULL,
sort_order integer NOT NULL,
is_primary boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE widget_instances (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
type text NOT NULL CHECK (type IN ('clock', 'image', 'pihole')),
title text NOT NULL,
enabled boolean NOT NULL DEFAULT true,
sort_order integer NOT NULL,
config jsonb NOT NULL DEFAULT '{}',
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE widget_cache (
widget_id uuid PRIMARY KEY REFERENCES widget_instances(id) ON DELETE CASCADE,
status text NOT NULL CHECK (status IN ('fresh', 'stale', 'error')),
data jsonb NULL,
error text NULL,
fetched_at timestamptz NULL,
expires_at timestamptz NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX groups_sort_order_idx ON groups(sort_order);
CREATE INDEX services_group_sort_order_idx ON services(group_id, sort_order);
CREATE INDEX services_ungrouped_sort_order_idx ON services(sort_order) WHERE group_id IS NULL;
CREATE INDEX service_urls_service_sort_order_idx ON service_urls(service_id, sort_order);
CREATE INDEX widget_instances_enabled_sort_order_idx ON widget_instances(enabled, sort_order);
CREATE INDEX asset_files_created_at_idx ON asset_files(created_at);
-- +goose StatementBegin
CREATE OR REPLACE FUNCTION set_updated_at()
RETURNS trigger AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- +goose StatementEnd
CREATE TRIGGER groups_set_updated_at BEFORE UPDATE ON groups
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
CREATE TRIGGER services_set_updated_at BEFORE UPDATE ON services
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
CREATE TRIGGER service_urls_set_updated_at BEFORE UPDATE ON service_urls
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
CREATE TRIGGER widget_instances_set_updated_at BEFORE UPDATE ON widget_instances
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
CREATE TRIGGER widget_cache_set_updated_at BEFORE UPDATE ON widget_cache
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
-- +goose Down
DROP TRIGGER IF EXISTS widget_cache_set_updated_at ON widget_cache;
DROP TRIGGER IF EXISTS widget_instances_set_updated_at ON widget_instances;
DROP TRIGGER IF EXISTS service_urls_set_updated_at ON service_urls;
DROP TRIGGER IF EXISTS services_set_updated_at ON services;
DROP TRIGGER IF EXISTS groups_set_updated_at ON groups;
DROP FUNCTION IF EXISTS set_updated_at();
DROP TABLE IF EXISTS widget_cache;
DROP TABLE IF EXISTS widget_instances;
DROP TABLE IF EXISTS service_urls;
DROP TABLE IF EXISTS services;
DROP TABLE IF EXISTS asset_files;
DROP TABLE IF EXISTS groups;