pref-matrix

Web interface to coordinate preferences
git clone https://git.instinctive.eu/pref-matrix.git
Log | Files | Refs | README | LICENSE

commit 8cafa2cf61e4fdbcce85282b907a6c0cba54fdf5
parent 97fc8eaf8ef4d01045b0a177c78e27b2705edc95
Author: Natasha Kerensikova <natgh@instinctive.eu>
Date:   Mon, 22 Jan 2024 19:04:43 +0000

Topic support
Diffstat:
Msrc/pref-matrix.scm | 175++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mtest/run.sh | 35+++++++++++++++++------------------
Mtest/test-1.scm | 24+++++++++++++-----------
Mtest/test-2-dump.sql | 3++-
Mtest/test-2.scm | 2++
5 files changed, 153 insertions(+), 86 deletions(-)

diff --git a/src/pref-matrix.scm b/src/pref-matrix.scm @@ -201,9 +201,6 @@ (assert (= 2 (db-version))) -(exec (sql/transient db - "INSERT OR IGNORE INTO topic(id,name) VALUES (1,'TODO');")) - ;;;;;;;;;;;;;;;;;;; ;; Database Query @@ -213,27 +210,56 @@ key))) (if result result default-value))) -(define (object-id name) - (query fetch-value (sql db "SELECT id FROM object WHERE name=?;") name)) +(define (topic-file-name tid) + (query fetch-value (sql db "SELECT name FROM topic WHERE id=?;") tid)) + +(define (topic-id name) + (query fetch-value (sql db "SELECT id FROM topic WHERE name=?;") name)) + +(define (writable-topic-id name) + (let* ((resolved-name (if name name (get-config "default_topic" #f))) + (row (if resolved-name + (query fetch-row + (sql db "SELECT id,closed FROM topic WHERE name=?;") + resolved-name) + '()))) + (if (and (not (null? row)) (zero? (cadr row))) + (car row) + #f))) + +(define (topic-id-list) + (query (map-rows car) (sql db "SELECT id FROM topic;"))) -(define (object-list) - (query (map-rows car) (sql db "SELECT name FROM object ORDER BY name;"))) +(define (object-id tid name) + (query fetch-value + (sql db "SELECT id FROM object WHERE topic_id=? AND name=?;") + tid + name)) -(define (subject-id name) - (query fetch-value (sql db "SELECT id FROM subject WHERE name=?;") name)) +(define (object-list tid) + (query (map-rows car) + (sql db "SELECT name FROM object WHERE topic_id=? ORDER BY name;") + tid)) -(define (subject-list) - (query (map-rows car) (sql db "SELECT name FROM subject ORDER BY name;"))) +(define (subject-id tid name) + (query fetch-value + (sql db "SELECT id FROM subject WHERE topic_id=? AND name=?;") + tid + name)) -(define (subject-pref name start limit) +(define (subject-list tid) + (query (map-rows car) + (sql db "SELECT name FROM subject WHERE topic_id=? ORDER BY name;") + tid)) + +(define (subject-pref tid name) (query fetch-rows (sql db "SELECT object.name,val FROM pref OUTER LEFT JOIN object ON object.id = obj_id OUTER LEFT JOIN subject ON subject.id = sub_id - WHERE subject.name=? - ORDER BY object.name - LIMIT ?,?;") - name start limit)) + WHERE subject.topic_id=? AND subject.name=? + ORDER BY object.name") + tid name)) ;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data File Generation @@ -274,10 +300,15 @@ ("\"" . "\\\"") ("\\" . "\\\\"))) +(define valid-file-name-irregex + (sre->irregex '(+ (or alphanumeric "-" "_")))) +(define (valid-file-name? name) + (irregex-match? valid-file-name-irregex name)) + (define (json-escape raw-str) (string-translate* raw-str json-escape-map)) -(define (subject-json name) +(define (subject-json tid name) (string-append "{" (string-intersperse @@ -286,64 +317,80 @@ (json-escape (car row)) "\":" (number->string (cadr row)))) - (subject-pref name 0 -1)) + (subject-pref tid name)) ",") "}")) -(define (all-json) +(define (topic-json tid) (string-append "[[\"" - (string-intersperse (map json-escape (object-list)) "\",\"") + (string-intersperse (map json-escape (object-list tid)) "\",\"") "\"],{" (string-intersperse (map (lambda (name) - (string-append "\"" (json-escape name) "\":" (subject-json name))) - (subject-list)) + (string-append "\"" + (json-escape name) + "\":" + (subject-json tid name))) + (subject-list tid)) ",") "}]")) +(define (generate-topic-json tid) + (let ((name (topic-file-name tid))) + (when (valid-file-name? name) + (with-output-to-file + (string-append + (get-config "json-prefix" "") + name + ".json") + (lambda () (write-string (topic-json tid))))))) + (define (generate-json) - (with-output-to-file - (string-append - (get-config "json-prefix" "") - "all.json") - (lambda () (write-string (all-json))))) + (for-each generate-topic-json (topic-id-list))) ;;;;;;;;;;;;;;;;;;;;; ;; Database Updates -(define-traced (new-object name) - (if (or (zero? (string-length name)) - (query fetch-value - (sql db "SELECT id FROM object WHERE name=?;") - name)) +(define-traced (new-topic name) + (if (or (zero? (string-length name)) (topic-id name)) #f (begin - (exec (sql db "INSERT INTO object(topic_id,name) VALUES (1,?);") name) + (exec (sql db "INSERT INTO topic(name) VALUES (?);") name) (let ((result (last-insert-rowid db))) - (unless replaying? (generate-json)) + (unless replaying? (generate-topic-json result)) result)))) -(define-traced (new-subject name) - (if (or (zero? (string-length name)) - (query fetch-value - (sql db "SELECT id FROM subject WHERE name=?;") - name)) - #f - (begin - (exec (sql db "INSERT INTO subject(topic_id,name) VALUES (1,?);") name) - (let ((result (last-insert-rowid db))) - (unless replaying? (generate-json)) - result)))) +(define-traced (new-object topic name) + (let ((tid (writable-topic-id topic))) + (if (or (not tid) (zero? (string-length name)) (object-id tid name)) + #f + (begin + (exec (sql db "INSERT INTO object(topic_id,name) VALUES (?,?);") + tid name) + (let ((result (last-insert-rowid db))) + (unless replaying? (generate-topic-json tid)) + result))))) + +(define-traced (new-subject topic name) + (let ((tid (writable-topic-id topic))) + (if (or (not tid) (zero? (string-length name)) (subject-id tid name)) + #f + (begin + (exec (sql db "INSERT INTO subject(topic_id,name) VALUES (?,?);") + tid name) + (let ((result (last-insert-rowid db))) + (unless replaying? (generate-topic-json tid)) + result))))) (define-half-traced (set-config key val) (exec (sql db "INSERT OR REPLACE INTO config(key,val) VALUES (?,?);") key val)) -(define (set-pref sub-id object-name value) - (let ((obj-id (object-id object-name))) +(define (set-pref tid sub-id object-name value) + (let ((obj-id (object-id tid object-name))) (if obj-id (begin (exec (sql db "INSERT OR REPLACE INTO pref(sub_id,obj_id,val) @@ -354,15 +401,16 @@ (last-insert-rowid db)) #f))) -(define-traced (set-subject-pref subject-name alist) - (let ((sub-id (subject-id subject-name))) +(define-traced (set-subject-pref topic-name subject-name alist) + (let* ((tid (writable-topic-id topic-name)) + (sub-id (if tid (subject-id tid subject-name) #f))) (if sub-id (let ((result (map (lambda (pair) - (set-pref sub-id (car pair) (string->number (cdr pair)))) + (set-pref tid sub-id (car pair) (string->number (cdr pair)))) alist))) - (unless replaying? (generate-json)) + (unless replaying? (generate-topic-json tid)) result) #f))) @@ -404,11 +452,22 @@ (lambda () (cmd-sleep) . body)) cmd-list))))) -(defcmd new-object +(defcmd new-topic (let* ((data (read-urlencoded-request-data (current-request))) (name (alist-ref 'name data eq? #f))) (if name - (let ((result (with-db (new-object name)))) + (let ((result (with-db (new-topic name)))) + (if result + (send-status 'ok) + (send-status 'conflict "Name already exists"))) + (send-status 'bad-request "Missing parameter")))) + +(defcmd new-object + (let* ((data (read-urlencoded-request-data (current-request))) + (topic (alist-ref 'topic data eq? #f)) + (name (alist-ref 'name data eq? #f))) + (if name + (let ((result (with-db (new-object topic name)))) (if result (send-status 'ok) (send-status 'conflict "Name already exists"))) @@ -416,20 +475,24 @@ (defcmd new-subject (let* ((data (read-urlencoded-request-data (current-request))) - (name (alist-ref 'name data eq? #f))) + (topic (alist-ref 'topic data eq? #f)) + (name (alist-ref 'name data eq? #f))) (if name - (let ((result (with-db (new-subject name)))) + (let ((result (with-db (new-subject topic name)))) (if result (send-status 'ok) (send-status 'conflict "Name already exists"))) (send-status 'bad-request "Missing parameter")))) (defcmd set-pref - (let* ((data (read-urlencoded-request-data (current-request)))) + (let* ((all-data (read-urlencoded-request-data (current-request))) + (topic (if (eq? (caar all-data) 'topic) (cdar all-data) #f)) + (data (if topic (cdr all-data) all-data))) (if (eq? (caar data) 'sub) (begin (with-db (set-subject-pref + topic (cdar data) (map (lambda (pair) (cons (symbol->string (car pair)) (cdr pair))) diff --git a/test/run.sh b/test/run.sh @@ -24,7 +24,7 @@ fi : "${TEST_DIR:=$(dirname "$0")}" : "${TMP_DIR:=/tmp}" -TO_CLEAN="test-all.json" +TO_CLEAN="test-default.json" trap 'rm -f ${TO_CLEAN}' EXIT @@ -45,10 +45,10 @@ echo -n "" >|"${TEST_TRACE}" "$@" :memory: "${TEST_TRACE}" "${TEST_DIR}/test-1.scm" sed '/; 2[0-9][0-9][0-9]-/d;$s/$/\n(generate-json)\n(exit)/' "${TEST_TRACE}" \ | diff -u "${TEST_DIR}/test-1.scm" - -diff -u "${TEST_DIR}/test-1.json" test-all.json +diff -u "${TEST_DIR}/test-1.json" test-default.json -################# -## Test 2: HTTP +#################################### +## Test 2: HTTP with default topic "$@" "${TEST_DB}" "${TEST_TRACE}" "${TEST_DIR}/test-2.scm" & SRV_PID=$! @@ -58,35 +58,35 @@ trap 'rm -f ${TO_CLEAN}; kill ${SRV_PID}' EXIT sleep 1 curl -s -d 'name=foo' 'http://localhost:9090/new-subject' >>"${TEST_TRACE}" -diff -u "${TEST_DIR}/test-2-01.json" test-all.json +diff -u "${TEST_DIR}/test-2-01.json" test-default.json curl -s -d 'name=01' 'http://localhost:9090/new-object' >>"${TEST_TRACE}" -diff -u "${TEST_DIR}/test-2-02.json" test-all.json +diff -u "${TEST_DIR}/test-2-02.json" test-default.json curl -s -d 'name=03' 'http://localhost:9090/new-object' >>"${TEST_TRACE}" -diff -u "${TEST_DIR}/test-2-03.json" test-all.json +diff -u "${TEST_DIR}/test-2-03.json" test-default.json curl -s -d 'name=bar' 'http://localhost:9090/new-subject' >>"${TEST_TRACE}" -diff -u "${TEST_DIR}/test-2-04.json" test-all.json +diff -u "${TEST_DIR}/test-2-04.json" test-default.json curl -s -d 'sub=bar' -d '01=3' -d '04=4' \ 'http://localhost:9090/bin/set-pref' >>"${TEST_TRACE}" -diff -u "${TEST_DIR}/test-2-05.json" test-all.json +diff -u "${TEST_DIR}/test-2-05.json" test-default.json curl -s -d 'name=02' 'http://localhost:9090/do/new-object' >>"${TEST_TRACE}" -diff -u "${TEST_DIR}/test-2-06.json" test-all.json +diff -u "${TEST_DIR}/test-2-06.json" test-default.json curl -s -d 'name=bar' 'http://localhost:9090/new-subject' >>"${TEST_TRACE}" -diff -u "${TEST_DIR}/test-2-06.json" test-all.json +diff -u "${TEST_DIR}/test-2-06.json" test-default.json curl -s -d 'name=04' 'http://localhost:9090/new-object' >>"${TEST_TRACE}" -diff -u "${TEST_DIR}/test-2-07.json" test-all.json +diff -u "${TEST_DIR}/test-2-07.json" test-default.json curl -s -d 'sub=meow' -d '04=2' -d '01=4' \ 'http://localhost:9090/set-pref' >>"${TEST_TRACE}" -diff -u "${TEST_DIR}/test-2-07.json" test-all.json +diff -u "${TEST_DIR}/test-2-07.json" test-default.json curl -s -d 'name=meow' 'http://localhost:9090/new-subject' >>"${TEST_TRACE}" -diff -u "${TEST_DIR}/test-2-08.json" test-all.json +diff -u "${TEST_DIR}/test-2-08.json" test-default.json curl -s -d 'sub=foo' -d '01=1' -d '04=2' -d '01=4' \ 'http://localhost:9090/set-pref' >>"${TEST_TRACE}" -diff -u "${TEST_DIR}/test-2-09.json" test-all.json +diff -u "${TEST_DIR}/test-2-09.json" test-default.json curl -s -d 'sub=bar' -d '01=0' \ 'http://localhost:9090/set-pref' >>"${TEST_TRACE}" -diff -u "${TEST_DIR}/test-2-10.json" test-all.json +diff -u "${TEST_DIR}/test-2-10.json" test-default.json curl -s -d 'name=04' 'http://localhost:9090/new-object' >>"${TEST_TRACE}" -diff -u "${TEST_DIR}/test-2-10.json" test-all.json +diff -u "${TEST_DIR}/test-2-10.json" test-default.json kill "${SRV_PID}" trap 'rm -f ${TO_CLEAN}' EXIT @@ -100,5 +100,4 @@ cp -f "${TEST_DIR}/test-2-v1.sqlite" "${TEST_DB}" rm -f "${TEST_DB}-shm" "${TEST_DB}-wal" "$@" "${TEST_DB}" "${TEST_TRACE}" "${TEST_DIR}/test-3.scm" sqlite3 "${TEST_DB}" .dump \ - | sed "/'default_topic'/d;s/'default'/'TODO'/" \ | diff -u "${TEST_DIR}/test-2-dump.sql" - diff --git a/test/test-1.scm b/test/test-1.scm @@ -1,24 +1,26 @@ -(new-subject "foo") +(new-topic "default") ; -> 1 -(new-subject "bar") +(new-subject "default" "foo") +; -> 1 +(new-subject "default" "bar") ; -> 2 -(new-subject "meow") +(new-subject "default" "meow") ; -> 3 -(new-object "01") +(new-object "default" "01") ; -> 1 -(new-object "02") +(new-object "default" "02") ; -> 2 -(new-object "03") +(new-object "default" "03") ; -> 3 -(new-object "04") +(new-object "default" "04") ; -> 4 -(new-object "05") +(new-object "default" "05") ; -> 5 -(set-subject-pref "bar" '(("01" . "4"))) +(set-subject-pref "default" "bar" '(("01" . "4"))) ; -> (1) -(set-subject-pref "foo" '(("01" . "2") ("02" . "4") ("03" . "1") ("04" . "3") ("05" . "5"))) +(set-subject-pref "default" "foo" '(("01" . "2") ("02" . "4") ("03" . "1") ("04" . "3") ("05" . "5"))) ; -> (2 3 4 5 6) -(set-subject-pref "meow" '()) +(set-subject-pref "default" "meow" '()) ; -> () (set-config "json-prefix" "test-") (set-config "server-port" 9090) diff --git a/test/test-2-dump.sql b/test/test-2-dump.sql @@ -3,10 +3,11 @@ BEGIN TRANSACTION; CREATE TABLE config (key TEXT PRIMARY KEY, val ANY); INSERT INTO config VALUES('json-prefix','test-'); INSERT INTO config VALUES('server-port',9090); +INSERT INTO config VALUES('default_topic','default'); CREATE TABLE topic (id INTEGER PRIMARY KEY, name TEXT NOT NULL, closed INTEGER NOT NULL DEFAULT 0); -INSERT INTO topic VALUES(1,'TODO',0); +INSERT INTO topic VALUES(1,'default',0); CREATE TABLE subject (id INTEGER PRIMARY KEY, topic_id NOT NULL REFERENCES topic(id) ON UPDATE CASCADE ON DELETE CASCADE, diff --git a/test/test-2.scm b/test/test-2.scm @@ -1,2 +1,4 @@ (set-config "json-prefix" "test-") (set-config "server-port" 9090) +(set-config "default_topic" "default") +(new-topic "default")