From d285587754302f5913408f60f3224b354df422d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Pant=C5=AF=C4=8Dek?= Date: Sun, 13 Apr 2025 20:50:47 +0200 Subject: [PATCH 01/10] Add more qemu tooling scripts. --- tools/build-in-qemu.sh | 24 +++++++++++++++++++++ tools/run-build-qemu-system.sh | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 tools/run-build-qemu-system.sh 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 From b65740e21438bda18dd5a2e5d4f64c696f937f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Pant=C5=AF=C4=8Dek?= Date: Sun, 13 Apr 2025 20:51:02 +0200 Subject: [PATCH 02/10] Add the org file with plan. --- 0PLAN.org | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 0PLAN.org diff --git a/0PLAN.org b/0PLAN.org new file mode 100644 index 0000000..befe26b --- /dev/null +++ b/0PLAN.org @@ -0,0 +1,89 @@ +#+title: Frontend + +* Plan + +** 0.2 + +- [ ] use localStorage to store user + - [ ] add barcode scanner to get the user + - [ ] allow editing + - [ ] add UserSelect component +- [X] integrate script for starting build qemu system +- [X] add org file to the repository after cleanup + +** 0.3 + +- [ ] versioned schema support + - [ ] barschema table + - [ ] function to check + - [ ] function to set + - [ ] initial creation of tables at version 1 assuming OK if tables exist + +** 0.4 + +- [ ] add inventory check table +- [ ] add inventory item check table +- [ ] create new check +- [ ] check item +- [ ] close check +- [ ] inventory check component + +** Later + +- [ ] add static token support + - [ ] hardwired token for the React.JS app? +- [ ] authentication + - [ ] login/logout + - [ ] allow API calls only with login token + +* Finished + +** 0.1 + +- [X] statically compiled binary HTTP server +- [X] include directory tree contents +- [X] handle frontend with mime type +- [X] move app to / again +- [X] load certificates +- [X] deployment preparations + - [X] crosscompilation - no, compile in qemu or on separate host + - [X] connect to postgres + - [X] use qr-scanner - no, qr-barcode-scanner is needed + - [X] rsync + build in qemu or host without react (filters or something) +- [X] API infrastructure + - [X] handle API calls + - [X] API registry syntax + - [X] lookup barcode in DB + - [X] separate module brmbar-data for queries + - [X] separate module api-servlets + +* Qemu + +#+BEGIN_SRC +wget img zip # 2019-04-08-raspbian-stretch.zip +unzip 2019-04-08-raspbian-stretch.zip +sudo mkdir /mnt/image +sudo mount -o loop,offset=4194304 2019-04-08-raspbian-stretch.img /mnt/image/ +# In qemu-system-armhf terminal! +cp kernel, DT +umount /mnt/image +resize img +#+END_SRC + +sources.list - archive debian (remove rpi), archive raspberry +https://archive.raspberrypi.org/debian/ +https://archive.debian.org/debian/ +apt-get --no + +* React - Vite + +#+BEGIN_SRC sh + npm create vite@latest + # "frontend" + # React + # Javascript + cd frontend + npm install + npm run dev + npm run build +#+END_SRC From 0677419d40f90eadee40b2acb5b6a4242a2480a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Pant=C5=AF=C4=8Dek?= Date: Sun, 13 Apr 2025 21:16:45 +0200 Subject: [PATCH 03/10] Start work on general workflow. --- frontend/src/App.jsx | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 2f4a741..ce437fb 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -8,6 +8,38 @@ import './App.css'; import 'bootstrap/dist/css/bootstrap.min.css'; function App() { + // Ensure we have persistent (informal) user information + const [user, setUser] = useState(""); + useEffect(() => { + localStorage.setItem('user', user); + }, [user]); + useEffect(() => { + const user = localStorage.getItem('user'); + if (user) { + setUser(user); + } + }, []); + + // If no user, must be scanned/set otherwise + return ( + + {user === "" ? + + : + } + + ); +} + +function NoUserView({setUser}) { + return

No user!

