From 2f8e709402d026e0aa176718791b0c9e28820950 Mon Sep 17 00:00:00 2001 From: Kevin Lyda <kevin@lyda.ie> Date: Sat, 10 May 2025 20:26:39 +0100 Subject: [PATCH] First pass at storage module. --- folders/sql/1_create_table.up.sql | 11 +- go.mod | 82 +++++++++ go.sum | 185 +++++++++++++++++++ storage/connection.go | 64 +++++++ storage/db.go | 31 ++++ storage/{storage.go => doc.go} | 0 storage/folders.sql.go | 197 +++++++++++++++++++++ storage/generate.go | 3 + storage/messages.sql.go | 124 +++++++++++++ storage/migrations/1_create_table.down.sql | 21 +++ storage/migrations/1_create_table.up.sql | 161 +++++++++++++++++ storage/models.go | 87 +++++++++ storage/queries/folders.sql | 41 +++++ storage/queries/messages.sql | 17 ++ storage/queries/seed.sql | 10 ++ storage/queries/users.sql | 8 + storage/seed.sql.go | 39 ++++ storage/sqlc.yaml | 10 ++ storage/users.sql.go | 59 ++++++ 19 files changed, 1145 insertions(+), 5 deletions(-) create mode 100644 storage/connection.go create mode 100644 storage/db.go rename storage/{storage.go => doc.go} (100%) create mode 100644 storage/folders.sql.go create mode 100644 storage/generate.go create mode 100644 storage/messages.sql.go create mode 100644 storage/migrations/1_create_table.down.sql create mode 100644 storage/migrations/1_create_table.up.sql create mode 100644 storage/models.go create mode 100644 storage/queries/folders.sql create mode 100644 storage/queries/messages.sql create mode 100644 storage/queries/seed.sql create mode 100644 storage/queries/users.sql create mode 100644 storage/seed.sql.go create mode 100644 storage/sqlc.yaml create mode 100644 storage/users.sql.go diff --git a/folders/sql/1_create_table.up.sql b/folders/sql/1_create_table.up.sql index d518fc2..14b47eb 100644 --- a/folders/sql/1_create_table.up.sql +++ b/folders/sql/1_create_table.up.sql @@ -85,8 +85,8 @@ BEGIN SELECT RAISE (ABORT, 'GENERAL folder is protected'); END; -INSERT INTO folders (name, description, system, shownew, owner) - VALUES ('GENERAL', 'Default general bulletin folder.', 1, 1, 'SYSTEM'); +INSERT INTO folders (name, description, system, shownew) + VALUES ('GENERAL', 'Default general bulletin folder.', 1, 1); CREATE TABLE owners ( folder VARCHAR(25) REFERENCES folders(name) ON DELETE CASCADE ON UPDATE CASCADE, @@ -103,6 +103,8 @@ BEGIN UPDATE owners SET update_at=CURRENT_TIMESTAMP WHERE folder=NEW.folder AND owner=NEW.owner; END; +INSERT INTO owners (folder, owner) VALUES ('GENERAL', 'SYSTEM'); + CREATE TABLE messages ( id INT NOT NULL, folder VARCHAR(25) REFERENCES folders(name) ON DELETE CASCADE ON UPDATE CASCADE, @@ -149,14 +151,13 @@ CREATE TABLE access ( PRIMARY KEY (login, folder) ) WITHOUT ROWID; ---- TODO: The following is incomplete: ---- User configs. +--- User folder configs. CREATE TABLE config ( login VARCHAR(25) REFERENCES users(login) ON DELETE CASCADE ON UPDATE CASCADE, folder VARCHAR(25) REFERENCES folders(name) ON DELETE CASCADE ON UPDATE CASCADE, always INT NOT NULL DEFAULT 0, alert INT NOT NULL DEFAULT 0, --- 0=no, 1=brief, 2=readnew - always INT NOT NULL + PRIMARY KEY (login, folder) ) WITHOUT ROWID; --- System configs. diff --git a/go.mod b/go.mod index 09e4514..a8ad251 100644 --- a/go.mod +++ b/go.mod @@ -15,23 +15,105 @@ require ( ) require ( + cel.dev/expr v0.19.1 // indirect + dario.cat/mergo v1.0.1 // indirect + filippo.io/edwards25519 v1.1.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/Masterminds/sprig/v3 v3.3.0 // indirect + github.com/aarondl/json v0.0.0-20221020222930-8b0db17ef1bf // indirect + github.com/aarondl/opt v0.0.0-20230114172057-b91f370c41f0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/coder/websocket v1.8.12 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect + github.com/cubicdaiya/gonp v1.0.4 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fatih/structtag v1.2.0 // indirect + github.com/fergusstrange/embedded-postgres v1.26.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gdamore/encoding v1.0.1 // indirect + github.com/go-sql-driver/mysql v1.9.2 // indirect + github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect + github.com/gofrs/uuid v4.4.0+incompatible // indirect + github.com/google/cel-go v0.24.1 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.4 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/knadh/koanf/maps v0.1.1 // indirect + github.com/knadh/koanf/parsers/yaml v0.1.0 // indirect + github.com/knadh/koanf/providers/confmap v0.1.0 // indirect + github.com/knadh/koanf/providers/env v0.1.0 // indirect + github.com/knadh/koanf/providers/file v0.1.0 // indirect + github.com/knadh/koanf/v2 v2.1.0 // indirect + github.com/lib/pq v1.10.9 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/pganalyze/pg_query_go/v6 v6.1.0 // indirect + github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb // indirect + github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86 // indirect + github.com/pingcap/log v1.1.0 // indirect + github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0 // indirect + github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/riza-io/grpc-go v0.2.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/sqlc-dev/sqlc v1.29.0 // indirect + github.com/stephenafamo/bob v0.34.2 // indirect + github.com/stephenafamo/scan v0.6.2 // indirect + github.com/stephenafamo/sqlparser v0.0.0-20250408111851-b937299b5b7d // indirect + github.com/stoewer/go-strcase v1.2.0 // indirect + github.com/tetratelabs/wazero v1.9.0 // indirect + github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d // indirect + github.com/urfave/cli/v2 v2.23.7 // indirect + github.com/volatiletech/inflect v0.0.1 // indirect + github.com/volatiletech/strmangle v0.0.6 // indirect + github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 // indirect + github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect + github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.38.0 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sync v0.14.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.32.0 // indirect golang.org/x/text v0.25.0 // indirect + golang.org/x/tools v0.33.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.71.1 // indirect + google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.65.2 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.10.0 // indirect + mvdan.cc/gofumpt v0.7.0 // indirect +) + +tool ( + github.com/sqlc-dev/sqlc/cmd/sqlc + github.com/stephenafamo/bob/gen/bobgen-sql ) diff --git a/go.sum b/go.sum index 5fafbe6..4b3c073 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,25 @@ +cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= +cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/aarondl/json v0.0.0-20221020222930-8b0db17ef1bf h1:+edM69bH/X6JpYPmJYBRLanAMe1V5yRXYU3hHUovGcE= +github.com/aarondl/json v0.0.0-20221020222930-8b0db17ef1bf/go.mod h1:FZqLhJSj2tg0ZN48GB1zvj00+ZYcHPqgsC7yzcgCq6k= +github.com/aarondl/opt v0.0.0-20230114172057-b91f370c41f0 h1:vLrhbOWVPxtHao/QthU8pcpI4DbtSGnWgH7qIJf8F6k= +github.com/aarondl/opt v0.0.0-20230114172057-b91f370c41f0/go.mod h1:l4/5NZtYd/SIohsFhaJQQe+sPOTG22furpZ5FvcYOzk= github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc= github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= @@ -10,19 +28,46 @@ github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= +github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/cubicdaiya/gonp v1.0.4 h1:ky2uIAJh81WiLcGKBVD5R7KsM/36W6IqqTy6Bo6rGws= +github.com/cubicdaiya/gonp v1.0.4/go.mod h1:iWGuP/7+JVTn02OWhRemVbMmG1DOUnmrGTYYACpOI0I= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/fergusstrange/embedded-postgres v1.26.0 h1:mTgUBNST+6zro0TkIb9Fuo9Qg8mSU0ILus9jZKmFmJg= +github.com/fergusstrange/embedded-postgres v1.26.0/go.mod h1:t/MLs0h9ukYM6FSt99R7InCHs1nW0ordoVCcnzmpTYw= +github.com/friendsofgo/errors v0.9.2/go.mod h1:yCvFW5AkDIL9qn7suHVLiI/gH228n7PC4Pn44IGoTOI= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU= github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= +github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= +github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-migrate/migrate/v4 v4.18.3 h1:EYGkoOsvgHHfm5U/naS1RP/6PL/Xv3S4B/swMiAmDLs= github.com/golang-migrate/migrate/v4 v4.18.3/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/cel-go v0.24.1 h1:jsBCtxG8mM5wiUJDSGUqU0K7Mtr3w7Eyv00rw4DiZxI= +github.com/google/cel-go v0.24.1/go.mod h1:Hdf9TqOaTNSFQA1ybQaRqATVoK7m/zcf7IMhGXP5zI8= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -32,8 +77,37 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg= +github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w= +github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY= +github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU= +github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU= +github.com/knadh/koanf/providers/env v0.1.0 h1:LqKteXqfOWyx5Ab9VfGHmjY9BvRXi+clwyZozgVRiKg= +github.com/knadh/koanf/providers/env v0.1.0/go.mod h1:RE8K9GbACJkeEnkl8L/Qcj8p4ZyPXZIQ191HJi44ZaQ= +github.com/knadh/koanf/providers/file v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c= +github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA= +github.com/knadh/koanf/v2 v2.1.0 h1:eh4QmHHBuU8BybfIJ8mB8K8gsGCD/AUQTdwGq/GzId8= +github.com/knadh/koanf/v2 v2.1.0/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -44,10 +118,28 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pganalyze/pg_query_go/v6 v6.1.0 h1:jG5ZLhcVgL1FAw4C/0VNQaVmX1SUJx71wBGdtTtBvls= +github.com/pganalyze/pg_query_go/v6 v6.1.0/go.mod h1:nvTHIuoud6e1SfrUaFwHqT0i4b5Nr+1rPWVds3B5+50= +github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb h1:3pSi4EDG6hg0orE1ndHkXvX6Qdq2cZn8gAPir8ymKZk= +github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg= +github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86 h1:tdMsjOqUR7YXHoBitzdebTvOjs/swniBTOLy5XiMtuE= +github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86/go.mod h1:exzhVYca3WRtd6gclGNErRWb1qEgff3LYta0LvRmON4= +github.com/pingcap/log v1.1.0 h1:ELiPxACz7vdo1qAvvaWJg1NrYFoY6gqAh/+Uo6aXdD8= +github.com/pingcap/log v1.1.0/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4= +github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0 h1:W3rpAI3bubR6VWOcwxDIG0Gz9G5rl5b3SL116T0vBt0= +github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0/go.mod h1:+8feuexTKcXHZF/dkDfvCwEyBAmgb4paFc3/WeYV2eE= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 h1:wSmWgpuccqS2IOfmYrbRiUgv+g37W5suLLLxwwniTSc= +github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494/go.mod h1:yipyliwI08eQ6XwDm1fEwKPdF/xdbkiHtrU+1Hg+vc4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/tview v0.0.0-20250501113434-0c592cd31026 h1:ij8h8B3psk3LdMlqkfPTKIzeGzTaZLOiyplILMlxPAM= @@ -56,20 +148,81 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/riza-io/grpc-go v0.2.0 h1:2HxQKFVE7VuYstcJ8zqpN84VnAoJ4dCL6YFhJewNcHQ= +github.com/riza-io/grpc-go v0.2.0/go.mod h1:2bDvR9KkKC3KhtlSHfR3dAXjUMT86kg4UfWFyVGWqi8= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/sqlc-dev/sqlc v1.29.0 h1:HQctoD7y/i29Bao53qXO7CZ/BV9NcvpGpsJWvz9nKWs= +github.com/sqlc-dev/sqlc v1.29.0/go.mod h1:BavmYw11px5AdPOjAVHmb9fctP5A8GTziC38wBF9tp0= +github.com/stephenafamo/bob v0.34.2 h1:eXMmAE9YPKIyFKMXmI6wYI+dQxxuAnzulbmyHBctSGk= +github.com/stephenafamo/bob v0.34.2/go.mod h1:EVqAHXIxKPppvrkVsy/+YiUyHDWueIh0srPENffFhNE= +github.com/stephenafamo/scan v0.6.2 h1:mEjx1P1MuimqALCXfZEV8+KAiVcByrgngqKatgHag9I= +github.com/stephenafamo/scan v0.6.2/go.mod h1:FhIUJ8pLNyex36xGFiazDJJ5Xry0UkAi+RkWRrEcRMg= +github.com/stephenafamo/sqlparser v0.0.0-20250408111851-b937299b5b7d h1:VJwkSvMTq76O/CgKR9fvx4Dxf3cLx21ueBvVvFBriVY= +github.com/stephenafamo/sqlparser v0.0.0-20250408111851-b937299b5b7d/go.mod h1:2ATW++wFz7Mvc/N+nUtQnU+9VIGAxrn8m9JCLDSWMsQ= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= +github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= +github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d h1:dOMI4+zEbDI37KGb0TI44GUAwxHF9cMsIoDTJ7UmgfU= +github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s= +github.com/urfave/cli/v2 v2.23.7 h1:YHDQ46s3VghFHFf1DdF+Sh7H4RqhcM+t0TmZRJx4oJY= +github.com/urfave/cli/v2 v2.23.7/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/urfave/cli/v3 v3.3.2 h1:BYFVnhhZ8RqT38DxEYVFPPmGFTEf7tJwySTXsVRrS/o= github.com/urfave/cli/v3 v3.3.2/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= +github.com/volatiletech/inflect v0.0.1 h1:2a6FcMQyhmPZcLa+uet3VJ8gLn/9svWhJxJYwvE8KsU= +github.com/volatiletech/inflect v0.0.1/go.mod h1:IBti31tG6phkHitLlr5j7shC5SOo//x0AjDzaJU1PLA= +github.com/volatiletech/strmangle v0.0.6 h1:AdOYE3B2ygRDq4rXDij/MMwq6KVK/pWAYxpC7CLrkKQ= +github.com/volatiletech/strmangle v0.0.6/go.mod h1:ycDvbDkjDvhC0NUU8w3fWwl5JEMTV56vTKXzR3GeR+0= +github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 h1:mJdDDPblDfPe7z7go8Dvv1AJQDI3eQ/5xith3q2mFlo= +github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07/go.mod h1:Ak17IJ037caFp4jpCw/iQQ7/W74Sqpb1YuKJU6HTKfM= +github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 h1:OvLBa8SqJnZ6P+mjlzc2K7PM22rRUPE1x32G9DTPrC4= +github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -77,6 +230,7 @@ golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -85,6 +239,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -100,6 +256,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -132,6 +289,9 @@ golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= @@ -140,6 +300,29 @@ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxb golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= +google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s= @@ -166,3 +349,5 @@ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= +mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= diff --git a/storage/connection.go b/storage/connection.go new file mode 100644 index 0000000..0b7a8c7 --- /dev/null +++ b/storage/connection.go @@ -0,0 +1,64 @@ +package storage + +import ( + "embed" + "errors" + "os" + "path" + + "github.com/adrg/xdg" + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/source/iofs" + "github.com/jmoiron/sqlx" + + // Included to connect to sqlite. + _ "github.com/golang-migrate/migrate/v4/database/sqlite" + _ "modernc.org/sqlite" +) + +//go:embed migrations/*.sql +var migrationsFS embed.FS + +// Store is the store for bulletin. +type Store struct { + user string + db *sqlx.DB +} + +// Open opens the bulletin database. +func Open(user string) (*Store, error) { + fdir := path.Join(xdg.DataHome, "BULLETIN") + err := os.MkdirAll(fdir, 0700) + if err != nil { + return nil, errors.New("bulletin directory problem") + } + fdb := path.Join(fdir, "bulletin.db") + + // Run db migrations if needed. + migrations, err := iofs.New(migrationsFS, "migrations") + if err != nil { + return nil, err + } + m, err := migrate.NewWithSourceInstance("iofs", migrations, + "sqlite://"+fdb+"?_pragma=foreign_keys(1)") + if err != nil { + return nil, err + } + err = m.Up() + if err != nil && err != migrate.ErrNoChange { + return nil, err + } + m.Close() + + store := &Store{user: user} + store.db, err = sqlx.Connect("sqlite", "file://"+fdb+"?_pragma=foreign_keys(1)") + if err != nil { + return nil, errors.New("bulletin database problem") + } + return store, nil +} + +// Close closes the db backing the store. +func (fstore *Store) Close() { + fstore.db.Close() +} diff --git a/storage/db.go b/storage/db.go new file mode 100644 index 0000000..7f31d97 --- /dev/null +++ b/storage/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 + +package storage + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/storage/storage.go b/storage/doc.go similarity index 100% rename from storage/storage.go rename to storage/doc.go diff --git a/storage/folders.sql.go b/storage/folders.sql.go new file mode 100644 index 0000000..c2df48f --- /dev/null +++ b/storage/folders.sql.go @@ -0,0 +1,197 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: folders.sql + +package storage + +import ( + "context" + "database/sql" +) + +const createFolder = `-- name: CreateFolder :exec + INSERT INTO folders ( + name, always, brief, description, notify, readnew, shownew, system, + expire, visibility +) VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, ? +) +` + +type CreateFolderParams struct { + Name string + Always int64 + Brief int64 + Description string + Notify int64 + Readnew int64 + Shownew int64 + System int64 + Expire int64 + Visibility int64 +} + +func (q *Queries) CreateFolder(ctx context.Context, arg CreateFolderParams) error { + _, err := q.db.ExecContext(ctx, createFolder, + arg.Name, + arg.Always, + arg.Brief, + arg.Description, + arg.Notify, + arg.Readnew, + arg.Shownew, + arg.System, + arg.Expire, + arg.Visibility, + ) + return err +} + +const deleteFolder = `-- name: DeleteFolder :exec +DELETE FROM folders WHERE name=? +` + +func (q *Queries) DeleteFolder(ctx context.Context, name string) error { + _, err := q.db.ExecContext(ctx, deleteFolder, name) + return err +} + +const findFolderExact = `-- name: FindFolderExact :one +SELECT name FROM folders where name = ? +` + +func (q *Queries) FindFolderExact(ctx context.Context, name string) (string, error) { + row := q.db.QueryRowContext(ctx, findFolderExact, name) + err := row.Scan(&name) + return name, err +} + +const findFolderPrefix = `-- name: FindFolderPrefix :one +SELECT name FROM folders where name LIKE ? +ORDER BY name +LIMIT 1 +` + +func (q *Queries) FindFolderPrefix(ctx context.Context, name string) (string, error) { + row := q.db.QueryRowContext(ctx, findFolderPrefix, name) + err := row.Scan(&name) + return name, err +} + +const getFolderExpire = `-- name: GetFolderExpire :one +SELECT expire FROM folders WHERE name = ? +` + +func (q *Queries) GetFolderExpire(ctx context.Context, name string) (int64, error) { + row := q.db.QueryRowContext(ctx, getFolderExpire, name) + var expire int64 + err := row.Scan(&expire) + return expire, err +} + +const isFolderAccess = `-- name: IsFolderAccess :one +SELECT 1 FROM folders AS f LEFT JOIN owners AS c ON f.name = c.folder + WHERE f.name = ? AND (f.visibility = 0 OR c.OWNER = ?) +` + +type IsFolderAccessParams struct { + Name string + Owner sql.NullString +} + +func (q *Queries) IsFolderAccess(ctx context.Context, arg IsFolderAccessParams) (int64, error) { + row := q.db.QueryRowContext(ctx, isFolderAccess, arg.Name, arg.Owner) + var column_1 int64 + err := row.Scan(&column_1) + return column_1, err +} + +const isFolderOwner = `-- name: IsFolderOwner :one +SELECT 1 FROM folders AS f LEFT JOIN owners AS c ON f.name = c.folder + WHERE f.name = ? AND c.OWNER = ? +` + +type IsFolderOwnerParams struct { + Name string + Owner sql.NullString +} + +func (q *Queries) IsFolderOwner(ctx context.Context, arg IsFolderOwnerParams) (int64, error) { + row := q.db.QueryRowContext(ctx, isFolderOwner, arg.Name, arg.Owner) + var column_1 int64 + err := row.Scan(&column_1) + return column_1, err +} + +const listFolder = `-- name: ListFolder :many +SELECT f.name, count(m.id) as count, f.description +FROM folders AS f LEFT JOIN messages AS m ON f.name = m.folder +GROUP By f.name +ORDER BY f.name +` + +type ListFolderRow struct { + Name string + Count int64 + Description string +} + +func (q *Queries) ListFolder(ctx context.Context) ([]ListFolderRow, error) { + rows, err := q.db.QueryContext(ctx, listFolder) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListFolderRow + for rows.Next() { + var i ListFolderRow + if err := rows.Scan(&i.Name, &i.Count, &i.Description); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listFolderForAdmin = `-- name: ListFolderForAdmin :many +SELECT f.name, count(m.id) as count, f.description +FROM folders AS f LEFT JOIN messages AS m ON f.name = m.folder +GROUP By f.name +ORDER BY f.name +` + +type ListFolderForAdminRow struct { + Name string + Count int64 + Description string +} + +func (q *Queries) ListFolderForAdmin(ctx context.Context) ([]ListFolderForAdminRow, error) { + rows, err := q.db.QueryContext(ctx, listFolderForAdmin) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListFolderForAdminRow + for rows.Next() { + var i ListFolderForAdminRow + if err := rows.Scan(&i.Name, &i.Count, &i.Description); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/storage/generate.go b/storage/generate.go new file mode 100644 index 0000000..f590de0 --- /dev/null +++ b/storage/generate.go @@ -0,0 +1,3 @@ +package storage + +//go:generate go tool github.com/sqlc-dev/sqlc/cmd/sqlc generate diff --git a/storage/messages.sql.go b/storage/messages.sql.go new file mode 100644 index 0000000..62b82a4 --- /dev/null +++ b/storage/messages.sql.go @@ -0,0 +1,124 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: messages.sql + +package storage + +import ( + "context" + "database/sql" + "time" +) + +const createMessage = `-- name: CreateMessage :exec +INSERT INTO messages ( + id, folder, author, subject, message, permanent, shutdown, expiration +) VALUES ( + (SELECT COALESCE(MAX(id), 0) + 1 FROM messages AS m WHERE m.folder = ?), + ?, ?, ?, ?, ?, ?, ?) +` + +type CreateMessageParams struct { + Folder sql.NullString + Folder_2 sql.NullString + Author sql.NullString + Subject string + Message string + Permanent int64 + Shutdown int64 + Expiration time.Time +} + +func (q *Queries) CreateMessage(ctx context.Context, arg CreateMessageParams) error { + _, err := q.db.ExecContext(ctx, createMessage, + arg.Folder, + arg.Folder_2, + arg.Author, + arg.Subject, + arg.Message, + arg.Permanent, + arg.Shutdown, + arg.Expiration, + ) + return err +} + +const listMessages = `-- name: ListMessages :many +SELECT id, folder, author, subject, message, expiration, create_at, update_at +FROM messages +WHERE folder = ? +` + +type ListMessagesRow struct { + ID int64 + Folder sql.NullString + Author sql.NullString + Subject string + Message string + Expiration time.Time + CreateAt time.Time + UpdateAt time.Time +} + +func (q *Queries) ListMessages(ctx context.Context, folder sql.NullString) ([]ListMessagesRow, error) { + rows, err := q.db.QueryContext(ctx, listMessages, folder) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListMessagesRow + for rows.Next() { + var i ListMessagesRow + if err := rows.Scan( + &i.ID, + &i.Folder, + &i.Author, + &i.Subject, + &i.Message, + &i.Expiration, + &i.CreateAt, + &i.UpdateAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const markMessage = `-- name: MarkMessage :exec +INSERT INTO mark (login, folder, msgid) VALUES (?, ?, ?) +` + +type MarkMessageParams struct { + Login sql.NullString + Folder sql.NullString + Msgid sql.NullInt64 +} + +func (q *Queries) MarkMessage(ctx context.Context, arg MarkMessageParams) error { + _, err := q.db.ExecContext(ctx, markMessage, arg.Login, arg.Folder, arg.Msgid) + return err +} + +const setMessageSeen = `-- name: SetMessageSeen :exec +INSERT INTO seen (login, folder, msgid) VALUES (?, ?, ?) +` + +type SetMessageSeenParams struct { + Login sql.NullString + Folder sql.NullString + Msgid sql.NullInt64 +} + +func (q *Queries) SetMessageSeen(ctx context.Context, arg SetMessageSeenParams) error { + _, err := q.db.ExecContext(ctx, setMessageSeen, arg.Login, arg.Folder, arg.Msgid) + return err +} diff --git a/storage/migrations/1_create_table.down.sql b/storage/migrations/1_create_table.down.sql new file mode 100644 index 0000000..8a1a47e --- /dev/null +++ b/storage/migrations/1_create_table.down.sql @@ -0,0 +1,21 @@ +--- Dropped in reverse order to deal with foreign keys. +DROP TABLE mark; +DROP TABLE read; + +DROP INDEX messages_idx_expiration; +DROP INDEX messages_idx_shutdown; +DROP TABLE messages; + +DROP TRIGGER co_owners_after_update_update_at; +DROP TABLE co_owners; + +DROP TRIGGER folders_before_delete_protect; +DROP TRIGGER folders_after_update_update_at; +DROP TRIGGER folders_before_update_validate; +DROP TRIGGER folders_before_insert_validate; +DROP TABLE folders; + +DROP TRIGGER users_before_delete_protect; +DROP TRIGGER users_before_update_protect; +DROP TRIGGER users_after_update_update_at; +DROP TABLE users; diff --git a/storage/migrations/1_create_table.up.sql b/storage/migrations/1_create_table.up.sql new file mode 100644 index 0000000..0aafda9 --- /dev/null +++ b/storage/migrations/1_create_table.up.sql @@ -0,0 +1,161 @@ +CREATE TABLE users ( + login VARCHAR(12) NOT NULL PRIMARY KEY, + name VARCHAR(53) NOT NULL, + admin INT DEFAULT 0 NOT NULL, + moderator INT DEFAULT 0 NOT NULL, + alert INT NOT NULL DEFAULT 0, --- 0=no, 1=brief, 2=readnew + disabled INT DEFAULT 0 NOT NULL, + last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + create_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + update_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL +) WITHOUT ROWID; + +CREATE TRIGGER users_after_update_update_at + AFTER UPDATE ON users FOR EACH ROW + WHEN NEW.update_at = OLD.update_at --- avoid infinite loop +BEGIN + UPDATE users SET update_at=CURRENT_TIMESTAMP WHERE login=NEW.login; +END; + +CREATE TRIGGER users_before_update_protect + AFTER UPDATE ON users FOR EACH ROW + WHEN OLD.login = 'SYSTEM' AND (NEW.login != OLD.login OR NEW.admin != 1) +BEGIN + SELECT RAISE (ABORT, 'SYSTEM user is protected'); +END; + +CREATE TRIGGER users_before_delete_protect + BEFORE DELETE on users FOR EACH ROW + WHEN OLD.login = 'SYSTEM' +BEGIN + SELECT RAISE (ABORT, 'SYSTEM user is protected'); +END; + +CREATE TABLE folders ( + name VARCHAR(25) NOT NULL PRIMARY KEY, + always INT DEFAULT 0 NOT NULL, + brief INT DEFAULT 0 NOT NULL, + description VARCHAR(53) DEFAULT 0 NOT NULL, + notify INT DEFAULT 0 NOT NULL, + readnew INT DEFAULT 0 NOT NULL, + shownew INT DEFAULT 0 NOT NULL, + system INT DEFAULT 0 NOT NULL, + expire INT DEFAULT 14 NOT NULL, + --- public=0, semiprivate=1, private=2 + visibility INT DEFAULT 0 NOT NULL, + create_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + update_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL +) WITHOUT ROWID; + +CREATE TRIGGER folders_before_insert_validate + BEFORE INSERT on folders +BEGIN + SELECT + CASE + WHEN NEW.name != UPPER(NEW.name) OR NEW.name GLOB '*[^A-Z0-9_-]*' THEN + RAISE (ABORT, 'Invalid folder name') + END; +END; + +CREATE TRIGGER folders_before_update_validate + BEFORE UPDATE on folders +BEGIN + SELECT + CASE + WHEN NEW.name != UPPER(NEW.name) OR NEW.name GLOB '*[^A-Z0-9_-]*' THEN + RAISE (ABORT, 'Invalid folder name') + WHEN OLD.name = 'GENERAL' AND OLD.name != NEW.name THEN + RAISE (ABORT, 'GENERAL folder is protected') + END; +END; + +CREATE TRIGGER folders_after_update_update_at + AFTER UPDATE ON folders FOR EACH ROW + WHEN NEW.update_at = OLD.update_at --- avoid infinite loop +BEGIN + UPDATE folders SET update_at=CURRENT_TIMESTAMP WHERE name=NEW.name; +END; + +CREATE TRIGGER folders_before_delete_protect + BEFORE DELETE on folders FOR EACH ROW + WHEN OLD.name = 'GENERAL' +BEGIN + SELECT RAISE (ABORT, 'GENERAL folder is protected'); +END; + +CREATE TABLE owners ( + folder VARCHAR(25) REFERENCES folders(name) ON DELETE CASCADE ON UPDATE CASCADE, + owner VARCHAR(25) REFERENCES users(login) ON UPDATE CASCADE, + create_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + update_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + PRIMARY KEY (folder, owner) +) WITHOUT ROWID; + +CREATE TRIGGER owners_after_update_update_at + AFTER UPDATE ON owners FOR EACH ROW + WHEN NEW.update_at = OLD.update_at --- avoid infinite loop +BEGIN + UPDATE owners SET update_at=CURRENT_TIMESTAMP WHERE folder=NEW.folder AND owner=NEW.owner; +END; + +CREATE TABLE messages ( + id INT NOT NULL, + folder VARCHAR(25) REFERENCES folders(name) ON DELETE CASCADE ON UPDATE CASCADE, + author VARCHAR(25) REFERENCES users(login) ON UPDATE CASCADE, + subject VARCHAR(53) NOT NULL, + message TEXT NOT NULL, + permanent INT DEFAULT 0 NOT NULL, + shutdown INT DEFAULT 0 NOT NULL, + expiration TIMESTAMP NOT NULL, + create_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + update_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + PRIMARY KEY (id, folder) +) WITHOUT ROWID; +CREATE INDEX messages_idx_shutdown ON messages(shutdown); +CREATE INDEX messages_idx_expiration ON messages(expiration); + +CREATE TABLE seen ( + login VARCHAR(25) REFERENCES users(login) ON DELETE CASCADE ON UPDATE CASCADE, + folder VARCHAR(25) REFERENCES folders(name) ON DELETE CASCADE ON UPDATE CASCADE, + msgid INT, + PRIMARY KEY (folder, login, msgid), + CONSTRAINT read_fk_id_folder + FOREIGN KEY (msgid, folder) + REFERENCES messages(id, folder) + ON DELETE CASCADE + ON UPDATE CASCADE +) WITHOUT ROWID; + +CREATE TABLE mark ( + login VARCHAR(25) REFERENCES users(login) ON DELETE CASCADE ON UPDATE CASCADE, + folder VARCHAR(25) REFERENCES folders(name) ON DELETE CASCADE ON UPDATE CASCADE, + msgid INT, + PRIMARY KEY (folder, login, msgid), + CONSTRAINT mark_fk_id_folder + FOREIGN KEY (msgid, folder) + REFERENCES messages(id, folder) + ON DELETE CASCADE + ON UPDATE CASCADE +) WITHOUT ROWID; + +CREATE TABLE access ( + login VARCHAR(25) REFERENCES users(login) ON DELETE CASCADE ON UPDATE CASCADE, + folder VARCHAR(25) REFERENCES folders(name) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY (login, folder) +) WITHOUT ROWID; + +--- User folder configs. +CREATE TABLE config ( + login VARCHAR(25) REFERENCES users(login) ON DELETE CASCADE ON UPDATE CASCADE, + folder VARCHAR(25) REFERENCES folders(name) ON DELETE CASCADE ON UPDATE CASCADE, + always INT NOT NULL DEFAULT 0, + alert INT NOT NULL DEFAULT 0, --- 0=no, 1=brief, 2=readnew + PRIMARY KEY (login, folder) +) WITHOUT ROWID; + +--- System configs. +CREATE TABLE system ( + name VARCHAR(12) NOT NULL PRIMARY KEY, + default_expire INT NOT NULL DEFAULT -1, + expire_limit INT NOT NULL DEFAULT -1 +); diff --git a/storage/models.go b/storage/models.go new file mode 100644 index 0000000..13279ef --- /dev/null +++ b/storage/models.go @@ -0,0 +1,87 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 + +package storage + +import ( + "database/sql" + "time" +) + +type Access struct { + Login sql.NullString + Folder sql.NullString +} + +type Config struct { + Login sql.NullString + Folder sql.NullString + Always int64 + Alert int64 +} + +type Folder struct { + Name string + Always int64 + Brief int64 + Description string + Notify int64 + Readnew int64 + Shownew int64 + System int64 + Expire int64 + Visibility int64 + CreateAt time.Time + UpdateAt time.Time +} + +type Mark struct { + Login sql.NullString + Folder sql.NullString + Msgid sql.NullInt64 +} + +type Message struct { + ID int64 + Folder sql.NullString + Author sql.NullString + Subject string + Message string + Permanent int64 + Shutdown int64 + Expiration time.Time + CreateAt time.Time + UpdateAt time.Time +} + +type Owner struct { + Folder sql.NullString + Owner sql.NullString + CreateAt time.Time + UpdateAt time.Time +} + +type Seen struct { + Login sql.NullString + Folder sql.NullString + Msgid sql.NullInt64 +} + +type System struct { + Name string + DefaultExpire int64 + ExpireLimit int64 +} + +type User struct { + Login string + Name string + Admin int64 + Moderator int64 + Alert int64 + Disabled int64 + LastLogin time.Time + CreateAt time.Time + UpdateAt time.Time +} diff --git a/storage/queries/folders.sql b/storage/queries/folders.sql new file mode 100644 index 0000000..70d6749 --- /dev/null +++ b/storage/queries/folders.sql @@ -0,0 +1,41 @@ +-- name: CreateFolder :exec + INSERT INTO folders ( + name, always, brief, description, notify, readnew, shownew, system, + expire, visibility +) VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, ? +); + +-- name: ListFolderForAdmin :many +SELECT f.name, count(m.id) as count, f.description +FROM folders AS f LEFT JOIN messages AS m ON f.name = m.folder +GROUP By f.name +ORDER BY f.name; + +-- name: ListFolder :many +SELECT f.name, count(m.id) as count, f.description +FROM folders AS f LEFT JOIN messages AS m ON f.name = m.folder +GROUP By f.name +ORDER BY f.name; + +-- name: FindFolderExact :one +SELECT name FROM folders where name = ?; + +-- name: FindFolderPrefix :one +SELECT name FROM folders where name LIKE ? +ORDER BY name +LIMIT 1; + +-- name: IsFolderAccess :one +SELECT 1 FROM folders AS f LEFT JOIN owners AS c ON f.name = c.folder + WHERE f.name = ? AND (f.visibility = 0 OR c.OWNER = ?); + +-- name: IsFolderOwner :one +SELECT 1 FROM folders AS f LEFT JOIN owners AS c ON f.name = c.folder + WHERE f.name = ? AND c.OWNER = ?; + +-- name: DeleteFolder :exec +DELETE FROM folders WHERE name=?; + +-- name: GetFolderExpire :one +SELECT expire FROM folders WHERE name = ?; diff --git a/storage/queries/messages.sql b/storage/queries/messages.sql new file mode 100644 index 0000000..2f87c68 --- /dev/null +++ b/storage/queries/messages.sql @@ -0,0 +1,17 @@ +-- name: CreateMessage :exec +INSERT INTO messages ( + id, folder, author, subject, message, permanent, shutdown, expiration +) VALUES ( + (SELECT COALESCE(MAX(id), 0) + 1 FROM messages AS m WHERE m.folder = ?), + ?, ?, ?, ?, ?, ?, ?); + +-- name: SetMessageSeen :exec +INSERT INTO seen (login, folder, msgid) VALUES (?, ?, ?); + +-- name: MarkMessage :exec +INSERT INTO mark (login, folder, msgid) VALUES (?, ?, ?); + +-- name: ListMessages :many +SELECT id, folder, author, subject, message, expiration, create_at, update_at +FROM messages +WHERE folder = ?; diff --git a/storage/queries/seed.sql b/storage/queries/seed.sql new file mode 100644 index 0000000..a1f0a31 --- /dev/null +++ b/storage/queries/seed.sql @@ -0,0 +1,10 @@ +-- name: SeedUserSystem :exec + INSERT INTO users (login, name, admin) + VALUES ('SYSTEM', 'System User', 1); + +-- name: SeedFolderGeneral :exec + INSERT INTO folders (name, description, system, shownew) + VALUES ('GENERAL', 'Default general bulletin folder.', 1, 1); + +-- name: SeedGeneralOwner :exec + INSERT INTO owners (folder, owner) VALUES ('GENERAL', 'SYSTEM'); diff --git a/storage/queries/users.sql b/storage/queries/users.sql new file mode 100644 index 0000000..50ca0c6 --- /dev/null +++ b/storage/queries/users.sql @@ -0,0 +1,8 @@ +-- name: GetUser :one +SELECT login, name, admin, disabled FROM users WHERE login = ?; + +-- name: AddUser :exec +INSERT INTO users (login, name, admin) VALUES (?, ?, ?); + +-- name: IsUserAdmin :one +SELECT admin FROM users WHERE login = ?; diff --git a/storage/seed.sql.go b/storage/seed.sql.go new file mode 100644 index 0000000..a52da4f --- /dev/null +++ b/storage/seed.sql.go @@ -0,0 +1,39 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: seed.sql + +package storage + +import ( + "context" +) + +const seedFolderGeneral = `-- name: SeedFolderGeneral :exec + INSERT INTO folders (name, description, system, shownew) + VALUES ('GENERAL', 'Default general bulletin folder.', 1, 1) +` + +func (q *Queries) SeedFolderGeneral(ctx context.Context) error { + _, err := q.db.ExecContext(ctx, seedFolderGeneral) + return err +} + +const seedGeneralOwner = `-- name: SeedGeneralOwner :exec + INSERT INTO owners (folder, owner) VALUES ('GENERAL', 'SYSTEM') +` + +func (q *Queries) SeedGeneralOwner(ctx context.Context) error { + _, err := q.db.ExecContext(ctx, seedGeneralOwner) + return err +} + +const seedUserSystem = `-- name: SeedUserSystem :exec + INSERT INTO users (login, name, admin) + VALUES ('SYSTEM', 'System User', 1) +` + +func (q *Queries) SeedUserSystem(ctx context.Context) error { + _, err := q.db.ExecContext(ctx, seedUserSystem) + return err +} diff --git a/storage/sqlc.yaml b/storage/sqlc.yaml new file mode 100644 index 0000000..a870855 --- /dev/null +++ b/storage/sqlc.yaml @@ -0,0 +1,10 @@ +--- +version: "2" +sql: + - engine: "sqlite" + queries: "queries/" + schema: "migrations/" + gen: + go: + package: "storage" + out: "." diff --git a/storage/users.sql.go b/storage/users.sql.go new file mode 100644 index 0000000..d51be82 --- /dev/null +++ b/storage/users.sql.go @@ -0,0 +1,59 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: users.sql + +package storage + +import ( + "context" +) + +const addUser = `-- name: AddUser :exec +INSERT INTO users (login, name, admin) VALUES (?, ?, ?) +` + +type AddUserParams struct { + Login string + Name string + Admin int64 +} + +func (q *Queries) AddUser(ctx context.Context, arg AddUserParams) error { + _, err := q.db.ExecContext(ctx, addUser, arg.Login, arg.Name, arg.Admin) + return err +} + +const getUser = `-- name: GetUser :one +SELECT login, name, admin, disabled FROM users WHERE login = ? +` + +type GetUserRow struct { + Login string + Name string + Admin int64 + Disabled int64 +} + +func (q *Queries) GetUser(ctx context.Context, login string) (GetUserRow, error) { + row := q.db.QueryRowContext(ctx, getUser, login) + var i GetUserRow + err := row.Scan( + &i.Login, + &i.Name, + &i.Admin, + &i.Disabled, + ) + return i, err +} + +const isUserAdmin = `-- name: IsUserAdmin :one +SELECT admin FROM users WHERE login = ? +` + +func (q *Queries) IsUserAdmin(ctx context.Context, login string) (int64, error) { + row := q.db.QueryRowContext(ctx, isUserAdmin, login) + var admin int64 + err := row.Scan(&admin) + return admin, err +} -- GitLab