···11if nix flake show &>/dev/null; then
22 use flake
33fi
44-export DATABASE_URL="postgres://lumina:lumina_pw@localhost:5432/lumina_config"
44+export DATABASE_URL="sqlite3://$(pwd)/data/instance.db"
···11+-- migrate:up
22+33+CREATE TABLE IF NOT EXISTS logs (
44+ id INTEGER PRIMARY KEY AUTOINCREMENT,
55+ level TEXT CHECK(level IN ('INFO', 'WARN', 'ERROR', 'DEBUG')),
66+ message TEXT NOT NULL,
77+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
88+);
99+1010+-- Users: Storing UUIDs as TEXT, but we'll remove the dashes to make it minecraft-like UUIDS, this saves a little space :3
1111+CREATE TABLE IF NOT EXISTS users (
1212+ id TEXT PRIMARY KEY NOT NULL CHECK(length(id) = 32),
1313+ foreign_instance_id TEXT,
1414+ foreign_user_id TEXT,
1515+ email TEXT NOT NULL UNIQUE,
1616+ username TEXT NOT NULL UNIQUE,
1717+ password TEXT NOT NULL
1818+);
1919+2020+-- Items: On the Postgres equevalent, this was a split task between the ultimate source
2121+-- of all posts (timelines) and the types table. In this variant I had an opportunity to create some order.
2222+-- The timelines table is still the table we look up most posts in... BUT: the items table now carries the items themselves.
2323+CREATE TABLE IF NOT EXISTS items (
2424+ id TEXT PRIMARY KEY NOT NULL CHECK(length(id) = 32),
2525+ item_type TEXT CHECK(item_type IN ('text', 'media', 'article')), -- DM's and such will be added later, I don't have reference types for those yet.
2626+ author_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
2727+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
2828+);
2929+3030+-- Timelines:
3131+CREATE TABLE IF NOT EXISTS timelines (
3232+ tlid TEXT NOT NULL CHECK(length(tlid) = 32),
3333+ item_id TEXT NOT NULL REFERENCES items (id) ON DELETE CASCADE,
3434+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
3535+ PRIMARY KEY (tlid, item_id)
3636+);
3737+3838+-- Sessions:
3939+CREATE TABLE IF NOT EXISTS sessions (
4040+ id TEXT PRIMARY KEY NOT NULL CHECK(length(id) = 32),
4141+ user_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
4242+ session_key TEXT NOT NULL UNIQUE,
4343+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
4444+);
4545+4646+-- Item Content
4747+CREATE TABLE IF NOT EXISTS post_text (
4848+ id TEXT PRIMARY KEY REFERENCES items (id) ON DELETE CASCADE,
4949+ content TEXT NOT NULL,
5050+ foreign_instance_id TEXT,
5151+ foreign_post_id TEXT
5252+);
5353+5454+CREATE TABLE IF NOT EXISTS post_media (
5555+ id TEXT PRIMARY KEY REFERENCES items (id) ON DELETE CASCADE,
5656+ upload_id TEXT NOT NULL,
5757+ caption TEXT,
5858+ foreign_instance_id TEXT,
5959+ foreign_post_id TEXT
6060+);
6161+6262+CREATE TABLE IF NOT EXISTS post_article (
6363+ id TEXT PRIMARY KEY REFERENCES items (id) ON DELETE CASCADE,
6464+ title TEXT NOT NULL,
6565+ content TEXT NOT NULL,
6666+ foreign_instance_id TEXT,
6767+ foreign_post_id TEXT
6868+);
6969+7070+-- Indices for performance
7171+CREATE INDEX IF NOT EXISTS idx_items_author ON items(author_id);
7272+CREATE INDEX IF NOT EXISTS idx_timelines_ts ON timelines(timestamp);
7373+7474+-- migrate:down
7575+7676+DROP INDEX IF EXISTS idx_timelines_ts;
7777+DROP INDEX IF EXISTS idx_items_author;
7878+DROP TABLE IF EXISTS post_article;
7979+DROP TABLE IF EXISTS post_media;
8080+DROP TABLE IF EXISTS post_text;
8181+DROP TABLE IF EXISTS sessions;
8282+DROP TABLE IF EXISTS timelines;
8383+DROP TABLE IF EXISTS items;
8484+DROP TABLE IF EXISTS users;
8585+DROP TABLE IF EXISTS logs;
+58
db/schema.sql
···11+CREATE TABLE IF NOT EXISTS "schema_migrations" (version varchar(128) primary key);
22+CREATE TABLE logs (
33+ id INTEGER PRIMARY KEY AUTOINCREMENT,
44+ level TEXT CHECK(level IN ('INFO', 'WARN', 'ERROR', 'DEBUG')),
55+ message TEXT NOT NULL,
66+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
77+);
88+CREATE TABLE users (
99+ id TEXT PRIMARY KEY NOT NULL CHECK(length(id) = 32),
1010+ foreign_instance_id TEXT,
1111+ foreign_user_id TEXT,
1212+ email TEXT NOT NULL UNIQUE,
1313+ username TEXT NOT NULL UNIQUE,
1414+ password TEXT NOT NULL
1515+);
1616+CREATE TABLE items (
1717+ id TEXT PRIMARY KEY NOT NULL CHECK(length(id) = 32),
1818+ item_type TEXT CHECK(item_type IN ('text', 'media', 'article')), -- DM's and such will be added later, I don't have reference types for those yet.
1919+ author_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
2020+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
2121+);
2222+CREATE TABLE timelines (
2323+ tlid TEXT NOT NULL CHECK(length(tlid) = 32),
2424+ item_id TEXT NOT NULL REFERENCES items (id) ON DELETE CASCADE,
2525+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
2626+ PRIMARY KEY (tlid, item_id)
2727+);
2828+CREATE TABLE sessions (
2929+ id TEXT PRIMARY KEY NOT NULL CHECK(length(id) = 32),
3030+ user_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
3131+ session_key TEXT NOT NULL UNIQUE,
3232+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
3333+);
3434+CREATE TABLE post_text (
3535+ id TEXT PRIMARY KEY REFERENCES items (id) ON DELETE CASCADE,
3636+ content TEXT NOT NULL,
3737+ foreign_instance_id TEXT,
3838+ foreign_post_id TEXT
3939+);
4040+CREATE TABLE post_media (
4141+ id TEXT PRIMARY KEY REFERENCES items (id) ON DELETE CASCADE,
4242+ upload_id TEXT NOT NULL,
4343+ caption TEXT,
4444+ foreign_instance_id TEXT,
4545+ foreign_post_id TEXT
4646+);
4747+CREATE TABLE post_article (
4848+ id TEXT PRIMARY KEY REFERENCES items (id) ON DELETE CASCADE,
4949+ title TEXT NOT NULL,
5050+ content TEXT NOT NULL,
5151+ foreign_instance_id TEXT,
5252+ foreign_post_id TEXT
5353+);
5454+CREATE INDEX idx_items_author ON items(author_id);
5555+CREATE INDEX idx_timelines_ts ON timelines(timestamp);
5656+-- Dbmate schema migrations
5757+INSERT INTO "schema_migrations" (version) VALUES
5858+ ('20260403165425');
-42
env.Dockerfile
···11-# syntax=docker/dockerfile:1
22-33-FROM alpine:3.19
44-55-ENV MISE_DATA_DIR="/mise"
66-ENV MISE_CONFIG_DIR="/mise"
77-ENV MISE_CACHE_DIR="/mise/cache"
88-ENV MISE_INSTALL_PATH="/usr/local/bin/mise"
99-ENV BUN_INSTALL="/usr/local/bin/bun"
1010-ENV PATH="/usr/local/bin/bun/bin:/mise/shims:$PATH"
1111-1212-RUN apk add --no-cache curl git unzip build-base bash
1313-1414-# Install bun outside of mise because Alpine uses musl libc which the mise bun package does not support
1515-RUN curl -fsSL https://bun.sh/install | bash
1616-RUN curl https://mise.run | sh
1717-1818-WORKDIR /build
1919-# Copy and install the mise.toml file first to leverage Docker cache
2020-COPY mise.toml ./mise.toml
2121-COPY mise/ ./mise/
2222-RUN mise trust && mise unuse bun && mise install
2323-2424-# ------- Prefetch Rust dependencies -------
2525-2626-# Copy the manifests cargo needs to prefetch dependencies
2727-COPY Cargo.toml Cargo.lock ./
2828-RUN mkdir server
2929-COPY server/Cargo.toml ./server/
3030-# Prefetch dependencies
3131-RUN cargo fetch --locked
3232-3333-# ------- Prefetch Bun dependencies -------
3434-3535-RUN mkdir client
3636-COPY client/package.json client/bun.lock ./client/
3737-RUN mise run bun-install --locked
3838-3939-# ------- Prefetch Gleam dependencies -------
4040-4141-COPY client/gleam.toml client/manifest.toml ./client/
4242-RUN mise run prefetch-gleam-deps
···3131 "run"
3232 ];
3333 WorkingDir = "/data";
3434- # Env should be at the same level as Cmd
3534 Env = [
3636- "PATH=${pkgs.jre21_minimal}/bin:${pkgs.rcon-cli}/bin:/usr/bin:/bin"
3535+ "PATH=/usr/bin:/bin"
3736 "PORT=3000"
3837 ];
3938 };
···5150 packages.container = myImage;
52515352 devShells.default = pkgs.mkShell {
5353+ shellHook = ''
5454+ bun install --cwd=client/ --silent --only-missing
5555+ echo "❄️ dev environment loaded, use 'just dev' next, or use 'just --list' for recipies."
5656+ '';
5457 buildInputs = with pkgs; [
5558 # Gleam application
5659 gleam
···5962 bun
6063 tailwindcss_4
6164 # Task runner
6262- watchexec
6363- just
6464- # Build image and run development pg using:
6565+ watchexec
6666+ just
6767+ # Migrations
6868+ dbmate
6969+ sqlite
7070+ # Containerisation
6571 podman
6672 ];
6773 };
-78
migrations/0001_luminadb.sql
···11--- Create logs table
22-CREATE TABLE IF NOT EXISTS logs
33-(
44- type VARCHAR NOT NULL,
55- message TEXT NOT NULL,
66- timestamp TIMESTAMP NOT NULL
77-);
88-99--- Create users table
1010-CREATE TABLE IF NOT EXISTS users
1111-(
1212- id UUID DEFAULT gen_random_uuid() UNIQUE PRIMARY KEY,
1313- foreign_instance_id VARCHAR,
1414- foreign_user_id UUID,
1515- email VARCHAR NOT NULL UNIQUE,
1616- username VARCHAR NOT NULL UNIQUE,
1717- password VARCHAR NOT NULL
1818-);
1919-2020--- Create timelines table
2121-CREATE TABLE IF NOT EXISTS timelines
2222-(
2323- tlid UUID NOT NULL,
2424- item_id UUID NOT NULL,
2525- timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
2626- PRIMARY KEY (tlid, item_id)
2727-);
2828-2929--- Create item type lookup table
3030-CREATE TABLE IF NOT EXISTS itemtypelookupdb
3131-(
3232- itemtype VARCHAR NOT NULL,
3333- item_id UUID NOT NULL PRIMARY KEY
3434-);
3535-3636--- Create sesions table
3737-CREATE TABLE IF NOT EXISTS sessions
3838-(
3939- id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
4040- user_id UUID NOT NULL REFERENCES users (id) ON DELETE CASCADE,
4141- session_key VARCHAR NOT NULL UNIQUE,
4242- created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
4343-);
4444-4545--- Create table for posts in text type
4646-CREATE TABLE IF NOT EXISTS post_text
4747-(
4848- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
4949- author_id UUID REFERENCES users (id),
5050- content TEXT NOT NULL,
5151- created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
5252- foreign_instance_id VARCHAR,
5353- foreign_post_id VARCHAR
5454-);
5555-5656--- Create table for posts of media type
5757-CREATE TABLE IF NOT EXISTS post_media
5858-(
5959- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
6060- author_id UUID REFERENCES users (id),
6161- minio_object_id VARCHAR NOT NULL,
6262- caption TEXT,
6363- created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
6464- foreign_instance_id VARCHAR,
6565- foreign_post_id VARCHAR
6666-);
6767-6868--- Create table for posts of article type
6969-CREATE TABLE IF NOT EXISTS post_article
7070-(
7171- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
7272- author_id UUID REFERENCES users (id),
7373- title VARCHAR NOT NULL,
7474- content TEXT NOT NULL,
7575- created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
7676- foreign_instance_id VARCHAR,
7777- foreign_post_id VARCHAR
7878-);