diff options
| author | EuAndreh <eu@euandre.org> | 2025-09-20 15:45:46 -0300 |
|---|---|---|
| committer | EuAndreh <eu@euandre.org> | 2025-09-20 15:50:05 -0300 |
| commit | aee466b543b9d87a0206a59b1ade4686aa1f459e (patch) | |
| tree | 386b25e72df3e44acc052bfc2806523d190f8de3 | |
| parent | meta.capim: Incorporate previous long-description file (diff) | |
| download | papod-aee466b543b9d87a0206a59b1ade4686aa1f459e.tar.gz papod-aee466b543b9d87a0206a59b1ade4686aa1f459e.tar.xz | |
Remove SQLite code
| -rw-r--r-- | Makefile | 7 | ||||
| -rw-r--r-- | src/papod.go | 3759 | ||||
| -rw-r--r-- | tests/papod.go | 5374 |
3 files changed, 143 insertions, 8997 deletions
@@ -16,7 +16,7 @@ MANDIR = $(SHAREDIR)/man EXEC = ./ ## Where to store the installation. Empty by default. DESTDIR = -LDLIBS = --static -lscrypt-kdf -lsqlite3 -lm +LDLIBS = --static -lscrypt-kdf GOCFLAGS = -I $(GOLIBDIR) GOLDFLAGS = -L $(GOLIBDIR) N = `nproc` @@ -108,10 +108,6 @@ $(manpages.XX.N.adoc): po/doc/po4a.cfg po4a --no-update --translate-only $@ po/doc/po4a.cfg -.PRECIOUS: tests/queries.sql -tests/queries.sql: tests/main.bin ALWAYS - env TESTING_DUMP_SQL_QUERIES=1 $(EXEC)tests/main.bin | diff -U10 $@ - - tests.bin-check = \ tests/main.bin-check \ $(functional/main.go:.go=.bin-check) \ @@ -120,7 +116,6 @@ $(tests.bin-check): $(EXEC)$*.bin check-unit: $(tests.bin-check) -check-unit: tests/queries.sql integration-tests = \ diff --git a/src/papod.go b/src/papod.go index b6544da..5bf2467 100644 --- a/src/papod.go +++ b/src/papod.go @@ -3,8 +3,8 @@ package papod import ( "bufio" "bytes" - "database/sql" "errors" + "flag" "fmt" "io" "log/slog" @@ -13,11 +13,9 @@ import ( "regexp" "strings" "sync" - "time" + "acude" "cracha" - "fiinha" - "golite" "uuid" "pds" "stm" @@ -27,27 +25,11 @@ import ( -const ( - defaultPrefix = "papod" - rollbackErrorFmt = "rollback error: %w; while executing: %w" - - NEW_CHANNEL = "new-channel" - - pingFrequency = time.Duration(30) * time.Second - pongMaxLatency = time.Duration(5) * time.Second -) - - - -type dbconfigT struct{ - shared *sql.DB - dbpath string - prefix string +type newUserT struct{ } -type queryT struct{ - write string - read string +type userT struct{ + username string } type queriesT struct{ @@ -55,156 +37,7 @@ type queriesT struct{ userByUUID func(uuid.UUID) (userT, error) updateUser func(userT) error deleteUser func(uuid.UUID) error - addNetwork func(userT, newNetworkT, uuid.UUID) (networkT, error) - getNetwork func(userT, uuid.UUID) (networkT, error) - networks func(userT, func(networkT) error) error - setNetwork func(memberT, networkT) error - nipNetwork func(memberT) error - membership func(userT, networkT) (memberT, error) - addMember func(memberT, newMemberT) (memberT, error) - addRole func(memberT, string, memberT) error - dropRole func(memberT, string, memberT) error - showMember func(memberT, uuid.UUID) (memberT, error) - members func(memberT, func(memberT) error) error - editMember func(memberT, memberT) error - dropMember func(memberT, uuid.UUID) error - addChannel func(memberT, newChannelT) (channelT, error) - chanByName func(memberT, string) (channelT, error) - channels func(memberT, func(channelT) error) error - setChannel func(memberT, channelT) error - endChannel func(memberT, uuid.UUID) error - join func(memberT, uuid.UUID) error - part func(memberT, channelT) error - names func(memberT, uuid.UUID, func(memberT) error) error - addEvent func(newEventT) (eventT, error) - allAfter func(memberT, uuid.UUID, func(eventT) error) error - logMessage func(userT, messageT) error - close func() error -} - -type newUserT struct{ - uuid uuid.UUID - username string - displayName string -} - -type userT struct{ - id int64 - timestamp time.Time - uuid uuid.UUID - username string - displayName string - pictureID *uuid.UUID -} - -type NetworkType string -const ( - NetworkType_Public NetworkType = "public" - NetworkType_Private NetworkType = "private" - NetworkType_Unlisted NetworkType = "unlisted" -) - -type MemberStatus string -const ( - MemberStatus_Active MemberStatus = "active" - MemberStatus_Inactive MemberStatus = "inactive" - MemberStatus_Removed MemberStatus = "removed" -) - -type newNetworkT struct{ - uuid uuid.UUID - name string - description string - type_ NetworkType -} - -type networkT struct{ - id int64 - timestamp time.Time - uuid uuid.UUID - name string - description string - type_ NetworkType -} - -type newMemberT struct{ - userID uuid.UUID - memberID uuid.UUID - username string -} - -type memberT struct{ - id int64 - timestamp time.Time - uuid uuid.UUID - username string - displayName string - pictureID *uuid.UUID - status MemberStatus - roles []string -} - -type newChannelT struct{ - id int64 - timestamp time.Time - uuid uuid.UUID - publicName *string - label string - description string - virtual bool -} - -type channelT struct{ - id int64 - timestamp time.Time - uuid uuid.UUID - publicName *string - label string - description string - virtual bool -} - -type SourceType string -const ( - SourceType_Logon SourceType = "logon" -) - -type sourceT struct{ - uuid uuid.UUID - type_ SourceType - metadata *map[string]interface{} -} - -type EventType string -const ( - EventType_UserJoin EventType = "user-join" - EventType_UserMessage EventType = "user-message" -) - -type newEventT struct{ - eventID uuid.UUID - channelID uuid.UUID - source sourceT - type_ EventType - payload string - metadata *map[string]interface{} -} - -type eventT struct{ - id int64 - timestamp time.Time - uuid uuid.UUID - channelID uuid.UUID - source sourceT - type_ EventType - payload string - metadata *map[string]interface{} -} - -type eventEntryT struct{ - event eventT - previous *eventT - isFirst bool + createNetwork func(userT, newNetworkT, uuid.UUID) (networkT, error) } type messageT struct{ @@ -219,19 +52,22 @@ type replyT struct{ params []string } +type taskT struct{ +} + +type actionT struct{ + replies []replyT + shouldClose bool + err error + task []taskT +} + type listenersT struct{ daemon net.Listener commander net.Listener close func() error } -type consumerT struct{ - topic string - name string - // FIXME: use generic to avoid circular reference? - handlerFn func(papodT) func(fiinha.Message) error -} - type netConnI interface{ Write(p []byte) (n int, err error) Close() error @@ -252,3264 +88,48 @@ type metricsT struct{ sentReply func(...any) } -type stateMutableT struct{ - // disconnect func(*connectionT) - subscribe func(string, []string) -} - type stateT struct{ members *pds.Map[string, []string] users *pds.Map[string, []uuid.UUID] connections *pds.Map[uuid.UUID, connectionT] } -// TODO: key for members should be the channelID, not its name -type stateMutableDataT struct{ - connections map[uuid.UUID]*connectionT - users map[string][]uuid.UUID - members map[string]map[string][]uuid.UUID -} - type papodT struct{ - auth cracha.IAuth - queue fiinha.IQueue - queries queriesT + acude acude.AcudeI + cracha cracha.CrachaI listeners listenersT - consumers []consumerT - stateMutable stateMutableT - state *stm.AtomT[*stateT] + state *stm.AtomT[stateT] metrics metricsT - // logger g.Logger } -type IPapod interface{ +type PapoI interface{ Start() error Close() error } - - -func serialized[A any, B any](callback func(...A) B) (func(...A) B, func()) { - in := make(chan []A) - out := make(chan B) - - closed := false - var ( - closeWg sync.WaitGroup - closeMutex sync.Mutex - ) - closeWg.Add(1) - - go func() { - for input := range in { - out <- callback(input...) - } - close(out) - closeWg.Done() - }() - - fn := func(input ...A) B { - in <- input - return (<- out) - } - - closeFn := func() { - closeMutex.Lock() - defer closeMutex.Unlock() - if closed { - return - } - close(in) - closed = true - closeWg.Wait() - } - - return fn, closeFn -} - -func execSerialized(query string, db *sql.DB) (func(...any) error, func()) { - return serialized(func(args ...any) error { - return inTx(db, func(tx *sql.Tx) error { - _, err := tx.Exec(query, args...) - return err - }) - }) -} - -func tryRollback(tx *sql.Tx, err error) error { - rollbackErr := tx.Rollback() - if rollbackErr != nil { - return fmt.Errorf( - rollbackErrorFmt, - rollbackErr, - err, - ) - } - - return err +type argsT struct{ + allArgs []string + subArgs []string + baseDir string + tag string } -func inTx(db *sql.DB, fn func(*sql.Tx) error) error { - tx, err := db.Begin() - if err != nil { - return err - } - - err = fn(tx) - if err != nil { - return tryRollback(tx, err) - } - - err = tx.Commit() - if err != nil { - return tryRollback(tx, err) - } - - return nil +type envT struct{ + args argsT + in io.Reader + out io.Writer + err io.Writer } -/// "papod_users".uuid is the same as cracha_users.uuid. Not a foreign key to -/// allow them to live in different physical locations. Adding it here feels -/// less like an optimization related decision, and more of a coupling one. The -/// way that New() works now uses the same databasePath for the fiinha.IQueue -/// *and* cracha.IAuth, but cracha in no way exposes where it stores the user -/// UUID or how the it is handled. This has similarities to how events here -/// don't reference the fiinha.Message.ID via foreign keys either. They're -/// treated only as opaque IDs. -func createTablesSQL(prefix string) queryT { - const tmpl_write = ` - -- TODO: unconfirmed premise: statements within a trigger are - -- part of the transaction that caused it, and so are - -- atomic. - -- See also: - -- https://stackoverflow.com/questions/77441888/ - -- https://stackoverflow.com/questions/30511116/ - - CREATE TABLE IF NOT EXISTS "%s_users" ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - timestamp TEXT NOT NULL DEFAULT ( - %s - ), - -- provided by cracha - user_uuid BLOB NOT NULL UNIQUE, - username TEXT NOT NULL, - display_name TEXT NOT NULL, - picture_uuid BLOB UNIQUE, - deleted INT NOT NULL CHECK(deleted IN (0, 1)) - ) STRICT; - CREATE TABLE IF NOT EXISTS "%s_user_changes" ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - timestamp TEXT NOT NULL DEFAULT ( - %s - ), - user_id INTEGER NOT NULL, - attribute TEXT NOT NULL CHECK( - attribute IN ( - 'username', - 'display_name', - 'picture_uuid', - 'deleted' - ) - ), - value_text TEXT, - value_blob BLOB, - value_bool INT CHECK(value_bool IN (0, 1)), - op INT NOT NULL CHECK(op IN (0, 1)) - ) STRICT; - CREATE TRIGGER IF NOT EXISTS "%s_user_new" - AFTER INSERT ON "%s_users" - BEGIN - INSERT INTO "%s_user_changes" ( - user_id, attribute, value_text, op - ) VALUES - (NEW.id, 'username', NEW.username, true), - (NEW.id, 'display_name', NEW.display_name, true) - ; - INSERT INTO "%s_user_changes" ( - user_id, attribute, value_bool, op - ) VALUES - (NEW.id, 'deleted', NEW.deleted, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_user_new_picture_uuid" - AFTER INSERT ON "%s_users" - WHEN NEW.picture_uuid IS NOT NULL - BEGIN - INSERT INTO "%s_user_changes" ( - user_id, attribute, value_blob, op - ) VALUES - (NEW.id, 'picture_uuid', NEW.picture_uuid, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_user_update_username" - AFTER UPDATE ON "%s_users" - WHEN OLD.username != NEW.username - BEGIN - INSERT INTO "%s_user_changes" ( - user_id, attribute, value_text, op - ) VALUES - (NEW.id, 'username', OLD.username, false), - (NEW.id, 'username', NEW.username, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_user_update_display_name" - AFTER UPDATE ON "%s_users" - WHEN OLD.display_name != NEW.display_name - BEGIN - INSERT INTO "%s_user_changes" ( - user_id, attribute, value_text, op - ) VALUES - (NEW.id, 'display_name', OLD.display_name, false), - (NEW.id, 'display_name', NEW.display_name, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_user_add_picture_uuid" - AFTER UPDATE ON "%s_users" - WHEN ( - OLD.picture_uuid IS NULL AND - NEW.picture_uuid IS NOT NULL - ) - BEGIN - INSERT INTO "%s_user_changes" ( - user_id, attribute, value_blob, op - ) VALUES - (NEW.id, 'picture_uuid', NEW.picture_uuid, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_user_remove_picture_uuid" - AFTER UPDATE ON "%s_users" - WHEN ( - OLD.picture_uuid IS NOT NULL AND - NEW.picture_uuid IS NULL - ) - BEGIN - INSERT INTO "%s_user_changes" ( - user_id, attribute, value_blob, op - ) VALUES - (NEW.id, 'picture_uuid', OLD.picture_uuid, false) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_user_update_picture_uuid" - AFTER UPDATE ON "%s_users" - WHEN ( - OLD.picture_uuid IS NOT NULL AND - NEW.picture_uuid IS NOT NULL AND - OLD.picture_uuid != NEW.picture_uuid - ) - BEGIN - INSERT INTO "%s_user_changes" ( - user_id, attribute, value_blob, op - ) VALUES - (NEW.id, 'picture_uuid', OLD.picture_uuid, false), - (NEW.id, 'picture_uuid', NEW.picture_uuid, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_user_update_deleted" - AFTER UPDATE ON "%s_users" - WHEN OLD.deleted != NEW.deleted - BEGIN - INSERT INTO "%s_user_changes" ( - user_id, attribute, value_bool, op - ) VALUES - (NEW.id, 'deleted', OLD.deleted, false), - (NEW.id, 'deleted', NEW.deleted, true) - ; - END; - - CREATE TABLE IF NOT EXISTS "%s_sessions" ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - timestamp TEXT NOT NULL DEFAULT ( - %s - ), - -- provided by cracha - session_uuid BLOB NOT NULL UNIQUE, - user_id INTEGER NOT NULL - REFERENCES "%s_users"(id), - finished_at TEXT - ); - CREATE TABLE IF NOT EXISTS "%s_connections" ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - timestamp TEXT NOT NULL DEFAULT ( - %s - ), - uuid BLOB NOT NULL UNIQUE, - finished_at TEXT - ); - CREATE TABLE IF NOT EXISTS "%s_logons" ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - timestamp TEXT NOT NULL DEFAULT ( - %s - ), - session_id INTEGER NOT NULL - REFERENCES "%s_sessions"(id), - connection_id INTEGER NOT NULL - REFERENCES "%s_connections"(id), - UNIQUE (session_id, connection_id) - ) STRICT; - - CREATE TABLE IF NOT EXISTS "%s_networks" ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - timestamp TEXT NOT NULL DEFAULT ( - %s - ), - uuid BLOB NOT NULL UNIQUE, - name TEXT NOT NULL, - description TEXT NOT NULL, - type TEXT NOT NULL CHECK( - type IN ('public', 'private', 'unlisted') - ), - deleted INT NOT NULL CHECK(deleted IN (0, 1)) - ) STRICT; - CREATE INDEX IF NOT EXISTS "%s_networks_type" - ON "%s_networks"(type); - CREATE TABLE IF NOT EXISTS "%s_network_changes" ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - timestamp TEXT NOT NULL DEFAULT ( - %s - ), - network_id INTEGER NOT NULL, - attribute TEXT NOT NULL CHECK( - attribute IN ( - 'name', - 'description', - 'type', - 'deleted', - 'logon_id' -- FIXME - ) - ), - value TEXT NOT NULL, - op INT NOT NULL CHECK(op IN (0, 1)) - ) STRICT; - CREATE TRIGGER IF NOT EXISTS "%s_network_new" - AFTER INSERT ON "%s_networks" - BEGIN - INSERT INTO "%s_network_changes" ( - network_id, attribute, value, op - ) VALUES - (NEW.id, 'name', NEW.name, true), - (NEW.id, 'description', NEW.description, true), - (NEW.id, 'type', NEW.type, true), - (NEW.id, 'deleted', NEW.deleted, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_network_update_name" - AFTER UPDATE ON "%s_networks" - WHEN OLD.name != NEW.name - BEGIN - INSERT INTO "%s_network_changes" ( - network_id, attribute, value, op - ) VALUES - (NEW.id, 'name', OLD.name, false), - (NEW.id, 'name', NEW.name, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_network_update_description" - AFTER UPDATE ON "%s_networks" - WHEN OLD.description != NEW.description - BEGIN - INSERT INTO "%s_network_changes" ( - network_id, attribute, value, op - ) VALUES - (NEW.id, 'description', OLD.description, false), - (NEW.id, 'description', NEW.description, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_network_update_type" - AFTER UPDATE ON "%s_networks" - WHEN OLD.description != NEW.description - BEGIN - INSERT INTO "%s_network_changes" ( - network_id, attribute, value, op - ) VALUES - (NEW.id, 'type', OLD.type, false), - (NEW.id, 'type', NEW.type, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_network_update_deleted" - AFTER UPDATE ON "%s_networks" - WHEN OLD.deleted != NEW.deleted - BEGIN - INSERT INTO "%s_network_changes" ( - network_id, attribute, value, op - ) VALUES - (NEW.id, 'deleted', OLD.deleted, false), - (NEW.id, 'deleted', NEW.deleted, true) - ; - END; - - CREATE TABLE IF NOT EXISTS "%s_members" ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - timestamp TEXT NOT NULL DEFAULT ( - %s - ), - uuid BLOB NOT NULL UNIQUE, - network_id INTEGER NOT NULL - REFERENCES "%s_networks"(id), - user_id INTEGER NOT NULL, - username TEXT NOT NULL, - display_name TEXT NOT NULL, - picture_uuid BLOB UNIQUE, - status TEXT NOT NULL CHECK( - status IN ('active', 'inactive', 'removed') - ), - active_uniq TEXT CHECK( - active_uniq IN ('active', NULL) - ), - UNIQUE (network_id, username, active_uniq), - UNIQUE (network_id, user_id) - ) STRICT; - CREATE TABLE IF NOT EXISTS "%s_member_changes" ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - timestamp TEXT NOT NULL DEFAULT ( - %s - ), - member_id INTEGER NOT NULL, - attribute TEXT NOT NULL CHECK( - attribute IN ( - 'username', - 'display_name', - 'picture_uuid', - 'status', - 'logon_id' -- FIXME - ) - ), - value_text TEXT, - value_blob BLOB, - op INT NOT NULL CHECK(op IN (0, 1)) - ) STRICT; - CREATE TRIGGER IF NOT EXISTS "%s_member_new" - AFTER INSERT ON "%s_members" - BEGIN - INSERT INTO "%s_member_changes" ( - member_id, attribute, value_text, op - ) VALUES - (NEW.id, 'username', NEW.username, true), - (NEW.id, 'display_name', NEW.display_name, true), - (NEW.id, 'status', NEW.status, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_member_new_picture_uuid" - AFTER INSERT ON "%s_members" - WHEN NEW.picture_uuid IS NOT NULL - BEGIN - INSERT INTO "%s_member_changes" ( - member_id, attribute, value_blob, op - ) VALUES - (NEW.id, 'picture_uuid', NEW.picture_uuid, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_member_update_username" - AFTER UPDATE ON "%s_members" - WHEN OLD.username != NEW.username - BEGIN - INSERT INTO "%s_member_changes" ( - member_id, attribute, value_text, op - ) VALUES - (NEW.id, 'username', OLD.username, false), - (NEW.id, 'username', NEW.username, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_member_update_display_name" - AFTER UPDATE ON "%s_members" - WHEN OLD.display_name != NEW.display_name - BEGIN - INSERT INTO "%s_member_changes" ( - member_id, attribute, value_text, op - ) VALUES - (NEW.id, 'display_name', OLD.display_name, false), - (NEW.id, 'display_name', NEW.display_name, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_member_update_status" - AFTER UPDATE ON "%s_members" - WHEN OLD.status != NEW.status - BEGIN - INSERT INTO "%s_member_changes" ( - member_id, attribute, value_text, op - ) VALUES - (NEW.id, 'status', OLD.status, false), - (NEW.id, 'status', NEW.status, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_member_add_picture_uuid" - AFTER UPDATE ON "%s_members" - WHEN ( - OLD.picture_uuid IS NULL AND - NEW.picture_uuid IS NOT NULL - ) - BEGIN - INSERT INTO "%s_member_changes" ( - member_id, attribute, value_blob, op - ) VALUES - (NEW.id, 'picture_uuid', NEW.picture_uuid, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_member_remove_picture_uuid" - AFTER UPDATE ON "%s_members" - WHEN ( - OLD.picture_uuid IS NOT NULL AND - NEW.picture_uuid IS NULL - ) - BEGIN - INSERT INTO "%s_member_changes" ( - member_id, attribute, value_blob, op - ) VALUES - (NEW.id, 'picture_uuid', OLD.picture_uuid, false) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_member_update_picture_uuid" - AFTER UPDATE ON "%s_members" - WHEN ( - OLD.picture_uuid IS NOT NULL AND - NEW.picture_uuid IS NOT NULL AND - OLD.picture_uuid != NEW.picture_uuid - ) - BEGIN - INSERT INTO "%s_member_changes" ( - member_id, attribute, value_blob, op - ) VALUES - (NEW.id, 'picture_uuid', OLD.picture_uuid, false), - (NEW.id, 'picture_uuid', NEW.picture_uuid, true) - ; - END; - - CREATE TABLE IF NOT EXISTS "%s_member_roles" ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - member_id INTEGER NOT NULL - REFERENCES "%s_members"(id), - role TEXT NOT NULL, - UNIQUE (member_id, role) - ) STRICT; - CREATE TABLE IF NOT EXISTS "%s_member_role_changes" ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - timestamp TEXT NOT NULL DEFAULT ( - %s - ), - role_id INTEGER NOT NULL, - attribute TEXT NOT NULL CHECK( - attribute IN ( - 'role', - 'logon_id' -- FIXME - ) - ), - value TEXT NOT NULL, - op INT NOT NULL CHECK(op IN (0, 1)) - ) STRICT; - CREATE TRIGGER IF NOT EXISTS "%s_member_role_add" - AFTER INSERT ON "%s_member_roles" - BEGIN - INSERT INTO "%s_member_role_changes" ( - role_id, attribute, value, op - ) VALUES - (NEW.id, 'role', NEW.role, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_member_role_remove" - AFTER DELETE ON "%s_member_roles" - BEGIN - INSERT INTO "%s_member_role_changes" ( - role_id, attribute, value, op - ) VALUES - (OLD.id, 'role', OLD.role, false) - ; - END; - CREATE TABLE IF NOT EXISTS "%s_channels" ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - timestamp TEXT NOT NULL DEFAULT ( - %s - ), - uuid BLOB NOT NULL UNIQUE, - network_id INTEGER NOT NULL - REFERENCES "%s_networks"(id), - public_name TEXT, - label TEXT NOT NULL, - description TEXT NOT NULL, - virtual INT NOT NULL CHECK(virtual IN (0, 1)), - UNIQUE (network_id, public_name) - ) STRICT; - CREATE TABLE IF NOT EXISTS "%s_channel_changes" ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - timestamp TEXT NOT NULL DEFAULT ( - %s - ), - channel_id INTEGER NOT NULL, - attribute TEXT NOT NULL CHECK( - attribute IN ( - 'public_name', - 'label', - 'description', - 'virtual', - 'logon_id' -- FIXME - ) - ), - value_text TEXT, - value_bool INT CHECK(value_bool IN (0, 1)), - op INT NOT NULL CHECK(op IN (0, 1)) - ) STRICT; - CREATE TRIGGER IF NOT EXISTS "%s_channel_new" - AFTER INSERT ON "%s_channels" - BEGIN - INSERT INTO "%s_channel_changes" ( - channel_id, attribute, value_text, op - ) VALUES - (NEW.id, 'label', NEW.label, true), - (NEW.id, 'description', NEW.description, true) - ; - INSERT INTO "%s_channel_changes" ( - channel_id, attribute, value_bool, op - ) VALUES - (NEW.id, 'virtual', NEW.virtual, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_channel_new_public_name" - AFTER INSERT ON "%s_channels" - WHEN NEW.public_name IS NOT NULL - BEGIN - INSERT INTO "%s_channel_changes" ( - channel_id, attribute, value_text, op - ) VALUES - (NEW.id, 'public_name', NEW.public_name, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_channel_update_label" - AFTER UPDATE ON "%s_channels" - WHEN OLD.label != NEW.label - BEGIN - INSERT INTO "%s_channel_changes" ( - channel_id, attribute, value_text, op - ) VALUES - (NEW.id, 'label', OLD.label, false), - (NEW.id, 'label', NEW.label, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_channel_update_description" - AFTER UPDATE ON "%s_channels" - WHEN OLD.description != NEW.description - BEGIN - INSERT INTO "%s_channel_changes" ( - channel_id, attribute, value_text, op - ) VALUES - (NEW.id, 'description', OLD.description, false), - (NEW.id, 'description', NEW.description, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_channel_update_virtual" - AFTER UPDATE ON "%s_channels" - WHEN OLD.virtual != NEW.virtual - BEGIN - INSERT INTO "%s_channel_changes" ( - channel_id, attribute, value_bool, op - ) VALUES - (NEW.id, 'virtual', OLD.virtual, false), - (NEW.id, 'virtual', NEW.virtual, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_channel_add_public_name" - AFTER UPDATE ON "%s_channels" - WHEN ( - OLD.public_name IS NULL AND - NEW.public_name IS NOT NULL - ) - BEGIN - INSERT INTO "%s_channel_changes" ( - channel_id, attribute, value_text, op - ) VALUES - (NEW.id, 'public_name', NEW.public_name, true) - ; - END; - CREATE TRIGGER IF NOT EXISTS "%s_channel_remove_public_name" - AFTER UPDATE ON "%s_channels" - WHEN ( - OLD.public_name IS NOT NULL AND - NEW.public_name IS NULL - ) - BEGIN - INSERT INTO "%s_channel_changes" ( - channel_id, attribute, value_text, op - ) VALUES - (OLD.id, 'public_name', OLD.public_name, false) - ; - END; - - CREATE TABLE IF NOT EXISTS "%s_participants" ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - channel_id INTEGER NOT NULL - REFERENCES "%s_channels"(id), - member_id INTEGER NOT NULL - REFERENCES "%s_members"(id), - UNIQUE (channel_id, member_id) - ) STRICT; - CREATE TABLE IF NOT EXISTS "%s_participant_changes" ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - timestamp TEXT NOT NULL DEFAULT ( - %s - ), - participant_id INTEGER NOT NULL, - attribute TEXT NOT NULL CHECK( - attribute IN ( - 'connection_id' - ) - ), - value TEXT NOT NULL, - op INT NOT NULL CHECK(op IN (0, 1)) - ) STRICT; - - CREATE TABLE IF NOT EXISTS "%s_channel_events" ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - timestamp TEXT NOT NULL DEFAULT ( - %s - ), - uuid BLOB NOT NULL UNIQUE, - channel_id INTEGER NOT NULL - REFERENCES "%s_channels"(id), - source_uuid BLOB NOT NULL, - source_type TEXT NOT NULL CHECK( - source_type IN ( - 'logon' - ) - ), - source_metadata TEXT, - type TEXT NOT NULL CHECK( - type IN ( - 'user-join', - 'user-message' - ) - ), - payload TEXT NOT NULL, - metadata TEXT - ) STRICT; - - ` - return queryT{ - write: fmt.Sprintf( - tmpl_write, - prefix, - g.SQLiteNow, - prefix, - g.SQLiteNow, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - g.SQLiteNow, - prefix, - prefix, - g.SQLiteNow, - prefix, - g.SQLiteNow, - prefix, - prefix, - prefix, - g.SQLiteNow, - prefix, - prefix, - prefix, - g.SQLiteNow, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - g.SQLiteNow, - prefix, - prefix, - g.SQLiteNow, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - g.SQLiteNow, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - g.SQLiteNow, - prefix, - prefix, - g.SQLiteNow, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - g.SQLiteNow, - prefix, - g.SQLiteNow, - prefix, - ), - } -} - -func createTables(db *sql.DB, prefix string) error { - q := createTablesSQL(prefix) - - return inTx(db, func(tx *sql.Tx) error { - _, err := tx.Exec(q.write) - return err - }) -} - -func memberRolesSQL(prefix string) queryT { - const tmpl_read = ` - SELECT role FROM "%s_member_roles" - JOIN "%s_members" ON - "%s_member_roles".member_id = "%s_members".id - WHERE "%s_members".uuid = ? - ORDER BY "%s_member_roles".id; - ` - return queryT{ - read: fmt.Sprintf( - tmpl_read, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - ), - } -} - -func collectRoles(rows *sql.Rows) ([]string, error) { - roles := []string{} - - for rows.Next() { - var role string - err := rows.Scan(&role) - if err != nil { - rows.Close() - return nil, err - } - - roles = append(roles, role) - } - - return roles, g.WrapErrors(rows.Err(), rows.Close()) -} - -func createUserSQL(prefix string) queryT { - const tmpl_write = ` - INSERT INTO "%s_users" ( - user_uuid, username, display_name, picture_uuid, deleted - ) VALUES ( - ?, ?, ?, NULL, false - ) RETURNING id, timestamp; - ` - return queryT{ - write: fmt.Sprintf(tmpl_write, prefix), - } -} - -func createUserStmt( - cfg dbconfigT, -) (func(newUserT) (userT, error), func() error, error) { - q := createUserSQL(cfg.prefix) - - writeStmt, err := cfg.shared.Prepare(q.write) - if err != nil { - return nil, nil, err - } - - fn := func(newUser newUserT) (userT, error) { - user := userT{ - uuid: newUser.uuid, - username: newUser.username, - displayName: newUser.displayName, - } - - var timestr string - err := writeStmt.QueryRow( - newUser.uuid[:], - newUser.username, - newUser.displayName, - ).Scan(&user.id, ×tr) - if err != nil { - return userT{}, err - } - - user.timestamp, err = time.Parse(time.RFC3339Nano, timestr) - if err != nil { - return userT{}, err - } - - return user, nil - } - - return fn, writeStmt.Close, nil -} - -func userByUUIDSQL(prefix string) queryT { - const tmpl_read = ` - SELECT - id, - timestamp, - username, - display_name, - picture_uuid - FROM "%s_users" - WHERE - user_uuid = ? AND - deleted = false; - ` - return queryT{ - read: fmt.Sprintf(tmpl_read, prefix), - } -} - -func userByUUIDStmt( - cfg dbconfigT, -) (func(uuid.UUID) (userT, error), func() error, error) { - q := userByUUIDSQL(cfg.prefix) - - readStmt, err := cfg.shared.Prepare(q.read) - if err != nil { - return nil, nil, err - } - - fn := func(userID uuid.UUID) (userT, error) { - user := userT{ - uuid: userID, - } - - var ( - timestr string - picture_id_bytes []byte - ) - err := readStmt.QueryRow(userID[:]).Scan( - &user.id, - ×tr, - &user.username, - &user.displayName, - &picture_id_bytes, - ) - if err != nil { - return userT{}, err - } - if picture_id_bytes != nil { - pictureID := uuid.UUID(picture_id_bytes) - user.pictureID = &pictureID - } - - user.timestamp, err = time.Parse(time.RFC3339Nano, timestr) - if err != nil { - return userT{}, err - } - - return user, err - } - - return fn, readStmt.Close, nil -} - -func updateUserSQL(prefix string) queryT { - const tmpl_write = ` - UPDATE "%s_users" - SET - username = ?, - display_name = ?, - picture_uuid = ? - WHERE - id = ? AND - deleted = false - RETURNING id; - ` - return queryT{ - write: fmt.Sprintf(tmpl_write, prefix), - } -} - -func updateUserStmt( - cfg dbconfigT, -) (func(userT) error, func() error, error) { - q := updateUserSQL(cfg.prefix) - - writeStmt, err := cfg.shared.Prepare(q.write) - if err != nil { - return nil, nil, err - } - - fn := func(user userT) error { - var picture_id_bytes []byte = nil - if user.pictureID != nil { - picture_id_bytes = user.pictureID[:] - } - var _id int64 - return writeStmt.QueryRow( - user.username, - user.displayName, - picture_id_bytes, - user.id, - ).Scan(&_id) - } - - return fn, writeStmt.Close, nil -} - -func deleteUserSQL(prefix string) queryT { - const tmpl_write = ` - UPDATE "%s_users" - SET deleted = true - WHERE - user_uuid = ? AND - deleted = false - RETURNING id; - ` - return queryT{ - write: fmt.Sprintf(tmpl_write, prefix), - } -} - -func deleteUserStmt( - cfg dbconfigT, -) (func(uuid.UUID) error, func() error, error) { - q := deleteUserSQL(cfg.prefix) - - writeStmt, err := cfg.shared.Prepare(q.write) - if err != nil { - return nil, nil, err - } - - fn := func(userID uuid.UUID) error { - var _id int64 - return writeStmt.QueryRow(userID[:]).Scan(&_id) - } - - return fn, writeStmt.Close, nil -} - -func addNetworkSQL(prefix string) queryT { - const tmpl_write = ` - INSERT INTO "%s_networks" ( - uuid, name, description, type, deleted - ) - VALUES ( - ?, - ?, - ?, - ?, - false - ) RETURNING id; - - WITH creator AS ( - SELECT username, display_name, picture_uuid - FROM "%s_users" - WHERE id = ? AND deleted = false - ), new_network AS ( - SELECT id FROM "%s_networks" WHERE uuid = ? - ) - INSERT INTO "%s_members" ( - uuid, network_id, user_id, username, display_name, - picture_uuid, status, active_uniq - ) VALUES ( - ?, - (SELECT id FROM new_network), - ?, - (SELECT username FROM creator), - (SELECT display_name FROM creator), - (SELECT picture_uuid FROM creator), - 'active', - 'active' - ) RETURNING id; - - WITH new_member AS ( - SELECT id FROM "%s_members" WHERE uuid = ? - ) - INSERT INTO "%s_member_roles" (member_id, role) - VALUES ( - (SELECT id FROM new_member), - 'admin' - ), - ( - (SELECT id FROM new_member), - 'creator' - ) - RETURNING id; - ` - const tmpl_read = ` - SELECT id, timestamp FROM "%s_networks" - WHERE uuid = ? AND deleted = false; - ` - return queryT{ - write: fmt.Sprintf( - tmpl_write, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - ), - read: fmt.Sprintf(tmpl_read, prefix), - } -} - -func addNetworkStmt( - cfg dbconfigT, -) ( - func(userT, newNetworkT, uuid.UUID) (networkT, error), - func() error, - error, -) { - q := addNetworkSQL(cfg.prefix) - - readStmt, err := cfg.shared.Prepare(q.read) - if err != nil { - return nil, nil, err - } - - privateDB, err := sql.Open(golite.DriverName, cfg.dbpath) - if err != nil { - readStmt.Close() - return nil, nil, err - } - - writeFn, writeFnClose := execSerialized(q.write, privateDB) - - fn := func( - user userT, - newNetwork newNetworkT, - memberID uuid.UUID, - ) (networkT, error) { - network := networkT{ - uuid: newNetwork.uuid, - name: newNetwork.name, - description: newNetwork.description, - type_: newNetwork.type_, - } - - err := writeFn( - newNetwork.uuid[:], - newNetwork.name, - newNetwork.description, - newNetwork.type_, - user.id, - newNetwork.uuid[:], - memberID[:], - user.id, - memberID[:], - ) - if err != nil { - return networkT{}, err - } - - var timestr string - err = readStmt.QueryRow(network.uuid[:]).Scan( - &network.id, - ×tr, - ) - if err != nil { - return networkT{}, err - } - - network.timestamp, err = time.Parse(time.RFC3339Nano, timestr) - if err != nil { - return networkT{}, err - } - - return network, nil - } - - closeFn := func() error { - writeFnClose() - return g.SomeError(privateDB.Close(), readStmt.Close()) - } - - return fn, closeFn, nil -} - -func getNetworkSQL(prefix string) queryT { - const tmpl_read = ` - WITH probing_user AS ( - SELECT id FROM "%s_users" - WHERE id = ? AND deleted = false - ), target_network AS ( - SELECT id FROM "%s_networks" - WHERE uuid = ? AND deleted = false - ) - SELECT - id, - timestamp, - name, - description, - type - FROM "%s_networks" - WHERE - uuid = ? AND - deleted = false AND - ? IN probing_user AND - ( - type IN ('public', 'unlisted') OR - ? IN ( - SELECT user_id FROM "%s_members" - WHERE - user_id = ? AND - network_id IN target_network AND - status != 'removed' - ) - ); - ` - return queryT{ - read: fmt.Sprintf( - tmpl_read, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - ), - } -} - -func getNetworkStmt( - cfg dbconfigT, -) (func(userT, uuid.UUID) (networkT, error), func() error, error) { - q := getNetworkSQL(cfg.prefix) - - readStmt, err := cfg.shared.Prepare(q.read) - if err != nil { - return nil, nil, err - } - - fn := func(user userT, networkID uuid.UUID) (networkT, error) { - network := networkT{ - uuid: networkID, - } - - var timestr string - err := readStmt.QueryRow( - user.id, - networkID[:], - networkID[:], - user.id, - user.id, - user.id, - ).Scan( - &network.id, - ×tr, - &network.name, - &network.description, - &network.type_, - ) - if err != nil { - return networkT{}, err - } - - network.timestamp, err = time.Parse(time.RFC3339Nano, timestr) - if err != nil { - return networkT{}, err - } - - return network, nil - } - - return fn, readStmt.Close, nil -} - -func networkEach(rows *sql.Rows, callback func(networkT) error) error { - if rows == nil { - return nil - } - - for rows.Next() { - var ( - network networkT - timestr string - network_id_bytes []byte - deleted bool - ) - err := rows.Scan( - &network.id, - ×tr, - &network_id_bytes, - &network.name, - &network.description, - &network.type_, - &deleted, - ) - if err != nil { - return g.WrapErrors(rows.Close(), err) - } - network.uuid = uuid.UUID(network_id_bytes) - - if deleted { - return sql.ErrNoRows - } - - network.timestamp, err = time.Parse(time.RFC3339Nano, timestr) - if err != nil { - return g.WrapErrors(rows.Close(), err) - } - - err = callback(network) - if err != nil { - return g.WrapErrors(rows.Close(), err) - } - } - - return g.WrapErrors(rows.Err(), rows.Close()) -} - -func networksSQL(prefix string) queryT { - const tmpl_read = ` - WITH current_user AS ( - SELECT id, deleted FROM "%s_users" WHERE id = ? - ) - SELECT - "%s_networks".id, - "%s_networks".timestamp, - "%s_networks".uuid, - "%s_networks".name, - "%s_networks".description, - "%s_networks".type, - (SELECT deleted FROM current_user) - FROM "%s_networks" - JOIN "%s_members" ON - "%s_networks".id = "%s_members".network_id - WHERE ( - "%s_networks".type = 'public' OR - "%s_networks".id IN ( - SELECT network_id FROM "%s_members" - WHERE user_id IN (SELECT id FROM current_user) - ) - ) AND "%s_networks".deleted = false - ORDER BY "%s_networks".id; - ` - return queryT{ - read: fmt.Sprintf( - tmpl_read, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - ), - } -} - -func networksStmt( - cfg dbconfigT, -) (func(userT) (*sql.Rows, error), func() error, error) { - q := networksSQL(cfg.prefix) - - readStmt, err := cfg.shared.Prepare(q.read) - if err != nil { - return nil, nil, err - } - - fn := func(user userT) (*sql.Rows, error) { - return readStmt.Query(user.id) - } - - return fn, readStmt.Close, nil -} - -func setNetworkSQL(prefix string) queryT { - const tmpl_write = ` - UPDATE "%s_networks" - SET - name = ?, - description = ?, - type = ? - WHERE id = ? AND deleted = false - RETURNING ( - SELECT CASE WHEN EXISTS ( - SELECT role from "%s_member_roles" - WHERE - member_id = ? AND - role IN ( - 'admin', - 'network-settings-update' - ) AND ? IN ( - SELECT network_id - FROM "%s_members" - WHERE - id = ? AND - status = 'active' - ) - ) THEN true ELSE RAISE( - ABORT, - 'member not allowed to update network data' - ) END - ); - - ` - return queryT{ - write: fmt.Sprintf(tmpl_write, prefix, prefix, prefix), - } -} - -func setNetworkStmt( - cfg dbconfigT, -) (func(memberT, networkT) error, func() error, error) { - q := setNetworkSQL(cfg.prefix) - - writeStmt, err := cfg.shared.Prepare(q.write) - if err != nil { - return nil, nil, err - } - - fn := func(actor memberT, network networkT) error { - var _allowed bool - return writeStmt.QueryRow( - network.name, - network.description, - network.type_, - network.id, - actor.id, - network.id, - actor.id, - ).Scan(&_allowed) - } - - return fn, writeStmt.Close, nil -} - -func nipNetworkSQL(prefix string) queryT { - const tmpl_write = ` - WITH target_network AS ( - SELECT network_id AS id - FROM "%s_members" - WHERE - id = ? AND - status = 'active' - ) - UPDATE "%s_networks" - SET deleted = true - WHERE id IN target_network AND deleted = false - RETURNING ( - SELECT CASE WHEN EXISTS ( - SELECT role FROM "%s_member_roles" - WHERE - member_id = ? AND - role IN ( - 'admin' - ) - ) THEN true ELSE RAISE( - ABORT, - 'member not allowed to delete network' - ) END - ); - ` - return queryT{ - write: fmt.Sprintf(tmpl_write, prefix, prefix, prefix), - } -} - -func nipNetworkStmt( - cfg dbconfigT, -) (func(memberT) error, func() error, error) { - q := nipNetworkSQL(cfg.prefix) - - writeStmt, err := cfg.shared.Prepare(q.write) - if err != nil { - return nil, nil, err - } - - fn := func(actor memberT) error { - var _allowed bool - return writeStmt.QueryRow(actor.id, actor.id).Scan(&_allowed) - } - - return fn, writeStmt.Close, nil -} - -func membershipSQL(prefix string) queryT { - const tmpl_read = ` - SELECT - "%s_members".id, - "%s_members".timestamp, - "%s_members".uuid, - "%s_members".username, - "%s_members".display_name, - "%s_members".picture_uuid, - "%s_members".status - FROM "%s_members" - JOIN "%s_users" ON - "%s_users".id = "%s_members".user_id - JOIN "%s_networks" ON - "%s_networks".id = "%s_members".network_id - WHERE - "%s_members".user_id = ? AND - "%s_members".network_id = ? AND - "%s_members".status = 'active' AND - "%s_users".deleted = false AND - "%s_networks".deleted = false; - ` - return queryT{ - read: fmt.Sprintf( - tmpl_read, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - ), - } -} - -func membershipStmt( - cfg dbconfigT, -) (func(userT, networkT) (memberT, error), func() error, error) { - q := membershipSQL(cfg.prefix) - - readStmt, err := cfg.shared.Prepare(q.read) - if err != nil { - return nil, nil, err - } - - rolesStmt, err := cfg.shared.Prepare(memberRolesSQL(cfg.prefix).read) - if err != nil { - readStmt.Close() - return nil, nil, err - } - - fn := func(actor userT, network networkT) (memberT, error) { - member := memberT{} - - var ( - timestr string - member_id_bytes []byte - picture_id_bytes []byte - ) - err := readStmt.QueryRow(actor.id, network.id).Scan( - &member.id, - ×tr, - &member_id_bytes, - &member.username, - &member.displayName, - &picture_id_bytes, - &member.status, - ) - if err != nil { - return memberT{}, err - } - member.uuid = uuid.UUID(member_id_bytes) - - member.timestamp, err = time.Parse(time.RFC3339Nano, timestr) - if err != nil { - return memberT{}, err - } - - rows, err := rolesStmt.Query(member_id_bytes) - if err != nil { - return memberT{}, err - } - - member.roles, err = collectRoles(rows) - if err != nil { - return memberT{}, err - } - - return member, nil - } - - closeFn := func() error { - return g.SomeError( - readStmt.Close(), - rolesStmt.Close(), - ) - } - - return fn, closeFn, nil -} - -func addMemberSQL(prefix string) queryT { - const tmpl_write = ` - WITH target_user AS ( - SELECT id, username, display_name, picture_uuid - FROM "%s_users" - WHERE user_uuid = ? AND deleted = false - ), target_network AS ( - SELECT "%s_members".network_id AS id - FROM "%s_members" - JOIN "%s_networks" ON - "%s_members".network_id = "%s_networks".id - WHERE - "%s_members".id = ? AND - "%s_members".status = 'active' AND - "%s_networks".deleted = false - ) - INSERT INTO "%s_members" ( - uuid, network_id, user_id, username, display_name, - picture_uuid, status, active_uniq - ) VALUES ( - ?, - (SELECT id FROM target_network), - (SELECT id FROM target_user), - ?, - (SELECT display_name FROM target_user), - (SELECT picture_uuid FROM target_user), - 'active', - 'active' - ) RETURNING id, timestamp, display_name, picture_uuid, status, ( - SELECT CASE WHEN EXISTS ( - SELECT role from "%s_member_roles" - WHERE - member_id = ? AND - role IN ( - 'admin', - 'add-member' - ) - ) THEN true ELSE RAISE( - ABORT, - 'member not allowed to add another member' - ) END - ); - ` - return queryT{ - write: fmt.Sprintf( - tmpl_write, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - ), - } -} - -func addMemberStmt( - cfg dbconfigT, -) (func(memberT, newMemberT) (memberT, error), func() error, error) { - q := addMemberSQL(cfg.prefix) - - writeStmt, err := cfg.shared.Prepare(q.write) - if err != nil { - return nil, nil, err - } - - rolesStmt, err := cfg.shared.Prepare(memberRolesSQL(cfg.prefix).read) - if err != nil { - writeStmt.Close() - return nil, nil, err - } - - fn := func(actor memberT, newMember newMemberT) (memberT, error) { - member := memberT{ - uuid: newMember.memberID, - username: newMember.username, - } - - var ( - timestr string - picture_id_bytes []byte - _allowed bool - ) - err := writeStmt.QueryRow( - newMember.userID[:], - actor.id, - newMember.memberID[:], - newMember.username, - actor.id, - ).Scan( - &member.id, - ×tr, - &member.displayName, - &picture_id_bytes, - &member.status, - &_allowed, - ) - if err != nil { - return memberT{}, err - } - if picture_id_bytes != nil { - pictureID := uuid.UUID(picture_id_bytes) - member.pictureID = &pictureID - } - - member.timestamp, err = time.Parse(time.RFC3339Nano, timestr) - if err != nil { - return memberT{}, err - } - - rows, err := rolesStmt.Query(member.uuid[:]) - if err != nil { - return memberT{}, err - } - - member.roles, err = collectRoles(rows) - if err != nil { - return memberT{}, err - } - - return member, nil - } - - closeFn := func() error { - return g.SomeError( - writeStmt.Close(), - rolesStmt.Close(), - ) - } - - return fn, closeFn, nil -} - -func addRoleSQL(prefix string) queryT { - const tmpl_write = ` - INSERT INTO "%s_member_roles" (member_id, role) - VALUES (?, ?); - ` - return queryT{ - write: fmt.Sprintf(tmpl_write, prefix), - } -} - -func addRoleStmt( - cfg dbconfigT, -) (func(memberT, string, memberT) error, func() error, error) { - q := addRoleSQL(cfg.prefix) - - writeStmt, err := cfg.shared.Prepare(q.write) - if err != nil { - return nil, nil, err - } - - fn := func(actor memberT, role string, member memberT) error { - // FIXME: do authorization - _, err := writeStmt.Exec(member.id, role) - return err - } - - return fn, writeStmt.Close, nil -} - -func dropRoleSQL(prefix string) queryT { - const tmpl_write = ` - DELETE FROM "%s_member_roles" - WHERE - member_id = ? AND - role = ? - RETURNING 1; - ` - return queryT{ - write: fmt.Sprintf(tmpl_write, prefix), - } -} - -func dropRoleStmt( - cfg dbconfigT, -) (func(memberT, string, memberT) error, func() error, error) { - q := dropRoleSQL(cfg.prefix) - - writeStmt, err := cfg.shared.Prepare(q.write) - if err != nil { - return nil, nil, err - } - - fn := func(actor memberT, role string, member memberT) error { - // FIXME: do authorization - // _, err := writeStmt.Exec(member.id, role) - // return err - var _id int64 - return writeStmt.QueryRow(member.id, role).Scan(&_id) - } - - return fn, writeStmt.Close, nil -} - - -func showMemberSQL(prefix string) queryT { - const tmpl_read = ` - WITH current_network AS ( - SELECT network_id - FROM "%s_members" - WHERE id = ? - ) - SELECT - id, - timestamp, - username, - display_name, - picture_uuid, - status - FROM "%s_members" - WHERE - uuid = ? AND - network_id IN current_network; - ` - return queryT{ - read: fmt.Sprintf(tmpl_read, prefix, prefix), - } -} - -func showMemberStmt( - cfg dbconfigT, -) (func(memberT, uuid.UUID) (memberT, error), func() error, error) { - q := showMemberSQL(cfg.prefix) - - readStmt, err := cfg.shared.Prepare(q.read) - if err != nil { - return nil, nil, err - } - - rolesStmt, err := cfg.shared.Prepare(memberRolesSQL(cfg.prefix).read) - if err != nil { - readStmt.Close() - return nil, nil, err - } - - fn := func(actor memberT, memberID uuid.UUID) (memberT, error) { - member := memberT{ - uuid: memberID, - } - - var ( - timestr string - picture_id_bytes []byte - ) - err := readStmt.QueryRow(actor.id, memberID[:]).Scan( - &member.id, - ×tr, - &member.username, - &member.displayName, - &picture_id_bytes, - &member.status, - ) - if err != nil { - return memberT{}, err - } - if picture_id_bytes != nil { - pictureID := uuid.UUID(picture_id_bytes) - // FIXME: test this - member.pictureID = &pictureID - } - - member.timestamp, err = time.Parse(time.RFC3339Nano, timestr) - if err != nil { - return memberT{}, err - } - - rows, err := rolesStmt.Query(memberID[:]) - if err != nil { - return memberT{}, err - } - - member.roles, err = collectRoles(rows) - if err != nil { - return memberT{}, err - } - - return member, nil - } - - closeFn := func() error { - return g.SomeError( - readStmt.Close(), - rolesStmt.Close(), - ) - } - - return fn, closeFn, nil -} - -func memberEach(rows *sql.Rows, callback func(memberT) error) error { - if rows == nil { - return nil - } - - for rows.Next() { - var ( - member memberT - timestr string - member_id_bytes []byte - picture_id_bytes []byte - ) - err := rows.Scan( - &member.id, - ×tr, - &member_id_bytes, - &member.username, - &member.displayName, - &picture_id_bytes, - &member.status, - ) - if err != nil { - return g.WrapErrors(rows.Close(), err) - } - member.uuid = uuid.UUID(member_id_bytes) - if picture_id_bytes != nil { - pictureID := uuid.UUID(picture_id_bytes) - member.pictureID = &pictureID - } - - member.timestamp, err = time.Parse(time.RFC3339Nano, timestr) - if err != nil { - return g.WrapErrors(rows.Close(), err) - } - - err = callback(member) - if err != nil { - return g.WrapErrors(rows.Close(), err) - } - } - - return g.WrapErrors(rows.Err(), rows.Close()) -} - -func membersSQL(prefix string) queryT { - const tmpl_read = ` - WITH target_network AS ( - SELECT "%s_members".network_id - FROM "%s_members" - JOIN "%s_networks" ON - "%s_members".network_id = "%s_networks".id - WHERE - "%s_members".id = ? AND - "%s_networks".deleted = false - ) - SELECT - id, - timestamp, - uuid, - username, - display_name, - picture_uuid, - status - FROM "%s_members" - WHERE - network_id IN target_network AND - status = 'active'; - ` - return queryT{ - read: fmt.Sprintf( - tmpl_read, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - ), - } -} - -func membersStmt( - cfg dbconfigT, -) (func(memberT) (*sql.Rows, error), func() error, error) { - q := membersSQL(cfg.prefix) - - readStmt, err := cfg.shared.Prepare(q.read) - if err != nil { - return nil, nil, err - } - - fn := func(actor memberT) (*sql.Rows, error) { - return readStmt.Query(actor.id) - } - - return fn, readStmt.Close, nil -} - -func editMemberSQL(prefix string) queryT { - const tmpl_write = ` - UPDATE "%s_members" - SET - status = ? - WHERE id = ? - RETURNING id; - ` - return queryT{ - write: fmt.Sprintf(tmpl_write, prefix), - } -} - -func editMemberStmt( - cfg dbconfigT, -) (func(memberT, memberT) error, func() error, error) { - q := editMemberSQL(cfg.prefix) - - writeStmt, err := cfg.shared.Prepare(q.write) - if err != nil { - return nil, nil, err - } - - fn := func(actor memberT, member memberT) error { - var _id int64 - return writeStmt.QueryRow( - member.status, - member.id, - ).Scan(&_id) - } - - return fn, writeStmt.Close, nil -} - -func dropMemberSQL(prefix string) queryT { - const tmpl_write = ` - UPDATE "%s_members" SET status = 'removed' - WHERE uuid = ? RETURNING id; - - DELETE FROM "%s_member_roles" - WHERE - role != 'creator' AND - member_id IN ( - SELECT id FROM "%s_members" - WHERE uuid = ? - ) - ` - return queryT{ - write: fmt.Sprintf(tmpl_write, prefix, prefix, prefix), - } -} - -func dropMemberStmt( - cfg dbconfigT, -) (func(memberT, uuid.UUID) error, func() error, error) { - q := dropMemberSQL(cfg.prefix) - - privateDB, err := sql.Open(golite.DriverName, cfg.dbpath) - if err != nil { - return nil, nil, err - } - - writeFn, writeFnClose := execSerialized(q.write, privateDB) - - fn := func(actor memberT, memberID uuid.UUID) error { - err := writeFn(memberID[:], memberID[:]) - if err != nil { - return err - } - - // if res == 0 { // FIXME } - return nil - } - - closeFn := func() error { - writeFnClose() - return privateDB.Close() - } - - return fn, closeFn, nil -} - -func addChannelSQL(prefix string) queryT { - const tmpl_write = ` - WITH target_network AS ( - SELECT network_id AS id - FROM "%s_members" - WHERE id = ? - ) - INSERT INTO "%s_channels" ( - uuid, - network_id, - public_name, - label, - description, - virtual - ) VALUES ( - ?, - (SELECT id FROM target_network), - ?, - ?, - ?, - ? - ) RETURNING id, timestamp; - - WITH new_channel AS ( - SELECT id FROM "%s_channels" WHERE uuid = ? - ) - INSERT INTO "%s_participants" (channel_id, member_id) - VALUES ( - (SELECT id FROM new_channel), - ? - ); - ` - const tmpl_read = ` - SELECT id, timestamp FROM "%s_channels" - WHERE uuid = ?; - ` - return queryT{ - write: fmt.Sprintf(tmpl_write, prefix, prefix, prefix, prefix), - read: fmt.Sprintf(tmpl_read, prefix), - } -} - -func addChannelStmt( - cfg dbconfigT, -) (func (memberT, newChannelT) (channelT, error), func() error, error) { - q := addChannelSQL(cfg.prefix) - - readStmt, err := cfg.shared.Prepare(q.read) - if err != nil { - return nil, nil, err - } - - privateDB, err := sql.Open(golite.DriverName, cfg.dbpath) - if err != nil { - readStmt.Close() - return nil, nil, err - } - - writeFn, writeFnClose := execSerialized(q.write, privateDB) - - fn := func(actor memberT, newChannel newChannelT) (channelT, error) { - channel := channelT{ - uuid: newChannel.uuid, - publicName: newChannel.publicName, - label: newChannel.label, - description: newChannel.description, - virtual: newChannel.virtual, - } - - var timestr string - err := writeFn( - actor.id, - newChannel.uuid[:], - newChannel.publicName, - newChannel.label, - newChannel.description, - newChannel.virtual, - newChannel.uuid[:], - actor.id, - ) - if err != nil { - return channelT{}, err - } - - err = readStmt.QueryRow(newChannel.uuid[:]).Scan( - &channel.id, - ×tr, - ) - if err != nil { - return channelT{}, err - } - - channel.timestamp, err = time.Parse(time.RFC3339Nano, timestr) - if err != nil { - return channelT{}, err - } - - return channel, nil - } - - closeFn := func() error { - writeFnClose() - return readStmt.Close() - } - - return fn, closeFn, nil -} - -/* -func chanByName(prefix string) queryT { - const tmpl_read = ` - SELECT - id, - timestamp, - uuid, - public, - - ` - return queryT{ - read: fmt.Sprintf(tmpl_read, prefix), - } -} - -func chanByStmt( - cfg dbconfigT, -) ( -FIXME -*/ - -func channelEach(rows *sql.Rows, callback func(channelT) error) error { - if rows == nil { - return nil - } - - for rows.Next() { - var ( - channel channelT - timestr string - channel_id_bytes []byte - publicName sql.NullString - ) - err := rows.Scan( - &channel.id, - ×tr, - &channel_id_bytes, - &publicName, - &channel.label, - &channel.description, - &channel.virtual, - ) - if err != nil { - return g.WrapErrors(rows.Close(), err) - } - channel.uuid = uuid.UUID(channel_id_bytes) - if publicName.Valid { - channel.publicName = &publicName.String - } - - channel.timestamp, err = time.Parse(time.RFC3339Nano, timestr) - if err != nil { - return g.WrapErrors(rows.Close(), err) - } - - err = callback(channel) - if err != nil { - return g.WrapErrors(rows.Close(), err) - } - } - - return g.WrapErrors(rows.Err(), rows.Close()) -} - -func channelsSQL(prefix string) queryT { - const tmpl_read = ` - WITH current_network AS ( - SELECT network_id AS id - FROM "%s_members" - WHERE id = ? - ), member_private_channels AS ( - SELECT channel_id AS id - FROM "%s_participants" - WHERE member_id = ? - ) - SELECT - id, - timestamp, - uuid, - public_name, - label, - description, - virtual - FROM "%s_channels" - WHERE - network_id IN current_network AND - ( - public_name IS NOT NULL OR - id IN member_private_channels - ) - ORDER BY id; - ` - return queryT{ - read: fmt.Sprintf(tmpl_read, prefix, prefix, prefix), - } -} - -func channelsStmt( - cfg dbconfigT, -) (func(memberT) (*sql.Rows, error), func() error, error) { - q := channelsSQL(cfg.prefix) - - readStmt, err := cfg.shared.Prepare(q.read) - if err != nil { - return nil, nil, err - } - - fn := func(actor memberT) (*sql.Rows, error) { - return readStmt.Query(actor.id, actor.id) - } - - return fn, readStmt.Close, nil -} - -func setChannelSQL(prefix string) queryT { - const tmpl_write = ` - WITH participant_channel AS ( - SELECT channel_id AS id - FROM "%s_participants" - WHERE - member_id = ? AND - channel_id = ? - ) - UPDATE "%s_channels" - SET - description = ?, - public_name = ? - WHERE id IN participant_channel - RETURNING id; - ` - const tmpl_read = ` - SELECT ( - SELECT network_id AS id - FROM "%s_channels" - WHERE id = ? - ) AS channel_network_id, ( - SELECT network_id AS id - FROM "%s_members" - WHERE id = ? - ) AS member_network_id; - ` - return queryT{ - write: fmt.Sprintf(tmpl_write, prefix, prefix), - read: fmt.Sprintf(tmpl_read, prefix, prefix), - } -} - -func setChannelStmt( - cfg dbconfigT, -) (func(memberT, channelT) error, func() error, error) { - q := setChannelSQL(cfg.prefix) - - readStmt, err := cfg.shared.Prepare(q.read) - if err != nil { - return nil, nil, err - } - - writeStmt, err := cfg.shared.Prepare(q.write) - if err != nil { - readStmt.Close() - return nil, nil, err - } - - fn := func(actor memberT, channel channelT) error { - var ( - netid1 sql.NullInt64 - netid2 sql.NullInt64 - ) - err := readStmt.QueryRow(channel.id, actor.id).Scan( - &netid1, - &netid2, - ) - if err != nil { - return err - } - if !netid1.Valid || !netid2.Valid || - netid1.Int64 != netid2.Int64 { - return sql.ErrNoRows - } - - var _id int64 - return writeStmt.QueryRow( - actor.id, - channel.id, - channel.description, - channel.publicName, - ).Scan(&_id) - } - - closeFn := func() error { - return g.SomeError( - readStmt.Close(), - writeStmt.Close(), - ) - } - - return fn, closeFn, nil -} - -func endChannelSQL(prefix string) queryT { - const tmpl_write = ` - -- FIXME %s - ` - return queryT{ - write: fmt.Sprintf(tmpl_write, prefix), - } -} - -func endChannelStmt( - cfg dbconfigT, -) (func(memberT, uuid.UUID) error, func() error, error) { - q := endChannelSQL(cfg.prefix) - - writeStmt, err := cfg.shared.Prepare(q.write) - if err != nil { - return nil, nil, err - } - - fn := func(actor memberT, channelID uuid.UUID) error { - _, err := writeStmt.Exec(channelID[:]) - return err - } - - return fn, writeStmt.Close, nil -} - -func joinSQL(prefix string) queryT { - const tmpl_write = ` - WITH target_channel AS ( - SELECT id - FROM "%s_channels" - WHERE - uuid = ? AND - public_name IS NOT NULL - ) - INSERT INTO "%s_participants" (channel_id, member_id) - VALUES ( - (SELECT id FROM target_channel), - ? - ) RETURNING id; - ` - const tmpl_read = ` - SELECT ( - SELECT network_id AS id - FROM "%s_channels" - WHERE - uuid = ? AND - public_name IS NOT NULL - ) AS channel_network_id, ( - SELECT network_id AS id - FROM "%s_members" WHERE id = ? - ) AS member_network_id; - ` - return queryT{ - write: fmt.Sprintf(tmpl_write, prefix, prefix), - read: fmt.Sprintf(tmpl_read, prefix, prefix), - } -} - -func joinStmt( - cfg dbconfigT, -) (func(memberT, uuid.UUID) error, func() error, error) { - q := joinSQL(cfg.prefix) - - readStmt, err := cfg.shared.Prepare(q.read) - if err != nil { - return nil, nil, err - } - - writeStmt, err := cfg.shared.Prepare(q.write) - if err != nil { - readStmt.Close() - return nil, nil, err - } - - fn := func(actor memberT, channelID uuid.UUID) error { - var ( - netid1 sql.NullInt64 - netid2 sql.NullInt64 - ) - err := readStmt.QueryRow(channelID[:], actor.id).Scan( - &netid1, - &netid2, - ) - if err != nil { - return err - } - - if !netid1.Valid || !netid2.Valid || - netid1.Int64 != netid2.Int64 { - return sql.ErrNoRows - } - - var _id int64 - return writeStmt.QueryRow(channelID[:], actor.id).Scan(&_id) - } - - closeFn := func() error { - return g.SomeError( - readStmt.Close(), - writeStmt.Close(), - ) - } - - return fn, closeFn, nil -} - -func partSQL(prefix string) queryT { - const tmpl_write = ` - WITH target_channel AS ( - SELECT id - FROM "%s_channels" - WHERE - id = ? AND - virtual = false - ) - DELETE FROM "%s_participants" - WHERE - member_id = ? AND - channel_id IN target_channel - RETURNING 1; - ` - return queryT{ - write: fmt.Sprintf(tmpl_write, prefix, prefix), - } -} - -func partStmt( - cfg dbconfigT, -) (func(memberT, channelT) error, func() error, error) { - q := partSQL(cfg.prefix) - - writeStmt, err := cfg.shared.Prepare(q.write) - if err != nil { - return nil, nil, err - } - - fn := func(actor memberT, channel channelT) error { - var _id int64 - return writeStmt.QueryRow(channel.id, actor.id).Scan(&_id) - } - - return fn, writeStmt.Close, nil -} - -func nameEach(rows *sql.Rows, callback func(memberT) error) error { - if rows == nil { - return nil - } - - for rows.Next() { - var ( - member memberT - timestr string - member_id_bytes []byte - ) - err := rows.Scan( - &member.id, - ×tr, - &member_id_bytes, - ) - if err != nil { - return g.WrapErrors(rows.Close(), err) - } - member.uuid = uuid.UUID(member_id_bytes) - - member.timestamp, err = time.Parse(time.RFC3339Nano, timestr) - if err != nil { - return g.WrapErrors(rows.Close(), err) - } - - err = callback(member) - if err != nil { - return g.WrapErrors(rows.Close(), err) - } - } - - return g.WrapErrors(rows.Err(), rows.Close()) -} - -func namesSQL(prefix string) queryT { - const tmpl_read = ` - -- FIXME %s - ` - return queryT{ - read: fmt.Sprintf(tmpl_read, prefix), - } -} - -func namesStmt( - cfg dbconfigT, -) (func(memberT, uuid.UUID) (*sql.Rows, error), func() error, error) { - q := namesSQL(cfg.prefix) - - readStmt, err := cfg.shared.Prepare(q.read) - if err != nil { - return nil, nil, err - } - - fn := func(actor memberT, channelID uuid.UUID) (*sql.Rows, error) { - return readStmt.Query(channelID[:]) - } - - return fn, readStmt.Close, nil -} - -func addEventSQL(prefix string) queryT { - const tmpl_write = ` - INSERT INTO "%s_channel_events" ( - uuid, channel_id, source_uuid, source_type, - source_metadata, type, payload, metadata - ) VALUES ( - ?, - (SELECT id FROM "%s_channels" WHERE uuid = ?), - ?, - ?, - ?, - ?, - ?, - ? - ) RETURNING id, timestamp; - ` - return queryT{ - write: fmt.Sprintf(tmpl_write, prefix, prefix), - } -} - -func addEventStmt( - cfg dbconfigT, -) (func (newEventT) (eventT, error), func() error, error) { - q := addEventSQL(cfg.prefix) - - writeStmt, err := cfg.shared.Prepare(q.write) - if err != nil { - return nil, nil, err - } - - fn := func(newEvent newEventT) (eventT, error) { - event := eventT{ - uuid: newEvent.eventID, - channelID: newEvent.channelID, - source: newEvent.source, - type_: newEvent.type_, - payload: newEvent.payload, - metadata: newEvent.metadata, - } - - var timestr string - err := writeStmt.QueryRow( - newEvent.eventID[:], - newEvent.channelID[:], - newEvent.source.uuid[:], - newEvent.source.type_, - newEvent.source.metadata, - newEvent.type_, - newEvent.payload, - newEvent.metadata, - ).Scan(&event.id, ×tr) - if err != nil { - return eventT{}, err - } - - event.timestamp, err = time.Parse(time.RFC3339Nano, timestr) - if err != nil { - return eventT{}, err - } - - return event, nil - } - - return fn, writeStmt.Close, nil -} - -func eventEach(rows *sql.Rows, callback func(eventT) error) error { - if rows == nil { - return nil - } - - for rows.Next() { - var ( - event eventT - timestr string - event_id_bytes []byte - channel_id_bytes []byte - ) - err := rows.Scan( - &event.id, - ×tr, - &event_id_bytes, - &channel_id_bytes, - &event.type_, - &event.payload, - ) - if err != nil { - rows.Close() - return err - } - event.uuid = uuid.UUID(event_id_bytes) - event.channelID = uuid.UUID(channel_id_bytes) - - event.timestamp, err = time.Parse(time.RFC3339Nano, timestr) - if err != nil { - rows.Close() - return err - } - - err = callback(event) - if err != nil { - rows.Close() - return err - } - } - - return g.WrapErrors(rows.Err(), rows.Close()) -} - -func allAfterSQL(prefix string) queryT { - const tmpl_read = ` - WITH landmark_event AS ( - SELECT id, channel_id - FROM "%s_channel_events" - WHERE uuid = ? - ) - SELECT - "%s_channel_events".id, - "%s_channel_events".timestamp, - "%s_channel_events".uuid, - "%s_channels".uuid, - -- "%s_channel_events".connection_uuid, - "%s_channel_events".type, - "%s_channel_events".payload - FROM "%s_channel_events" - JOIN "%s_channels" ON - "%s_channel_events".channel_id = "%s_channels".id - WHERE - "%s_channel_events".id > ( - SELECT id FROM landmark_event - ) AND channel_id = ( - SELECT channel_id FROM landmark_event - ); - ` - return queryT{ - read: fmt.Sprintf( - tmpl_read, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - ), - } -} - -func allAfterStmt( - cfg dbconfigT, -) (func (memberT, uuid.UUID) (*sql.Rows, error), func() error, error) { - q := allAfterSQL(cfg.prefix) - - readStmt, err := cfg.shared.Prepare(q.read) - if err != nil { - return nil, nil, err - } - - fn := func(actor memberT, eventID uuid.UUID) (*sql.Rows, error) { - return readStmt.Query(eventID[:]) - } - - return fn, readStmt.Close, nil -} - -func logMessageSQL(prefix string) queryT{ - const tmpl_write = ` - -- FIXME %s - ` - return queryT{ - write: fmt.Sprintf(tmpl_write, prefix), - } -} - -func logMessageStmt( - cfg dbconfigT, -) (func(userT, messageT) error, func() error, error) { - q := logMessageSQL(cfg.prefix) - - writeStmt, err := cfg.shared.Prepare(q.write) - if err != nil { - return nil, nil, err - } - - // FIXME: actor? - fn := func(user userT, message messageT) error { - return nil // FIXME - _, err := writeStmt.Exec(user, message) - return err - } - - return fn, writeStmt.Close, nil -} - -func initDB( - dbpath string, - prefix string, -) (queriesT, error) { - err := g.ValidateSQLTablePrefix(prefix) - if err != nil { - return queriesT{}, err - } - - shared, err := sql.Open(golite.DriverName, dbpath) - if err != nil { - return queriesT{}, err - } - - cfg := dbconfigT{ - shared: shared, - dbpath: dbpath, - prefix: prefix, - } - - createTablesErr := createTables(shared, prefix) - createUser, createUserClose, createUserErr := createUserStmt(cfg) - userByUUID, userByUUIDClose, userByUUIDErr := userByUUIDStmt(cfg) - updateUser, updateUserClose, updateUserErr := updateUserStmt(cfg) - deleteUser, deleteUserClose, deleteUserErr := deleteUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - getNetwork, getNetworkClose, getNetworkErr := getNetworkStmt(cfg) - networks, networksClose, networksErr := networksStmt(cfg) - setNetwork, setNetworkClose, setNetworkErr := setNetworkStmt(cfg) - nipNetwork, nipNetworkClose, nipNetworkErr := nipNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - addMember, addMemberClose, addMemberErr := addMemberStmt(cfg) - addRole, addRoleClose, addRoleErr := addRoleStmt(cfg) - dropRole, dropRoleClose, dropRoleErr := dropRoleStmt(cfg) - showMember, showMemberClose, showMemberErr := showMemberStmt(cfg) - members, membersClose, membersErr := membersStmt(cfg) - editMember, editMemberClose, editMemberErr := editMemberStmt(cfg) - dropMember, dropMemberClose, dropMemberErr := dropMemberStmt(cfg) - addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg) - channels, channelsClose, channelsErr := channelsStmt(cfg) - setChannel, setChannelClose, setChannelErr := setChannelStmt(cfg) - endChannel, endChannelClose, endChannelErr := endChannelStmt(cfg) - join, joinClose, joinErr := joinStmt(cfg) - part, partClose, partErr := partStmt(cfg) - names, namesClose, namesErr := namesStmt(cfg) - addEvent, addEventClose, addEventErr := addEventStmt(cfg) - allAfter, allAfterClose, allAfterErr := allAfterStmt(cfg) - logMessage, logMessageClose, logMessageErr := logMessageStmt(cfg) - - closeFn := func() error { - return g.SomeFnError( - createUserClose, - userByUUIDClose, - updateUserClose, - deleteUserClose, - addNetworkClose, - getNetworkClose, - networksClose, - setNetworkClose, - nipNetworkClose, - membershipClose, - addMemberClose, - addRoleClose, - dropRoleClose, - showMemberClose, - membersClose, - editMemberClose, - dropMemberClose, - addChannelClose, - channelsClose, - setChannelClose, - endChannelClose, - joinClose, - partClose, - namesClose, - addEventClose, - allAfterClose, - logMessageClose, - ) - } - - err = g.SomeError( - createTablesErr, - createUserErr, - userByUUIDErr, - updateUserErr, - deleteUserErr, - addNetworkErr, - getNetworkErr, - networksErr, - setNetworkErr, - nipNetworkErr, - membershipErr, - addMemberErr, - addRoleErr, - dropRoleErr, - showMemberErr, - membersErr, - editMemberErr, - dropMemberErr, - addChannelErr, - channelsErr, - setChannelErr, - endChannelErr, - joinErr, - partErr, - namesErr, - addEventErr, - allAfterErr, - logMessageErr, - ) - if err != nil { - closeFn() - return queriesT{}, err - } - - var connMutex sync.RWMutex - return queriesT{ - createUser: func(a newUserT) (userT, error) { - connMutex.RLock() - defer connMutex.RUnlock() - return createUser(a) - }, - userByUUID: func(a uuid.UUID) (userT, error) { - connMutex.RLock() - defer connMutex.RUnlock() - return userByUUID(a) - }, - updateUser: func(a userT) error { - connMutex.RLock() - defer connMutex.RUnlock() - return updateUser(a) - }, - deleteUser: func(a uuid.UUID) error { - connMutex.RLock() - defer connMutex.RUnlock() - return deleteUser(a) - }, - addNetwork: func( - a userT, - b newNetworkT, - c uuid.UUID, - ) (networkT, error) { - connMutex.RLock() - defer connMutex.RUnlock() - return addNetwork(a, b, c) - }, - getNetwork: func(a userT, b uuid.UUID) (networkT, error) { - connMutex.RLock() - defer connMutex.RUnlock() - return getNetwork(a, b) - }, - networks: func( - a userT, - callback func(networkT) error, - ) error { - var ( - err error - rows *sql.Rows - ) - { - connMutex.RLock() - defer connMutex.RUnlock() - rows, err = networks(a) - } - if err != nil { - return err - } - - return networkEach(rows, callback) - }, - setNetwork: func(a memberT, b networkT) error { - connMutex.RLock() - defer connMutex.RUnlock() - return setNetwork(a, b) - }, - nipNetwork: func(a memberT) error { - connMutex.RLock() - defer connMutex.RUnlock() - return nipNetwork(a) - }, - membership: func(a userT, b networkT) (memberT, error) { - connMutex.RLock() - defer connMutex.RUnlock() - return membership(a, b) - }, - addMember: func(a memberT, b newMemberT) (memberT, error) { - connMutex.RLock() - defer connMutex.RUnlock() - return addMember(a, b) - }, - addRole: func(a memberT, b string, c memberT) error { - connMutex.RLock() - defer connMutex.RUnlock() - return addRole(a, b, c) - }, - dropRole: func(a memberT, b string, c memberT) error { - connMutex.RLock() - defer connMutex.RUnlock() - return dropRole(a, b, c) - }, - showMember: func(a memberT, b uuid.UUID) (memberT, error) { - connMutex.RLock() - defer connMutex.RUnlock() - return showMember(a, b) - }, - members: func(a memberT, callback func(memberT) error) error { - var ( - err error - rows *sql.Rows - ) - { - connMutex.RLock() - defer connMutex.RUnlock() - rows, err = members(a) - } - if err != nil { - return err - } - - return memberEach(rows, callback) - }, - editMember: func(a memberT, b memberT) error { - connMutex.RLock() - defer connMutex.RUnlock() - return editMember(a, b) - }, - dropMember: func(a memberT, b uuid.UUID) error { - connMutex.RLock() - defer connMutex.RUnlock() - return dropMember(a, b) - }, - addChannel: func( - a memberT, b newChannelT, - ) (channelT, error) { - connMutex.RLock() - defer connMutex.RUnlock() - return addChannel(a, b) - }, - channels: func( - a memberT, - callback func(channelT) error, - ) error { - var ( - err error - rows *sql.Rows - ) - { - connMutex.RLock() - defer connMutex.RUnlock() - rows, err = channels(a) - } - if err != nil { - return err - } - - return channelEach(rows, callback) - }, - setChannel: func(a memberT, b channelT) error { - connMutex.RLock() - defer connMutex.RUnlock() - return setChannel(a, b) - }, - endChannel: func(a memberT, b uuid.UUID) error { - connMutex.RLock() - defer connMutex.RUnlock() - return endChannel(a, b) - }, - join: func(a memberT, b uuid.UUID) error { - connMutex.RLock() - defer connMutex.RUnlock() - return join(a, b) - }, - part: func(a memberT, b channelT) error { - connMutex.RLock() - defer connMutex.RUnlock() - return part(a, b) - }, - names: func( - a memberT, - b uuid.UUID, - callback func(memberT) error, - ) error { - var ( - err error - rows *sql.Rows - ) - { - connMutex.RLock() - defer connMutex.RUnlock() - rows, err = names(a, b) - } - if err != nil { - return err - } - - return nameEach(rows, callback) - }, - addEvent: func(a newEventT) (eventT, error) { - connMutex.RLock() - defer connMutex.RUnlock() - return addEvent(a) - }, - allAfter: func( - a memberT, - b uuid.UUID, - callback func(eventT) error, - ) error { - var ( - err error - rows *sql.Rows - ) - { - connMutex.RLock() - defer connMutex.RUnlock() - rows, err = allAfter(a, b) - } - if err != nil { - return err - } - - return eventEach(rows, callback) - }, - logMessage: func(a userT, b messageT) error { - connMutex.Lock() - defer connMutex.Unlock() - return logMessage(a, b) - }, - close: func() error { - connMutex.Lock() - defer connMutex.Unlock() - return closeFn() - }, - }, nil -} - -func newChannelHandler(papod papodT) func(fiinha.Message) error { - return func(message fiinha.Message) error { - return nil - } -} - -func buildConsumers(prefix string) []consumerT { - return []consumerT{ - consumerT{ - topic: NEW_CHANNEL, - name: prefix + NEW_CHANNEL, - handlerFn: newChannelHandler, - }, - } -} - -func unregisterConsumers( - unsubscribeFn func(string, string), - consumers []consumerT, -) { - for _, c := range consumers { - unsubscribeFn(c.topic, c.name) - } -} - -func registerConsumers( - subscribeFn func(string, string, func(fiinha.Message) error) error, - unsubscribeFn func(string, string), - papod papodT, - consumers []consumerT, -) error { - for _, c := range consumers { - err := subscribeFn(c.topic, c.name, c.handlerFn(papod)) - if err != nil { - unregisterConsumers(unsubscribeFn, consumers) - return err - } - } - return nil -} func initListeners( daemonSocketPath string, commanderSocketPath string, ) (listenersT, error) { + _ = os.Remove(daemonSocketPath) + _ = os.Remove(commanderSocketPath) + daemon, err := net.Listen("unix", daemonSocketPath) if err != nil { return listenersT{}, err @@ -3525,202 +145,89 @@ func initListeners( daemon: daemon, commander: commander, close: func() error { - return g.SomeError( - daemon.Close(), - commander.Close(), + return g.SomeFnError( + daemon.Close, + commander.Close, ) }, }, nil } -// TODO: lock is global, should be by network -func newStateMutable() stateMutableT { - var rwmutex sync.RWMutex - state := stateMutableDataT{ - connections: map[uuid.UUID]*connectionT{}, - users: map[string][]uuid.UUID{}, - members: map[string]map[string][]uuid.UUID{}, - } - return stateMutableT{ - /* - connected: func(connection *connectionT) { - rwmutex.Lock() - defer rwmutex.Unlock() - state.connections[connection.uuid] = connection - }, - disconnect: func(connection *connectionT) { - { - rwmutex.Lock() - defer rwmutex.Unlock() - delete(state.connections, connection.uuid) - delete(state.users, connection.user.username) - delete(state.members, connection.user.username) - } - err := connection.conn.Close() - if err != nil { - g.Warning( - "Failed to close the connection", - "close-error", - "from", "daemon", - "err", err, - ) - } - }, - authenticated: func(connection *connectionT) { - username := connection.user.username - rwmutex.Lock() - defer rwmutex.Unlock() - if state.users[username] == nil { - state.users[username] = []uuid.UUID{} - } - state.users[username] = append( - state.users[username], - connection.uuid, - ) - }, - */ - subscribe: func( - username string, - channelNames []string, - ) { - rwmutex.Lock() - defer rwmutex.Unlock() - for _, channelName := range channelNames { - if state.members[channelName] == nil { - state.members[channelName] = - map[string][]uuid.UUID{} - } - state.members[channelName][username] = - state.users[username] - } - }, - /* - members: func(channelName string) []string { - rwmutex.RLock() - defer rwmutex.RUnlock() - usernames := make( - []string, - len(state.members[channelName]), - ) - i := 0 - for username, _ := range state.members[channelName] { - usernames[i] = username - i++ - } - return usernames - }, - connections: func(username string) []uuid.UUID { - rwmutex.RLock() - defer rwmutex.RUnlock() - connections := make( - []uuid.UUID, - len(state.users[username]), - ) - copy(connections, state.users[username]) - return connections - }, - connection: func(connectionID uuid.UUID) *connectionT { - rwmutex.RLock() - defer rwmutex.RUnlock() - return state.connections[connectionID] - }, - */ - } -} - -func newState() *stateT { - return &stateT{ +func newState() stateT { + return stateT{ members: pds.NewMap[string, []string](nil), users: pds.NewMap[string, []uuid.UUID](nil), connections: pds.NewMap[uuid.UUID, connectionT](nil), } } -func buildMetrics(prefix string) metricsT { +func buildMetrics(tag string) metricsT { return metricsT{ activeConnections: g.MakeGauge( "active-connection", - "prefix", prefix, + "tag", tag, ), nicksInChannel: g.MakeGauge( "nicks-in-channel", - "prefix", prefix, + "tag", tag, ), sendToClientError: g.MakeCounter( "send-to-client-error", - "prefix", prefix, + "tag", tag, ), receivedMessage: g.MakeCounter( "received-message", - "prefix", prefix, + "tag", tag, ), sentReply: g.MakeCounter( "sent-reply", - "prefix", prefix, + "tag", tag, ), } } -func NewWithPrefix( - databasePath string, - prefix string, - daemonSocketPath string, - commanderSocketPath string, -) (IPapod, error) { - queue, err := fiinha.New(databasePath) - if err != nil { - return papodT{}, err - } +func initDB(path string) (acude.AcudeI, error) { + return nil, nil +} - auth, err := cracha.New(databasePath) +func newPapod(baseDir string, tag string) (papodT, error) { + databasePath := baseDir + "/papod.dedo" + daemonSocketPath := baseDir + "/papod.daemon.socket" + commanderSocketPath := baseDir + "/papod.commander.socket" + + cracha, err := cracha.New(databasePath) if err != nil { - queue.Close() return papodT{}, err } listeners, err := initListeners(daemonSocketPath, commanderSocketPath) if err != nil { - queue.Close() - auth.Close() + cracha.Close() return papodT{}, err } - queries, err := initDB(databasePath, prefix) + acude, err := initDB(databasePath) if err != nil { - queue.Close() - auth.Close() + cracha.Close() listeners.close() return papodT{}, err } - consumers := buildConsumers(prefix) - stateMutable := newStateMutable() - state := newState() - // receivers := makeReceivers() - metrics := buildMetrics(prefix) - // logger := g.NewLogger("prefix", prefix, "program", "papod") - return papodT{ - queue: queue, - auth: auth, - queries: queries, + acude: acude, + cracha: cracha, listeners: listeners, - consumers: consumers, - stateMutable: stateMutable, - state: stm.Atom(state), - // receivers: receivers, - metrics: metrics, - // logger: logger, + state: stm.Atom(newState()), + metrics: buildMetrics(tag), }, nil } -func New(basePath string) (IPapod, error) { - return NewWithPrefix( - basePath + "/papod.db", - defaultPrefix, - basePath + "/papod.daemon.sock", - basePath + "/papod.commander.sock", - ) +func NewWith(baseDir string, tag string) (PapoI, error) { + return newPapod(baseDir, tag) +} + +func New() (PapoI, error) { + return NewWith(".", "papod-default") } func splitOnCRLF(data []byte, _atEOF bool) (int, []byte, error) { @@ -3796,24 +303,8 @@ func parseMessage(rawMessage string) (messageT, error) { return msg, nil } -func asNewEvent(msg messageT) newEventT { - return newEventT{} -} - -func joinEvent(member memberT) eventT { - return eventT{} -} - -func asMessage(event eventT) messageT { - return messageT{} -} - -func asReply(event eventT) replyT { - return replyT{} -} - -func removeConnection(state *stateT, connection *connectionT) *stateT { - return state +func removeConnection(state stateT, connection *connectionT) stateT { + return state // FIXME } /// Is this death by a thousand goroutines? Is the runtime able to handle the @@ -3824,7 +315,7 @@ func removeConnection(state *stateT, connection *connectionT) *stateT { func broadcastMessage( message messageT, channelName string, - state *stateT, + state stateT, ) { usernames, _ := state.members.Get(channelName) for _, username := range usernames { @@ -4147,8 +638,8 @@ func handleJOIN( msg messageT, ) ([]replyT, bool, error) { // FIXME: add to database - channels := strings.FieldsFunc(msg.params[0], splitCommas) - papod.stateMutable.subscribe(connection.user.username, channels) + // channels := strings.FieldsFunc(msg.params[0], splitCommas) + // papod.stateMutable.subscribe(connection.user.username, channels) return []replyT{ _RPL_NOTOPIC (connection, msg), @@ -4189,7 +680,6 @@ func handleAWAY( msg messageT, ) ([]replyT, bool, error) { replyFn := _RPL_NOWAWAY - if len(msg.params) == 0 { replyFn = _RPL_UNAWAY } @@ -4304,6 +794,7 @@ func handleUnknown( msg messageT, ) ([]replyT, bool, error) { // FIXME: user doesn't exist when unauthenticated + /* err := papod.queries.logMessage(userT{ }, msg) if err != nil { g.Warning( @@ -4314,6 +805,7 @@ func handleUnknown( "err", err, ) } + */ return []replyT{ _ERR_UNKNOWNCOMMAND(connection, msg), @@ -4422,7 +914,7 @@ func processMessage( ) } - stm.Swap(papod.state, func(state *stateT) *stateT { + stm.Swap(papod.state, func(state stateT) stateT { return removeConnection(state, connection) }) err := connection.conn.Close() @@ -4439,10 +931,13 @@ func processMessage( } if shouldClose { + // FIXME // papod.stateMutable.disconnect(connection) } } +func processTasks() // FIXME + func handleConnection(papod papodT, conn net.Conn) { connection := connectionT{ uuid: uuid.New(), @@ -4509,27 +1004,17 @@ func mkbgrun() (func(func()), func()) { } func (papod papodT) Start() error { - err := registerConsumers( - papod.queue.Subscribe, - papod.queue.Unsubscribe, - papod, - papod.consumers, - ) - if err != nil { - return err - } - - // FIXME: papod.logger.Info g.Info("Starting service", "lifecycle-event", "event", "starting-server", slog.Group( "versions", - "gobang", g.Version, "cracha", cracha.Version, - "fiinha", fiinha.Version, - "golite", golite.Version, - "uuid", uuid.Version, + "uuid", uuid.Version, + "pds", pds.Version, + "stm", stm.Version, "papod", Version, + "gobang", g.Version, + "gotext", gt.Version, ), ) @@ -4543,7 +1028,7 @@ func (papod papodT) Start() error { func (papod papodT) Close() error { // FIXME: does this wait for current handlers to wait? Well, it should. - unregisterConsumers(papod.queue.Unsubscribe, papod.consumers) + /* return g.WrapErrors( papod.listeners.close(), // papod.connCloser.closeAll(), @@ -4551,36 +1036,76 @@ func (papod papodT) Close() error { papod.queue.Close(), papod.queries.close(), ) + */ + return nil } -func basePathFrom(args []string) (string, error) { - if len(args) < 2 { - return os.Getwd() - } else { - return args[1], nil - } +func usage(argv0 string, w io.Writer) { + fmt.Fprintf( + w, + gt.Gettext("Usage: %s [-t TAG] [BASEDIR]"), + argv0, + ) } +func getopt(allArgs []string, w io.Writer) (argsT, int) { + argv0 := allArgs[0] + argv := allArgs[1:] + fs := flag.NewFlagSet("", flag.ContinueOnError) + fs.Usage = func() {} + fs.SetOutput(w) + tag := fs.String( + "t", + "", + "The specific instance tag for inclusion in the log", + ) + if fs.Parse(argv) != nil { + usage(argv0, w) + return argsT{}, 2 + } -func Main() { - g.Init("program", "papod") - - basePath, err := basePathFrom(os.Args) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) + subArgs := fs.Args() + baseDir := "." + if len(subArgs) != 0 { + baseDir = subArgs[0] } - ipapod, err := New(basePath) + return argsT{ + allArgs: allArgs, + subArgs: subArgs, + baseDir: baseDir, + tag: *tag, + }, 0 +} + +func run(env envT) int { + papod, err := newPapod(env.args.baseDir, env.args.tag) if err != nil { - fmt.Printf("Failed to create papod: %v\n", err) - os.Exit(1) + fmt.Fprintln(env.err, err) + return 1 } - err = ipapod.Start() + err = papod.Start() if err != nil { - fmt.Printf(gt.Gettext("Failed to start: %v\n"), err) - os.Exit(1) + fmt.Fprintln(env.err, err) + return 1 } + + return 0 +} + + + +func Main() { + g.Init() + gt.Init(Name, LOCALEDIR) + args, rc := getopt(os.Args, os.Stderr) + g.ExitIf(rc) + os.Exit(run(envT{ + args: args, + in: os.Stdin, + out: os.Stdout, + err: os.Stderr, + })) } diff --git a/tests/papod.go b/tests/papod.go index 3e82d82..3f0e557 100644 --- a/tests/papod.go +++ b/tests/papod.go @@ -2,5304 +2,14 @@ package papod import ( "bufio" - "crypto/rand" - "database/sql" "errors" - "fmt" - "io" - "os" - "reflect" "strings" - "time" - "golite" - "uuid" g "gobang" ) -type userChangeT struct{ - id int64 - timestamp time.Time - user_id int64 - attribute string - valueStr *string - valueBlob *uuid.UUID - valueBool *bool - op bool -} - -type networkChangeT struct{ - id int64 - timestamp time.Time - network_id int64 - attribute string - value string - op bool -} - -type channelChangeT struct{ - id int64 - timestamp time.Time - channel_id int64 - attribute string - value string - op bool -} - - - -func userChangesSQL(prefix string) string { - const tmpl = ` - SELECT - "%s_user_changes".id, - "%s_user_changes".timestamp, - "%s_user_changes".user_id, - "%s_user_changes".attribute, - "%s_user_changes".value_text, - "%s_user_changes".value_blob, - "%s_user_changes".value_bool, - "%s_user_changes".op - FROM "%s_user_changes" - JOIN "%s_users" ON - "%s_user_changes".user_id = "%s_users".id - WHERE "%s_users".user_uuid = ? - ORDER BY "%s_user_changes".id ASC; - ` - return fmt.Sprintf( - tmpl, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - ) -} - -func makeUserChanges(db *sql.DB, prefix string) func(uuid.UUID) []userChangeT { - q := userChangesSQL(prefix) - - return func(userID uuid.UUID) []userChangeT { - userChanges := []userChangeT{} - rows, err := db.Query(q, userID[:]) - g.TErrorIf(err) - defer rows.Close() - - for rows.Next() { - userChange := userChangeT{} - var ( - timestr string - value_bytes []byte - ) - err := rows.Scan( - &userChange.id, - ×tr, - &userChange.user_id, - &userChange.attribute, - &userChange.valueStr, - &value_bytes, - &userChange.valueBool, - &userChange.op, - ) - g.TErrorIf(err) - - if value_bytes != nil { - valueBlob := uuid.UUID(value_bytes) - userChange.valueBlob = &valueBlob - } - - userChange.timestamp, err = time.Parse( - time.RFC3339Nano, - timestr, - ) - g.TErrorIf(err) - - userChanges = append(userChanges, userChange) - } - - g.TErrorIf(rows.Err()) - return userChanges - } -} - -func networkChangesSQL(prefix string) string { - const tmpl = ` - SELECT - "%s_network_changes".id, - "%s_network_changes".timestamp, - "%s_network_changes".network_id, - "%s_network_changes".attribute, - "%s_network_changes".value, - "%s_network_changes".op - FROM "%s_network_changes" - JOIN "%s_networks" ON - "%s_network_changes".network_id = "%s_networks".id - WHERE "%s_networks".uuid = ? - ORDER BY "%s_network_changes".id ASC; - ` - return fmt.Sprintf( - tmpl, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - prefix, - ) -} - -func makeNetworkChanges( - db *sql.DB, - prefix string, -) func(uuid.UUID) []networkChangeT { - q := networkChangesSQL(prefix) - - return func(networkID uuid.UUID) []networkChangeT { - networkChanges := []networkChangeT{} - rows, err := db.Query(q, networkID[:]) - g.TErrorIf(err) - defer rows.Close() - - for rows.Next() { - networkChange := networkChangeT{} - var timestr string - err := rows.Scan( - &networkChange.id, - ×tr, - &networkChange.network_id, - &networkChange.attribute, - &networkChange.value, - &networkChange.op, - ) - g.TErrorIf(err) - - networkChange.timestamp, err = time.Parse( - time.RFC3339Nano, - timestr, - ) - g.TErrorIf(err) - - networkChanges = append(networkChanges, networkChange) - } - - g.TErrorIf(rows.Err()) - return networkChanges - } -} - -func channelChangesSQL(prefix string) string { - const tmpl = ` - SELECT - id, - timestamp, - channel_id, - attribute, - value_text, - value_bool, - op - FROM "%s_channel_changes" - WHERE channel_id = ? - ORDER BY id ASC; - ` - return fmt.Sprintf(tmpl, prefix) -} - -func makeChannelChanges( - db *sql.DB, - prefix string, -) func(int64) []channelChangeT { - q := channelChangesSQL(prefix) - - return func(id int64) []channelChangeT { - channelChanges := []channelChangeT{} - rows, err := db.Query(q, id) - g.TErrorIf(err) - defer rows.Close() - - for rows.Next() { - channelChange := channelChangeT{} - var ( - timestr string - valueString sql.NullString - valueBool sql.NullBool - ) - err := rows.Scan( - &channelChange.id, - ×tr, - &channelChange.channel_id, - &channelChange.attribute, - &valueString, - &valueBool, - &channelChange.op, - ) - g.TErrorIf(err) - - if valueString.Valid { - channelChange.value = valueString.String - } else if valueBool.Valid { - if valueBool.Bool { - channelChange.value = "true" - } else { - channelChange.value = "false" - } - } - - channelChange.timestamp, err = time.Parse( - time.RFC3339Nano, - timestr, - ) - g.TErrorIf(err) - - channelChanges = append(channelChanges, channelChange) - } - - g.TErrorIf(rows.Err()) - return channelChanges - } -} - -func mknstring(n int) string { - buffer := make([]byte, n) - _, err := io.ReadFull(rand.Reader, buffer) - g.TErrorIf(err) - return string(buffer) -} - -func mkstring() string { - return mknstring(32) -} - - -func test_defaultPrefix() { - g.TestStart("defaultPrefix") - - g.Testing("the defaultPrefix is valid", func() { - g.TErrorIf(g.ValidateSQLTablePrefix(defaultPrefix)) - }) -} - -func test_tryRollback() { - g.TestStart("tryRollback()") - - myErr := errors.New("bottom error") - - db, err := sql.Open(golite.DriverName, golite.InMemory) - g.TErrorIf(err) - defer db.Close() - - - g.Testing("the error is propagated if rollback doesn't fail", func() { - tx, err := db.Begin() - g.TErrorIf(err) - - err = tryRollback(tx, myErr) - g.TAssertEqual(err, myErr) - }) - - g.Testing("a wrapped error when rollback fails", func() { - tx, err := db.Begin() - g.TErrorIf(err) - - err = tx.Commit() - g.TErrorIf(err) - - err = tryRollback(tx, myErr) - g.TAssertEqual(reflect.DeepEqual(err, myErr), false) - g.TAssertEqual(errors.Is(err, myErr), true) - }) -} - -func test_inTx() { - g.TestStart("inTx()") - - db, err := sql.Open(golite.DriverName, golite.InMemory) - g.TErrorIf(err) - defer db.Close() - - - g.Testing("when fn() errors, we propagate it", func() { - myErr := errors.New("to be propagated") - err := inTx(db, func(tx *sql.Tx) error { - return myErr - }) - g.TAssertEqual(err, myErr) - }) - - g.Testing("on nil error we get nil", func() { - err := inTx(db, func(tx *sql.Tx) error { - return nil - }) - g.TErrorIf(err) - }) -} - -func test_createTables() { - g.TestStart("createTables()") - - db, err := sql.Open(golite.DriverName, golite.InMemory) - g.TErrorIf(err) - defer db.Close() - - - g.Testing("tables exist afterwards", func() { - const tmpl_read = ` - SELECT id FROM "%s_channel_events" LIMIT 1; - ` - qRead := fmt.Sprintf(tmpl_read, defaultPrefix) - - _, err := db.Exec(qRead) - g.TErrorNil(err) - - err = createTables(db, defaultPrefix) - g.TErrorIf(err) - - _, err = db.Exec(qRead) - g.TErrorIf(err) - }) - - g.Testing("we can do it multiple times", func() { - g.TErrorIf(g.SomeError( - createTables(db, defaultPrefix), - createTables(db, defaultPrefix), - createTables(db, defaultPrefix), - )) - }) -} - -func test_createUserStmt() { - g.TestStart("createUserStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - g.TErrorIf(createUserErr) - defer g.SomeFnError( - createUserClose, - db.Close, - ) - - userChanges := makeUserChanges(db, prefix) - - - g.Testing("userID's must be unique", func() { - newUser := newUserT{ - uuid: uuid.New(), - } - - _, err1 := createUser(newUser) - _, err2 := createUser(newUser) - g.TErrorIf(err1) - g.TAssertEqual( - err2.(golite.Error).ExtendedCode, - golite.ErrConstraintUnique, - ) - }) - - g.Testing("a new user starts without a pictureID", func() { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - g.TAssertEqual(user.pictureID == nil, true) - }) - - g.Testing("the database fills default and generated values", func() { - newUser := newUserT{ - uuid: uuid.New(), - username: "the username", - displayName: "the display name", - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - g.TAssertEqual(user.id == 0, false) - g.TAssertEqual(user.timestamp == time.Time{}, false) - g.TAssertEqual(user.uuid, newUser.uuid) - g.TAssertEqual(user.username, "the username") - g.TAssertEqual(user.displayName, "the display name") - }) - - g.Testing("users can have duplicate names and usernames", func() { - username := mkstring() - displayName := mkstring() - - newUser1 := newUserT{ - uuid: uuid.New(), - username: username, - displayName: displayName, - } - - newUser2 := newUserT{ - uuid: uuid.New(), - username: username, - displayName: displayName, - } - - _, err1 := createUser(newUser1) - _, err2 := createUser(newUser2) - g.TErrorIf(err1) - g.TErrorIf(err2) - }) - - g.Testing("new user trigger inserts into *_user_changes", func() { - newUser := newUserT{ - uuid: uuid.New(), - username: mkstring(), - displayName: mkstring(), - } - - _, err := createUser(newUser) - g.TErrorIf(err) - - changes := userChanges(newUser.uuid) - g.TAssertEqual(len(changes), 3) - g.TAssertEqual( - []string{ - changes[0].attribute, - changes[1].attribute, - changes[2].attribute, - *changes[0].valueStr, - *changes[1].valueStr, - }, - []string{ - "username", - "display_name", - "deleted", - newUser.username, - newUser.displayName, - }, - ) - g.TAssertEqual(*changes[2].valueBool, false) - g.TAssertEqual( - []bool{ - changes[0].op, - changes[1].op, - changes[2].op, - changes[0].valueBlob == nil, - changes[0].valueBool == nil, - changes[1].valueBlob == nil, - changes[1].valueBool == nil, - changes[2].valueStr == nil, - changes[2].valueBlob == nil, - }, - []bool{ - true, - true, - true, - true, - true, - true, - true, - true, - true, - }, - ) - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - createUserClose(), - createUserClose(), - createUserClose(), - )) - }) -} - -func test_userByUUIDStmt() { - g.TestStart("userByUUIDStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - userByUUID, userByUUIDClose, userByUUIDErr := userByUUIDStmt(cfg) - deleteUser, deleteUserClose, deleteUserErr := deleteUserStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - deleteUserErr, - userByUUIDErr, - )) - defer g.SomeFnError( - createUserClose, - deleteUserClose, - userByUUIDClose, - db.Close, - ) - - - g.Testing("when a user doesn't exist, we get sql.ErrNoRows", func() { - _, err := userByUUID(uuid.New()) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("after creating, we can retrieve the user", func() { - newUser := newUserT{ - uuid: uuid.New(), - username: mkstring(), - displayName: mkstring(), - } - - user1, err := createUser(newUser) - g.TErrorIf(err) - - user2, err := userByUUID(newUser.uuid) - g.TErrorIf(err) - - g.TAssertEqual(user2.uuid, newUser.uuid) - g.TAssertEqual(user2.username, newUser.username) - g.TAssertEqual(user2.displayName, newUser.displayName) - g.TAssertEqual(user2.pictureID == nil, true) - - g.TAssertEqual(user2, user1) - }) - - g.Testing("we can't retrieve a user once deleted",func() { - newUser := newUserT{ - uuid: uuid.New(), - } - - _, err := createUser(newUser) - g.TErrorIf(err) - - _, err = userByUUID(newUser.uuid) - g.TErrorIf(err) - - err = deleteUser(newUser.uuid) - g.TErrorIf(err) - - _, err = userByUUID(newUser.uuid) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - userByUUIDClose(), - userByUUIDClose(), - userByUUIDClose(), - )) - }) -} - -func test_updateUserStmt() { - g.TestStart("updateUserStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - userByUUID, userByUUIDClose, userByUUIDErr := userByUUIDStmt(cfg) - updateUser, updateUserClose, updateUserErr := updateUserStmt(cfg) - deleteUser, deleteUserClose, deleteUserErr := deleteUserStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - userByUUIDErr, - updateUserErr, - deleteUserErr, - )) - defer g.SomeFnError( - createUserClose, - userByUUIDClose, - updateUserClose, - deleteUserClose, - db.Close, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - username: mkstring(), - displayName: mkstring(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - userChanges := makeUserChanges(db, prefix) - - - g.Testing("a user needs to exist to be updated", func() { - virtualUser := userT{ - id: 1234, - } - - g.TAssertEqual(updateUser(virtualUser), sql.ErrNoRows) - }) - - g.Testing("after updating, fetching gives us the newer data", func() { - newUser := newUserT{ - uuid: uuid.New(), - username: "first username", - displayName: "first display name", - } - - user1, err := createUser(newUser) - g.TErrorIf(err) - - g.TAssertEqual(user1.username, newUser.username) - g.TAssertEqual(user1.displayName, newUser.displayName) - - - user2 := user1 - user2.username = "second username" - user2.displayName = "second display name" - - err = updateUser(user2) - g.TErrorIf(err) - - g.TAssertEqual(user2.id, user1.id) - g.TAssertEqual(user2.timestamp, user1.timestamp) - g.TAssertEqual(user2.uuid, user1.uuid) - - - user3, err := userByUUID(user2.uuid) - g.TErrorIf(err) - - g.TAssertEqual(user3, user2) - g.TAssertEqual(user3.username, "second username") - g.TAssertEqual(user3.displayName, "second display name") - }) - - g.Testing("can't update a deleted user", func() { - user := create() - - err = deleteUser(user.uuid) - g.TErrorIf(err) - - err = updateUser(user) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("we can add (and remove) a picture to the user", func() { - user1 := create() - g.TAssertEqual(user1.pictureID == nil, true) - - pictureID := uuid.New() - user2 := user1 - user2.pictureID = &pictureID - err = updateUser(user2) - g.TErrorIf(err) - g.TAssertEqual(user2.pictureID == nil, false) - - user3, err := userByUUID(user1.uuid) - g.TErrorIf(err) - g.TAssertEqual(user3.pictureID == nil, false) - g.TAssertEqual(user3, user2) - - user4 := user3 - user4.pictureID = nil - err = updateUser(user4) - g.TErrorIf(err) - - user5, err := userByUUID(user1.uuid) - g.TErrorIf(err) - g.TAssertEqual(user5.pictureID == nil, true) - g.TAssertEqual(user5, user4) - }) - - g.Testing("we can't update the timestamp or uuid", func() { - user1 := create() - - user2 := user1 - user2.timestamp = user2.timestamp.Add(time.Minute * 1) - user2.uuid = uuid.New() - err := updateUser(user2) - g.TErrorIf(err) - - user3, err := userByUUID(user1.uuid) - g.TErrorIf(err) - g.TAssertEqual(user3, user1) - }) - - g.Testing("no extra writes to *_user_changes when not updated", func() { - user := create() - g.TAssertEqual(len(userChanges(user.uuid)), 3) - - err := updateUser(user) - g.TErrorIf(err) - g.TAssertEqual(len(userChanges(user.uuid)), 3) - }) - - g.Testing("new username end up in *_user_changes when updated", func() { - newUser := newUserT{ - uuid: uuid.New(), - username: "first username", - displayName: "display name", - } - - user, err := createUser(newUser) - g.TErrorIf(err) - g.TAssertEqual(len(userChanges(user.uuid)), 3) - - user.username = "second username" - g.TErrorIf(updateUser(user)) - - changes := userChanges(user.uuid)[3:] - g.TAssertEqual(len(changes), 2) - g.TAssertEqual( - []string{ - changes[0].attribute, - changes[1].attribute, - *changes[0].valueStr, - *changes[1].valueStr, - }, - []string{ - "username", - "username", - "first username", - "second username", - }, - ) - g.TAssertEqual( - []bool{ - changes[0].op, - changes[1].op, - changes[0].valueBlob == nil, - changes[0].valueBool == nil, - changes[1].valueBlob == nil, - changes[1].valueBool == nil, - }, - []bool{ - false, - true, - true, - true, - true, - true, - }, - ) - }) - - g.Testing("displayName end up in *_user_changes when updated", func() { - newUser := newUserT{ - uuid: uuid.New(), - username: "username", - displayName: "first display name", - } - - user, err := createUser(newUser) - g.TErrorIf(err) - g.TAssertEqual(len(userChanges(user.uuid)), 3) - - user.displayName = "second display name" - g.TErrorIf(updateUser(user)) - changes := userChanges(user.uuid)[3:] - g.TAssertEqual(len(changes), 2) - g.TAssertEqual( - []string{ - changes[0].attribute, - changes[1].attribute, - *changes[0].valueStr, - *changes[1].valueStr, - }, - []string{ - "display_name", - "display_name", - "first display name", - "second display name", - }, - ) - g.TAssertEqual( - []bool{ - changes[0].op, - changes[1].op, - changes[0].valueBlob == nil, - changes[0].valueBool == nil, - changes[1].valueBlob == nil, - changes[1].valueBool == nil, - }, - []bool{ - false, - true, - true, - true, - true, - true, - }, - ) - }) - - g.Testing("pictureID end up in *_user_changes when updated", func() { - newUser := newUserT{ - uuid: uuid.New(), - username: "username", - displayName: "first display name", - } - pictureID := uuid.New() - - user, err := createUser(newUser) - g.TErrorIf(err) - g.TAssertEqual(len(userChanges(user.uuid)), 3) - - user.pictureID = &pictureID - g.TErrorIf(updateUser(user)) - changes := userChanges(user.uuid)[3:] - g.TAssertEqual(len(changes), 1) - g.TAssertEqual(changes[0].attribute, "picture_uuid") - g.TAssertEqual(*changes[0].valueBlob, pictureID) - g.TAssertEqual(changes[0].op, true) - - user.pictureID = nil - g.TErrorIf(updateUser(user)) - changes = userChanges(user.uuid)[4:] - g.TAssertEqual(len(changes), 1) - g.TAssertEqual(changes[0].attribute, "picture_uuid") - g.TAssertEqual(*changes[0].valueBlob, pictureID) - g.TAssertEqual( - []bool{ - changes[0].op, - changes[0].valueStr == nil, - changes[0].valueBool == nil, - }, - []bool{ - false, - true, - true, - }, - ) - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - updateUserClose(), - updateUserClose(), - updateUserClose(), - )) - }) -} - -func test_deleteUserStmt() { - g.TestStart("deleteUserStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - deleteUser, deleteUserClose, deleteUserErr := deleteUserStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - deleteUserErr, - )) - defer g.SomeFnError( - createUserClose, - deleteUserClose, - db.Close, - ) - - userChanges := makeUserChanges(db, prefix) - - - g.Testing("a user needs to exist to be deleted", func() { - err := deleteUser(uuid.New()) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("error if deleted more than once", func() { - newUser := newUserT{ - uuid: uuid.New(), - } - - _, err := createUser(newUser) - g.TErrorIf(err) - - err1 := deleteUser(newUser.uuid) - err2 := deleteUser(newUser.uuid) - g.TErrorIf(err1) - g.TAssertEqual(err2, sql.ErrNoRows) - }) - - g.Testing("deletion triggers insertion into *_user_changes", func() { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - g.TAssertEqual(len(userChanges(user.uuid)), 3) - - g.TErrorIf(deleteUser(user.uuid)) - changes := userChanges(user.uuid)[3:] - g.TAssertEqual(len(changes), 2) - g.TAssertEqual( - []string{ - changes[0].attribute, - changes[1].attribute, - }, - []string{ "deleted", "deleted" }, - ) - g.TAssertEqual( - []bool{ - *changes[0].valueBool, - changes[0].op, - changes[0].valueStr == nil, - changes[0].valueBlob == nil, - *changes[1].valueBool, - changes[1].op, - changes[1].valueStr == nil, - changes[1].valueBlob == nil, - }, - []bool{ - false, - false, - true, - true, - true, - true, - true, - true, - }, - ) - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - deleteUserClose(), - deleteUserClose(), - deleteUserClose(), - )) - }) -} - -func test_addNetworkStmt() { - g.TestStart("addNetworkStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - deleteUser, deleteUserClose, deleteUserErr := deleteUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - members, membersClose, membersErr := membersStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - deleteUserErr, - addNetworkErr, - membershipErr, - membersErr, - )) - defer g.SomeFnError( - createUserClose, - deleteUserClose, - addNetworkClose, - membershipClose, - membersClose, - db.Close, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - allMembers := func(actor memberT, networkID uuid.UUID) []memberT { - rows, err := members(actor) - g.TErrorIf(err) - defer rows.Close() - - var members []memberT - err = memberEach(rows, func(member memberT) error { - members = append(members, member) - return nil - }) - g.TErrorIf(err) - - return members - } - - networkChanges := makeNetworkChanges(db, prefix) - - - g.Testing("a user can create a network", func() { - creator := create() - - newNetwork := newNetworkT{ - uuid: uuid.New(), - name: "the network name", - description: "the network description", - type_: NetworkType_Unlisted, - } - - network, err := addNetwork(creator, newNetwork, uuid.New()) - g.TErrorIf(err) - - g.TAssertEqual(network.id == 0, false) - g.TAssertEqual(network.timestamp == time.Time{}, false) - g.TAssertEqual(network.uuid, newNetwork.uuid) - g.TAssertEqual(network.name, "the network name") - g.TAssertEqual(network.description, "the network description") - g.TAssertEqual(network.type_, NetworkType_Unlisted) - }) - - g.Testing("the creator needs to exist", func() { - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Unlisted, - } - - virtualUser := userT{ - id: 1234, - } - - _, err := addNetwork(virtualUser, newNetwork, uuid.New()) - g.TAssertEqual( - err.(golite.Error).ExtendedCode, - golite.ErrConstraintNotNull, - ) - }) - - g.Testing("we can't add the same network twice", func() { - creator := create() - - newNetwork := newNetworkT{ - uuid: uuid.New(), - name: mkstring(), - type_: NetworkType_Unlisted, - } - - _, err1 := addNetwork(creator, newNetwork, uuid.New()) - _, err2 := addNetwork(creator, newNetwork, uuid.New()) - g.TErrorIf(err1) - g.TAssertEqual( - err2.(golite.Error).ExtendedCode, - golite.ErrConstraintUnique, - ) - }) - - g.Testing("a user can create multiple networks", func() { - creator := create() - - newNetwork1 := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Unlisted, - } - newNetwork2 := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Unlisted, - } - - _, err1 := addNetwork(creator, newNetwork1, uuid.New()) - _, err2 := addNetwork(creator, newNetwork2, uuid.New()) - g.TErrorIf(err1) - g.TErrorIf(err2) - }) - - g.Testing("a deleted user can't create a network", func() { - creator := create() - - newNetwork1 := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Unlisted, - } - newNetwork2 := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Unlisted, - } - - _, err := addNetwork(creator, newNetwork1, uuid.New()) - g.TErrorIf(err) - - err = deleteUser(creator.uuid) - g.TErrorIf(err) - _, err = addNetwork(creator, newNetwork2, uuid.New()) - g.TAssertEqual( - err.(golite.Error).ExtendedCode, - golite.ErrConstraintNotNull, - ) - }) - - g.Testing("new network triggers inserts to the changes table", func() { - creator := create() - - newNetwork := newNetworkT{ - uuid: uuid.New(), - name: "the network name", - description: "the network description", - type_: NetworkType_Unlisted, - } - - _, err := addNetwork(creator, newNetwork, uuid.New()) - g.TErrorIf(err) - - changes := networkChanges(newNetwork.uuid) - g.TAssertEqual(len(changes), 4) - g.TAssertEqual( - []string{ - changes[0].attribute, - changes[1].attribute, - changes[2].attribute, - changes[3].attribute, - changes[0].value, - changes[1].value, - changes[2].value, - changes[3].value, - }, - []string{ - "name", - "description", - "type", - "deleted", - "the network name", - "the network description", - "unlisted", - "0", - }, - ) - g.TAssertEqual( - []bool{ - changes[0].op, - changes[1].op, - changes[2].op, - }, - []bool{ true, true, true }, - ) - }) - - g.Testing("the creator is automatically a member", func() { - creator := create() - memberID := uuid.New() - - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Unlisted, - } - - network, err := addNetwork(creator, newNetwork, memberID) - g.TErrorIf(err) - - member, err := membership(creator, network) - g.TErrorIf(err) - - members := allMembers(member, network.uuid) - g.TAssertEqual(len(members), 1) - g.TAssertEqual(members[0].uuid, memberID) - g.TAssertEqual(members[0].status, MemberStatus_Active) - }) - - g.Testing(`the creator has "creator" and "admin" roles`, func() { - creator := create() - memberID := uuid.New() - - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Unlisted, - } - - network, err := addNetwork(creator, newNetwork, memberID) - g.TErrorIf(err) - - member, err := membership(creator, network) - g.TErrorIf(err) - - g.TAssertEqual(member.roles, []string{"admin", "creator"}) - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - addNetworkClose(), - addNetworkClose(), - addNetworkClose(), - )) - }) -} - -func test_getNetworkStmt() { - g.TestStart("getNetworkStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - deleteUser, deleteUserClose, deleteUserErr := deleteUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - getNetwork, getNetworkClose, getNetworkErr := getNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - addMember, addMemberClose, addMemberErr := addMemberStmt(cfg) - dropMember, dropMemberClose, dropMemberErr := dropMemberStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - deleteUserErr, - addNetworkErr, - getNetworkErr, - membershipErr, - addMemberErr, - dropMemberErr, - )) - defer g.SomeFnError( - createUserClose, - deleteUserClose, - addNetworkClose, - getNetworkClose, - membershipClose, - addMemberClose, - dropMemberClose, - db.Close, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func(user userT, type_ NetworkType) (networkT, memberT) { - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: type_, - } - - network, err := addNetwork(user, newNetwork, uuid.New()) - g.TErrorIf(err) - - member, err := membership(user, network) - g.TErrorIf(err) - - return network, member - } - - - g.Testing("what we get is the same that was created", func() { - creator := create() - network1, _ := add(creator, NetworkType_Public) - - network2, err := getNetwork(creator, network1.uuid) - g.TErrorIf(err) - g.TAssertEqual(network2, network1) - }) - - g.Testing("a network needs to exist", func() { - _, err := getNetwork(create(), uuid.New()) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("the probing member needs to exist", func() { - creator := create() - network, _ := add(creator, NetworkType_Public) - - virtualUser := userT{ - id: 1234, - } - - _, err := getNetwork(creator, network.uuid) - g.TErrorIf(err) - - _, err = getNetwork(virtualUser, network.uuid) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("the probing user can see any public network", func() { - creator := create() - user := create() - network, _ := add(creator, NetworkType_Public) - - network1, err1 := getNetwork(creator, network.uuid) - network2, err2 := getNetwork(user, network.uuid) - g.TErrorIf(err1) - g.TErrorIf(err2) - g.TAssertEqual(network1, network) - g.TAssertEqual(network2, network) - }) - - g.Testing("the probing user sees the given unlisted network", func() { - creator := create() - user := create() - network, _ := add(creator, NetworkType_Unlisted) - - network1, err1 := getNetwork(creator, network.uuid) - network2, err2 := getNetwork(user, network.uuid) - g.TErrorIf(err1) - g.TErrorIf(err2) - g.TAssertEqual(network1, network) - g.TAssertEqual(network2, network) - }) - - g.Testing("the probing user can't see a private network", func() { - creator := create() - user := create() - network, _ := add(creator, NetworkType_Private) - - _, err1 := getNetwork(creator, network.uuid) - _, err2 := getNetwork(user, network.uuid) - g.TErrorIf(err1) - g.TAssertEqual(err2, sql.ErrNoRows) - }) - - g.Testing("the probing user must be a member to see it", func() { - creator := create() - user := create() - network, member := add(creator, NetworkType_Private) - newMember := newMemberT{ - userID: user.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - _, err := addMember(member, newMember) - g.TErrorIf(err) - - network1, err1 := getNetwork(creator, network.uuid) - network2, err2 := getNetwork(user, network.uuid) - g.TErrorIf(err1) - g.TErrorIf(err2) - g.TAssertEqual(network1, network) - g.TAssertEqual(network2, network) - }) - - g.Testing("we can get the network if the creator was deleted", func() { - creator := create() - user := create() - network, member := add(creator, NetworkType_Public) - newMember := newMemberT{ - userID: user.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - _, err := addMember(member, newMember) - g.TErrorIf(err) - - network1, err := getNetwork(creator, network.uuid) - g.TErrorIf(err) - - err = deleteUser(creator.uuid) - g.TErrorIf(err) - - network2, err := getNetwork(user, network.uuid) - g.TErrorIf(err) - g.TAssertEqual(network2, network1) - }) - - g.Testing("a deleted creator can't get a network", func() { - creator := create() - network, _ := add(creator, NetworkType_Public) - - _, err := getNetwork(creator, network.uuid) - g.TErrorIf(err) - - err = deleteUser(creator.uuid) - g.TErrorIf(err) - - _, err = getNetwork(creator, network.uuid) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("a deleted user can't get a public network", func() { - user := create() - network, _ := add(create(), NetworkType_Public) - - _, err := getNetwork(user, network.uuid) - g.TErrorIf(err) - - err = deleteUser(user.uuid) - g.TErrorIf(err) - - _, err = getNetwork(user, network.uuid) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("a deleted member can't get a private network", func() { - creator := create() - user := create() - network, member := add(creator, NetworkType_Private) - newMember := newMemberT{ - userID: user.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - _, err := addMember(member, newMember) - g.TErrorIf(err) - - _, err = getNetwork(user, network.uuid) - g.TErrorIf(err) - - err = deleteUser(user.uuid) - g.TErrorIf(err) - - _, err = getNetwork(user, network.uuid) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("a removed member can't get a private network", func() { - creator := create() - user := create() - network, member := add(creator, NetworkType_Private) - newMember := newMemberT{ - userID: user.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - _, err := getNetwork(user, network.uuid) - g.TAssertEqual(err, sql.ErrNoRows) - - _, err = addMember(member, newMember) - g.TErrorIf(err) - - _, err = getNetwork(user, network.uuid) - g.TErrorIf(err) - - err = dropMember(member, newMember.memberID) - g.TErrorIf(err) - - _, err = getNetwork(user, network.uuid) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - getNetworkClose(), - getNetworkClose(), - getNetworkClose(), - )) - }) -} - -func test_networkEach() { - g.TestStart("networkEach()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - networks, networksClose, networksErr := networksStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - addNetworkErr, - networksErr, - )) - defer g.SomeFnError( - createUserClose, - addNetworkClose, - networksClose, - db.Close, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func(user userT) uuid.UUID { - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Public, - } - - _, err := addNetwork(user, newNetwork, uuid.New()) - g.TErrorIf(err) - - return newNetwork.uuid - } - - - g.Testing("callback is not called on empty set", func() { - rows, err := networks(create()) - g.TErrorIf(err) - defer rows.Close() - - networkEach(rows, func(networkT) error { - g.Unreachable() - return nil - }) - }) - - g.Testing("the callback is called once for each entry", func() { - creator := create() - networkIDs := []uuid.UUID{ - add(creator), - add(creator), - add(creator), - } - - rows, err := networks(creator) - g.TErrorIf(err) - defer rows.Close() - - var collectedIDs[]uuid.UUID - err = networkEach(rows, func(network networkT) error { - collectedIDs = append(collectedIDs, network.uuid) - return nil - }) - g.TErrorIf(err) - - g.TAssertEqual(collectedIDs, networkIDs) - }) - - g.Testing("we halt if the timestamp is ill-formatted", func() { - creator := create() - - add(creator) - add(creator) - networkID := add(creator) - add(creator) - - const tmpl = ` - UPDATE "%s_networks" - SET timestamp = %s - WHERE uuid = ?; - ` - q1 := fmt.Sprintf(tmpl, prefix, "'01/01/1970'") - _, err := db.Exec(q1, networkID[:]) - g.TErrorIf(err) - - rows, err := networks(creator) - g.TErrorIf(err) - defer rows.Close() - - n := 0 - err = networkEach(rows, func(networkT) error { - n++ - return nil - }) - - g.TAssertEqual( - err, - &time.ParseError{ - Layout: time.RFC3339Nano, - Value: "01/01/1970", - LayoutElem: "2006", - ValueElem: "01/01/1970", - Message: "", - }, - ) - g.TAssertEqual(n, 5) - - q2 := fmt.Sprintf(tmpl, prefix, g.SQLiteNow) - _, err = db.Exec(q2, networkID[:]) - g.TErrorIf(err) - }) - - g.Testing("we halt if the callback returns an error", func() { - creator := create() - myErr := errors.New("callback error early return") - - rows1, err := networks(creator) - g.TErrorIf(err) - defer rows1.Close() - - n1 := 0 - err1 := networkEach(rows1, func(networkT) error { - n1++ - if n1 == 3 { - return myErr - } - return nil - }) - - rows2, err := networks(creator) - g.TErrorIf(err) - defer rows2.Close() - - n2 := 0 - err2 := networkEach(rows2, func(networkT) error { - n2++ - return nil - }) - - g.TAssertEqual(err1, myErr) - g.TErrorIf(err2) - g.TAssertEqual(n1, 3) - g.TAssertEqual(n2, 7) - }) - - g.Testing("noop when given nil for *sql.Rows", func() { - err := networkEach(nil, func(networkT) error { - g.Unreachable() - return nil - }) - g.TErrorIf(err) - }) -} - -func test_networksStmt() { - g.TestStart("networksStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - deleteUser, deleteUserClose, deleteUserErr := deleteUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - networks, networksClose, networksErr := networksStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - deleteUserErr, - addNetworkErr, - networksErr, - )) - defer g.SomeFnError( - createUserClose, - deleteUserClose, - addNetworkClose, - networksClose, - db.Close, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func(user userT, type_ NetworkType) networkT { - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: type_, - } - - network, err := addNetwork(user, newNetwork, uuid.New()) - g.TErrorIf(err) - - return network - } - - allNetworks := func(user userT) ([]networkT, error) { - rows, err := networks(user) - if err != nil { - return nil, err - } - defer rows.Close() - - networkList := []networkT{} - err = networkEach(rows, func(network networkT) error { - networkList = append(networkList, network) - return nil - }) - if err != nil { - return nil, err - } - - return networkList, nil - } - - - g.Testing("when there are no networks, we get 0", func() { - networks, err := allNetworks(create()) - g.TErrorIf(err) - g.TAssertEqual(len(networks), 0) - }) - - g.Testing("if we have only private networks, we also get none", func() { - creator := create() - add(creator, NetworkType_Private) - add(creator, NetworkType_Private) - add(creator, NetworkType_Private) - - networks, err := allNetworks(create()) - g.TErrorIf(err) - g.TAssertEqual(len(networks), 0) - }) - - g.Testing("when only unlisted networks, we also get none", func() { - creator := create() - add(creator, NetworkType_Unlisted) - add(creator, NetworkType_Unlisted) - add(creator, NetworkType_Unlisted) - - networks, err := allNetworks(create()) - g.TErrorIf(err) - g.TAssertEqual(len(networks), 0) - }) - - g.Testing("we can get a list of public networks", func() { - creator := create() - expected := []networkT{ - add(creator, NetworkType_Public), - add(creator, NetworkType_Public), - add(creator, NetworkType_Public), - } - - networks, err := allNetworks(create()) - g.TErrorIf(err) - g.TAssertEqual(networks, expected) - }) - - g.Testing("a member user can see their's private networks", func() { - creator1 := create() - creator2 := create() - add(creator1, NetworkType_Private) - add(creator1, NetworkType_Private) - add(creator1, NetworkType_Private) - add(creator2, NetworkType_Private) - add(creator2, NetworkType_Private) - add(creator2, NetworkType_Private) - - networks, err := allNetworks(creator2) - g.TErrorIf(err) - g.TAssertEqual(len(networks), 6) - }) - - g.Testing("a member user can see their's unlisted networks", func() { - creator1 := create() - creator2 := create() - add(creator1, NetworkType_Unlisted) - add(creator1, NetworkType_Unlisted) - add(creator1, NetworkType_Unlisted) - add(creator2, NetworkType_Unlisted) - add(creator2, NetworkType_Unlisted) - add(creator2, NetworkType_Unlisted) - - networks, err := allNetworks(creator2) - g.TErrorIf(err) - g.TAssertEqual(len(networks), 6) - }) - - g.Testing("a deleted user can't list anything", func() { - creator := create() - g.TErrorIf(deleteUser(creator.uuid)) - - _, err := allNetworks(creator) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - networksClose(), - networksClose(), - networksClose(), - )) - }) -} - -func test_setNetworkStmt() { - g.TestStart("setNetworkStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - getNetwork, getNetworkClose, getNetworkErr := getNetworkStmt(cfg) - setNetwork, setNetworkClose, setNetworkErr := setNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - addMember, addMemberClose, addMemberErr := addMemberStmt(cfg) - addRole, addRoleClose, addRoleErr := addRoleStmt(cfg) - dropRole, dropRoleClose, dropRoleErr := dropRoleStmt(cfg) - editMember, editMemberClose, editMemberErr := editMemberStmt(cfg) - dropMember, dropMemberClose, dropMemberErr := dropMemberStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - addNetworkErr, - getNetworkErr, - setNetworkErr, - membershipErr, - addMemberErr, - addRoleErr, - dropRoleErr, - editMemberErr, - dropMemberErr, - )) - defer g.SomeFnError( - createUserClose, - addNetworkClose, - getNetworkClose, - setNetworkClose, - membershipClose, - addMemberClose, - addRoleClose, - dropRoleClose, - editMemberClose, - dropMemberClose, - db.Close, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func(user userT) (networkT, memberT) { - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Public, - } - memberID := uuid.New() - - network, err := addNetwork(user, newNetwork, memberID) - g.TErrorIf(err) - - member, err := membership(user, network) - g.TErrorIf(err) - - return network, member - } - - networkChanges := makeNetworkChanges(db, prefix) - - - g.Testing("creator can change the network", func() { - creator := create() - network1, member := add(creator) - - name := network1.name + "name suffix" - network1.name = name - err := setNetwork(member, network1) - g.TErrorIf(err) - - network2, err := getNetwork(creator, network1.uuid) - g.TErrorIf(err) - g.TAssertEqual(network2.name, name) - }) - - g.Testing(`"network-settings-update" can change the network`, func() { - creator := create() - admin := create() - network, creatorMember := add(creator) - newMember := newMemberT{ - userID: admin.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - adminMember, err := addMember(creatorMember, newMember) - g.TErrorIf(err) - - err = addRole( - creatorMember, - "network-settings-update", - adminMember, - ) - g.TErrorIf(err) - - name := network.name + "name suffix" - network.name = name - err = setNetwork(adminMember, network) - g.TErrorIf(err) - }) - - g.Testing(`"admin" can change the network`, func() { - creator := create() - admin := create() - network, creatorMember := add(creator) - newMember := newMemberT{ - userID: admin.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - adminMember, err := addMember(creatorMember, newMember) - g.TErrorIf(err) - - err = addRole(creatorMember, "admin", adminMember) - g.TErrorIf(err) - - name := network.name + "name suffix" - network.name = name - err = setNetwork(adminMember, network) - g.TErrorIf(err) - }) - - g.Testing("ex-member creator looses ability to change it", func() { - creator := create() - network, member := add(creator) - - err := dropMember(member, member.uuid) - g.TErrorIf(err) - - network.name = network.name + "name suffix" - err = setNetwork(member, network) - g.TAssertEqual( - err.(golite.Error).ExtendedCode, - golite.ErrConstraintTrigger, - ) - }) - - g.Testing("ex-admin member looses ability to change it", func() { - creator := create() - admin := create() - network, creatorMember := add(creator) - newMember := newMemberT{ - userID: admin.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - adminMember, err := addMember(creatorMember, newMember) - g.TErrorIf(err) - - err = addRole(creatorMember, "admin", adminMember) - g.TErrorIf(err) - - name := network.name + "name suffix" - network.name = name - err = setNetwork(adminMember, network) - g.TErrorIf(err) - - err = dropRole(creatorMember, "admin", adminMember) - g.TErrorIf(err) - - err = setNetwork(adminMember, network) - g.TAssertEqual( - err.(golite.Error).ExtendedCode, - golite.ErrConstraintTrigger, - ) - }) - - g.Testing("unauthorized users can't change the network", func() { - creator := create() - user := create() - network, creatorMember := add(creator) - newMember := newMemberT{ - userID: user.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - userMember, err := addMember(creatorMember, newMember) - g.TErrorIf(err) - - network.name = "member can't set the name" - err = setNetwork(userMember, network) - g.TAssertEqual( - err.(golite.Error).ExtendedCode, - golite.ErrConstraintTrigger, - ) - }) - - g.Testing("non members can't change the network", func() { - creator := create() - network, member := add(creator) - _, otherMember := add(creator) - - network.name = "non member can't set the name xablauzinho" - err1 := setNetwork(otherMember, network) - err2 := setNetwork(member, network) - g.TAssertEqual( - err1.(golite.Error).ExtendedCode, - golite.ErrConstraintTrigger, - ) - g.TErrorIf(err2) - }) - - g.Testing("after setting, getting gives us the newer data", func() { - creator := create() - network1, member := add(creator) - - network2 := network1 - network2.name = "first network name" - network2.description = "first network description" - network2.type_ = NetworkType_Private - - err := setNetwork(member, network2) - g.TErrorIf(err) - - network3, err := getNetwork(creator, network1.uuid) - g.TErrorIf(err) - g.TAssertEqual(network3, network2) - }) - - g.Testing("the uuid, timestamp or creator never changes", func() { - creator := create() - network1, member := add(creator) - - network2 := network1 - network2.uuid = uuid.New() - network2.timestamp = time.Time{} - - err := setNetwork(member, network2) - g.TErrorIf(err) - - network3, err := getNetwork(creator, network1.uuid) - g.TErrorIf(err) - g.TAssertEqual(network3, network1) - g.TAssertEqual(reflect.DeepEqual(network3, network2), false) - }) - - g.Testing("inactive member can't set the network", func() { - network, member := add(create()) - - member.status = "inactive" - err := editMember(member, member) - g.TErrorIf(err) - - err = setNetwork(member, network) - g.TAssertEqual( - err.(golite.Error).ExtendedCode, - golite.ErrConstraintTrigger, - ) - }) - - g.Testing("removed member can't set the network", func() { - network, member := add(create()) - - member.status = "removed" - err := editMember(member, member) - g.TErrorIf(err) - - err = setNetwork(member, network) - g.TAssertEqual( - err.(golite.Error).ExtendedCode, - golite.ErrConstraintTrigger, - ) - }) - - g.Testing("no extra writes to changes table when no updates", func() { - network, member := add(create()) - - lenBefore := len(networkChanges(network.uuid)) - - err := setNetwork(member, network) - g.TErrorIf(err) - - lenAfter := len(networkChanges(network.uuid)) - - g.TAssertEqual(lenBefore, lenAfter) - }) - - g.Testing("updates do cause writes to changes table", func() { - creator := create() - newNetwork := newNetworkT{ - uuid: uuid.New(), - name: "first name", - description: "first description", - type_: NetworkType_Public, - } - memberID := uuid.New() - - network, err := addNetwork(creator, newNetwork, memberID) - g.TErrorIf(err) - - member, err := membership(creator, network) - g.TErrorIf(err) - - lenBefore := len(networkChanges(network.uuid)) - - network.name = "second name" - network.description = "second description" - network.type_ = NetworkType_Unlisted - err = setNetwork(member, network) - g.TErrorIf(err) - - changes := networkChanges(network.uuid)[lenBefore:] - g.TAssertEqual(len(changes), 6) - g.TAssertEqual( - []string{ - changes[0].attribute, - changes[1].attribute, - changes[2].attribute, - changes[3].attribute, - changes[4].attribute, - changes[5].attribute, - changes[0].value, - changes[1].value, - changes[2].value, - changes[3].value, - changes[4].value, - changes[5].value, - }, - []string{ - "type", - "type", - "description", - "description", - "name", - "name", - "public", - "unlisted", - "first description", - "second description", - "first name", - "second name", - }, - ) - g.TAssertEqual( - []bool{ - changes[0].op, - changes[1].op, - changes[2].op, - changes[3].op, - changes[4].op, - changes[5].op, - }, - []bool{ - false, - true, - false, - true, - false, - true, - }, - ) - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - setNetworkClose(), - setNetworkClose(), - setNetworkClose(), - )) - }) -} - -func test_nipNetworkStmt() { - g.TestStart("nipNetworkStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - getNetwork, getNetworkClose, getNetworkErr := getNetworkStmt(cfg) - networks, networksClose, networksErr := networksStmt(cfg) - setNetwork, setNetworkClose, setNetworkErr := setNetworkStmt(cfg) - nipNetwork, nipNetworkClose, nipNetworkErr := nipNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - addMember, addMemberClose, addMemberErr := addMemberStmt(cfg) - addRole, addRoleClose, addRoleErr := addRoleStmt(cfg) - editMember, editMemberClose, editMemberErr := editMemberStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - addNetworkErr, - getNetworkErr, - networksErr, - setNetworkErr, - nipNetworkErr, - membershipErr, - addMemberErr, - addRoleErr, - editMemberErr, - )) - defer g.SomeFnError( - createUserClose, - addNetworkClose, - getNetworkClose, - networksClose, - setNetworkClose, - nipNetworkClose, - membershipClose, - addMemberClose, - addRoleClose, - editMemberClose, - db.Close, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func(user userT) (networkT, memberT) { - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Public, - } - - network, err := addNetwork(user, newNetwork, uuid.New()) - g.TErrorIf(err) - - member, err := membership(user, network) - g.TErrorIf(err) - - return network, member - } - - allNetworks := func(user userT) []networkT { - rows, err := networks(user) - g.TErrorIf(err) - defer rows.Close() - - networkList := []networkT{} - err = networkEach(rows, func(network networkT) error { - networkList = append(networkList, network) - return nil - }) - g.TErrorIf(err) - - return networkList - } - - networkChanges := makeNetworkChanges(db, prefix) - - - g.Testing("can't `get` a deleted network", func() { - creator := create() - network, member := add(creator) - err := nipNetwork(member) - g.TErrorIf(err) - - _, err = getNetwork(creator, network.uuid) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("after deletion it vanishes from `networks()`", func() { - creator := create() - _, member := add(creator) - - g.TAssertEqual(len(allNetworks(creator)), 1) - - err := nipNetwork(member) - g.TErrorIf(err) - - g.TAssertEqual(len(allNetworks(creator)), 0) - }) - - g.Testing("can't `set` a deleted network", func() { - network, member := add(create()) - err := nipNetwork(member) - g.TErrorIf(err) - - err = setNetwork(member, network) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("can't delete a network more than once", func() { - _, member := add(create()) - - err1 := nipNetwork(member) - err2 := nipNetwork(member) - g.TErrorIf(err1) - g.TAssertEqual(err2, sql.ErrNoRows) - }) - - g.Testing("can't get membership of a delete network", func() { - creator := create() - network, member := add(creator) - - _, err := membership(creator, network) - g.TErrorIf(err) - - err = nipNetwork(member) - g.TErrorIf(err) - - _, err = membership(creator, network) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("an admin can delete a network", func() { - admin := create() - _, creatorMember := add(create()) - newMember := newMemberT{ - userID: admin.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - adminMember, err := addMember(creatorMember, newMember) - g.TErrorIf(err) - - err = addRole(creatorMember, "admin", adminMember) - g.TErrorIf(err) - - err = nipNetwork(adminMember) - g.TErrorIf(err) - }) - - g.Testing("a member can't delete", func() { - user := create() - _, creatorMember := add(create()) - newMember := newMemberT{ - userID: user.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - userMember, err := addMember(creatorMember, newMember) - g.TErrorIf(err) - - err = nipNetwork(userMember) - g.TAssertEqual( - err.(golite.Error).ExtendedCode, - golite.ErrConstraintTrigger, - ) - }) - - g.Testing("an inactive admin member also can't", func() { - user := create() - _, member := add(create()) - newMember := newMemberT{ - userID: user.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - member.status = "inactive" - err := editMember(member, member) - g.TErrorIf(err) - - _, err = addMember(member, newMember) - g.TAssertEqual( - err.(golite.Error).ExtendedCode, - golite.ErrConstraintNotNull, - ) - }) - - g.Testing("deletion triggers writes to the changes table", func() { - network, member := add(create()) - - changes1Len := len(networkChanges(network.uuid)) - - err := nipNetwork(member) - g.TErrorIf(err) - - changes := networkChanges(network.uuid) - g.TAssertEqual(len(changes), changes1Len + 2) - - changes = changes[changes1Len:] - g.TAssertEqual( - []string{ - changes[0].attribute, - changes[1].attribute, - changes[0].value, - changes[1].value, - }, - []string{ - "deleted", - "deleted", - "0", - "1", - }, - ) - g.TAssertEqual( - []bool{ - changes[0].op, - changes[1].op, - }, - []bool{ - false, - true, - }, - ) - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - nipNetworkClose(), - nipNetworkClose(), - nipNetworkClose(), - )) - }) -} - -func test_membershipStmt() { - g.TestStart("membershipStmt") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - deleteUser, deleteUserClose, deleteUserErr := deleteUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - nipNetwork, nipNetworkClose, nipNetworkErr := nipNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - addMember, addMemberClose, addMemberErr := addMemberStmt(cfg) - dropMember, dropMemberClose, dropMemberErr := dropMemberStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - deleteUserErr, - addNetworkErr, - nipNetworkErr, - membershipErr, - addMemberErr, - dropMemberErr, - )) - defer g.SomeFnError( - createUserClose, - deleteUserClose, - addNetworkClose, - nipNetworkClose, - membershipClose, - addMemberClose, - dropMemberClose, - db.Close, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func(user userT) networkT { - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Private, - } - - network, err := addNetwork(user, newNetwork, uuid.New()) - g.TErrorIf(err) - - return network - } - - - g.Testing("user needs to exist", func() { - virtualUser := userT{ - id: 1234, - } - network := add(create()) - - _, err := membership(virtualUser, network) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("network needs to exist", func() { - virtualNetwork := networkT{ - id: 1234, - } - - _, err := membership(create(), virtualNetwork) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("the member contains all of its roles", func() { - creator := create() - network := add(creator) - - member, err := membership(creator, network) - g.TErrorIf(err) - g.TAssertEqual(member.roles, []string{ "admin", "creator" }) - }) - - g.Testing("a deleted user can't get their membership", func() { - creator := create() - network := add(creator) - - err := deleteUser(creator.uuid) - g.TErrorIf(err) - - _, err = membership(creator, network) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("can't get member from a deleted network", func() { - creator := create() - network := add(creator) - - member, err := membership(creator, network) - g.TErrorIf(err) - - err = nipNetwork(member) - g.TErrorIf(err) - - _, err = membership(creator, network) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("we get the same data as `addMember()`", func() { - creator := create() - user := create() - network := add(creator) - newMember := newMemberT{ - userID: user.uuid, - memberID: uuid.New(), - username: "a username", - } - - creatorMember, err := membership(creator, network) - g.TErrorIf(err) - - member1, err := addMember(creatorMember, newMember) - g.TErrorIf(err) - - member2, err := membership(user, network) - g.TErrorIf(err) - - g.TAssertEqual(member2, member1) - }) - - g.Testing("can't get membership of ex-member", func() { - creator := create() - admin := create() - network := add(creator) - newMember := newMemberT{ - userID: admin.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - creatorMember, err := membership(creator, network) - g.TErrorIf(err) - - adminMember, err := addMember(creatorMember, newMember) - g.TErrorIf(err) - - err = dropMember(adminMember, adminMember.uuid) - g.TErrorIf(err) - - _, err = membership(admin, network) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("a non-member gets an error", func() { - network := add(create()) - - _, err := membership(create(), network) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - membershipClose(), - membershipClose(), - membershipClose(), - )) - }) -} - -func test_addMemberStmt() { - g.TestStart("addMemberStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - deleteUser, deleteUserClose, deleteUserErr := deleteUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - nipNetwork, nipNetworkClose, nipNetworkErr := nipNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - addMember, addMemberClose, addMemberErr := addMemberStmt(cfg) - addRole, addRoleClose, addRoleErr := addRoleStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - deleteUserErr, - addNetworkErr, - nipNetworkErr, - membershipErr, - addMemberErr, - addRoleErr, - )) - defer g.SomeFnError( - createUserClose, - deleteUserClose, - addNetworkClose, - nipNetworkClose, - membershipClose, - addMemberClose, - addRoleClose, - db.Close, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func(user userT) (networkT, memberT) { - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Public, - } - - network, err := addNetwork(user, newNetwork, uuid.New()) - g.TErrorIf(err) - - member, err := membership(user, network) - g.TErrorIf(err) - - return network, member - } - - - g.Testing("the user needs to exist", func() { - _, member := add(create()) - newMember := newMemberT{ - userID: uuid.New(), - memberID: uuid.New(), - username: mkstring(), - } - - _, err := addMember(member, newMember) - g.TAssertEqual( - err.(golite.Error).ExtendedCode, - golite.ErrConstraintNotNull, - ) - }) - - g.Testing(`the member with role "add-member" is allowed`, func() { - user1 := create() - user2 := create() - _, member0 := add(create()) - newMember1 := newMemberT{ - userID: user1.uuid, - memberID: uuid.New(), - username: mkstring(), - } - newMember2 := newMemberT{ - userID: user2.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - member1, err := addMember(member0, newMember1) - g.TErrorIf(err) - - err = addRole(member0, "add-member", member1) - g.TErrorIf(err) - - _, err = addMember(member1, newMember2) - g.TErrorIf(err) - }) - - g.Testing(`the member with role "admin" is allowed`, func() { - user1 := create() - user2 := create() - _, member0 := add(create()) - newMember1 := newMemberT{ - userID: user1.uuid, - memberID: uuid.New(), - username: mkstring(), - } - newMember2 := newMemberT{ - userID: user2.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - member1, err := addMember(member0, newMember1) - g.TErrorIf(err) - - err = addRole(member0, "admin", member1) - g.TErrorIf(err) - - _, err = addMember(member1, newMember2) - g.TErrorIf(err) - }) - - g.Testing(`member without role "add-member" is forbidden`, func() { - user1 := create() - user2 := create() - _, member0 := add(create()) - newMember1 := newMemberT{ - userID: user1.uuid, - memberID: uuid.New(), - username: mkstring(), - } - newMember2 := newMemberT{ - userID: user2.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - member1, err := addMember(member0, newMember1) - g.TErrorIf(err) - - _, err = addMember(member1, newMember2) - g.TAssertEqual( - err.(golite.Error).ExtendedCode, - golite.ErrConstraintTrigger, - ) - }) - - g.Testing("can't add the same user twice", func() { - creator := create() - user := create() - _, member := add(creator) - newMember := newMemberT{ - userID: user.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - _, err1 := addMember(member, newMember) - _, err2 := addMember(member, newMember) - g.TErrorIf(err1) - g.TAssertEqual( - err2.(golite.Error).ExtendedCode, - golite.ErrConstraintUnique, - ) - }) - - g.Testing("the memberID must be unique", func() { - creator := create() - user1 := create() - user2 := create() - _, member := add(creator) - memberID := uuid.New() - newMember1 := newMemberT{ - userID: user1.uuid, - memberID: memberID, - username: mkstring(), - } - newMember2 := newMemberT{ - userID: user2.uuid, - memberID: memberID, - username: mkstring(), - } - - _, err1 := addMember(member, newMember1) - _, err2 := addMember(member, newMember2) - g.TErrorIf(err1) - g.TAssertEqual( - err2.(golite.Error).ExtendedCode, - golite.ErrConstraintUnique, - ) - }) - - g.Testing("the new member can't be a deleted user", func() { - creator := create() - user := create() - _, member := add(creator) - newMember := newMemberT{ - userID: user.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - err := deleteUser(user.uuid) - g.TErrorIf(err) - - _, err = addMember(member, newMember) - g.TAssertEqual( - err.(golite.Error).ExtendedCode, - golite.ErrConstraintNotNull, - ) - }) - - g.Testing("can't add to a deleted network", func() { - creator := create() - user := create() - _, member := add(creator) - newMember := newMemberT{ - userID: user.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - err := nipNetwork(member) - g.TErrorIf(err) - - _, err = addMember(member, newMember) - g.TAssertEqual( - err.(golite.Error).ExtendedCode, - golite.ErrConstraintNotNull, - ) - }) - - g.Testing("the same user can be a member of distinct networks", func() { - // FIXME - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - addMemberClose(), - addMemberClose(), - addMemberClose(), - )) - }) -} - -func test_addRoleStmt() { - g.TestStart("addRoleStmt") - - // FIXME -} - -func test_dropRoleStmt() { - g.TestStart("dropRoleStmt") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - dropRole, dropRoleClose, dropRoleErr := dropRoleStmt(cfg) - showMember, showMemberClose, showMemberErr := showMemberStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - addNetworkErr, - membershipErr, - dropRoleErr, - showMemberErr, - )) - defer g.SomeFnError( - createUserClose, - addNetworkClose, - membershipClose, - dropRoleClose, - showMemberClose, - db.Close, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func(user userT) (networkT, memberT) { - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Public, - } - - network, err := addNetwork(user, newNetwork, uuid.New()) - g.TErrorIf(err) - - member, err := membership(user, network) - g.TErrorIf(err) - - return network, member - } - - - g.Testing("acting member must exist", func() { - _, member := add(create()) - virtualMember := memberT{} - - err := dropRole(virtualMember, "a-role", member) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("target member must also exist", func() { - _, member := add(create()) - virtualMember := memberT{} - - err := dropRole(member, "a-role", virtualMember) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("noop if member doesn't have the role", func() { - return // FIXME - _, member := add(create()) - g.TAssertEqual(member.roles, []string{ "admin", "creator" }) - - err := dropRole(member, "does-not-exist", member) - g.TErrorIf(err) - - member, err = showMember(member, member.uuid) - g.TErrorIf(err) - g.TAssertEqual(member.roles, []string{ "admin", "creator" }) - }) - - g.Testing("role is removed when exists", func() { - _, member := add(create()) - g.TAssertEqual(member.roles, []string{ "admin", "creator" }) - - err := dropRole(member, "admin", member) - g.TErrorIf(err) - - member, err = showMember(member, member.uuid) - g.TErrorIf(err) - g.TAssertEqual(member.roles, []string{ "creator" }) - }) - - g.Testing("member can remove all roles from themselves", func() { - // FIXME - }) - - g.Testing(`member with "role-write" can drop others roles`, func() { - // FIXME - }) - - g.Testing(`member without "role-write" can't`, func() { - // FIXME - }) - - g.Testing("does not affect other members from other networks", func() { - // FIXME - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - dropRoleClose(), - dropRoleClose(), - dropRoleClose(), - )) - }) -} - -func test_showMemberStmt() { - g.TestStart("showMemberStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - showMember, showMemberClose, showMemberErr := showMemberStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - addNetworkErr, - membershipErr, - showMemberErr, - )) - defer g.SomeFnError( - createUserClose, - addNetworkClose, - membershipClose, - showMemberClose, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func(user userT) (networkT, memberT) { - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Public, - } - - network, err := addNetwork(user, newNetwork, uuid.New()) - g.TErrorIf(err) - - member, err := membership(user, network) - g.TErrorIf(err) - - return network, member - } - - - g.Testing("target member must exist", func() { - _, member := add(create()) - _, err := showMember(member, uuid.New()) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("acting member must exist", func() { - virtualMember := memberT{ - id: 1234, - } - - _, member := add(create()) - _, err := showMember(virtualMember, member.uuid) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("target user must belong to the same network", func() { - _, member1 := add(create()) - _, member2 := add(create()) - - _, err := showMember(member1, member2.uuid) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("we get the full member", func() { - _, member1 := add(create()) - member2, err := showMember(member1, member1.uuid) - g.TErrorIf(err) - - g.TAssertEqual(member2, member1) - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - showMemberClose(), - showMemberClose(), - showMemberClose(), - )) - }) -} - -func test_memberEach() { - g.TestStart("memberEach()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - addMember, addMemberClose, addMemberErr := addMemberStmt(cfg) - members, membersClose, membersErr := membersStmt(cfg) - editMember, editMemberClose, editMemberErr := editMemberStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - addNetworkErr, - membershipErr, - addMemberErr, - membersErr, - editMemberErr, - )) - defer g.SomeFnError( - createUserClose, - addNetworkClose, - membershipClose, - addMemberClose, - membersClose, - editMemberClose, - db.Close, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func() memberT { - creator := create() - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Public, - } - - network, err := addNetwork(creator, newNetwork, uuid.New()) - g.TErrorIf(err) - - member, err := membership(creator, network) - g.TErrorIf(err) - - return member - } - - addM := func(actor memberT, status MemberStatus) memberT { - user := create() - newMember := newMemberT{ - userID: user.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - member, err := addMember(actor, newMember) - g.TErrorIf(err) - - member.status = status - err = editMember(actor, member) - g.TErrorIf(err) - - return member - } - - - g.Testing("callback is called once for new network", func() { - member := add() - - rows, err := members(member) - g.TErrorIf(err) - defer rows.Close() - - memberIDs := []uuid.UUID{} - err = memberEach(rows, func(member memberT) error { - memberIDs = append(memberIDs, member.uuid) - return nil - }) - - g.TAssertEqual(len(memberIDs), 1) - g.TAssertEqual(memberIDs[0], member.uuid) - }) - - g.Testing("we halt if the callback returns an error", func() { - myErr := errors.New("callback custom error") - member := add() - expectedIDs := []uuid.UUID{ - member.uuid, - addM(member, MemberStatus_Active).uuid, - addM(member, MemberStatus_Active).uuid, - addM(member, MemberStatus_Active).uuid, - addM(member, MemberStatus_Active).uuid, - addM(member, MemberStatus_Active).uuid, - } - - rows, err := members(member) - g.TErrorIf(err) - defer rows.Close() - - memberIDs := []uuid.UUID{} - err = memberEach(rows, func(member memberT) error { - if len(memberIDs) == 3 { - return myErr - } - - memberIDs = append(memberIDs, member.uuid) - return nil - }) - g.TAssertEqual(err, myErr) - g.TAssertEqual(len(memberIDs), 3) - g.TAssertEqual(memberIDs, expectedIDs[0:3]) - }) - - g.Testing("noop when given nil for *sql.Rows", func() { - err := memberEach(nil, func(memberT) error { - g.Unreachable() - return nil - }) - g.TErrorIf(err) - }) -} - -func test_membersStmt() { - g.TestStart("membersStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - nipNetwork, nipNetworkClose, nipNetworkErr := nipNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - addMember, addMemberClose, addMemberErr := addMemberStmt(cfg) - members, membersClose, membersErr := membersStmt(cfg) - editMember, editMemberClose, editMemberErr := editMemberStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - addNetworkErr, - nipNetworkErr, - membershipErr, - addMemberErr, - membersErr, - editMemberErr, - )) - defer g.SomeFnError( - createUserClose, - addNetworkClose, - nipNetworkClose, - membershipClose, - addMemberClose, - membersClose, - editMemberClose, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func() memberT { - creator := create() - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Public, - } - - network, err := addNetwork(creator, newNetwork, uuid.New()) - g.TErrorIf(err) - - member, err := membership(creator, network) - g.TErrorIf(err) - - member.roles = nil - - return member - } - - addM := func(actor memberT, status MemberStatus) memberT { - user := create() - newMember := newMemberT{ - userID: user.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - member, err := addMember(actor, newMember) - g.TErrorIf(err) - - member.status = status - err = editMember(actor, member) - g.TErrorIf(err) - - member.roles = nil - - return member - } - - allMembers := func(member memberT) []memberT { - rows, err := members(member) - g.TErrorIf(err) - defer rows.Close() - - memberList := []memberT{} - err = memberEach(rows, func(member memberT) error { - memberList = append(memberList, member) - return nil - }) - g.TErrorIf(err) - - return memberList - } - - - // FIXME: members from other networks do not show up - g.Testing("inactive and removed members aren't listed", func() { - member := add() - expected := []memberT{ - member, - addM(member, MemberStatus_Active), - addM(member, MemberStatus_Inactive), - addM(member, MemberStatus_Inactive), - addM(member, MemberStatus_Removed), - addM(member, MemberStatus_Removed), - } - - given := allMembers(member) - - g.TAssertEqual(len(given), 2) - g.TAssertEqual(given[0], expected[0]) - g.TAssertEqual(given, expected[0:2]) - }) - - g.Testing("a deleted network has 0 members", func() { - member := add() - - g.TAssertEqual(len(allMembers(member)), 1) - - err = nipNetwork(member) - g.TErrorIf(err) - - g.TAssertEqual(len(allMembers(member)), 0) - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - membersClose(), - membersClose(), - membersClose(), - )) - }) -} - -func test_editMemberStmt() { - g.TestStart("editMemberStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - nipNetwork, nipNetworkClose, nipNetworkErr := nipNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - showMember, showMemberClose, showMemberErr := showMemberStmt(cfg) - editMember, editMemberClose, editMemberErr := editMemberStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - addNetworkErr, - nipNetworkErr, - membershipErr, - showMemberErr, - editMemberErr, - )) - defer g.SomeFnError( - createUserClose, - addNetworkClose, - nipNetworkClose, - membershipClose, - showMemberClose, - editMemberClose, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func() memberT { - user := create() - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Public, - } - - network, err := addNetwork(user, newNetwork, uuid.New()) - g.TErrorIf(err) - - member, err := membership(user, network) - g.TErrorIf(err) - - return member - } - - // FIXME - // memberChanges := makeMemberChanges(db, prefix) - - // FIXME - - if nipNetwork != nil && showMember != nil && editMember != nil {} - if add != nil {} - - g.Testing("edit triggers writes to changes table", func() { - // FIXME - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - editMemberClose(), - editMemberClose(), - editMemberClose(), - )) - }) -} - -func test_dropMemberStmt() { - // FIXME - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - // FIXME - )) - }) -} - -func test_addChannelStmt() { - g.TestStart("addChannelStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - addNetworkErr, - membershipErr, - addChannelErr, - )) - defer g.SomeFnError( - createUserClose, - addNetworkClose, - membershipClose, - addChannelClose, - db.Close, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func() memberT { - creator := create() - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Public, - } - - network, err := addNetwork(creator, newNetwork, uuid.New()) - g.TErrorIf(err) - - member, err := membership(creator, network) - g.TErrorIf(err) - - return member - } - - channelChanges := makeChannelChanges(db, prefix) - - - g.Testing("the new channel has the data it was given", func() { - member := add() - publicName := "a-name" - newChannel := newChannelT{ - uuid: uuid.New(), - publicName: &publicName, - label: "a-label", - description: "the description", - virtual: false, - } - - channel, err := addChannel(member, newChannel) - g.TErrorIf(err) - - g.TAssertEqual(channel.id == 0, false) - g.TAssertEqual(channel.timestamp == time.Time{}, false) - g.TAssertEqual(channel.uuid, newChannel.uuid) - g.TAssertEqual(*channel.publicName, "a-name") - g.TAssertEqual(channel.label, "a-label") - g.TAssertEqual(channel.description, "the description") - g.TAssertEqual(channel.virtual, false) - }) - - g.Testing("new channel causes inserts to the changes table", func() { - member := add() - publicName := "another name" - newChannel := newChannelT{ - uuid: uuid.New(), - publicName: &publicName, - label: "the label", - description: "the description", - virtual: false, - } - - channel, err := addChannel(member, newChannel) - g.TErrorIf(err) - - changes := channelChanges(channel.id) - g.TAssertEqual(len(changes), 4) - g.TAssertEqual( - []string{ - changes[0].attribute, - changes[1].attribute, - changes[2].attribute, - changes[3].attribute, - changes[0].value, - changes[1].value, - changes[2].value, - changes[3].value, - }, - []string{ - "public_name", - "label", - "description", - "virtual", - "another name", - "the label", - "the description", - "false", - }, - ) - g.TAssertEqual( - []bool{ - changes[0].op, - changes[1].op, - changes[2].op, - changes[3].op, - }, - []bool{ - true, - true, - true, - true, - }, - ) - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - addChannelClose(), - addChannelClose(), - addChannelClose(), - )) - }) -} - -func test_channelEach() { - g.TestStart("channelEach()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg) - channels, channelsClose, channelsErr := channelsStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - addNetworkErr, - membershipErr, - addChannelErr, - channelsErr, - )) - defer g.SomeFnError( - createUserClose, - addNetworkClose, - membershipClose, - addChannelClose, - channelsClose, - db.Close, - ) - - add := func() memberT { - newUser := newUserT{ - uuid: uuid.New(), - } - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Public, - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - network, err := addNetwork(user, newNetwork, uuid.New()) - g.TErrorIf(err) - - member, err := membership(user, network) - g.TErrorIf(err) - - return member - } - - addC := func(member memberT) channelT { - publicName := mkstring() - newChannel := newChannelT{ - uuid: uuid.New(), - publicName: &publicName, - label: mkstring(), - description: mkstring(), - virtual: true, - } - - channel, err := addChannel(member, newChannel) - g.TErrorIf(err) - - return channel - } - - - g.Testing("callback is not called on empty set", func() { - rows, err := channels(add()) - g.TErrorIf(err) - defer rows.Close() - - err = channelEach(rows, func(channelT) error { - g.Unreachable() - return nil - }) - g.TErrorIf(err) - }) - - g.Testing("the callback is called once for each entry", func() { - member := add() - expected := []channelT{ - addC(member), - addC(member), - addC(member), - } - - rows, err := channels(member) - g.TErrorIf(err) - defer rows.Close() - - channels := []channelT{} - err = channelEach(rows, func(channel channelT) error { - channels = append(channels, channel) - return nil - }) - g.TErrorIf(err) - - g.TAssertEqual(channels, expected) - }) - - g.Testing("we halt if the callback returns an error", func() { - member := add() - myErr := errors.New("callback error early return") - addC(member) - addC(member) - addC(member) - addC(member) - addC(member) - - rows1, err1 := channels(member) - rows2, err2 := channels(member) - g.TErrorIf(err1) - g.TErrorIf(err2) - defer rows1.Close() - defer rows2.Close() - - n1 := 0 - n2 := 0 - - err1 = channelEach(rows1, func(channelT) error { - n1++ - if n1 == 3 { - return myErr - } - return nil - }) - - err2 = channelEach(rows2, func(channelT) error { - n2++ - return nil - }) - - g.TAssertEqual(err1, myErr) - g.TErrorIf(err2) - g.TAssertEqual(n1, 3) - g.TAssertEqual(n2, 5) - }) - - g.Testing("noop when given nil for *sql.Rows", func() { - err := channelEach(nil, func(channelT) error { - g.Unreachable() - return nil - }) - g.TErrorIf(err) - }) -} - -func test_channelsStmt() { - g.TestStart("channelsStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - addMember, addMemberClose, addMemberErr := addMemberStmt(cfg) - addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg) - channels, channelsClose, channelsErr := channelsStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - addNetworkErr, - membershipErr, - addMemberErr, - addChannelErr, - channelsErr, - )) - defer g.SomeFnError( - createUserClose, - addNetworkClose, - membershipClose, - addMemberClose, - addChannelClose, - channelsClose, - db.Close, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func() memberT { - user := create() - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Public, - } - - network, err := addNetwork(user, newNetwork, uuid.New()) - g.TErrorIf(err) - - member, err := membership(user, network) - g.TErrorIf(err) - - return member - } - - addM := func(member memberT) memberT { - user := create() - newMember := newMemberT{ - userID: user.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - addedMember, err := addMember(member, newMember) - g.TErrorIf(err) - - return addedMember - } - - addC := func( - member memberT, - publicName *string, - virtual bool, - ) channelT { - newChannel := newChannelT{ - uuid: uuid.New(), - publicName: publicName, - label: mkstring(), - description: mkstring(), - virtual: virtual, - } - - channel, err := addChannel(member, newChannel) - g.TErrorIf(err) - - return channel - } - - allChannels := func(member memberT) ([]channelT, error) { - rows, err := channels(member) - if err != nil { - return nil, err - } - defer rows.Close() - - channelList := []channelT{} - err = channelEach(rows, func(channel channelT) error { - channelList = append(channelList, channel) - return nil - }) - if err != nil { - return nil, err - } - - return channelList, nil - } - - - g.Testing("when there are no channels, we get 0", func() { - channels, err := allChannels(add()) - g.TErrorIf(err) - g.TAssertEqual(len(channels), 0) - }) - - g.Testing("when only private channels, owner gets all", func() { - member := add() - addC(member, nil, true) - addC(member, nil, true) - addC(member, nil, false) - addC(member, nil, false) - - channels, err := allChannels(member) - g.TErrorIf(err) - g.TAssertEqual(len(channels), 4) - }) - - g.Testing("when only private channels, others get none", func() { - member1 := add() - member2 := addM(member1) - addC(member1, nil, true) - addC(member1, nil, true) - addC(member1, nil, false) - addC(member1, nil, false) - - channels1, err1 := allChannels(member1) - channels2, err2 := allChannels(member2) - g.TErrorIf(err1) - g.TErrorIf(err2) - g.TAssertEqual(len(channels1), 4) - g.TAssertEqual(len(channels2), 0) - }) - - g.Testing("private channels we are a member of show up", func() { - member1 := add() - member2 := addM(member1) - name1 := "channel-name-1" - name2 := "channel-name-2" - addC(member1, nil, true) - addC(member2, nil, true) - addC(member1, nil, false) - addC(member2, nil, false) - addC(member1, &name1, true) - addC(member2, &name2, false) - - channels, err := allChannels(member1) - g.TErrorIf(err) - g.TAssertEqual(len(channels), 4) - }) - - g.Testing("we never list channels from other networks", func() { - member := add() - name1 := "a name 1" - name2 := "a name 2" - addC(member, nil, true) - addC(member, nil, false) - addC(member, &name1, true) - addC(member, &name2, false) - - channels, err := allChannels(add()) - g.TErrorIf(err) - g.TAssertEqual(len(channels), 0) - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - channelsClose(), - channelsClose(), - channelsClose(), - )) - }) -} - -func test_setChannelStmt() { - g.TestStart("setChannelStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - addMember, addMemberClose, addMemberErr := addMemberStmt(cfg) - addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg) - setChannel, setChannelClose, setChannelErr := setChannelStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - addNetworkErr, - membershipErr, - addMemberErr, - addChannelErr, - setChannelErr, - )) - defer g.SomeFnError( - createUserClose, - addNetworkClose, - membershipClose, - addMemberClose, - addChannelClose, - setChannelClose, - db.Close, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func() memberT { - creator := create() - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Public, - } - - network, err := addNetwork(creator, newNetwork, uuid.New()) - g.TErrorIf(err) - - member, err := membership(creator, network) - g.TErrorIf(err) - - return member - } - - addM := func(actor memberT) memberT { - user := create() - newMember := newMemberT{ - userID: user.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - member, err := addMember(actor, newMember) - g.TErrorIf(err) - - return member - } - - addC := func(actor memberT, description string) channelT { - publicName := mkstring() - newChannel := newChannelT{ - uuid: uuid.New(), - publicName: &publicName, - label: mkstring(), - description: description, - virtual: false, - } - - channel, err := addChannel(actor, newChannel) - g.TErrorIf(err) - - return channel - } - - - g.Testing("acting member must exist", func() { - channel := addC(add(), "description") - virtualMember := memberT{ - id: 1234, - } - - err := setChannel(virtualMember, channel) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("target channel must exist", func() { - virtualChannel := channelT{ - id: 1234, - } - - err := setChannel(add(), virtualChannel) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("member can't set topic of other network", func() { - member := add() - otherMember := add() - channel := addC(member, "desc") - - err := setChannel(otherMember, channel) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("actor must participate in the channel to set topic", func() { - member1 := add() - member2 := addM(member1) - channel := addC(member1, "desc") - - err := setChannel(member2, channel) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("participant can edit", func() { - member := add() - channel := addC(member, "first description") - - channel.description = "second description" - err := setChannel(member, channel) - g.TErrorIf(err) - }) - - // we can make a private channel public - // we can make a public channel private - - g.Testing("update adds entries to *_changes table", func() { - // FIXME - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - setChannelClose(), - setChannelClose(), - setChannelClose(), - )) - }) -} - -func test_endChannelStmt() { - // FIXME - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - // FIXME - )) - }) -} - -func test_joinStmt() { - g.TestStart("joinStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - addMember, addMemberClose, addMemberErr := addMemberStmt(cfg) - addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg) - setChannel, setChannelClose, setChannelErr := setChannelStmt(cfg) - join, joinClose, joinErr := joinStmt(cfg) - part, partClose, partErr := partStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - addNetworkErr, - membershipErr, - addMemberErr, - addChannelErr, - setChannelErr, - joinErr, - partErr, - )) - defer g.SomeFnError( - createUserClose, - addNetworkClose, - membershipClose, - addMemberClose, - addChannelClose, - setChannelClose, - joinClose, - partClose, - db.Close, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func() memberT { - creator := create() - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Public, - } - - network, err := addNetwork(creator, newNetwork, uuid.New()) - g.TErrorIf(err) - - member, err := membership(creator, network) - g.TErrorIf(err) - - return member - } - - addM := func(actor memberT) memberT { - user := create() - newMember := newMemberT{ - userID: user.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - member, err := addMember(actor, newMember) - g.TErrorIf(err) - - return member - } - - addC := func(actor memberT, publicName *string, virtual bool) channelT { - newChannel := newChannelT{ - uuid: uuid.New(), - publicName: publicName, - label: mkstring(), - description: mkstring(), - virtual: virtual, - } - - channel, err := addChannel(actor, newChannel) - g.TErrorIf(err) - - return channel - } - - - g.Testing("acting member must exist", func() { - name := "name" - channel := addC(add(), &name, false) - virtualMember := memberT{ - id: 1234, - } - - err := join(virtualMember, channel.uuid) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("target channel must exist", func() { - err := join(add(), uuid.New()) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("can't join a private channel", func() { - creator := add() - member := addM(creator) - channel := addC(creator, nil, false) - - err := join(member, channel.uuid) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("can't join a virtual channel", func() { - creator := add() - member := addM(creator) - channel := addC(creator, nil, true) - - err := join(member, channel.uuid) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("can't join channel in different network", func() { - name := "name" - member1 := add() - member2 := add() - channel := addC(member1, &name, false) - - err := join(member2, channel.uuid) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("creator can't rejoin after leaving private channel", func() { - creator := add() - channel := addC(creator, nil, false) - - err := part(creator, channel) - g.TErrorIf(err) - - err = join(creator, channel.uuid) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("can't join public channel one already participates", func() { - name := "name" - creator := add() - channel := addC(creator, &name, false) - - err := join(creator, channel.uuid) - g.TAssertEqual( - err.(golite.Error).ExtendedCode, - golite.ErrConstraintUnique, - ) - }) - - g.Testing("neither a private channel", func() { - creator := add() - channel := addC(creator, nil, false) - - err := join(creator, channel.uuid) - return // FIXME - g.TAssertEqual( - err, - golite.ErrConstraintUnique, - ) - }) - - g.Testing("after made public, one can join a channel", func() { - name := "name" - creator := add() - member := addM(creator) - channel := addC(creator, nil, false) - - err := join(member, channel.uuid) - g.TAssertEqual(err, sql.ErrNoRows) - - channel.publicName = &name - err = setChannel(creator, channel) - g.TErrorIf(err) - - err = join(member, channel.uuid) - g.TErrorIf(err) - }) - - // FIXME - // creates "user-join" event in feed - // joining adds rows to *_changes table - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - joinClose(), - joinClose(), - joinClose(), - )) - }) -} - -func test_partStmt() { - g.TestStart("partStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - addMember, addMemberClose, addMemberErr := addMemberStmt(cfg) - addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg) - part, partClose, partErr := partStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - addNetworkErr, - membershipErr, - addMemberErr, - addChannelErr, - partErr, - )) - defer g.SomeFnError( - createUserClose, - addNetworkClose, - membershipClose, - addMemberClose, - addChannelClose, - partClose, - db.Close, - ) - - create := func() userT{ - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func() memberT { - creator := create() - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Public, - } - - network, err := addNetwork(creator, newNetwork, uuid.New()) - g.TErrorIf(err) - - member, err := membership(creator, network) - g.TErrorIf(err) - - return member - } - - addM := func(actor memberT) memberT { - user := create() - newMember := newMemberT{ - userID: user.uuid, - memberID: uuid.New(), - username: mkstring(), - } - - member, err := addMember(actor, newMember) - g.TErrorIf(err) - - return member - } - - addC := func(actor memberT, virtual bool) channelT { - publicName := "public name" - newChannel := newChannelT{ - uuid: uuid.New(), - publicName: &publicName, - label: mkstring(), - description: mkstring(), - virtual: virtual, - } - - channel, err := addChannel(actor, newChannel) - g.TErrorIf(err) - - return channel - } - - - g.Testing("acting member must exist", func() { - channel := addC(add(), false) - virtualMember := memberT{ - id: 1234, - } - - err := part(virtualMember, channel) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("target channel must exist", func() { - virtualChannel := channelT{ - id: 1234, - } - - err := part(add(), virtualChannel) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("must be a member to part", func() { - creator := add() - member := addM(creator) - channel := addC(creator, false) - - err := part(member, channel) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("can't part from a virtual channel", func() { - member := add() - channel := addC(member, true) - - err := part(member, channel) - g.TAssertEqual(err, sql.ErrNoRows) - }) - - g.Testing("can part from non-virtual channel", func() { - member := add() - channel := addC(member, false) - - err := part(member, channel) - g.TErrorIf(err) - }) - - - // FIXME - // parting adds rows to *_changes table - // after parting, vanishes from member channel list - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - partClose(), - partClose(), - partClose(), - )) - }) -} - -func test_nameEach() { - // FIXME -} - -func test_namesStmt() { - // FIXME - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - // FIXME - )) - }) -} - -func test_addEventStmt() { - g.TestStart("addEventStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg) - addEvent, addEventClose, addEventErr := addEventStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - addNetworkErr, - membershipErr, - addChannelErr, - addEventErr, - )) - defer g.SomeFnError( - createUserClose, - addNetworkClose, - membershipClose, - addChannelClose, - addEventClose, - db.Close, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func() memberT { - creator := create() - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Public, - } - - network, err := addNetwork(creator, newNetwork, uuid.New()) - g.TErrorIf(err) - - member, err := membership(creator, network) - g.TErrorIf(err) - - return member - } - - addC := func(actor memberT) channelT { - publicName := mkstring() - newChannel := newChannelT{ - uuid: uuid.New(), - publicName: &publicName, - label: mkstring(), - description: mkstring(), - virtual: false, - } - - channel, err := addChannel(actor, newChannel) - g.TErrorIf(err) - - return channel - } - - - g.Testing("we can create new events", func() { - creator := add() - channel := addC(creator) - newEvent := newEventT{ - eventID: uuid.New(), - channelID: channel.uuid, - source: sourceT{ - uuid: creator.uuid, - type_: SourceType_Logon, - }, - type_: EventType_UserMessage, - payload: "the-payload", - } - - event, err := addEvent(newEvent) - g.TErrorIf(err) - - g.TAssertEqual(event.id == 0, false) - g.TAssertEqual(event.timestamp == time.Time{}, false) - g.TAssertEqual(event.uuid, newEvent.eventID) - g.TAssertEqual(event.channelID, newEvent.channelID) - g.TAssertEqual(event.source, newEvent.source) - g.TAssertEqual(event.type_, EventType_UserMessage) - g.TAssertEqual(event.payload, "the-payload") - g.TAssertEqual(event.metadata == nil, true) - }) - - g.Testing("eventID's must be unique", func() { - creator := add() - channel := addC(creator) - newEvent := newEventT{ - eventID: uuid.New(), - channelID: channel.uuid, - source: sourceT{ - uuid: creator.uuid, - type_: SourceType_Logon, - }, - type_: EventType_UserMessage, - payload: "the payload", - } - - _, err1 := addEvent(newEvent) - _, err2 := addEvent(newEvent) - g.TErrorIf(err1) - g.TAssertEqual( - err2.(golite.Error).ExtendedCode, - golite.ErrConstraintUnique, - ) - }) - - g.Testing("multiple messages can have the same source", func() { - source := sourceT{ - uuid: uuid.New(), - type_: SourceType_Logon, - } - - newEvent1 := newEventT{ - eventID: uuid.New(), - channelID: addC(add()).uuid, - source: source, - type_: EventType_UserMessage, - } - newEvent2 := newEventT{ - eventID: uuid.New(), - channelID: addC(add()).uuid, - source: source, - type_: EventType_UserMessage, - } - - _, err1 := addEvent(newEvent1) - _, err2 := addEvent(newEvent2) - g.TErrorIf(err1) - g.TErrorIf(err2) - }) - - g.Testing("messages can be duplicated: same type and payload", func() { - type_ := EventType_UserMessage - payload := "a-payload" - - newEvent1 := newEventT{ - eventID: uuid.New(), - channelID: addC(add()).uuid, - source: sourceT{ - uuid: uuid.New(), - type_: SourceType_Logon, - }, - type_: type_, - payload: payload, - } - newEvent2 := newEventT{ - eventID: uuid.New(), - channelID: addC(add()).uuid, - source: sourceT{ - uuid: uuid.New(), - type_: SourceType_Logon, - }, - type_: type_, - payload: payload, - } - - _, err1 := addEvent(newEvent1) - _, err2 := addEvent(newEvent2) - g.TErrorIf(err1) - g.TErrorIf(err2) - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - addEventClose(), - addEventClose(), - addEventClose(), - )) - }) -} - -func test_eventEach() { - g.TestStart("eventEach()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg) - addEvent, addEventClose, addEventErr := addEventStmt(cfg) - allAfter, allAfterClose, allAfterErr := allAfterStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - addNetworkErr, - membershipErr, - addChannelErr, - addEventErr, - allAfterErr, - )) - defer g.SomeFnError( - createUserClose, - addNetworkClose, - membershipClose, - addChannelClose, - addEventClose, - allAfterClose, - db.Close, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func() memberT { - creator := create() - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Public, - } - - network, err := addNetwork(creator, newNetwork, uuid.New()) - g.TErrorIf(err) - - member, err := membership(creator, network) - g.TErrorIf(err) - - return member - } - - addC := func(actor memberT) channelT { - publicName := mkstring() - newChannel := newChannelT{ - uuid: uuid.New(), - publicName: &publicName, - label: mkstring(), - description: mkstring(), - virtual: false, - } - - channel, err := addChannel(actor, newChannel) - g.TErrorIf(err) - - return channel - } - - eventCount := 0 - addE := func(channelID uuid.UUID) eventT { - eventCount++ - newEvent := newEventT{ - // FIXME: missing eventID? - channelID: channelID, - source: sourceT{ - uuid: uuid.New(), - type_: SourceType_Logon, - }, - type_: EventType_UserMessage, - payload: fmt.Sprintf("event %s", eventCount), - } - - event, err := addEvent(newEvent) - g.TErrorIf(err) - - return event - } - - - g.Testing("callback is not called when there is no message", func() { - eventID := uuid.New() - member := add() - rows, err := allAfter(member, eventID) - g.TErrorIf(err) - defer rows.Close() - - err = eventEach(rows, func(eventT) error { - g.Unreachable() - return nil - }) - g.TErrorIf(err) - }) - - g.Testing("the callback is called once for each entry", func() { - return // FIXME - - eventID := uuid.New() - member := add() - channel := addC(member) - expected := []eventT{ - addE(channel.uuid), - addE(channel.uuid), - addE(channel.uuid), - } - - rows, err := allAfter(member, eventID) - g.TErrorIf(err) - defer rows.Close() - - events := []eventT{} - err = eventEach(rows, func(event eventT) error { - events = append(events, event) - return nil - }) - g.TErrorIf(err) - - g.TAssertEqual(events, expected) - }) - - g.Testing("it halts if a callback returns an error", func() { - return // FIXME - - eventID := uuid.New() - member := add() - channel := addC(member) - myErr := errors.New("callback error early return") - addE(channel.uuid) - addE(channel.uuid) - addE(channel.uuid) - addE(channel.uuid) - addE(channel.uuid) - - rows1, err1 := allAfter(member, eventID) - rows2, err2 := allAfter(member, eventID) - g.TErrorIf(err1) - g.TErrorIf(err2) - defer rows1.Close() - defer rows2.Close() - - n1 := 0 - n2 := 0 - - err1 = eventEach(rows1, func(eventT) error { - n1++ - if n1 == 3 { - return myErr - } - return nil - }) - - err2 = eventEach(rows2, func(eventT) error { - n2++ - return nil - }) - - g.TAssertEqual(n1, myErr) - g.TErrorIf(err2) - g.TAssertEqual(n1, 3) - g.TAssertEqual(n2, 5) - }) - - g.Testing("noop when given a nil for *sql.Rows", func() { - err := eventEach(nil, func(eventT) error { - g.Unreachable() - return nil - }) - g.TErrorIf(err) - }) -} - -func test_allAfterStmt() { - g.TestStart("allAfterStmt()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - db, err := sql.Open(golite.DriverName, dbpath) - g.TErrorIf(err) - g.TErrorIf(createTables(db, prefix)) - - cfg := dbconfigT{ - shared: db, - dbpath: dbpath, - prefix: prefix, - } - createUser, createUserClose, createUserErr := createUserStmt(cfg) - addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg) - membership, membershipClose, membershipErr := membershipStmt(cfg) - addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg) - addEvent, addEventClose, addEventErr := addEventStmt(cfg) - allAfter, allAfterClose, allAfterErr := allAfterStmt(cfg) - g.TErrorIf(g.SomeError( - createUserErr, - addNetworkErr, - membershipErr, - addChannelErr, - addEventErr, - allAfterErr, - )) - defer g.SomeFnError( - createUserClose, - addNetworkClose, - membershipClose, - addChannelClose, - addEventClose, - allAfterClose, - db.Close, - ) - - create := func() userT { - newUser := newUserT{ - uuid: uuid.New(), - } - - user, err := createUser(newUser) - g.TErrorIf(err) - - return user - } - - add := func() memberT { - creator := create() - newNetwork := newNetworkT{ - uuid: uuid.New(), - type_: NetworkType_Public, - } - - network, err := addNetwork(creator, newNetwork, uuid.New()) - g.TErrorIf(err) - - member, err := membership(creator, network) - g.TErrorIf(err) - - return member - } - - addC := func(actor memberT) channelT { - publicName := mkstring() - newChannel := newChannelT{ - uuid: uuid.New(), - publicName: &publicName, - label: mkstring(), - description: mkstring(), - virtual: false, - } - - channel, err := addChannel(actor, newChannel) - g.TErrorIf(err) - - return channel - } - - eventCount := 0 - addE := func(channelID uuid.UUID) eventT { - eventCount++ - newEvent := newEventT{ - eventID: uuid.New(), - channelID: channelID, - source: sourceT{ - uuid: uuid.New(), - type_: SourceType_Logon, - }, - type_: EventType_UserMessage, - payload: fmt.Sprintf("event %s", eventCount), - } - - event, err := addEvent(newEvent) - g.TErrorIf(err) - - return event - } - - // FIXME - allEvents := func(eventID uuid.UUID) []eventT { - rows, err := allAfter(memberT{}, eventID) - g.TErrorIf(err) - defer rows.Close() - - events := []eventT{} - err = eventEach(rows, func(event eventT) error { - events = append(events, event) - return nil - }) - g.TErrorIf(err) - - return events - } - - - g.Testing("after joining the channel, there are no events", func() { - return // FIXME - - eventID := uuid.New() - member := add() - channel := addC(member) - - expected := []eventT{ - // FIXME: missing "user-join" event - addE(channel.uuid), - addE(channel.uuid), - addE(channel.uuid), - } - - given := allEvents(eventID) - g.TAssertEqual(given, expected) - }) - - g.Testing("we don't get events from other channels", func() { - return // FIXME - - eventID := uuid.New() - member := add() - channel1 := addC(member) - channel2 := addC(member) - - events := []eventT{ - addE(channel1.uuid), - addE(channel1.uuid), - addE(channel2.uuid), - addE(channel2.uuid), - addE(channel1.uuid), - addE(channel1.uuid), - } - - given := allEvents(eventID) - g.TAssertEqual(given, events[2:4]) - }) - - g.Testing("as we change the reference point, the list changes", func() { - return // FIXME - - eventID := uuid.New() - member := add() - channel := addC(member) - - events := []eventT{ - addE(channel.uuid), - addE(channel.uuid), - addE(channel.uuid), - addE(channel.uuid), - addE(channel.uuid), - } - - g.TAssertEqual(len(allEvents(eventID)), 5) - g.TAssertEqual(len(allEvents(events[0].uuid)), 4) - g.TAssertEqual(len(allEvents(events[1].uuid)), 3) - g.TAssertEqual(len(allEvents(events[2].uuid)), 2) - g.TAssertEqual(len(allEvents(events[3].uuid)), 1) - g.TAssertEqual(len(allEvents(events[4].uuid)), 0) - }) - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - allAfterClose(), - allAfterClose(), - allAfterClose(), - )) - }) -} - -func test_logMessageStmt() { - g.TestStart("logMessageStmt()") - - // FIXME - - g.Testing("no error if closed more than once", func() { - g.TErrorIf(g.SomeError( - // FIXME - )) - }) -} - -func test_initDB() { - g.TestStart("initDB()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - queries, err := initDB(dbpath, prefix) - g.TErrorIf(err) - defer queries.close() - - - g.Testing("we can perform all wrapped operations", func() { - // FIXME - }) -} - -func test_queriesTclose() { - g.TestStart("queriesT.close()") - - const ( - dbpath = golite.InMemory - prefix = defaultPrefix - ) - - queries, err := initDB(dbpath, prefix) - g.TErrorIf(err) - defer queries.close() - - - g.Testing("closing more than once does not error", func() { - g.TErrorIf(g.SomeError( - queries.close(), - queries.close(), - queries.close(), - )) - }) -} - func test_splitOnCRLF() { g.TestStart("splitOnCRLF()") @@ -5661,93 +371,9 @@ func test_addTrailingSeparator() { } -func dumpQueries() { - queries := []struct{name string; fn func(string) queryT}{ - { "createTables", createTablesSQL }, - { "memberRoles", memberRolesSQL }, - { "createUser", createUserSQL }, - { "userByUUID", userByUUIDSQL }, - { "updateUser", updateUserSQL }, - { "deleteUser", deleteUserSQL }, - { "addNetwork", addNetworkSQL }, - { "getNetwork", getNetworkSQL }, - { "networks", networksSQL }, - { "setNetwork", setNetworkSQL }, - { "nipNetwork", nipNetworkSQL }, - { "membership", membershipSQL }, - { "addMember", addMemberSQL }, - { "addRole", addRoleSQL }, - { "dropRole", dropRoleSQL }, - { "showMember", showMemberSQL }, - { "members", membersSQL }, - { "editMember", editMemberSQL }, - { "dropMember", dropMemberSQL }, - { "addChannel", addChannelSQL }, - { "channels", channelsSQL }, - { "setChannel", setChannelSQL }, - { "endChannel", endChannelSQL }, - { "join", joinSQL }, - { "part", partSQL }, - { "names", namesSQL }, - { "addEvent", addEventSQL }, - { "allAfter", allAfterSQL }, - { "logMessage", logMessageSQL }, - } - for _, query := range queries { - q := query.fn(defaultPrefix) - fmt.Printf("\n-- %s.sql:", query.name) - fmt.Printf("\n-- write:%s\n", q.write) - fmt.Printf("\n-- read:%s\n", q.read) - } -} - - func MainTest() { - if os.Getenv("TESTING_DUMP_SQL_QUERIES") != "" { - dumpQueries() - return - } - g.Init() - test_defaultPrefix() - test_tryRollback() - test_inTx() - test_createTables() - test_createUserStmt() - test_userByUUIDStmt() - test_updateUserStmt() - test_deleteUserStmt() - test_addNetworkStmt() - test_getNetworkStmt() - test_networkEach() - test_networksStmt() - test_setNetworkStmt() - test_nipNetworkStmt() - test_membershipStmt() - test_addMemberStmt() - test_addRoleStmt() - test_dropRoleStmt() - test_showMemberStmt() - test_memberEach() - test_membersStmt() - test_editMemberStmt() - test_dropMemberStmt() - test_addChannelStmt() - test_channelEach() - test_channelsStmt() - test_setChannelStmt() - test_endChannelStmt() - test_joinStmt() - test_partStmt() - test_nameEach() - test_namesStmt() - test_addEventStmt() - test_eventEach() - test_allAfterStmt() - test_logMessageStmt() - test_initDB() - test_queriesTclose() test_splitOnCRLF() test_splitOnRawMessage() test_parseMessageParams() |
