diff --git a/NOTES.md b/NOTES.md index 2e325420f0a0d5844aa81cbfcf4f4bfa67c2b376..7148dbda0a351f7962eacf04a96fc5e5d2a28bd1 100644 --- a/NOTES.md +++ b/NOTES.md @@ -56,10 +56,10 @@ Top level: SET: ACCESS +ALWAYS BRIEF DEFAULT_EXPIRE - EXPIRE_LIMIT FOLDER +NOALWAYS NOBRIEF - NONOTIFY NOPROMPT_EXPIRE NOREADNEW NOSHOWNEW - NOSYSTEM NOTIFY PROMPT_EXPIRE READNEW - SHOWNEW SYSTEM + EXPIRE_LIMIT FOLDER NOACCESS +NOALWAYS + NOBRIEF NOPROMPT_EXPIRE NOREADNEW NOSHOWNEW + NOSYSTEM PROMPT_EXPIRE READNEW SHOWNEW + SYSTEM SHOW: diff --git a/dclish/completer.go b/dclish/completer.go index 82d7f674573a6f90b89b9497dcbd4d9cedc55cce..2eace9b1858081ec45d6016671dfa418c5d1394c 100644 --- a/dclish/completer.go +++ b/dclish/completer.go @@ -76,6 +76,5 @@ func (c Completer) Do(line []rune, pos int) ([][]rune, int) { } // Command completely typed in. - // TODO: figure out flags. return newline, pos } diff --git a/folders/folders.go b/folders/folders.go index 2f9cdf6e513f2551d2f7b5a9814c036f6806b0a4..fef08be1b60e655962c05a98f5338272a5a51d1e 100644 --- a/folders/folders.go +++ b/folders/folders.go @@ -115,10 +115,8 @@ func IsFolderOwner(folder, login string) bool { // DeleteFolder deletes a folder. func DeleteFolder(name string) error { - // TODO: make sure user can delete this table. ctx := storage.Context() err := this.Q.DeleteFolder(ctx, name) - // TODO: process this error a bit more to give a better error message. if err != nil { return err } diff --git a/folders/messages.go b/folders/messages.go index 055ec887514a944439b554699cceaa96f8492764..07a6645fc58ccee3b8a3867c9eb3b2b900f320e5 100644 --- a/folders/messages.go +++ b/folders/messages.go @@ -25,7 +25,6 @@ func CreateMessage(author, subject, message, folder string, permanent, shutdown exp := time.Now().AddDate(0, 0, int(days)).UTC() expiration = &exp } - // TODO: replace _ with rows and check. err := this.Q.CreateMessage(ctx, storage.CreateMessageParams{ Folder: folder, Author: author, @@ -139,11 +138,29 @@ func LastMessage(folder string) int64 { // ListMessages lists messages. func ListMessages(folder string) ([]storage.Message, error) { ctx := storage.Context() - // TODO: options aren't implemented - need to set them? rows, err := this.Q.ListMessages(ctx, folder) return rows, err } +// WroteAllMessages returns true if login wrote all msgids +func WroteAllMessages(login string, msgids []int64) bool { + ctx := storage.Context() + + for _, msgid := range msgids { + msg, err := this.Q.GetMessage(ctx, storage.GetMessageParams{ + ID: msgid, + Folder: this.Folder.Name, + }) + if err != nil { + return false + } + if msg.Author != login { + return false + } + } + return true +} + // DeleteMessages deletes a list of messages. func DeleteMessages(msgids []int64) error { ctx := storage.Context() diff --git a/pager/pager.go b/pager/pager.go index 9ecd1dbe12e1f8ed5c0b6ee573f8ab44398f0e7a..79ddd9ea8e7170990d835d52aa64daf04b1b1d64 100644 --- a/pager/pager.go +++ b/pager/pager.go @@ -79,7 +79,6 @@ func Pager(content string) bool { return false } - // TODO: get '/' to work for searching. switch key { case ' ': // page down start += pageSize diff --git a/repl/folders.go b/repl/folders.go index c8b7f4a49b39b4094a819242e14f41534325ca29..7f385af52692bb69c63e6dad23b4a34beb739f98 100644 --- a/repl/folders.go +++ b/repl/folders.go @@ -181,6 +181,12 @@ func ActionModify(cmd *dclish.Command) error { // ActionRemove handles the `REMOVE` command. This modifies a folder. func ActionRemove(cmd *dclish.Command) error { + if this.User.Login != this.Folder.Owner && this.User.Admin == 0 { + return errors.New("Must be folder owner or admin to delete the folder") + } + if this.Folder.Name == "GENERAL" { + return errors.New("Can't delete folder GENERAL") + } err := folders.DeleteFolder(cmd.Args[0]) if err == nil { fmt.Println("Folder removed.") diff --git a/repl/mail.go b/repl/mail.go index 5b4469d107be3512cc9b70b5da8cb9d02bc0bc53..addac2a04f029882cab14248e3b90467ed10b18b 100644 --- a/repl/mail.go +++ b/repl/mail.go @@ -1,38 +1,25 @@ package repl import ( - "fmt" + "errors" "git.lyda.ie/kevin/bulletin/dclish" ) -/* - -Instead of making MAIL and FORWARD the same, why not have "MAIL" enter a mail -mode where you can read mails. - -Alternatively, make "mail" just a folder like any other except set to private and make the owner that user. Maybe have a leading colon that CREATE can't use. - -Or... just get rid of the mail bit. - -Or... tie it into actual smtp mail. - -*/ - // ActionForward handles the `FORWARD` command. func ActionForward(_ *dclish.Command) error { - fmt.Println("ERROR: mail system is yet TODO.") + errors.New("Mail system is not yet implemented (see issue 9)") return nil } // ActionMail handles the `MAIL` command. func ActionMail(_ *dclish.Command) error { - fmt.Println("ERROR: mail system is yet TODO.") + errors.New("Mail system is not yet implemented (see issue 9)") return nil } // ActionRespond handles the `RESPOND` command. func ActionRespond(_ *dclish.Command) error { - fmt.Println("ERROR: mail system is yet TODO.") + errors.New("Mail system is not yet implemented (see issue 9)") return nil } diff --git a/repl/messages.go b/repl/messages.go index 16d202755137b47e5f4aff110b9ede9e04ed36db..ee6211685ccbc95ee5b0bb40f31358be0d9454e5 100644 --- a/repl/messages.go +++ b/repl/messages.go @@ -20,7 +20,6 @@ import ( // ActionDirectory handles the `DIRECTORY` command. This lists all the // messages in the current folder. func ActionDirectory(cmd *dclish.Command) error { - // TODO: flag parsing. showExpiration := false if cmd.Flags["/EXPIRATION"].Value == "true" { showExpiration = true @@ -77,19 +76,19 @@ func ActionAdd(cmd *dclish.Command) error { optAll = 1 } if cmd.Flags["/ALL"].Set { - fmt.Printf("TODO: optAll is not yet implemented - you set it to %d\n", optAll) + fmt.Printf("/ALL is not yet implemented - you set it to %d\n", optAll) } if cmd.Flags["/BELL"].Value == "true" { optBell = 1 } if cmd.Flags["/BELL"].Set { - fmt.Printf("TODO: optBell is not yet implemented - you set it to %d\n", optBell) + fmt.Printf("/BELL is not yet implemented - you set it to %d\n", optBell) } if cmd.Flags["/BROADCAST"].Value == "true" { optBroadcast = 1 } if cmd.Flags["/BROADCAST"].Set { - fmt.Printf("TODO: optBroadcast is not yet implemented - you set it to %d\n", optBroadcast) + fmt.Printf("/BROADCAST is not yet implemented - you set it to %d\n", optBroadcast) } if cmd.Flags["/EXPIRATION"].Value != "" { // dd-mmm-yyyy, or delta time: dddd @@ -109,7 +108,7 @@ func ActionAdd(cmd *dclish.Command) error { optExtract = 1 } if cmd.Flags["/EXTRACT"].Set { - fmt.Printf("TODO: optExtract is not yet implemented - you set it to %d\n", optExtract) + fmt.Printf("/EXTRACT is not yet implemented - you set it to %d\n", optExtract) } if cmd.Flags["/FOLDER"].Value != "" { optFolder = strings.Split(cmd.Flags["/FOLDER"].Value, ",") @@ -118,7 +117,7 @@ func ActionAdd(cmd *dclish.Command) error { optIndent = 1 } if cmd.Flags["/INDENT"].Set { - fmt.Printf("TODO: optIndent is not yet implemented - you set it to %d\n", optIndent) + fmt.Printf("/INDENT is not yet implemented - you set it to %d\n", optIndent) } if cmd.Flags["/PERMANENT"].Value == "true" { optPermanent = 1 @@ -130,7 +129,7 @@ func ActionAdd(cmd *dclish.Command) error { optSignature = 1 } if cmd.Flags["/SIGNATURE"].Set { - fmt.Printf("TODO: optSignature is not yet implemented - you set it to %d\n", optSignature) + fmt.Printf("/SIGNATURE is not yet implemented - you set it to %d\n", optSignature) } if cmd.Flags["/SUBJECT"].Value != "" { optSubject = cmd.Flags["/SUBJECT"].Value @@ -139,7 +138,7 @@ func ActionAdd(cmd *dclish.Command) error { optSystem = 1 } if cmd.Flags["/SYSTEM"].Set { - fmt.Printf("TODO: optSystem is not yet implemented - you set it to %d\n", optSystem) + fmt.Printf("/SYSTEM is not yet implemented - you set it to %d\n", optSystem) } if len(optFolder) == 0 { @@ -547,7 +546,6 @@ func ActionReply(cmd *dclish.Command) error { // ActionSeen handles the `SEEN` command. func ActionSeen(cmd *dclish.Command) error { - // TODO: review help. var err error msgids := []int64{this.MsgID} if len(cmd.Args) == 1 { @@ -565,7 +563,6 @@ func ActionSeen(cmd *dclish.Command) error { // ActionUnseen handles the `UNSEEN` command. func ActionUnseen(cmd *dclish.Command) error { - // TODO: review help. var err error msgids := []int64{this.MsgID} if len(cmd.Args) == 1 { @@ -576,14 +573,13 @@ func ActionUnseen(cmd *dclish.Command) error { } err = folders.MarkUnseen(msgids) if err != nil { - fmt.Printf("ERROR: %s.\n", err) + return err } return nil } // ActionDelete handles the `DELETE` command. func ActionDelete(cmd *dclish.Command) error { - // TODO: Follow permissions. var err error all := false @@ -595,9 +591,12 @@ func ActionDelete(cmd *dclish.Command) error { fmt.Println("ERROR: Can't specify both message numbers and /ALL flag.") return nil } + if this.User.Admin == 0 { + return errors.New("Must be admin to use /ALL") + } err := folders.DeleteAllMessages() if err != nil { - fmt.Printf("ERROR: %s.\n", err) + return err } return nil } @@ -606,13 +605,15 @@ func ActionDelete(cmd *dclish.Command) error { if len(cmd.Args) == 1 { msgids, err = ParseNumberList(cmd.Args[0]) if err != nil { - fmt.Printf("ERROR: %s.\n", err) - return nil + return err } } + if this.User.Admin == 0 && !folders.WroteAllMessages(this.User.Login, msgids) { + return errors.New("Can't delete messages you haven't written") + } err = folders.DeleteMessages(msgids) if err != nil { - fmt.Printf("ERROR: %s.\n", err) + return err } return nil } diff --git a/repl/xfer.go b/repl/xfer.go index c4c22542104e0ff160310a2af727827bb4bb216f..997a356acd5d1ee259ebb23ee2495a02d5e3e6a6 100644 --- a/repl/xfer.go +++ b/repl/xfer.go @@ -10,7 +10,6 @@ import ( // ActionCopy handles the `COPY` command. func ActionCopy(cmd *dclish.Command) error { - // TODO: handle flags. folder := cmd.Args[0] msgids := []int64{this.MsgID} if len(cmd.Args) == 2 { @@ -20,7 +19,7 @@ func ActionCopy(cmd *dclish.Command) error { return err } } - fmt.Printf("TODO: copy %+v to %s\n", msgids, folder) + fmt.Printf("copy %+v to %s is not implemented\n", msgids, folder) /* msg, err := folders.ReadMessage(this.User.Login, this.Folder.Name, msgid) if err != nil { @@ -32,7 +31,6 @@ func ActionCopy(cmd *dclish.Command) error { // ActionMove handles the `MOVE` command. func ActionMove(cmd *dclish.Command) error { - // TODO: handle flags. folder := cmd.Args[0] msgids := []int64{this.MsgID} if len(cmd.Args) == 2 { @@ -42,7 +40,7 @@ func ActionMove(cmd *dclish.Command) error { return err } } - fmt.Printf("TODO: move %+v to %s\n", msgids, folder) + fmt.Printf("move %+v to %s is not implemented\n", msgids, folder) /* msg, err := folders.ReadMessage(this.User.Login, this.Folder.Name, msgid) if err != nil { diff --git a/storage/messages.sql.go b/storage/messages.sql.go index ad7545c638d28ca44eb4ee61671c761057e1332f..84d85a8fa52a9972cbbe6cb08e825fd2b5d5b8d1 100644 --- a/storage/messages.sql.go +++ b/storage/messages.sql.go @@ -124,7 +124,7 @@ type GetLastReadRow struct { Author string } -// - TODO: These get the max message written, not read. Leaving for now; easier to test. +// GetLastRead gets the last message read by a login in a folder. func (q *Queries) GetLastRead(ctx context.Context, folder string) ([]GetLastReadRow, error) { rows, err := q.db.QueryContext(ctx, getLastRead, folder) if err != nil { diff --git a/storage/queries/messages.sql b/storage/queries/messages.sql index afef9bdbd56b5fc5df8713144597c23c4d55600e..5a49f8176808dd05c183ae32cfb9da6024ed3d15 100644 --- a/storage/queries/messages.sql +++ b/storage/queries/messages.sql @@ -38,7 +38,7 @@ DELETE FROM messages WHERE folder = ?; -- name: LastMsgidIgnoringSeen :one SELECT CAST(MAX(id) AS INT) FROM messages AS m WHERE m.folder = ?1; ---- TODO: These get the max message written, not read. Leaving for now; easier to test. +-- GetLastRead gets the last message read by a login in a folder. -- name: GetLastRead :many SELECT CAST(MAX(m.id) AS INT) AS id, m.author FROM messages AS m, users AS u WHERE folder = ? AND u.login == m.author