diff --git a/conference_ops.go b/conference_ops.go
index fca36e7..9e52061 100644
--- a/conference_ops.go
+++ b/conference_ops.go
@@ -482,6 +482,64 @@ func NukeMessage(ctxt ui.AmContext) (string, any) {
return mbox.Render(ctxt)
}
+/* PruneMessageAttachment prunes (removes and deletes) a message's attachmeent.
+ * Parameters:
+ * ctxt - The AmContext for the request.
+ * Returns:
+ * Command string dictating what to be rendered.
+ * Data as a parameter for the command string.
+ */
+func PruneMessageAttachment(ctxt ui.AmContext) (string, any) {
+ if ctxt.CurrentUser().IsAnon {
+ return "error", ENOPERM
+ }
+ conf := ctxt.GetScratch("currentConference").(*database.Conference)
+ myLevel := ctxt.GetScratch("levelInConference").(uint16)
+ topic := ctxt.GetScratch("currentTopic").(*database.Topic)
+ msgNum, err := strconv.Atoi(ctxt.URLParam("msg"))
+ if err != nil {
+ return "error", err
+ }
+ hdrs, err := database.AmGetPostRange(ctxt.Ctx(), topic, int32(msgNum), int32(msgNum))
+ if err != nil {
+ return "error", err
+ } else if len(hdrs) != 1 {
+ return "error", EPOSTREF
+ }
+ if !conf.TestPermission("Conference.Nuke", myLevel) {
+ return "error", ENOPERM
+ }
+
+ // Load the message box, and, if we have a valid "yes," then perform the prune!
+ mbox, err := ui.AmLoadMessageBox("prune")
+ if err != nil {
+ return "error", err
+ }
+ if mbox.Validate(ctxt, "yes") {
+ // do the pruning!
+ err := hdrs[0].PruneAttachment(ctxt.Ctx(), ctxt.CurrentUser(), ctxt.RemoteIP())
+ if err != nil {
+ return "error", err
+ }
+ return "redirect", fmt.Sprintf("/comm/%s/conf/%s/r/%d?r=%d&ac=1", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number, hdrs[0].Num)
+ }
+
+ // Set up to display the message box.
+ link, err := hdrs[0].Link(ctxt.Ctx(), "community")
+ if err != nil {
+ return "error", err
+ }
+ creator, err := hdrs[0].Creator(ctxt.Ctx())
+ if err != nil {
+ return "error", err
+ }
+ mbox.SetMessage(fmt.Sprintf(`You are about to prune the attachment of message <%s>,
+ originally composed by <%s>!`, link, creator.Username))
+ mbox.SetLink("no", fmt.Sprintf("/comm/%s/conf/%s/r/%d?r=%d&ac=1", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number, hdrs[0].Num))
+ mbox.SetLink("yes", fmt.Sprintf("/comm/%s/conf/%s/op/%d/prune/%d", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number, hdrs[0].Num))
+ return mbox.Render(ctxt)
+}
+
/* MoveMessageForm displays the form for moving a message..
* Parameters:
* ctxt - The AmContext for the request.
diff --git a/database/audit.go b/database/audit.go
index f55a4f2..69577e3 100644
--- a/database/audit.go
+++ b/database/audit.go
@@ -77,6 +77,7 @@ const (
AuditConferenceDelete = 315
AuditConferenceMoveMessage = 316
AuditConferenceStickyTopic = 317
+ AuditConferencePruneAttachment = 318
)
// auditWriteQueue is a channel to store audit records in the background.
diff --git a/database/post.go b/database/post.go
index c94e309..67bec12 100644
--- a/database/post.go
+++ b/database/post.go
@@ -223,6 +223,21 @@ func (p *PostHeader) HitAttachment(ctx context.Context) error {
return err
}
+// PruneAttachment prunes (removes and deletes) the attachment of this post.
+func (p *PostHeader) PruneAttachment(ctx context.Context, u *User, ipaddr string) error {
+ if p.ScribbleDate != nil && p.ScribbleUid != nil {
+ return errors.New("no attachment on scribbled post")
+ }
+ rs, err := amdb.ExecContext(ctx, "DELETE FROM postattach WHERE postid = ?", p.PostId)
+ if err == nil {
+ rowCount, err := rs.RowsAffected()
+ if err == nil && rowCount > 1 {
+ AmStoreAudit(AmNewAudit(AuditConferencePruneAttachment, u.Uid, ipaddr, fmt.Sprintf("post=%d", p.PostId)))
+ }
+ }
+ return err
+}
+
// Text returns the text associated with a post.
func (p *PostHeader) Text(ctx context.Context) (string, error) {
var dbdata []PostData
diff --git a/docs/MISSINGFUNCS.md b/docs/MISSINGFUNCS.md
index 4daf0ec..8edcd45 100644
--- a/docs/MISSINGFUNCS.md
+++ b/docs/MISSINGFUNCS.md
@@ -73,6 +73,6 @@ _(italicized items can be deferred)_
- ~~Delete Conference~~
- ~~Add to Hotlist/Remove from Hotlist~~
- Actually implement pictures in posts
-- Related to bugs in Export Messages caused by bad data:
+- ~~Related to bugs in Export Messages caused by bad data:~~
- ~~Provide a per-conference flag that will set BuggyAttachment behavior~~
- - New feature: remove attachment from message (requires Conference.Nuke permission)
+ - ~~New feature: remove attachment from message (requires Conference.Nuke permission)~~
diff --git a/main.go b/main.go
index c1e23fa..be0d731 100644
--- a/main.go
+++ b/main.go
@@ -160,6 +160,7 @@ func setupEcho() *echo.Echo {
opsGroup.GET("/hide/:msg", ui.AmWrap(HideMessage))
opsGroup.GET("/scribble/:msg", ui.AmWrap(ScribbleMessage))
opsGroup.GET("/nuke/:msg", ui.AmWrap(NukeMessage))
+ opsGroup.GET("/prune/:msg", ui.AmWrap(PruneMessageAttachment))
opsGroup.GET("/publish/:msg", ui.AmWrap(PublishMessage))
opsGroup.GET("/move/:msg", ui.AmWrap(MoveMessageForm))
opsGroup.POST("/move/:msg", ui.AmWrap(MoveMessage))
diff --git a/ui/messagedefs.yaml b/ui/messagedefs.yaml
index 5b8c3eb..e171a40 100644
--- a/ui/messagedefs.yaml
+++ b/ui/messagedefs.yaml
@@ -31,6 +31,30 @@ messagedefs:
tone: "green"
icon: "✗"
text: "No, Cancel"
+ - id: "prune"
+ title: "Prune Message Attachment"
+ tone: "red"
+ destructive: true
+ message: "You are about to prune a message's attachment!"
+ warningIcon: "💣"
+ warningLines:
+ - text: "Warning: This action cannot be undone!"
+ bold: true
+ - text: "Pruning this message's attachment will permanently delete it from the system."
+ bold: false
+ buttons:
+ - id: "yes"
+ link: "placeholder"
+ confirm: true
+ tone: "red"
+ icon: "✓"
+ text: "Yes, Prune It"
+ - id: "no"
+ link: "placeholder"
+ confirm: false
+ tone: "green"
+ icon: "✗"
+ text: "No, Cancel"
- id: "deleteTopic"
title: "Delete Topic"
tone: "red"
diff --git a/ui/views/posts.jet b/ui/views/posts.jet
index 9bdb2ae..57295ce 100644
--- a/ui/views/posts.jet
+++ b/ui/views/posts.jet
@@ -138,6 +138,10 @@
{{ if canNuke }}
Nuke
+ {{ if post_attach.Filename != "" }}
+ Prune Attach
+ {{ end }}
{{ end }}
{{ if canMove }}