From a4bec7c9e08b8dd87c3170d97a2b1145e04d5515 Mon Sep 17 00:00:00 2001
From: Kevin Lyda <kevin@lyda.ie>
Date: Wed, 21 May 2025 22:29:36 +0100
Subject: [PATCH] Fix CHANGE

---
 repl/messages.go                         | 84 +++++++++++++++++++++++-
 storage/messages.sql.go                  | 55 ++++++++++++++--
 storage/migrations/1_create_table.up.sql |  1 +
 storage/models.go                        |  1 +
 storage/queries/messages.sql             | 11 ++++
 storage/standard.sql.go                  |  6 +-
 6 files changed, 146 insertions(+), 12 deletions(-)

diff --git a/repl/messages.go b/repl/messages.go
index 1a7dde0..70f589a 100644
--- a/repl/messages.go
+++ b/repl/messages.go
@@ -194,8 +194,13 @@ func ActionBack(_ *dclish.Command) error {
 // an existing message.
 func ActionChange(cmd *dclish.Command) error {
 	var err error
+	isAdmin := this.User.Admin == 1
+
 	optAll := false
 	if cmd.Flags["/ALL"].Value == "true" {
+		if !isAdmin {
+			return errors.New("Must be admin")
+		}
 		optAll = true
 	}
 
@@ -220,6 +225,9 @@ func ActionChange(cmd *dclish.Command) error {
 		return errors.New("Can only be used in the GENERAL folder")
 	}
 	if cmd.Flags["/GENERAL"].Value == "true" {
+		if !isAdmin {
+			return errors.New("Must be admin")
+		}
 		optGeneral = true
 	}
 	if cmd.Flags["/GENERAL"].Set && !optGeneral {
@@ -231,12 +239,15 @@ func ActionChange(cmd *dclish.Command) error {
 		optNew = true
 	}
 
-	optNumber := []int64{this.MsgID}
+	msgids := []int64{this.MsgID}
+	if cmd.Flags["/NUMBER"].Set && optAll {
+		return errors.New("Can't set /ALL and /NUMBER")
+	}
 	if cmd.Flags["/NUMBER"].Set && cmd.Flags["/NUMBER"].Value == "" {
 		return errors.New("Must supply message number(s) if /NUMBER is set")
 	}
 	if cmd.Flags["/NUMBER"].Value != "" {
-		optNumber, err = ParseNumberList(cmd.Flags["/NUMBER"].Value)
+		msgids, err = ParseNumberList(cmd.Flags["/NUMBER"].Value)
 		if err != nil {
 			return err
 		}
@@ -249,6 +260,9 @@ func ActionChange(cmd *dclish.Command) error {
 
 	optShutdown := false
 	if cmd.Flags["/SHUTDOWN"].Value == "true" {
+		if !isAdmin {
+			return errors.New("Must be admin")
+		}
 		optShutdown = true
 	}
 
@@ -262,7 +276,7 @@ func ActionChange(cmd *dclish.Command) error {
 
 	optSystem := false
 	if cmd.Flags["/SYSTEM"].Set {
-		if this.User.Admin == 0 {
+		if !isAdmin {
 			return errors.New("Must be admin")
 		}
 		if this.Folder.Name != "GENERAL" {
@@ -285,6 +299,70 @@ func ActionChange(cmd *dclish.Command) error {
 	if optSystem && optGeneral {
 		return errors.New("Can't specify /SYSTEM and /GENERAL")
 	}
+	if optNew && !optText {
+		return errors.New("Must specify /TEXT if you specify /NEW")
+	}
+	ctx := storage.Context()
+	if optAll {
+		if optText {
+			return errors.New("Can't specify /TEXT with /ALL")
+		}
+		msgids, err = this.Q.ListMessageIDs(ctx, this.Folder.Name)
+		if err != nil {
+			return err
+		}
+	}
+
+	isFolderOwner := folders.IsFolderOwner(this.Folder.Name, this.User.Login)
+	for i := range msgids {
+		msg, err := folders.ReadMessage(this.User.Login, this.Folder.Name, msgids[i])
+		if err != nil {
+			return err
+		}
+		if msg.ID == 0 {
+			return fmt.Errorf("Failed to retrieve message number %d", msgids[i])
+		}
+		if !(isAdmin || isFolderOwner || msg.Author == this.User.Login) {
+			return errors.New("Only admin or folder owner or author can change message")
+		}
+		if optSystem {
+			msg.System = 1
+		} else if optGeneral {
+			msg.System = 0
+		}
+		if cmd.Flags["/EXPIRATION"].Set {
+			msg.Expiration = *optExpiration
+		}
+		if optPermanent {
+			msg.Permanent = 1
+		}
+		if optShutdown {
+			msg.Shutdown = 1
+		}
+		if cmd.Flags["/SUBJECT"].Set {
+			msg.Subject = optSubject
+		}
+		if optText {
+			if optNew {
+				msg.Message = ""
+			}
+			msg.Message, err = editor.Editor(
+				fmt.Sprintf("Enter message for '%s'...", msg.Subject),
+				"Edit message",
+				msg.Message)
+		}
+		err = this.Q.UpdateMessage(ctx, storage.UpdateMessageParams{
+			System:     msg.System,
+			Expiration: msg.Expiration,
+			Permanent:  msg.Permanent,
+			Shutdown:   msg.Shutdown,
+			Subject:    msg.Subject,
+			Message:    msg.Message,
+		})
+		if err != nil {
+			return err
+		}
+	}
 
 	fmt.Println("TODO: flags parsed, now need to do something.")
 	return nil
diff --git a/storage/messages.sql.go b/storage/messages.sql.go
index 5a5fb40..3e46406 100644
--- a/storage/messages.sql.go
+++ b/storage/messages.sql.go
@@ -233,7 +233,7 @@ func (q *Queries) PrevMsgidIgnoringSeen(ctx context.Context, arg PrevMsgidIgnori
 }
 
 const readMessage = `-- name: ReadMessage :one
-SELECT id, folder, author, subject, message, permanent, shutdown, expiration, create_at, update_at FROM messages WHERE folder = ? AND id = ?
+SELECT id, folder, author, subject, message, permanent, system, shutdown, expiration, create_at, update_at FROM messages WHERE folder = ? AND id = ?
 `
 
 type ReadMessageParams struct {
@@ -251,6 +251,7 @@ func (q *Queries) ReadMessage(ctx context.Context, arg ReadMessageParams) (Messa
 		&i.Subject,
 		&i.Message,
 		&i.Permanent,
+		&i.System,
 		&i.Shutdown,
 		&i.Expiration,
 		&i.CreateAt,
@@ -260,7 +261,7 @@ func (q *Queries) ReadMessage(ctx context.Context, arg ReadMessageParams) (Messa
 }
 
 const search = `-- name: Search :many
-SELECT id, folder, author, subject, message, permanent, shutdown, expiration, create_at, update_at FROM messages
+SELECT id, folder, author, subject, message, permanent, system, shutdown, expiration, create_at, update_at FROM messages
   WHERE (message LIKE '%' || ?1 || '%'
      OR subject LIKE '%' || ?1 || '%')
     AND id >= ?2
@@ -289,6 +290,7 @@ func (q *Queries) Search(ctx context.Context, arg SearchParams) ([]Message, erro
 			&i.Subject,
 			&i.Message,
 			&i.Permanent,
+			&i.System,
 			&i.Shutdown,
 			&i.Expiration,
 			&i.CreateAt,
@@ -308,7 +310,7 @@ func (q *Queries) Search(ctx context.Context, arg SearchParams) ([]Message, erro
 }
 
 const searchReply = `-- name: SearchReply :many
-SELECT id, folder, author, subject, message, permanent, shutdown, expiration, create_at, update_at FROM messages
+SELECT id, folder, author, subject, message, permanent, system, shutdown, expiration, create_at, update_at FROM messages
   WHERE subject = ?
     AND id >= ?
     AND folder = ?
@@ -336,6 +338,7 @@ func (q *Queries) SearchReply(ctx context.Context, arg SearchReplyParams) ([]Mes
 			&i.Subject,
 			&i.Message,
 			&i.Permanent,
+			&i.System,
 			&i.Shutdown,
 			&i.Expiration,
 			&i.CreateAt,
@@ -355,7 +358,7 @@ func (q *Queries) SearchReply(ctx context.Context, arg SearchReplyParams) ([]Mes
 }
 
 const searchReplyReverse = `-- name: SearchReplyReverse :many
-SELECT id, folder, author, subject, message, permanent, shutdown, expiration, create_at, update_at FROM messages
+SELECT id, folder, author, subject, message, permanent, system, shutdown, expiration, create_at, update_at FROM messages
   WHERE subject = ?
     AND id <= ?
     AND folder = ?
@@ -384,6 +387,7 @@ func (q *Queries) SearchReplyReverse(ctx context.Context, arg SearchReplyReverse
 			&i.Subject,
 			&i.Message,
 			&i.Permanent,
+			&i.System,
 			&i.Shutdown,
 			&i.Expiration,
 			&i.CreateAt,
@@ -403,7 +407,7 @@ func (q *Queries) SearchReplyReverse(ctx context.Context, arg SearchReplyReverse
 }
 
 const searchReverse = `-- name: SearchReverse :many
-SELECT id, folder, author, subject, message, permanent, shutdown, expiration, create_at, update_at FROM messages
+SELECT id, folder, author, subject, message, permanent, system, shutdown, expiration, create_at, update_at FROM messages
   WHERE (message LIKE '%' || ?1 || '%'
      OR subject LIKE '%' || ?1 || '%')
     AND id <= ?2
@@ -433,6 +437,7 @@ func (q *Queries) SearchReverse(ctx context.Context, arg SearchReverseParams) ([
 			&i.Subject,
 			&i.Message,
 			&i.Permanent,
+			&i.System,
 			&i.Shutdown,
 			&i.Expiration,
 			&i.CreateAt,
@@ -452,7 +457,7 @@ func (q *Queries) SearchReverse(ctx context.Context, arg SearchReverseParams) ([
 }
 
 const searchSubject = `-- name: SearchSubject :many
-SELECT id, folder, author, subject, message, permanent, shutdown, expiration, create_at, update_at FROM messages
+SELECT id, folder, author, subject, message, permanent, system, shutdown, expiration, create_at, update_at FROM messages
   WHERE subject LIKE '%' || ? || '%'
     AND id >= ?
     AND folder = ?
@@ -480,6 +485,7 @@ func (q *Queries) SearchSubject(ctx context.Context, arg SearchSubjectParams) ([
 			&i.Subject,
 			&i.Message,
 			&i.Permanent,
+			&i.System,
 			&i.Shutdown,
 			&i.Expiration,
 			&i.CreateAt,
@@ -499,7 +505,7 @@ func (q *Queries) SearchSubject(ctx context.Context, arg SearchSubjectParams) ([
 }
 
 const searchSubjectReverse = `-- name: SearchSubjectReverse :many
-SELECT id, folder, author, subject, message, permanent, shutdown, expiration, create_at, update_at FROM messages
+SELECT id, folder, author, subject, message, permanent, system, shutdown, expiration, create_at, update_at FROM messages
   WHERE subject LIKE '%' || ? || '%'
     AND id <= ?
     AND folder = ?
@@ -528,6 +534,7 @@ func (q *Queries) SearchSubjectReverse(ctx context.Context, arg SearchSubjectRev
 			&i.Subject,
 			&i.Message,
 			&i.Permanent,
+			&i.System,
 			&i.Shutdown,
 			&i.Expiration,
 			&i.CreateAt,
@@ -575,3 +582,37 @@ func (q *Queries) UnsetMessageSeen(ctx context.Context, arg UnsetMessageSeenPara
 	_, err := q.db.ExecContext(ctx, unsetMessageSeen, arg.Login, arg.Folder, arg.Msgid)
 	return err
 }
+
+const updateMessage = `-- name: UpdateMessage :exec
+UPDATE messages SET
+    subject = ?,
+    message = ?,
+    permanent = ?,
+    system = ?,
+    shutdown = ?,
+    expiration = ?
+  WHERE id = ?
+`
+
+type UpdateMessageParams struct {
+	Subject    string
+	Message    string
+	Permanent  int64
+	System     int64
+	Shutdown   int64
+	Expiration time.Time
+	ID         int64
+}
+
+func (q *Queries) UpdateMessage(ctx context.Context, arg UpdateMessageParams) error {
+	_, err := q.db.ExecContext(ctx, updateMessage,
+		arg.Subject,
+		arg.Message,
+		arg.Permanent,
+		arg.System,
+		arg.Shutdown,
+		arg.Expiration,
+		arg.ID,
+	)
+	return err
+}
diff --git a/storage/migrations/1_create_table.up.sql b/storage/migrations/1_create_table.up.sql
index 2f066f8..690387f 100644
--- a/storage/migrations/1_create_table.up.sql
+++ b/storage/migrations/1_create_table.up.sql
@@ -111,6 +111,7 @@ CREATE TABLE messages (
   subject     VARCHAR(53)  NOT NULL,
   message     TEXT         NOT NULL,
   permanent   INT          DEFAULT 0 NOT NULL,
+  system      INT          DEFAULT 0 NOT NULL,
   shutdown    INT          DEFAULT 0 NOT NULL,
   expiration  TIMESTAMP    NOT NULL,
   create_at   TIMESTAMP    DEFAULT CURRENT_TIMESTAMP NOT NULL,
diff --git a/storage/models.go b/storage/models.go
index 1292885..98bbe36 100644
--- a/storage/models.go
+++ b/storage/models.go
@@ -55,6 +55,7 @@ type Message struct {
 	Subject    string
 	Message    string
 	Permanent  int64
+	System     int64
 	Shutdown   int64
 	Expiration time.Time
 	CreateAt   time.Time
diff --git a/storage/queries/messages.sql b/storage/queries/messages.sql
index 901d71c..7d9abf3 100644
--- a/storage/queries/messages.sql
+++ b/storage/queries/messages.sql
@@ -95,3 +95,14 @@ SELECT * FROM messages
     AND id <= ?
     AND folder = ?
   ORDER BY DESC;
+
+-- name: UpdateMessage :exec
+UPDATE messages SET
+    subject = ?,
+    message = ?,
+    permanent = ?,
+    system = ?,
+    shutdown = ?,
+    expiration = ?
+  WHERE id = ?;
+
diff --git a/storage/standard.sql.go b/storage/standard.sql.go
index 01d0e14..be420e6 100644
--- a/storage/standard.sql.go
+++ b/storage/standard.sql.go
@@ -287,7 +287,7 @@ func (q *Queries) GetMark(ctx context.Context, arg GetMarkParams) (Mark, error)
 }
 
 const getMessage = `-- name: GetMessage :one
-SELECT id, folder, author, subject, message, permanent, shutdown, expiration, create_at, update_at FROM messages WHERE id = ? AND folder = ?
+SELECT id, folder, author, subject, message, permanent, system, shutdown, expiration, create_at, update_at FROM messages WHERE id = ? AND folder = ?
 `
 
 type GetMessageParams struct {
@@ -305,6 +305,7 @@ func (q *Queries) GetMessage(ctx context.Context, arg GetMessageParams) (Message
 		&i.Subject,
 		&i.Message,
 		&i.Permanent,
+		&i.System,
 		&i.Shutdown,
 		&i.Expiration,
 		&i.CreateAt,
@@ -558,7 +559,7 @@ func (q *Queries) ListMessageIDs(ctx context.Context, folder string) ([]int64, e
 }
 
 const listMessages = `-- name: ListMessages :many
-SELECT id, folder, author, subject, message, permanent, shutdown, expiration, create_at, update_at FROM messages WHERE folder = ? ORDER BY id
+SELECT id, folder, author, subject, message, permanent, system, shutdown, expiration, create_at, update_at FROM messages WHERE folder = ? ORDER BY id
 `
 
 func (q *Queries) ListMessages(ctx context.Context, folder string) ([]Message, error) {
@@ -577,6 +578,7 @@ func (q *Queries) ListMessages(ctx context.Context, folder string) ([]Message, e
 			&i.Subject,
 			&i.Message,
 			&i.Permanent,
+			&i.System,
 			&i.Shutdown,
 			&i.Expiration,
 			&i.CreateAt,
-- 
GitLab