{statusMsg}
-
+ >
);
}
diff --git a/install-eggs-arm.sh b/install-eggs-arm.sh
deleted file mode 100644
index fbd5da6..0000000
--- a/install-eggs-arm.sh
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/bin/sh
-#
-# install-eggs.sh
-#
-# Local installer of CHICKEN eggs required for building.
-#
-# ISC License
-#
-# Copyright 2023 Brmlab, z.s.
-# Dominik Pantůček
-#
-# Permission to use, copy, modify, and/or distribute this software
-# for any purpose with or without fee is hereby granted, provided
-# that the above copyright notice and this permission notice appear
-# in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
-# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
-# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
-# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
-# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
-# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-# Source root directory
-owd=$(pwd)
-cd $(dirname "$0")
-SRCDIR=$(pwd)
-cd "$owd"
-
-# Make temporary prefix directory (eggs shared throwaway files)
-TMPDIR=$(mktemp -d)
-
-# Installs given egg locally
-chicken_install() {
- echo "Installing $1 ..."
- # CHICKEN_INSTALL_PREFIX="$TMPDIR" \
- # CHICKEN_REPOSITORY_PATH="$SRCDIR/eggs-arm":`./cross-chicken-arm/bin/arm-chicken-install -repository` \
- # CHICKEN_INSTALL_REPOSITORY="$SRCDIR/eggs-arm" \
- # ./cross-chicken-arm/bin/arm-chicken-install "$1" 2>&1 | \
- # sed -u 's/^/ /'
-# CHICKEN_INSTALL_PREFIX="$TMPDIR" \
- ./cross-chicken-arm/bin/arm-chicken-install "$1" 2>&1 | \
- sed -u 's/^/ /'
-}
-
-# Removes throwaway files
-chicken_cleanup() {
- echo "Cleaning up ..."
- rm -fr ${TMPDIR}
-}
-
-# Always cleanup
-trap chicken_cleanup INT QUIT
-
-# Install required eggs
-chicken_install spiffy
-chicken_install openssl
-chicken_install postgresql
-
-# Normal termination cleanup
-chicken_cleanup
diff --git a/install-eggs.sh b/install-eggs.sh
index 2bcb95c..08c098c 100644
--- a/install-eggs.sh
+++ b/install-eggs.sh
@@ -57,6 +57,7 @@ chicken_install openssl
chicken_install spiffy
chicken_install postgresql
chicken_install json
+chicken_install posix-groups
# Normal termination cleanup
chicken_cleanup
diff --git a/schema/0000-init.sql b/schema/0000-init.sql
new file mode 100644
index 0000000..52afd7e
--- /dev/null
+++ b/schema/0000-init.sql
@@ -0,0 +1,313 @@
+--
+-- 0000-init.sql
+--
+-- Initial SQL schema construction as of 2025-04-20 (or so)
+--
+-- ISC License
+--
+-- Copyright 2023-2025 Brmlab, z.s.
+-- Dominik Pantůček
+--
+-- Permission to use, copy, modify, and/or distribute this software
+-- for any purpose with or without fee is hereby granted, provided
+-- that the above copyright notice and this permission notice appear
+-- in all copies.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+-- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+-- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+-- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+-- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+-- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+--
+
+-- To require fully-qualified names
+SELECT pg_catalog.set_config('search_path', '', false);
+
+-- Privileged schema with protected data
+CREATE SCHEMA IF NOT EXISTS brmbar_privileged;
+
+-- Initial versioning
+CREATE TABLE IF NOT EXISTS brmbar_privileged.brmbar_schema(
+ ver INTEGER NOT NULL
+);
+
+-- ----------------------------------------------------------------
+-- Legacy Schema Initialization
+-- ----------------------------------------------------------------
+
+DO $$
+DECLARE v INTEGER;
+BEGIN
+ SELECT ver FROM brmbar_privileged.brmbar_schema INTO v;
+ IF v IS NULL THEN
+ -- --------------------------------
+ -- Legacy Types
+
+ SELECT COUNT(*) INTO v
+ FROM pg_catalog.pg_type typ
+ INNER JOIN pg_catalog.pg_namespace nsp
+ ON nsp.oid = typ.typnamespace
+ WHERE nsp.nspname = 'public'
+ AND typ.typname='exchange_rate_direction';
+ IF v=0 THEN
+ RAISE NOTICE 'Creating type exchange_rate_direction';
+ CREATE TYPE public.exchange_rate_direction
+ AS ENUM ('source_to_target', 'target_to_source');
+ ELSE
+ RAISE NOTICE 'Type exchange_rate_direction already exists';
+ END IF;
+
+ SELECT COUNT(*) INTO v
+ FROM pg_catalog.pg_type typ
+ INNER JOIN pg_catalog.pg_namespace nsp
+ ON nsp.oid = typ.typnamespace
+ WHERE nsp.nspname = 'public'
+ AND typ.typname='account_type';
+ IF v=0 THEN
+ RAISE NOTICE 'Creating type account_type';
+ CREATE TYPE public.account_type
+ AS ENUM ('cash', 'debt', 'inventory', 'income', 'expense',
+ 'starting_balance', 'ending_balance');
+ ELSE
+ RAISE NOTICE 'Type account_type already exists';
+ END IF;
+
+ SELECT COUNT(*) INTO v
+ FROM pg_catalog.pg_type typ
+ INNER JOIN pg_catalog.pg_namespace nsp
+ ON nsp.oid = typ.typnamespace
+ WHERE nsp.nspname = 'public'
+ AND typ.typname='transaction_split_side';
+ IF v=0 THEN
+ RAISE NOTICE 'Creating type transaction_split_side';
+ CREATE TYPE public.transaction_split_side
+ AS ENUM ('credit', 'debit');
+ ELSE
+ RAISE NOTICE 'Type transaction_split_side already exists';
+ END IF;
+
+ -- --------------------------------
+ -- Currencies sequence, table and potential initial data
+
+ CREATE SEQUENCE IF NOT EXISTS public.currencies_id_seq
+ START WITH 2 INCREMENT BY 1;
+ CREATE TABLE IF NOT EXISTS public.currencies (
+ id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.currencies_id_seq'::regclass),
+ name VARCHAR(128) NOT NULL,
+ UNIQUE(name)
+ );
+ INSERT INTO public.currencies (id, name) VALUES (1, 'Kč')
+ ON CONFLICT DO NOTHING;
+
+ -- --------------------------------
+ -- Exchange rates table - no initial data required
+
+ CREATE TABLE IF NOT EXISTS public.exchange_rates (
+ valid_since TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL,
+
+ target INTEGER NOT NULL,
+ FOREIGN KEY (target) REFERENCES public.currencies (id),
+
+ source INTEGER NOT NULL,
+ FOREIGN KEY (source) REFERENCES public.currencies (id),
+
+ rate DECIMAL(12,2) NOT NULL,
+ rate_dir public.exchange_rate_direction NOT NULL
+ );
+
+ -- --------------------------------
+ -- Accounts sequence and table and 4 initial accounts
+
+ CREATE SEQUENCE IF NOT EXISTS public.accounts_id_seq
+ START WITH 2 INCREMENT BY 1;
+ CREATE TABLE IF NOT EXISTS public.accounts (
+ id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.accounts_id_seq'::regclass),
+
+ name VARCHAR(128) NOT NULL,
+ UNIQUE (name),
+
+ currency INTEGER NOT NULL,
+ FOREIGN KEY (currency) REFERENCES public.currencies (id),
+
+ acctype public.account_type NOT NULL,
+
+ active BOOLEAN NOT NULL DEFAULT TRUE
+ );
+ INSERT INTO public.accounts (id, name, currency, acctype)
+ VALUES (1, 'BrmBar Cash', (SELECT id FROM public.currencies WHERE name='Kč'), 'cash')
+ ON CONFLICT DO NOTHING;
+ INSERT INTO public.accounts (name, currency, acctype)
+ VALUES ('BrmBar Profits', (SELECT id FROM public.currencies WHERE name='Kč'), 'income')
+ ON CONFLICT DO NOTHING;
+ INSERT INTO public.accounts (name, currency, acctype)
+ VALUES ('BrmBar Excess', (SELECT id FROM public.currencies WHERE name='Kč'), 'income')
+ ON CONFLICT DO NOTHING;
+ INSERT INTO public.accounts (name, currency, acctype)
+ VALUES ('BrmBar Deficit', (SELECT id FROM public.currencies WHERE name='Kč'), 'expense')
+ ON CONFLICT DO NOTHING;
+
+ -- --------------------------------
+ -- Barcodes
+
+ CREATE TABLE IF NOT EXISTS public.barcodes (
+ barcode VARCHAR(128) PRIMARY KEY NOT NULL,
+
+ account INTEGER NOT NULL,
+ FOREIGN KEY (account) REFERENCES public.accounts (id)
+ );
+ INSERT INTO public.barcodes (barcode, account)
+ VALUES ('_cash_', (SELECT id FROM public.accounts WHERE acctype = 'cash'))
+ ON CONFLICT DO NOTHING;
+
+ -- --------------------------------
+ -- Transactions
+
+ CREATE SEQUENCE IF NOT EXISTS public.transactions_id_seq
+ START WITH 1 INCREMENT BY 1;
+ CREATE TABLE IF NOT EXISTS public.transactions (
+ id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.transactions_id_seq'::regclass),
+ time TIMESTAMP DEFAULT NOW() NOT NULL,
+
+ responsible INTEGER,
+ FOREIGN KEY (responsible) REFERENCES public.accounts (id),
+
+ description TEXT
+ );
+
+ -- --------------------------------
+ -- Transaction splits
+
+ CREATE SEQUENCE IF NOT EXISTS public.transaction_splits_id_seq
+ START WITH 1 INCREMENT BY 1;
+ CREATE TABLE IF NOT EXISTS public.transaction_splits (
+ id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('public.transaction_splits_id_seq'::regclass),
+
+ transaction INTEGER NOT NULL,
+ FOREIGN KEY (transaction) REFERENCES public.transactions (id),
+
+ side public.transaction_split_side NOT NULL,
+
+ account INTEGER NOT NULL,
+ FOREIGN KEY (account) REFERENCES public.accounts (id),
+ amount DECIMAL(12,2) NOT NULL,
+
+ memo TEXT
+ );
+
+ -- --------------------------------
+ -- Account balances view
+
+ CREATE OR REPLACE VIEW public.account_balances AS
+ SELECT ts.account AS id,
+ accounts.name,
+ accounts.acctype,
+ - sum(
+ CASE
+ WHEN ts.side = 'credit'::public.transaction_split_side THEN - ts.amount
+ ELSE ts.amount
+ END) AS crbalance
+ FROM public.transaction_splits ts
+ LEFT JOIN public.accounts ON accounts.id = ts.account
+ GROUP BY ts.account, accounts.name, accounts.acctype
+ ORDER BY (- sum(
+ CASE
+ WHEN ts.side = 'credit'::public.transaction_split_side THEN - ts.amount
+ ELSE ts.amount
+ END));
+
+ -- --------------------------------
+ -- Transaction nice splits view
+
+ CREATE OR REPLACE VIEW public.transaction_nicesplits AS
+ SELECT ts.id,
+ ts.transaction,
+ ts.account,
+ CASE
+ WHEN ts.side = 'credit'::public.transaction_split_side THEN - ts.amount
+ ELSE ts.amount
+ END AS amount,
+ a.currency,
+ ts.memo
+ FROM public.transaction_splits ts
+ LEFT JOIN public.accounts a ON a.id = ts.account
+ ORDER BY ts.id;
+
+ -- --------------------------------
+ -- Transaction cash sums view
+
+ CREATE OR REPLACE VIEW public.transaction_cashsums AS
+ SELECT t.id,
+ t."time",
+ sum(credit.credit_cash) AS cash_credit,
+ sum(debit.debit_cash) AS cash_debit,
+ a.name AS responsible,
+ t.description
+ FROM public.transactions t
+ LEFT JOIN ( SELECT cts.amount AS credit_cash,
+ cts.transaction AS cts_t
+ FROM public.transaction_nicesplits cts
+ LEFT JOIN public.accounts a_1 ON a_1.id = cts.account OR a_1.id = cts.account
+ WHERE a_1.currency = (( SELECT accounts.currency
+ FROM public.accounts
+ WHERE accounts.name::text = 'BrmBar Cash'::text))
+ AND (a_1.acctype = ANY (ARRAY['cash'::public.account_type, 'debt'::public.account_type]))
+ AND cts.amount < 0::numeric) credit ON credit.cts_t = t.id
+ LEFT JOIN ( SELECT dts.amount AS debit_cash,
+ dts.transaction AS dts_t
+ FROM public.transaction_nicesplits dts
+ LEFT JOIN public.accounts a_1 ON a_1.id = dts.account OR a_1.id = dts.account
+ WHERE a_1.currency = (( SELECT accounts.currency
+ FROM public.accounts
+ WHERE accounts.name::text = 'BrmBar Cash'::text))
+ AND (a_1.acctype = ANY (ARRAY['cash'::public.account_type, 'debt'::public.account_type]))
+ AND dts.amount > 0::numeric) debit ON debit.dts_t = t.id
+ LEFT JOIN public.accounts a ON a.id = t.responsible
+ GROUP BY t.id, a.name
+ ORDER BY t.id DESC;
+
+ -- --------------------------------
+ -- Function to check schema version (used in migrations)
+
+ CREATE OR REPLACE FUNCTION brmbar_privileged.has_exact_schema_version(
+ IN i_ver INTEGER
+ ) RETURNS INTEGER
+ VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $x$
+ DECLARE
+ v_ver INTEGER;
+ BEGIN
+ SELECT ver INTO v_ver FROM brmbar_privileged.brmbar_schema;
+ IF v_ver is NULL THEN
+ RETURN false;
+ ELSE
+ RETURN v_ver = i_ver;
+ END IF;
+ END;
+ $x$;
+
+ -- --------------------------------
+ --
+
+ CREATE OR REPLACE FUNCTION brmbar_privileged.upgrade_schema_version_to(
+ IN i_ver INTEGER
+ ) RETURNS INTEGER
+ VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $x$
+ DECLARE
+ v_ver INTEGER;
+ BEGIN
+ SELECT ver FROM brmbar_privileged.brmbar_schema INTO v_ver;
+ IF v_ver=(i_ver-1) THEN
+ UPDATE brmbar_privileged.brmbar_schema SET ver = i_ver;
+ ELSE
+ RAISE EXCEPTION 'Invalid brmbar schema version transition (% -> %)', v_ver, i_ver;
+ END IF;
+ END;
+ $x$;
+
+ -- Initialize version 1
+ INSERT INTO brmbar_privileged.brmbar_schema(ver) VALUES(1);
+ END IF;
+END;
+$$;
diff --git a/tools/build-in-qemu.sh b/tools/build-in-qemu.sh
index a14913f..ebd2093 100644
--- a/tools/build-in-qemu.sh
+++ b/tools/build-in-qemu.sh
@@ -1,4 +1,28 @@
#!/bin/sh
+#
+# build-in-qemu.sh
+#
+# Expects running armhf qemu system, builds the binary inside.
+#
+# ISC License
+#
+# Copyright 2025 Brmlab, z.s.
+# Dominik Pantůček
+#
+# Permission to use, copy, modify, and/or distribute this software
+# for any purpose with or without fee is hereby granted, provided
+# that the above copyright notice and this permission notice appear
+# in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
if [ -z "$1" ] ; then
echo "Usage: $0 password"
diff --git a/tools/run-build-qemu-system.sh b/tools/run-build-qemu-system.sh
new file mode 100644
index 0000000..c76d8b8
--- /dev/null
+++ b/tools/run-build-qemu-system.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+#
+# run-build-qemu-system.sh
+#
+# Runs the emulated armhf system for building the application.
+#
+# ISC License
+#
+# Copyright 2025 Brmlab, z.s.
+# Dominik Pantůček
+#
+# Permission to use, copy, modify, and/or distribute this software
+# for any purpose with or without fee is hereby granted, provided
+# that the above copyright notice and this permission notice appear
+# in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+qemu-system-armhf \
+ -machine raspi2b \
+ -nographic \
+ -dtb bcm2710-rpi-3-b-plus.dtb \
+ -m 1G \
+ -smp 4 \
+ -kernel kernel7.img \
+ -sd 2019-04-08-raspbian-stretch.img \
+ -append "rw earlyprintk loglevel=8 console=ttyAMA0,115200 root=/dev/mmcblk0p2 rootdelay=1 dwc_otg.fiq_fsm_enable=0" \
+ -usb \
+ -device usb-net,netdev=net0 \
+ -netdev user,id=net0,hostfwd=tcp::2222-:22