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