Add restore functionality for archived messages

- Added `RestoreMessageHandler` and route at `/account/messages/restore`
- Updated `users_messages` table to support `archived_at` reset
- Added restore button to archived messages template
- Ensures archived messages can be moved back into inbox
This commit is contained in:
2025-04-02 22:18:02 +01:00
parent dd83081271
commit db5352bc9c
8 changed files with 146 additions and 25 deletions

View File

@@ -11,13 +11,37 @@ import (
func MessagesInboxHandler(db *sql.DB) http.HandlerFunc { func MessagesInboxHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
userID, ok := helpers.GetCurrentUserID(r)
if !ok {
helpers.RenderError(w, r, 403)
return
}
page := helpers.Atoi(r.URL.Query().Get("page"))
if page < 1 {
page = 1
}
perPage := 10
totalCount := storage.GetInboxMessageCount(db, userID)
totalPages := (totalCount + perPage - 1) / perPage
if totalPages == 0 {
totalPages = 1
}
messages := storage.GetInboxMessages(db, userID, page, perPage)
data := BuildTemplateData(db, w, r) data := BuildTemplateData(db, w, r)
context := helpers.TemplateContext(w, r, data) context := helpers.TemplateContext(w, r, data)
context["Messages"] = messages
context["CurrentPage"] = page
context["TotalPages"] = totalPages
context["PageRange"] = helpers.PageRange(page, totalPages)
tmpl := helpers.LoadTemplateFiles("messages.html", "templates/account/messages/index.html") tmpl := helpers.LoadTemplateFiles("messages.html", "templates/account/messages/index.html")
err := tmpl.ExecuteTemplate(w, "layout", context) if err := tmpl.ExecuteTemplate(w, "layout", context); err != nil {
if err != nil {
helpers.RenderError(w, r, 500) helpers.RenderError(w, r, 500)
} }
} }
@@ -136,3 +160,23 @@ func SendMessageHandler(db *sql.DB) http.HandlerFunc {
} }
} }
} }
func RestoreMessageHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := helpers.Atoi(r.URL.Query().Get("id"))
userID, ok := helpers.GetCurrentUserID(r)
if !ok {
helpers.RenderError(w, r, 403)
return
}
err := storage.RestoreMessage(db, userID, id)
if err != nil {
helpers.SetFlash(w, r, "Failed to restore message.")
} else {
helpers.SetFlash(w, r, "Message restored.")
}
http.Redirect(w, r, "/account/messages/archived", http.StatusSeeOther)
}
}

View File

@@ -16,3 +16,11 @@ func GetTotalPages(db *sql.DB, tableName, whereClause string, args []interface{}
} }
return totalPages, totalCount return totalPages, totalCount
} }
func MakePageRange(current, total int) []int {
var pages []int
for i := 1; i <= total; i++ {
pages = append(pages, i)
}
return pages
}

View File

@@ -44,6 +44,7 @@ func TemplateFuncs() template.FuncMap {
}, },
"mul": func(a, b int) int { return a * b }, "mul": func(a, b int) int { return a * b },
"add": func(a, b int) int { return a + b }, "add": func(a, b int) int { return a + b },
"sub": func(a, b int) int { return a - b },
"min": func(a, b int) int { "min": func(a, b int) int {
if a < b { if a < b {
return a return a
@@ -66,6 +67,7 @@ func TemplateFuncs() template.FuncMap {
} }
return s[:max] + "..." return s[:max] + "..."
}, },
"PageRange": PageRange,
} }
} }
@@ -115,3 +117,11 @@ func rangeClass(n int) string {
return "50-plus" return "50-plus"
} }
} }
func PageRange(current, total int) []int {
var pages []int
for i := 1; i <= total; i++ {
pages = append(pages, i)
}
return pages
}

View File

