From 75d242c0414d6df56f7d8a60d0f7acd9f4776fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Tue, 26 May 2026 10:09:24 +0200 Subject: [PATCH 1/4] Read config from file in command line arguments --- config.go | 4 +--- main.go | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/config.go b/config.go index 0ba4ea2..71d5f5d 100644 --- a/config.go +++ b/config.go @@ -3,7 +3,6 @@ package main import ( // Standard "encoding/json" - "fmt" "os" ) @@ -27,9 +26,8 @@ type Config struct { } } -func readConfig() (err error) { +func readConfig(fname string) (err error) { var configData []byte - fname := fmt.Sprintf("%s/.config/notes2.json", os.Getenv("HOME")) configData, err = os.ReadFile(fname) if err != nil { return diff --git a/main.go b/main.go index d25f72f..80120a2 100644 --- a/main.go +++ b/main.go @@ -75,7 +75,7 @@ func initLog() { // {{{ } // }}} func main() { // {{{ initLog() - err := readConfig() + err := readConfig(FlagConfig) if err != nil { Log.Error("config", "error", err) os.Exit(1) From 4c513a5106d9ed43a678fa630dcab6c3065d2252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Tue, 26 May 2026 11:02:56 +0200 Subject: [PATCH 2/4] Reset SQL --- sql/00001.sql | 577 ++++++++++++++++++++++++++++++++++++++++++++++---- sql/00002.sql | 19 -- sql/00003.sql | 1 - sql/00004.sql | 2 - sql/00005.sql | 1 - sql/00006.sql | 1 - sql/00007.sql | 162 -------------- sql/00008.sql | 2 - sql/00009.sql | 1 - sql/00010.sql | 10 - sql/00011.sql | 166 --------------- sql/00012.sql | 166 --------------- 12 files changed, 538 insertions(+), 570 deletions(-) delete mode 100644 sql/00002.sql delete mode 100644 sql/00003.sql delete mode 100644 sql/00004.sql delete mode 100644 sql/00005.sql delete mode 100644 sql/00006.sql delete mode 100644 sql/00007.sql delete mode 100644 sql/00008.sql delete mode 100644 sql/00009.sql delete mode 100644 sql/00010.sql delete mode 100644 sql/00011.sql delete mode 100644 sql/00012.sql diff --git a/sql/00001.sql b/sql/00001.sql index 08d5266..7eb8273 100644 --- a/sql/00001.sql +++ b/sql/00001.sql @@ -1,48 +1,218 @@ -CREATE EXTENSION IF NOT EXISTS pg_trgm; -CREATE EXTENSION IF NOT EXISTS pgcrypto; +-- +-- Name: pg_trgm; Type: EXTENSION; Schema: -; Owner: - +-- -CREATE SEQUENCE node_updates; +CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public; -CREATE TABLE public."user" ( - id serial4 NOT NULL, - username varchar NOT NULL, - "name" varchar NOT NULL, - "password" varchar NOT NULL, - last_login timestamp DEFAULT now() NOT NULL, - timezone varchar DEFAULT 'UTC'::character varying NOT NULL, - CONSTRAINT user_pk PRIMARY KEY (id) + +-- +-- Name: EXTENSION pg_trgm; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION pg_trgm IS 'text similarity measurement and index searching based on trigrams'; + + +-- +-- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public; + + +-- +-- Name: EXTENSION pgcrypto; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions'; + + +-- +-- Name: json_ancestor_array; Type: TYPE; Schema: public; Owner: postgres +-- + +CREATE TYPE public.json_ancestor_array AS ( + "Ancestors" character varying[] ); -CREATE TABLE public.node ( - id serial4 NOT NULL, - user_id int4 NOT NULL, - "uuid" bpchar(36) DEFAULT gen_random_uuid() NOT NULL, - parent_uuid bpchar(36) NULL, +-- +-- Name: add_nodes(integer, character varying, jsonb); Type: PROCEDURE; Schema: public; Owner: postgres +-- - created timestamptz DEFAULT NOW() NOT NULL, - updated timestamptz DEFAULT NOW() NOT NULL, - deleted timestamptz NULL, +CREATE PROCEDURE public.add_nodes(IN p_user_id integer, IN p_client_uuid character varying, IN p_nodes jsonb) + LANGUAGE plpgsql + AS $$ - created_seq bigint NOT NULL DEFAULT nextval('node_updates'), - updated_seq bigint NOT NULL DEFAULT nextval('node_updates'), - deleted_seq bigint NULL, +DECLARE + node_data jsonb; + node_updated timestamptz; + db_updated timestamptz; + db_uuid bpchar; + db_client bpchar; + db_client_seq int; + node_uuid bpchar; + parent_uuid_nullable bpchar; - "name" varchar(256) DEFAULT ''::character varying NOT NULL, - "content" text DEFAULT ''::text NOT NULL, - content_encrypted text DEFAULT ''::text NOT NULL, - markdown bool DEFAULT false NOT NULL, +BEGIN + RAISE NOTICE '--------------------------'; + FOR node_data IN SELECT * FROM jsonb_array_elements(p_nodes) + LOOP + node_uuid = (node_data->>'UUID')::bpchar; + node_updated = (node_data->>'Updated')::timestamptz; - CONSTRAINT name_length CHECK ((length(TRIM(BOTH FROM name)) > 0)), - CONSTRAINT node_pk PRIMARY KEY (id), - CONSTRAINT user_fk FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE RESTRICT ON UPDATE RESTRICT -); -CREATE UNIQUE INDEX node_uuid_idx ON public.node USING btree (uuid); -CREATE INDEX node_search_index ON public.node USING gin (name gin_trgm_ops, content gin_trgm_ops); + IF node_data->>'ParentUUID' = '00000000-0000-0000-0000-000000000000' THEN + parent_uuid_nullable = NULL; + ELSE + parent_uuid_nullable = node_data->>'ParentUUID'; + END IF; -CREATE OR REPLACE FUNCTION node_update_timestamp() -RETURNS TRIGGER -LANGUAGE PLPGSQL -AS $$ + /* Retrieve the current modified timestamp for this node from the database. */ + SELECT + uuid, updated, client, client_sequence + INTO + db_uuid, db_updated, db_client, db_client_seq + FROM public."node" + WHERE + user_id = p_user_id AND + uuid = node_uuid; + + /* Is the node not in database? It needs to be created. */ + IF db_uuid IS NULL THEN + RAISE NOTICE '01 New node %', node_uuid; + INSERT INTO public."node" ( + user_id, "uuid", parent_uuid, created, updated, + "name", "content", markdown, "content_encrypted", + client, client_sequence + ) + VALUES( + p_user_id, + node_uuid, + parent_uuid_nullable, + (node_data->>'Created')::timestamptz, + (node_data->>'Updated')::timestamptz, + (node_data->>'Name')::varchar, + (node_data->>'Content')::text, + (node_data->>'Markdown')::bool, + '', /* content_encrypted */ + p_client_uuid, + (node_data->>'ClientSequence')::int + ); + CONTINUE; + END IF; + + + /* The client could send a specific node again if it didn't receive the OK from this procedure before. */ + IF db_updated = node_updated AND db_client = p_client_uuid AND db_client_seq = (node_data->>'ClientSequence')::int THEN + RAISE NOTICE '04, already recorded, %, %', db_client, db_client_seq; + CONTINUE; + END IF; + + /* Determine if the incoming node data is to go into history or replace the current node. */ + IF db_updated > node_updated THEN + RAISE NOTICE '02 DB newer, % > % (%))', db_updated, node_updated, node_uuid; + /* Incoming node is going straight to history since it is older than the current node. */ + INSERT INTO node_history( + user_id, "uuid", parents, created, updated, + "name", "content", markdown, "content_encrypted", + client, client_sequence + ) + VALUES( + p_user_id, + node_uuid, + (jsonb_populate_record(null::json_ancestor_array, node_data))."Ancestors", + (node_data->>'Created')::timestamptz, + (node_data->>'Updated')::timestamptz, + (node_data->>'Name')::varchar, + (node_data->>'Content')::text, + (node_data->>'Markdown')::bool, + '', /* content_encrypted */ + p_client_uuid, + (node_data->>'ClientSequence')::int + ) + ON CONFLICT (client, client_sequence) + DO NOTHING; + ELSE + RAISE NOTICE '03 Client newer, % > % (%, %)', node_updated, db_updated, node_uuid, (node_data->>'ClientSequence'); + /* Incoming node is newer and will replace the current node. + * + * The current node is copied to the node_history table and then modified in place + * with the incoming data. */ + INSERT INTO node_history( + user_id, "uuid", parents, + created, updated, "name", "content", markdown, "content_encrypted", + client, client_sequence + ) + SELECT + user_id, + "uuid", + ( + WITH RECURSIVE nodes AS ( + SELECT + uuid, + COALESCE(parent_uuid, '') AS parent_uuid, + name, + 0 AS depth + FROM node + WHERE + uuid = node_uuid + + UNION + + SELECT + n.uuid, + COALESCE(n.parent_uuid, '') AS parent_uuid, + n.name, + nr.depth+1 AS depth + FROM node n + INNER JOIN nodes nr ON n.uuid = nr.parent_uuid + ) + SELECT ARRAY ( + SELECT name + FROM nodes + ORDER BY depth DESC + OFFSET 1 /* discard itself */ + ) + ), + created, + updated, + name, + content, + markdown, + content_encrypted, + client, + client_sequence + FROM public."node" + WHERE + user_id = p_user_id AND + uuid = node_uuid + ON CONFLICT (client, client_sequence) + DO NOTHING; + + /* Current node in database is updated with incoming data. */ + UPDATE public."node" + SET + updated = (node_data->>'Updated')::timestamptz, + updated_seq = nextval('node_updates'), + name = (node_data->>'Name')::varchar, + content = (node_data->>'Content')::text, + markdown = (node_data->>'Markdown')::bool, + client = p_client_uuid, + client_sequence = (node_data->>'ClientSequence')::int + WHERE + user_id = p_user_id AND + uuid = node_uuid; + END IF; + + END LOOP; +END +$$; + +-- +-- Name: node_update_timestamp(); Type: FUNCTION; Schema: public; Owner: postgres +-- + +CREATE FUNCTION public.node_update_timestamp() RETURNS trigger + LANGUAGE plpgsql + AS $$ BEGIN IF NEW.updated = OLD.updated THEN UPDATE node @@ -56,6 +226,335 @@ BEGIN END; $$; -CREATE OR REPLACE TRIGGER node_update AFTER UPDATE ON node -FOR EACH ROW -EXECUTE PROCEDURE node_update_timestamp(); + +-- +-- Name: password_hash(character, bytea); Type: FUNCTION; Schema: public; Owner: postgres +-- + +CREATE FUNCTION public.password_hash(salt_hex character, pass bytea) RETURNS character + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN ( + SELECT + salt_hex || + encode( + sha256( + decode(salt_hex, 'hex') || /* salt in binary */ + pass /* password */ + ), + 'hex' + ) + ); +END; +$$; + + +-- +-- Name: client; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.client ( + id integer NOT NULL, + user_id integer NOT NULL, + client_uuid character(36) DEFAULT ''::bpchar NOT NULL, + created timestamp with time zone DEFAULT now() NOT NULL, + description character varying DEFAULT ''::character varying NOT NULL +); + + +-- +-- Name: client_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.client_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: client_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres +-- + +ALTER SEQUENCE public.client_id_seq OWNED BY public.client.id; + + +-- +-- Name: node_updates; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.node_updates + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: node; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.node ( + id integer NOT NULL, + user_id integer NOT NULL, + uuid character(36) DEFAULT gen_random_uuid() NOT NULL, + parent_uuid character(36), + created timestamp with time zone DEFAULT now() NOT NULL, + updated timestamp with time zone DEFAULT now() NOT NULL, + deleted timestamp with time zone, + created_seq bigint DEFAULT nextval('public.node_updates'::regclass) NOT NULL, + updated_seq bigint DEFAULT nextval('public.node_updates'::regclass) NOT NULL, + deleted_seq bigint, + name character varying(256) DEFAULT ''::character varying NOT NULL, + content text DEFAULT ''::text NOT NULL, + content_encrypted text DEFAULT ''::text NOT NULL, + markdown boolean DEFAULT false NOT NULL, + history boolean DEFAULT false NOT NULL, + client character(36) DEFAULT ''::bpchar NOT NULL, + client_sequence integer, + CONSTRAINT name_length CHECK ((length(TRIM(BOTH FROM name)) > 0)) +); + + +-- +-- Name: node_history; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.node_history ( + id integer NOT NULL, + user_id integer NOT NULL, + uuid character(36) NOT NULL, + parents character varying[], + created timestamp with time zone NOT NULL, + updated timestamp with time zone NOT NULL, + name character varying(256) NOT NULL, + content text NOT NULL, + content_encrypted text NOT NULL, + markdown boolean DEFAULT false NOT NULL, + client character(36) DEFAULT ''::bpchar NOT NULL, + client_sequence integer +); + + +-- +-- Name: node_history_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.node_history_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: node_history_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres +-- + +ALTER SEQUENCE public.node_history_id_seq OWNED BY public.node_history.id; + + +-- +-- Name: node_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.node_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: node_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres +-- + +ALTER SEQUENCE public.node_id_seq OWNED BY public.node.id; + + +-- +-- Name: test_data; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.test_data + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: user; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public."user" ( + id integer NOT NULL, + username character varying NOT NULL, + name character varying NOT NULL, + password character varying NOT NULL, + last_login timestamp without time zone DEFAULT now() NOT NULL, + timezone character varying DEFAULT 'UTC'::character varying NOT NULL +); + + +-- +-- Name: user_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.user_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: user_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres +-- + +ALTER SEQUENCE public.user_id_seq OWNED BY public."user".id; + + +-- +-- Name: client id; Type: DEFAULT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.client ALTER COLUMN id SET DEFAULT nextval('public.client_id_seq'::regclass); + + +-- +-- Name: node id; Type: DEFAULT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.node ALTER COLUMN id SET DEFAULT nextval('public.node_id_seq'::regclass); + + +-- +-- Name: node_history id; Type: DEFAULT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.node_history ALTER COLUMN id SET DEFAULT nextval('public.node_history_id_seq'::regclass); + + +-- +-- Name: user id; Type: DEFAULT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public."user" ALTER COLUMN id SET DEFAULT nextval('public.user_id_seq'::regclass); + + +-- +-- Name: client client_pk; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.client + ADD CONSTRAINT client_pk PRIMARY KEY (id); + + +-- +-- Name: node_history node_history_pk; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.node_history + ADD CONSTRAINT node_history_pk PRIMARY KEY (id); + + +-- +-- Name: node node_pk; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.node + ADD CONSTRAINT node_pk PRIMARY KEY (id); + + +-- +-- Name: user user_pk; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public."user" + ADD CONSTRAINT user_pk PRIMARY KEY (id); + + +-- +-- Name: client_uuid_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE UNIQUE INDEX client_uuid_idx ON public.client USING btree (client_uuid); + + +-- +-- Name: node_history_client_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE UNIQUE INDEX node_history_client_idx ON public.node_history USING btree (client, client_sequence); + + +-- +-- Name: node_history_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX node_history_idx ON public.node USING btree (history); + + +-- +-- Name: node_history_uuid_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX node_history_uuid_idx ON public.node USING btree (uuid); + + +-- +-- Name: node_search_index; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX node_search_index ON public.node USING gin (name public.gin_trgm_ops, content public.gin_trgm_ops); + + +-- +-- Name: node_uuid_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE UNIQUE INDEX node_uuid_idx ON public.node USING btree (uuid); + + +-- +-- Name: node node_update; Type: TRIGGER; Schema: public; Owner: postgres +-- + +CREATE TRIGGER node_update AFTER UPDATE ON public.node FOR EACH ROW EXECUTE FUNCTION public.node_update_timestamp(); + + +-- +-- Name: node_history node_history_user_fk; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.node_history + ADD CONSTRAINT node_history_user_fk FOREIGN KEY (user_id) REFERENCES public."user"(id) ON UPDATE RESTRICT ON DELETE RESTRICT; + + +-- +-- Name: node node_node_fk; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.node + ADD CONSTRAINT node_node_fk FOREIGN KEY (parent_uuid) REFERENCES public.node(uuid) ON UPDATE SET NULL ON DELETE SET NULL; + + +-- +-- Name: node user_fk; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.node + ADD CONSTRAINT user_fk FOREIGN KEY (user_id) REFERENCES public."user"(id) ON UPDATE RESTRICT ON DELETE RESTRICT; diff --git a/sql/00002.sql b/sql/00002.sql deleted file mode 100644 index 9d7bd8a..0000000 --- a/sql/00002.sql +++ /dev/null @@ -1,19 +0,0 @@ -CREATE FUNCTION public.password_hash(salt_hex char(32), pass bytea) -RETURNS char(96) -LANGUAGE plpgsql -AS -$$ -BEGIN - RETURN ( - SELECT - salt_hex || - encode( - sha256( - decode(salt_hex, 'hex') || /* salt in binary */ - pass /* password */ - ), - 'hex' - ) - ); -END; -$$; diff --git a/sql/00003.sql b/sql/00003.sql deleted file mode 100644 index 0fb2a51..0000000 --- a/sql/00003.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE public.node ADD CONSTRAINT node_node_fk FOREIGN KEY (parent_uuid) REFERENCES public.node("uuid") ON DELETE SET NULL ON UPDATE SET NULL; diff --git a/sql/00004.sql b/sql/00004.sql deleted file mode 100644 index 7ac464d..0000000 --- a/sql/00004.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE public.node ADD COLUMN history bool NOT NULL DEFAULT false; -CREATE INDEX node_history_idx ON public.node (history); diff --git a/sql/00005.sql b/sql/00005.sql deleted file mode 100644 index a366070..0000000 --- a/sql/00005.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE public.node ADD COLUMN client bpchar(36) NOT NULL DEFAULT ''; diff --git a/sql/00006.sql b/sql/00006.sql deleted file mode 100644 index 453b260..0000000 --- a/sql/00006.sql +++ /dev/null @@ -1 +0,0 @@ -DROP INDEX public.node_uuid_idx; diff --git a/sql/00007.sql b/sql/00007.sql deleted file mode 100644 index 40ce48e..0000000 --- a/sql/00007.sql +++ /dev/null @@ -1,162 +0,0 @@ -CREATE TYPE json_ancestor_array as ("Ancestors" varchar[]); - - -CREATE OR REPLACE PROCEDURE add_nodes(p_user_id int4, p_client_uuid varchar, p_nodes jsonb) -LANGUAGE PLPGSQL AS $$ - -DECLARE - node_data jsonb; - node_updated timestamptz; - db_updated timestamptz; - db_uuid bpchar; - db_client bpchar; - db_client_seq int; - node_uuid bpchar; - -BEGIN - RAISE NOTICE '--------------------------'; - FOR node_data IN SELECT * FROM jsonb_array_elements(p_nodes) - LOOP - node_uuid = (node_data->>'UUID')::bpchar; - node_updated = (node_data->>'Updated')::timestamptz; - - /* Retrieve the current modified timestamp for this node from the database. */ - SELECT - uuid, updated, client, client_sequence - INTO - db_uuid, db_updated, db_client, db_client_seq - FROM public."node" - WHERE - user_id = p_user_id AND - uuid = node_uuid; - - /* Is the node not in database? It needs to be created. */ - IF db_uuid IS NULL THEN - RAISE NOTICE '01 New node %', node_uuid; - INSERT INTO public."node" ( - user_id, "uuid", parent_uuid, created, updated, - "name", "content", markdown, "content_encrypted", - client, client_sequence - ) - VALUES( - p_user_id, - node_uuid, - (node_data->>'ParentUUID')::bpchar, - (node_data->>'Created')::timestamptz, - (node_data->>'Updated')::timestamptz, - (node_data->>'Name')::varchar, - (node_data->>'Content')::text, - (node_data->>'Markdown')::bool, - '', /* content_encrypted */ - p_client_uuid, - (node_data->>'ClientSequence')::int - ); - CONTINUE; - END IF; - - - /* The client could send a specific node again if it didn't receive the OK from this procedure before. */ - IF db_updated = node_updated AND db_client = p_client_uuid AND db_client_seq = (node_data->>'ClientSequence')::int THEN - RAISE NOTICE '04, already recorded, %, %', db_client, db_client_seq; - CONTINUE; - END IF; - - /* Determine if the incoming node data is to go into history or replace the current node. */ - IF db_updated > node_updated THEN - RAISE NOTICE '02 DB newer, % > % (%))', db_updated, node_updated, node_uuid; - /* Incoming node is going straight to history since it is older than the current node. */ - INSERT INTO node_history( - user_id, "uuid", parents, created, updated, - "name", "content", markdown, "content_encrypted", - client, client_sequence - ) - VALUES( - p_user_id, - node_uuid, - (jsonb_populate_record(null::json_ancestor_array, node_data))."Ancestors", - (node_data->>'Created')::timestamptz, - (node_data->>'Updated')::timestamptz, - (node_data->>'Name')::varchar, - (node_data->>'Content')::text, - (node_data->>'Markdown')::bool, - '', /* content_encrypted */ - p_client_uuid, - (node_data->>'ClientSequence')::int - ) - ON CONFLICT (client, client_sequence) - DO NOTHING; - ELSE - RAISE NOTICE '03 Client newer, % > % (%, %)', node_updated, db_updated, node_uuid, (node_data->>'ClientSequence'); - /* Incoming node is newer and will replace the current node. - * - * The current node is copied to the node_history table and then modified in place - * with the incoming data. */ - INSERT INTO node_history( - user_id, "uuid", parents, - created, updated, "name", "content", markdown, "content_encrypted", - client, client_sequence - ) - SELECT - user_id, - "uuid", - ( - WITH RECURSIVE nodes AS ( - SELECT - uuid, - COALESCE(parent_uuid, '') AS parent_uuid, - name, - 0 AS depth - FROM node - WHERE - uuid = node_uuid - - UNION - - SELECT - n.uuid, - COALESCE(n.parent_uuid, '') AS parent_uuid, - n.name, - nr.depth+1 AS depth - FROM node n - INNER JOIN nodes nr ON n.uuid = nr.parent_uuid - ) - SELECT ARRAY ( - SELECT name - FROM nodes - ORDER BY depth DESC - OFFSET 1 /* discard itself */ - ) - ), - created, - updated, - name, - content, - markdown, - content_encrypted, - client, - client_sequence - FROM public."node" - WHERE - user_id = p_user_id AND - uuid = node_uuid - ON CONFLICT (client, client_sequence) - DO NOTHING; - - /* Current node in database is updated with incoming data. */ - UPDATE public."node" - SET - updated = (node_data->>'Updated')::timestamptz, - updated_seq = nextval('node_updates'), - name = (node_data->>'Name')::varchar, - content = (node_data->>'Content')::text, - markdown = (node_data->>'Markdown')::bool, - client = p_client_uuid, - client_sequence = (node_data->>'ClientSequence')::int - WHERE - user_id = p_user_id AND - uuid = node_uuid; - END IF; - - END LOOP; -END -$$; diff --git a/sql/00008.sql b/sql/00008.sql deleted file mode 100644 index a91d54c..0000000 --- a/sql/00008.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE node ADD COLUMN Client_sequence int NULL; -ALTER TABLE node_history ADD COLUMN Client_sequence int NULL; diff --git a/sql/00009.sql b/sql/00009.sql deleted file mode 100644 index 332af3a..0000000 --- a/sql/00009.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE UNIQUE INDEX node_history_client_idx ON public.node_history (client,client_sequence); diff --git a/sql/00010.sql b/sql/00010.sql deleted file mode 100644 index c0f14ee..0000000 --- a/sql/00010.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE public.client ( - id serial NOT NULL, - user_id int4 NOT NULL, - client_uuid bpchar(36) DEFAULT '' NOT NULL, - created timestamptz DEFAULT NOW() NOT NULL, - description varchar DEFAULT '' NOT NULL, - CONSTRAINT client_pk PRIMARY KEY (id) -); - -CREATE UNIQUE INDEX client_uuid_idx ON public.client (client_uuid); diff --git a/sql/00011.sql b/sql/00011.sql deleted file mode 100644 index 5b67839..0000000 --- a/sql/00011.sql +++ /dev/null @@ -1,166 +0,0 @@ -CREATE OR REPLACE PROCEDURE add_nodes(p_user_id int4, p_client_uuid varchar, p_nodes jsonb) -LANGUAGE PLPGSQL AS $$ - -DECLARE - node_data jsonb; - node_updated timestamptz; - db_updated timestamptz; - db_uuid bpchar; - db_client bpchar; - db_client_seq int; - node_uuid bpchar; - parent_uuid bpchar; - -BEGIN - RAISE NOTICE '--------------------------'; - FOR node_data IN SELECT * FROM jsonb_array_elements(p_nodes) - LOOP - node_uuid = (node_data->>'UUID')::bpchar; - node_updated = (node_data->>'Updated')::timestamptz; - - IF node_data->>'ParentUUID' = '00000000-0000-0000-0000-000000000000' THEN - parent_uuid = NULL; - ELSE - parent_uuid = node_data->>'ParentUUID'; - END IF; - - /* Retrieve the current modified timestamp for this node from the database. */ - SELECT - uuid, updated, client, client_sequence - INTO - db_uuid, db_updated, db_client, db_client_seq - FROM public."node" - WHERE - user_id = p_user_id AND - uuid = node_uuid; - - /* Is the node not in database? It needs to be created. */ - IF db_uuid IS NULL THEN - RAISE NOTICE '01 New node %', node_uuid; - INSERT INTO public."node" ( - user_id, "uuid", parent_uuid, created, updated, - "name", "content", markdown, "content_encrypted", - client, client_sequence - ) - VALUES( - p_user_id, - node_uuid, - parent_uuid, - (node_data->>'Created')::timestamptz, - (node_data->>'Updated')::timestamptz, - (node_data->>'Name')::varchar, - (node_data->>'Content')::text, - (node_data->>'Markdown')::bool, - '', /* content_encrypted */ - p_client_uuid, - (node_data->>'ClientSequence')::int - ); - CONTINUE; - END IF; - - - /* The client could send a specific node again if it didn't receive the OK from this procedure before. */ - IF db_updated = node_updated AND db_client = p_client_uuid AND db_client_seq = (node_data->>'ClientSequence')::int THEN - RAISE NOTICE '04, already recorded, %, %', db_client, db_client_seq; - CONTINUE; - END IF; - - /* Determine if the incoming node data is to go into history or replace the current node. */ - IF db_updated > node_updated THEN - RAISE NOTICE '02 DB newer, % > % (%))', db_updated, node_updated, node_uuid; - /* Incoming node is going straight to history since it is older than the current node. */ - INSERT INTO node_history( - user_id, "uuid", parents, created, updated, - "name", "content", markdown, "content_encrypted", - client, client_sequence - ) - VALUES( - p_user_id, - node_uuid, - (jsonb_populate_record(null::json_ancestor_array, node_data))."Ancestors", - (node_data->>'Created')::timestamptz, - (node_data->>'Updated')::timestamptz, - (node_data->>'Name')::varchar, - (node_data->>'Content')::text, - (node_data->>'Markdown')::bool, - '', /* content_encrypted */ - p_client_uuid, - (node_data->>'ClientSequence')::int - ) - ON CONFLICT (client, client_sequence) - DO NOTHING; - ELSE - RAISE NOTICE '03 Client newer, % > % (%, %)', node_updated, db_updated, node_uuid, (node_data->>'ClientSequence'); - /* Incoming node is newer and will replace the current node. - * - * The current node is copied to the node_history table and then modified in place - * with the incoming data. */ - INSERT INTO node_history( - user_id, "uuid", parents, - created, updated, "name", "content", markdown, "content_encrypted", - client, client_sequence - ) - SELECT - user_id, - "uuid", - ( - WITH RECURSIVE nodes AS ( - SELECT - uuid, - COALESCE(parent_uuid, '') AS parent_uuid, - name, - 0 AS depth - FROM node - WHERE - uuid = node_uuid - - UNION - - SELECT - n.uuid, - COALESCE(n.parent_uuid, '') AS parent_uuid, - n.name, - nr.depth+1 AS depth - FROM node n - INNER JOIN nodes nr ON n.uuid = nr.parent_uuid - ) - SELECT ARRAY ( - SELECT name - FROM nodes - ORDER BY depth DESC - OFFSET 1 /* discard itself */ - ) - ), - created, - updated, - name, - content, - markdown, - content_encrypted, - client, - client_sequence - FROM public."node" - WHERE - user_id = p_user_id AND - uuid = node_uuid - ON CONFLICT (client, client_sequence) - DO NOTHING; - - /* Current node in database is updated with incoming data. */ - UPDATE public."node" - SET - updated = (node_data->>'Updated')::timestamptz, - updated_seq = nextval('node_updates'), - name = (node_data->>'Name')::varchar, - content = (node_data->>'Content')::text, - markdown = (node_data->>'Markdown')::bool, - client = p_client_uuid, - client_sequence = (node_data->>'ClientSequence')::int - WHERE - user_id = p_user_id AND - uuid = node_uuid; - END IF; - - END LOOP; -END -$$; diff --git a/sql/00012.sql b/sql/00012.sql deleted file mode 100644 index e62f011..0000000 --- a/sql/00012.sql +++ /dev/null @@ -1,166 +0,0 @@ -CREATE OR REPLACE PROCEDURE add_nodes(p_user_id int4, p_client_uuid varchar, p_nodes jsonb) -LANGUAGE PLPGSQL AS $$ - -DECLARE - node_data jsonb; - node_updated timestamptz; - db_updated timestamptz; - db_uuid bpchar; - db_client bpchar; - db_client_seq int; - node_uuid bpchar; - parent_uuid_nullable bpchar; - -BEGIN - RAISE NOTICE '--------------------------'; - FOR node_data IN SELECT * FROM jsonb_array_elements(p_nodes) - LOOP - node_uuid = (node_data->>'UUID')::bpchar; - node_updated = (node_data->>'Updated')::timestamptz; - - IF node_data->>'ParentUUID' = '00000000-0000-0000-0000-000000000000' THEN - parent_uuid_nullable = NULL; - ELSE - parent_uuid_nullable = node_data->>'ParentUUID'; - END IF; - - /* Retrieve the current modified timestamp for this node from the database. */ - SELECT - uuid, updated, client, client_sequence - INTO - db_uuid, db_updated, db_client, db_client_seq - FROM public."node" - WHERE - user_id = p_user_id AND - uuid = node_uuid; - - /* Is the node not in database? It needs to be created. */ - IF db_uuid IS NULL THEN - RAISE NOTICE '01 New node %', node_uuid; - INSERT INTO public."node" ( - user_id, "uuid", parent_uuid, created, updated, - "name", "content", markdown, "content_encrypted", - client, client_sequence - ) - VALUES( - p_user_id, - node_uuid, - parent_uuid_nullable, - (node_data->>'Created')::timestamptz, - (node_data->>'Updated')::timestamptz, - (node_data->>'Name')::varchar, - (node_data->>'Content')::text, - (node_data->>'Markdown')::bool, - '', /* content_encrypted */ - p_client_uuid, - (node_data->>'ClientSequence')::int - ); - CONTINUE; - END IF; - - - /* The client could send a specific node again if it didn't receive the OK from this procedure before. */ - IF db_updated = node_updated AND db_client = p_client_uuid AND db_client_seq = (node_data->>'ClientSequence')::int THEN - RAISE NOTICE '04, already recorded, %, %', db_client, db_client_seq; - CONTINUE; - END IF; - - /* Determine if the incoming node data is to go into history or replace the current node. */ - IF db_updated > node_updated THEN - RAISE NOTICE '02 DB newer, % > % (%))', db_updated, node_updated, node_uuid; - /* Incoming node is going straight to history since it is older than the current node. */ - INSERT INTO node_history( - user_id, "uuid", parents, created, updated, - "name", "content", markdown, "content_encrypted", - client, client_sequence - ) - VALUES( - p_user_id, - node_uuid, - (jsonb_populate_record(null::json_ancestor_array, node_data))."Ancestors", - (node_data->>'Created')::timestamptz, - (node_data->>'Updated')::timestamptz, - (node_data->>'Name')::varchar, - (node_data->>'Content')::text, - (node_data->>'Markdown')::bool, - '', /* content_encrypted */ - p_client_uuid, - (node_data->>'ClientSequence')::int - ) - ON CONFLICT (client, client_sequence) - DO NOTHING; - ELSE - RAISE NOTICE '03 Client newer, % > % (%, %)', node_updated, db_updated, node_uuid, (node_data->>'ClientSequence'); - /* Incoming node is newer and will replace the current node. - * - * The current node is copied to the node_history table and then modified in place - * with the incoming data. */ - INSERT INTO node_history( - user_id, "uuid", parents, - created, updated, "name", "content", markdown, "content_encrypted", - client, client_sequence - ) - SELECT - user_id, - "uuid", - ( - WITH RECURSIVE nodes AS ( - SELECT - uuid, - COALESCE(parent_uuid, '') AS parent_uuid, - name, - 0 AS depth - FROM node - WHERE - uuid = node_uuid - - UNION - - SELECT - n.uuid, - COALESCE(n.parent_uuid, '') AS parent_uuid, - n.name, - nr.depth+1 AS depth - FROM node n - INNER JOIN nodes nr ON n.uuid = nr.parent_uuid - ) - SELECT ARRAY ( - SELECT name - FROM nodes - ORDER BY depth DESC - OFFSET 1 /* discard itself */ - ) - ), - created, - updated, - name, - content, - markdown, - content_encrypted, - client, - client_sequence - FROM public."node" - WHERE - user_id = p_user_id AND - uuid = node_uuid - ON CONFLICT (client, client_sequence) - DO NOTHING; - - /* Current node in database is updated with incoming data. */ - UPDATE public."node" - SET - updated = (node_data->>'Updated')::timestamptz, - updated_seq = nextval('node_updates'), - name = (node_data->>'Name')::varchar, - content = (node_data->>'Content')::text, - markdown = (node_data->>'Markdown')::bool, - client = p_client_uuid, - client_sequence = (node_data->>'ClientSequence')::int - WHERE - user_id = p_user_id AND - uuid = node_uuid; - END IF; - - END LOOP; -END -$$; From 5a67c638c68ad0501cce53a9859cb1f754430edd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Tue, 26 May 2026 13:54:22 +0200 Subject: [PATCH 3/4] Regriession fix for title update causing trouble for content edit. --- static/js/node.mjs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/static/js/node.mjs b/static/js/node.mjs index acebbb7..7959831 100644 --- a/static/js/node.mjs +++ b/static/js/node.mjs @@ -48,7 +48,7 @@ export class N2NodeUI extends CustomHTMLElement { _mbus.subscribe('NODE_MODIFIED', () => { document.querySelector('#crumbs .crumbs')?.classList.add('node-modified') this.elIconSave.src = `/images/${_VERSION}/icon_save.svg` - this.render() + this.renderName() }) _mbus.subscribe('NODE_UNMODIFIED', () => { @@ -76,6 +76,9 @@ export class N2NodeUI extends CustomHTMLElement { this.showMarkdown(true) }// }}} + renderName() {// {{{ + this.elName.innerText = this.node?.get('Name') ?? '' + }// }}} render() {// {{{ this.elName.innerText = this.node?.get('Name') ?? '' this.elNodeContent.value = this.node?.get('Content') ?? '' From 662053e750f393908ecdeb6265964656415ded49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Tue, 26 May 2026 13:54:47 +0200 Subject: [PATCH 4/4] Bumped to v3 --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 80120a2..2e938ca 100644 --- a/main.go +++ b/main.go @@ -23,7 +23,7 @@ import ( "text/template" ) -const VERSION = "v2" +const VERSION = "v3" const CONTEXT_USER = 1 const SYNC_PAGINATION = 200