From e4a46cec89dc018a07688d32ca5d3337e250175a Mon Sep 17 00:00:00 2001 From: Kevin Lyda <kevin@lyda.ie> Date: Sat, 10 May 2025 09:04:43 +0100 Subject: [PATCH] Cleanup db in prep for storage module --- dclish/dclish.go | 28 +++++++++-- folders/sql/1_create_table.up.sql | 53 +++++++++++--------- repl/command.go | 82 +++++++++---------------------- repl/set.go | 34 ++++++++++++- 4 files changed, 109 insertions(+), 88 deletions(-) diff --git a/dclish/dclish.go b/dclish/dclish.go index 3e6cb9e..15788c5 100644 --- a/dclish/dclish.go +++ b/dclish/dclish.go @@ -106,11 +106,35 @@ func split(line string) []string { return words } +// PrefixMatch searches for a command in a list of possible commands. +func PrefixMatch(command string, commands []string) (string, error) { + cmd := strings.ToUpper(command) + possibles := []string{} + for i := range commands { + if strings.HasPrefix(cmd, commands[i]) { + possibles = append(possibles, commands[i]) + } + } + switch len(possibles) { + case 0: + return "", fmt.Errorf("Unknown command '%s'", command) + case 1: + return possibles[0], nil + default: + return "", fmt.Errorf("Ambiguous command '%s' (matches %s)", + command, strings.Join(possibles, ", ")) + } +} + // ParseAndRun parses a command line and runs the command. func (c Commands) ParseAndRun(line string) error { // Split into words. words := split(line) + return c.run(words) +} + +func (c Commands) run(words []string) error { // Find the command. wordup := strings.ToUpper(words[0]) cmd, ok := c[wordup] @@ -141,9 +165,7 @@ func (c Commands) ParseAndRun(line string) error { fmt.Printf("ERROR: missing subcommand for %s.\n", wordup) return nil } - // TODO: quoting is probably an issue here - break ParseAndRun into ParseAndRun + Run. - newline := strings.Join(words[1:], " ") - return cmd.Commands.ParseAndRun(newline) + return cmd.Commands.run(words[1:]) } if cmd.Action == nil { diff --git a/folders/sql/1_create_table.up.sql b/folders/sql/1_create_table.up.sql index 74bb11c..250faa1 100644 --- a/folders/sql/1_create_table.up.sql +++ b/folders/sql/1_create_table.up.sql @@ -1,8 +1,9 @@ CREATE TABLE users ( login VARCHAR(12) NOT NULL PRIMARY KEY, name VARCHAR(53) NOT NULL, - admin INT DEFAULT 0, - disabled INT DEFAULT 0, + admin INT DEFAULT 0 NOT NULL, + moderator INT DEFAULT 0 NOT NULL, + disabled INT DEFAULT 0 NOT NULL, last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, create_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, update_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL @@ -34,16 +35,15 @@ END; CREATE TABLE folders ( name VARCHAR(25) NOT NULL PRIMARY KEY, - always INT DEFAULT 0, - brief INT DEFAULT 0, - description VARCHAR(53) NOT NULL, - notify INT DEFAULT 0, - owner VARCHAR(25) REFERENCES users(login) ON UPDATE CASCADE, - readnew INT DEFAULT 0, - shownew INT DEFAULT 0, - system INT DEFAULT 0, - expire INT DEFAULT 14, - visibility TEXT DEFAULT 'public', + always INT DEFAULT 0 NOT NULL, + brief INT DEFAULT 0 NOT NULL, + description VARCHAR(53) DEFAULT 0 NOT NULL, + notify INT DEFAULT 0 NOT NULL, + readnew INT DEFAULT 0 NOT NULL, + shownew INT DEFAULT 0 NOT NULL, + system INT DEFAULT 0 NOT NULL, + expire INT DEFAULT 14 NOT NULL, + visibility TEXT DEFAULT 'public' NOT NULL, create_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, update_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL ) WITHOUT ROWID; @@ -87,7 +87,7 @@ END; INSERT INTO folders (name, description, system, shownew, owner) VALUES ('GENERAL', 'Default general bulletin folder.', 1, 1, 'SYSTEM'); -CREATE TABLE co_owners ( +CREATE TABLE owners ( folder VARCHAR(25) REFERENCES folders(name) ON DELETE CASCADE ON UPDATE CASCADE, owner VARCHAR(25) REFERENCES users(login) ON UPDATE CASCADE, create_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, @@ -95,11 +95,11 @@ CREATE TABLE co_owners ( PRIMARY KEY (folder, owner) ) WITHOUT ROWID; -CREATE TRIGGER co_owners_after_update_update_at - AFTER UPDATE ON co_owners FOR EACH ROW +CREATE TRIGGER owners_after_update_update_at + AFTER UPDATE ON owners FOR EACH ROW WHEN NEW.update_at = OLD.update_at --- avoid infinite loop BEGIN - UPDATE co_owners SET update_at=CURRENT_TIMESTAMP WHERE folder=NEW.folder AND owner=NEW.owner; + UPDATE owners SET update_at=CURRENT_TIMESTAMP WHERE folder=NEW.folder AND owner=NEW.owner; END; CREATE TABLE messages ( @@ -108,8 +108,8 @@ CREATE TABLE messages ( author VARCHAR(25) REFERENCES users(login) ON UPDATE CASCADE, subject VARCHAR(53) NOT NULL, message TEXT NOT NULL, - permanent INT DEFAULT 0, - shutdown INT DEFAULT 0, + permanent INT DEFAULT 0 NOT NULL, + shutdown INT DEFAULT 0 NOT NULL, expiration TIMESTAMP NOT NULL, create_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, update_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, @@ -118,7 +118,7 @@ CREATE TABLE messages ( CREATE INDEX messages_idx_shutdown ON messages(shutdown); CREATE INDEX messages_idx_expiration ON messages(expiration); -CREATE TABLE read ( +CREATE TABLE seen ( login VARCHAR(25) REFERENCES users(login) ON DELETE CASCADE ON UPDATE CASCADE, folder VARCHAR(25) REFERENCES folders(name) ON DELETE CASCADE ON UPDATE CASCADE, msgid INT, @@ -142,17 +142,24 @@ CREATE TABLE mark ( ON UPDATE CASCADE ) WITHOUT ROWID; +CREATE TABLE access ( + login VARCHAR(25) REFERENCES users(login) ON DELETE CASCADE ON UPDATE CASCADE, + folder VARCHAR(25) REFERENCES folders(name) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY (login, folder), +) WITHOUT ROWID; + --- TODO: The following is incomplete: --- User configs. CREATE TABLE config ( login VARCHAR(25) REFERENCES users(login) ON DELETE CASCADE ON UPDATE CASCADE, folder VARCHAR(25) REFERENCES folders(name) ON DELETE CASCADE ON UPDATE CASCADE, - always INT NOT NULL, - alert INT NOT NULL DEFAULT 0, --- 0=no, 1=brief, 2=readnew + always INT NOT NULL DEFAULT 0, + alert INT NOT NULL DEFAULT 0, --- 0=no, 1=brief, 2=readnew + always INT NOT NULL ) WITHOUT ROWID; --- System configs. CREATE TABLE system ( - default_expire INT NOT NULL DEFAULT -1, - expire_limit INT NOT NULL DEFAULT -1, + default_expire INT NOT NULL DEFAULT -1, + expire_limit INT NOT NULL DEFAULT -1, ) WITHOUT ROWID; diff --git a/repl/command.go b/repl/command.go index 88e4f1b..3053404 100644 --- a/repl/command.go +++ b/repl/command.go @@ -1439,16 +1439,22 @@ to see what is presently set. This is a privileged command. The parameters are one or more privileges separated by commas. To remove a privilege, specify the privilege preceeded by "NO". If /ID is -specified, the parameters are rights identifiers.`, - Flags: dclish.Flags{ - "/ID": { - Description: `/[NO]ID +specified, the parameters are rights identifiers. - If specified, then the rights identifier which is specified as the - parameter will allow users holding that rights identifier to execute - privileged commands. If /NOID is specified, the identifier is removed.`, - }, - }, +For the reimplementation this is used to manage users. The following parameters +are available. + + Format: + SET PRIVILEGES CREATE login + SET PRIVILEGES DELETE login + SET PRIVILEGES SSH [login] + SET PRIVILEGES [NO]ADMIN login + SET PRIVILEGES [NO]MOD login [folder] + SET PRIVILEGES ENABLE login + SET PRIVILEGES DISABLE login`, + MinArgs: 1, + MaxArgs: 3, + Action: ActionSetPrivileges, }, "PROMPT_EXPIRE": { Description: `Specifies that a user will be prompted for an expiration date when @@ -1492,17 +1498,8 @@ the EXIT command will cause you to skip to those folders. (See HELP SET SYSTEM for a description of a SYSTEM folder).`, Flags: dclish.Flags{ "/ALL": { - Description: ` Specifies that the SET [NO]READNEW option is the default for all users - for the specified folder. This is a privileged qualifier. The - difference between this and /DEFAULT is that the latter will only - apply to new users (i.e. any users which have never executed - BULLETIN).`, - }, - "/DEFAULT": { - Description: ` Specifies that the [NO]READNEW option is the default for the specified - folder. This is a privileged qualifier. It will only affect brand new - users (or those that have never logged in). Use /ALL to modify all - users.`, + Description: ` Specifies that the SET READNEW option is the default for all users for + the specified folder. This is a privileged qualifier.`, }, "/PERMANENT": { Description: `/[NO]PERMANENT @@ -1515,30 +1512,11 @@ SYSTEM for a description of a SYSTEM folder).`, "NOREADNEW": { Description: `Turns off READNEW. Format: - SET NOREADNEW - -NOTE: If you have several folders with READNEW enabled, each folder's -messages will be displayed separately. However, if you EXIT the READNEW -mode before all the folders have been displayed, you will not be alerted -of the new messages in the undisplayed folders the next time you login. -However, if you enter BULLETIN, you will be told that new messages are -present in those other folders. Also, it is not possible to EXIT the -READNEW mode if there are SYSTEM folders which have new messages. Typing -the EXIT command will cause you to skip to those folders. (See HELP SET -SYSTEM for a description of a SYSTEM folder).`, + SET NOREADNEW`, Flags: dclish.Flags{ "/ALL": { - Description: ` Specifies that the SET [NO]READNEW option is the default for all users - for the specified folder. This is a privileged qualifier. The - difference between this and /DEFAULT is that the latter will only - apply to new users (i.e. any users which have never executed - BULLETIN).`, - }, - "/DEFAULT": { - Description: ` Specifies that the [NO]READNEW option is the default for the specified - folder. This is a privileged qualifier. It will only affect brand new - users (or those that have never logged in). Use /ALL to modify all - users.`, + Description: ` Specifies that the SET NOREADNEW option is the default for all users + for the specified folder. This is a privileged qualifier.`, }, "/FOLDER": { Description: `/FOLDER=foldername @@ -1564,15 +1542,8 @@ In order to apply this to a specific folder, first select the folder Flags: dclish.Flags{ "/ALL": { Description: ` Specifies that the SET SHOWNEW option is the default for all users for - the specified folder. This is a privileged qualifier. The difference - between this and /DEFAULT is that the latter will only apply to new - users (i.e. any users which have never executed BULLETIN).`, + the specified folder. This is a privileged qualifier.`, }, - "/DEFAULT": { - Description: ` Specifies that the SHOWNEW option is the default for the specified - folder. This is a privileged qualifier. It will only affect brand new - users (or those that have never logged in). Use /ALL to modify all - users.`}, "/PERMANENT": { Description: `/[NO]PERMANENT @@ -1590,16 +1561,7 @@ In order to apply this to a specific folder, first select the folder Flags: dclish.Flags{ "/ALL": { Description: ` Specifies that the SET NOSHOWNEW option is the default for all users - for the specified folder. This is a privileged qualifier. The - difference between this and /DEFAULT is that the latter will only - apply to new users (i.e. any users which have never executed - BULLETIN).`, - }, - "/DEFAULT": { - Description: ` Specifies that the NOSHOWNEW option is the default for the specified - folder. This is a privileged qualifier. It will only affect brand new - users (or those that have never logged in). Use /ALL to modify all - users.`, + for the specified folder. This is a privileged qualifier.`, }, "/FOLDER": { Description: `/FOLDER=foldername diff --git a/repl/set.go b/repl/set.go index f87749f..bc6d276 100644 --- a/repl/set.go +++ b/repl/set.go @@ -89,8 +89,38 @@ func ActionSetNonotify(_ *dclish.Command) error { } // ActionSetPrivileges handles the `SET PRIVILEGES` command. -func ActionSetPrivileges(_ *dclish.Command) error { - fmt.Println("TODO: implement ActionSetPrivileges.") +func ActionSetPrivileges(cmd *dclish.Command) error { + // TODO: OK, need a better parser. + switch cmd.Args[0] { + case "CREATE": + if len(cmd.Args) != 2 { + fmt.Println("ERROR: Must pass single login.") + return nil + } + fmt.Println("TODO: Create user creation routine - see repl/repl.go.") + case "DELETE": + if len(cmd.Args) != 2 { + fmt.Println("ERROR: Must pass single login.") + return nil + } + fmt.Println("TODO: Create user delete routine - see repl/repl.go.") + case "SSH": + fmt.Println("TODO: Create ssh routine.") + case "ADMIN": + fmt.Println("TODO: Create an admin bit set/unset routine.") + case "NOADMIN": + fmt.Println("TODO: Create an admin bit set/unset routine.") + case "MOD": + fmt.Println("TODO: Create a mod bit set/unset routine.") + case "NOMOD": + fmt.Println("TODO: Create a mod bit set/unset routine.") + case "ENABLE": + fmt.Println("TODO: Create a disable bit set/unset routine.") + case "DISABLE": + fmt.Println("TODO: Create a disable bit set/unset routine.") + default: + fmt.Println("ERROR: Command not understood.") + } return nil } -- GitLab