; +} + +function UserView({user}) { + return

User={user}

; +} + +function BarItemScanner() { const [reqAccount, setReqAccount] = useState(''); const [actAccount, setActAccount] = useState(''); const [balance, setBalance] = useState(-1); From a0752bb73a72f3e0d12495fb1ad663264ee3cfe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Pant=C5=AF=C4=8Dek?= Date: Sun, 13 Apr 2025 21:20:59 +0200 Subject: [PATCH 04/10] Ensure it compiles with posix-groups egg as well. --- install-eggs-arm.sh | 64 --------------------------------------------- install-eggs.sh | 1 + 2 files changed, 1 insertion(+), 64 deletions(-) delete mode 100644 install-eggs-arm.sh 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 From 1815547daf2ab260ca25ef5d46a6f65048ecfa7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Pant=C5=AF=C4=8Dek?= Date: Mon, 14 Apr 2025 12:16:04 +0200 Subject: [PATCH 05/10] Work on user selection. --- 0PLAN.org | 6 ++--- backend/brminv.scm | 6 ++++- frontend/src/App.jsx | 61 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 58 insertions(+), 15 deletions(-) diff --git a/0PLAN.org b/0PLAN.org index befe26b..d985997 100644 --- a/0PLAN.org +++ b/0PLAN.org @@ -4,10 +4,10 @@ ** 0.2 -- [ ] use localStorage to store user +- [-] use localStorage to store user - [ ] add barcode scanner to get the user - - [ ] allow editing - - [ ] add UserSelect component + - [X] allow editing + - [X] add UserSelect component - [X] integrate script for starting build qemu system - [X] add org file to the repository after cleanup diff --git a/backend/brminv.scm b/backend/brminv.scm index 808bf70..48c5c28 100644 --- a/backend/brminv.scm +++ b/backend/brminv.scm @@ -47,6 +47,7 @@ (define -db-user- (make-parameter #f)) (define -db-name- (make-parameter #f)) (define -db-pass- (make-parameter #f)) +(define -db-enabled- (make-parameter #t)) (command-line print-help @@ -82,6 +83,8 @@ (-db-user- dbuser)) (-dp (dbpass) "Database password" (-db-pass- dbpass)) + (-dd () "Disable database" + (-db-enabled- #f)) ) (define ssl? (and (-certificate-) (-key-) #t)) @@ -120,7 +123,8 @@ (print "current user id: " (current-user-id)) (print "current effective user id: " (current-effective-user-id)) -(bar-db-init! (-db-name-) (-db-host-) (-db-user-) (-db-pass-)) +(when (-db-enabled-) + (bar-db-init! (-db-name-) (-db-host-) (-db-user-) (-db-pass-))) (define (handle-api-calls) (define plst (cdr (uri-path (request-uri (current-request))))) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index ce437fb..2ae663c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,8 +1,12 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import BarcodeScannerComponent from 'react-qr-barcode-scanner'; -import { Container, Row, Col } from 'react-bootstrap'; +import Container from 'react-bootstrap/Container'; +import Row from 'react-bootstrap/Row'; +import Col from 'react-bootstrap/Col'; import Table from 'react-bootstrap/Table'; import Alert from 'react-bootstrap/Alert'; +import Form from 'react-bootstrap/Form'; +import Button from 'react-bootstrap/Button'; import './App.css'; import 'bootstrap/dist/css/bootstrap.min.css'; @@ -10,15 +14,15 @@ import 'bootstrap/dist/css/bootstrap.min.css'; function App() { // Ensure we have persistent (informal) user information const [user, setUser] = useState(""); - useEffect(() => { - localStorage.setItem('user', user); - }, [user]); useEffect(() => { const user = localStorage.getItem('user'); if (user) { setUser(user); } }, []); + useEffect(() => { + localStorage.setItem('user', user); + }, [user]); // If no user, must be scanned/set otherwise return ( @@ -26,17 +30,52 @@ function App() { {user === "" ? : - } + } ); } function NoUserView({setUser}) { - return

No user!

; + const [preUser, setPreUser] = useState(""); + return ( + <> + + + + User: + setPreUser(v.target.value)} /> + Which user is using this App? + + + + + + Scanner + + + + + + + + + ); } -function UserView({user}) { - return

User={user}

; +function UserView({user, setUser}) { + return ( + + + {user} + + + + + + + ); } function BarItemScanner() { @@ -69,7 +108,7 @@ function BarItemScanner() { }; return ( - + <> {statusMsg} - + ); } From 96cadc4f47bf7b38d3b882d5a70218499f434ef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Pant=C5=AF=C4=8Dek?= Date: Mon, 14 Apr 2025 15:34:15 +0200 Subject: [PATCH 06/10] Reasonable anonymous component. --- frontend/src/App.css | 4 ++-- frontend/src/App.jsx | 51 ++++++++++++++++++++++---------------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index 467c548..6638b9a 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,6 +1,6 @@ .App { background-color: var(--background-color); color: var(--primary-text-color) !important; - min-height: 100vh; - width: 100%; + height: 100vh; + width: 10vh; } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 2ae663c..7d6f7f5 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -7,6 +7,7 @@ import Table from 'react-bootstrap/Table'; import Alert from 'react-bootstrap/Alert'; import Form from 'react-bootstrap/Form'; import Button from 'react-bootstrap/Button'; +import Card from 'react-bootstrap/Card'; import './App.css'; import 'bootstrap/dist/css/bootstrap.min.css'; @@ -26,41 +27,41 @@ function App() { // If no user, must be scanned/set otherwise return ( - + <> {user === "" ? : } - + ); } function NoUserView({setUser}) { const [preUser, setPreUser] = useState(""); return ( - <> - - - - User: - setPreUser(v.target.value)} /> - Which user is using this App? - - - - - - Scanner - - - - - - - - + + + BrmInv Anonymous + + User name: + setPreUser(v.target.value)} /> + Enter a user name or scan your barcode. + + + console.log("scanned")} + delay={1500} + /> + + + + + + ); } From 4a2d45824f7bb4e4efc86cbeb03eb9f9fca44d50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Pant=C5=AF=C4=8Dek?= Date: Wed, 16 Apr 2025 20:54:32 +0200 Subject: [PATCH 07/10] Finish 0.2 with barcodescanner for username. --- 0PLAN.org | 4 ++-- backend/texts.scm | 2 +- frontend/src/App.jsx | 5 ++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/0PLAN.org b/0PLAN.org index d985997..aef164a 100644 --- a/0PLAN.org +++ b/0PLAN.org @@ -4,8 +4,8 @@ ** 0.2 -- [-] use localStorage to store user - - [ ] add barcode scanner to get the user +- [X] use localStorage to store user + - [X] add barcode scanner to get the user - [X] allow editing - [X] add UserSelect component - [X] integrate script for starting build qemu system diff --git a/backend/texts.scm b/backend/texts.scm index 643e50e..de6ac11 100644 --- a/backend/texts.scm +++ b/backend/texts.scm @@ -38,7 +38,7 @@ (chicken format)) ;; Short banner - (define banner-line "BrmInv 0.1 (c) 2023-2025 Brmlab, z.s.") + (define banner-line "BrmInv 0.2 (c) 2023-2025 Brmlab, z.s.") ;; The license of this file and of the whole suite. (define license "ISC License diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 7d6f7f5..0f3b48f 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -51,7 +51,10 @@ function NoUserView({setUser}) { console.log("scanned")} + onUpdate={(err, result) => { + if (result) { + setPreUser(result.text); + }}} delay={1500} /> From be455700830578710525245d60961b0915718836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Pant=C5=AF=C4=8Dek?= Date: Sun, 20 Apr 2025 16:08:42 +0200 Subject: [PATCH 08/10] Add handcrafted schema. --- 0PLAN.org | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/0PLAN.org b/0PLAN.org index aef164a..e3c583d 100644 --- a/0PLAN.org +++ b/0PLAN.org @@ -2,15 +2,6 @@ * Plan -** 0.2 - -- [X] use localStorage to store user - - [X] add barcode scanner to get the user - - [X] allow editing - - [X] add UserSelect component -- [X] integrate script for starting build qemu system -- [X] add org file to the repository after cleanup - ** 0.3 - [ ] versioned schema support @@ -18,6 +9,7 @@ - [ ] function to check - [ ] function to set - [ ] initial creation of tables at version 1 assuming OK if tables exist +- [ ] users import from hackerbase ** 0.4 @@ -57,6 +49,15 @@ - [X] separate module brmbar-data for queries - [X] separate module api-servlets +** 0.2 + +- [X] use localStorage to store user + - [X] add barcode scanner to get the user + - [X] allow editing + - [X] add UserSelect component +- [X] integrate script for starting build qemu system +- [X] add org file to the repository after cleanup + * Qemu #+BEGIN_SRC From 4b1fd6568f805540cca5b232202edbb3df738c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Pant=C5=AF=C4=8Dek?= Date: Sun, 20 Apr 2025 16:35:54 +0200 Subject: [PATCH 09/10] Initial schema. --- schema/0000-init.sql | 275 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 schema/0000-init.sql diff --git a/schema/0000-init.sql b/schema/0000-init.sql new file mode 100644 index 0000000..d658398 --- /dev/null +++ b/schema/0000-init.sql @@ -0,0 +1,275 @@ +-- +-- 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 public.transaction_splits_id_seq + START WITH 1 INCREMENT BY 1; + CREATE TABLE 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; + + -- Initialize version 1 + -- INSERT INTO brmbar_privileged.brmbar_schema(ver) VALUES(1); + END IF; +END; +$$; From 0213a8011f323e4ff95de3b07539de1f66be968d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Pant=C5=AF=C4=8Dek?= Date: Sun, 20 Apr 2025 16:53:19 +0200 Subject: [PATCH 10/10] Finish schema version 1. --- schema/0000-init.sql | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/schema/0000-init.sql b/schema/0000-init.sql index d658398..52afd7e 100644 --- a/schema/0000-init.sql +++ b/schema/0000-init.sql @@ -180,9 +180,9 @@ BEGIN -- -------------------------------- -- Transaction splits - CREATE SEQUENCE public.transaction_splits_id_seq + CREATE SEQUENCE IF NOT EXISTS public.transaction_splits_id_seq START WITH 1 INCREMENT BY 1; - CREATE TABLE public.transaction_splits ( + 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, @@ -268,8 +268,46 @@ BEGIN 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); + INSERT INTO brmbar_privileged.brmbar_schema(ver) VALUES(1); END IF; END; $$;