@@ -72,6 +72,7 @@ func setupAccountRoutes(mux *http.ServeMux, db *sql.DB) {
mux.HandleFunc("/account/messages/read", middleware.Auth(true)(handlers.ReadMessageHandler(db))) mux.HandleFunc("/account/messages/read", middleware.Auth(true)(handlers.ReadMessageHandler(db)))
mux.HandleFunc("/account/messages/archive", middleware.Auth(true)(handlers.ArchiveMessageHandler(db))) mux.HandleFunc("/account/messages/archive", middleware.Auth(true)(handlers.ArchiveMessageHandler(db)))
mux.HandleFunc("/account/messages/archived", middleware.Auth(true)(handlers.ArchivedMessagesHandler(db))) mux.HandleFunc("/account/messages/archived", middleware.Auth(true)(handlers.ArchivedMessagesHandler(db)))
mux.HandleFunc("/account/messages/restore", middleware.Auth(true)(handlers.RestoreMessageHandler(db)))
mux.HandleFunc("/account/messages/send", middleware.Auth(true)(handlers.SendMessageHandler(db))) mux.HandleFunc("/account/messages/send", middleware.Auth(true)(handlers.SendMessageHandler(db)))
mux.HandleFunc("/account/notifications", middleware.Auth(true)(handlers.NotificationsHandler(db))) mux.HandleFunc("/account/notifications", middleware.Auth(true)(handlers.NotificationsHandler(db)))
mux.HandleFunc("/account/notifications/read", middleware.Auth(true)(handlers.MarkNotificationReadHandler(db))) mux.HandleFunc("/account/notifications/read", middleware.Auth(true)(handlers.MarkNotificationReadHandler(db)))

View File

@@ -127,3 +127,52 @@ func GetArchivedMessages(db *sql.DB, userID int, page, perPage int) []models.Mes
} }
return messages return messages
} }
func GetInboxMessages(db *sql.DB, userID int, page, perPage int) []models.Message {
offset := (page - 1) * perPage
rows, err := db.Query(`
SELECT id, senderId, recipientId, subject, message, is_read, created_at
FROM users_messages
WHERE recipientId = ? AND is_archived = FALSE
ORDER BY created_at DESC
LIMIT ? OFFSET ?
`, userID, perPage, offset)
if err != nil {
return nil
}
defer rows.Close()
var messages []models.Message
for rows.Next() {
var m models.Message
err := rows.Scan(
&m.ID, &m.SenderId, &m.RecipientId,
&m.Subject, &m.Message, &m.IsRead, &m.CreatedAt,
)
if err == nil {
messages = append(messages, m)
}
}
return messages
}
func GetInboxMessageCount(db *sql.DB, userID int) int {
var count int
err := db.QueryRow(`
SELECT COUNT(*) FROM users_messages
WHERE recipientId = ? AND is_archived = FALSE
`, userID).Scan(&count)
if err != nil {
return 0
}
return count
}
func RestoreMessage(db *sql.DB, userID, messageID int) error {
_, err := db.Exec(`
UPDATE users_messages
SET is_archived = FALSE, archived_at = NULL
WHERE id = ? AND recipientId = ?
`, messageID, userID)
return err
}

View File

@@ -13,6 +13,11 @@
</p> </p>
</div> </div>
</div> </div>
<form method="POST" action="/account/messages/restore" class="m-0">
{{ $.CSRFField }}
<input type="hidden" name="id" value="{{ .ID }}">
<button type="submit" class="btn btn-sm btn-outline-success">Restore</button>
</form>
{{ end }} {{ end }}
<!-- Pagination Controls --> <!-- Pagination Controls -->

View File

@@ -10,7 +10,7 @@
<br> <br>
<small class="text-muted">{{ .CreatedAt.Format "02 Jan 2006 15:04" }}</small> <small class="text-muted">{{ .CreatedAt.Format "02 Jan 2006 15:04" }}</small>
</div> </div>
<form method="POST" action="/account/messages/archive" class="m-0"> <form method="POST" action="/account/messages/archive?id={{ .ID }}" class="m-0">
{{ $.CSRFField }} {{ $.CSRFField }}
<input type="hidden" name="id" value="{{ .ID }}"> <input type="hidden" name="id" value="{{ .ID }}">
<button type="submit" class="btn btn-sm btn-outline-secondary">Archive</button> <button type="submit" class="btn btn-sm btn-outline-secondary">Archive</button>
@@ -18,34 +18,37 @@
</li> </li>
{{ end }} {{ end }}
</ul> </ul>
<!-- Pagination -->
<nav>
<ul class="pagination">
{{ if gt .CurrentPage 1 }}
<li class="page-item">
<a class="page-link" href="?page={{ sub .CurrentPage 1 }}">Previous</a>
</li>
{{ end }}
<!-- Pagination --> {{ range $i := .PageRange }}
<nav> <li class="page-item {{ if eq $i $.CurrentPage }}active{{ end }}">
<ul class="pagination"> <a class="page-link" href="?page={{ $i }}">{{ $i }}</a>
{{ if gt .CurrentPage 1 }} </li>
<li class="page-item"> {{ end }}
<a class="page-link" href="?page={{ sub .CurrentPage 1 }}">Previous</a>
</li>
{{ end }}
{{ range $i := .PageRange }} {{ if lt .CurrentPage .TotalPages }}
<li class="page-item {{ if eq $i $.CurrentPage }}active{{ end }}"> <li class="page-item">
<a class="page-link" href="?page={{ $i }}">{{ $i }}</a> <a class="page-link" href="?page={{ add .CurrentPage 1 }}">Next</a>
</li> </li>
{{ end }} {{ end }}
</ul>
</nav>
{{ if lt .CurrentPage .TotalPages }}
<li class="page-item">
<a class="page-link" href="?page={{ add .CurrentPage 1 }}">Next</a>
</li>
{{ end }}
</ul>
</nav>
{{ else }} {{ else }}
<div class="alert alert-info">No messages found.</div> <div class="alert alert-info">No messages found.</div>
{{ end }} {{ end }}
<a href="/account/messages/send" class="btn btn-primary mt-3">Compose Message</a> <div class="mt-3">
<a href="/account/messages/send" class="btn btn-primary">Compose Message</a>
<a href="/account/messages/archived" class="btn btn-outline-secondary ms-2">View Archived</a>
</div>
</div> </div>
{{ end }} {{ end }}

View File

@@ -103,6 +103,7 @@
{{ else }} {{ else }}
<li class="text-center text-muted py-2">No messages</li> <li class="text-center text-muted py-2">No messages</li>
{{ end }} {{ end }}
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li class="text-center"><a href="/account/messages" class="dropdown-item">View all messages</a></li> <li class="text-center"><a href="/account/messages" class="dropdown-item">View all messages</a></li>
</ul> </ul>