Feature: complete message inbox, view, and topbar integration

Added users_messages schema with correct field naming (senderId, recipientId)

Implemented message count and recent message fetch via storage.GetMessageCount and GetRecentMessages

Fixed field mismatches in SQL queries (recipientId vs recipient_id)

Displayed unread message badge in topbar with truncation for body preview

Linked messages in dropdown to full view (/account/messages/read?id=...)

Added fallback handling for unauthorized/invalid message access

Cleaned up BuildTemplateData to support full message context

Ensured CSRF/session/user context remains intact throughout
This commit is contained in:
2025-04-02 13:41:51 +01:00
parent b630296b8c
commit 2fd053777d
4 changed files with 110 additions and 118 deletions

View File

@@ -9,17 +9,17 @@ import (
func GetMessageCount(db *sql.DB, userID int) (int, error) { func GetMessageCount(db *sql.DB, userID int) (int, error) {
var count int var count int
err := db.QueryRow(` err := db.QueryRow(`
SELECT COUNT(*) FROM user_messages SELECT COUNT(*) FROM users_messages
WHERE recipient_id = ? AND is_read = FALSE WHERE recipientId = ? AND is_read = FALSE
`, userID).Scan(&count) `, userID).Scan(&count)
return count, err return count, err
} }
func GetRecentMessages(db *sql.DB, userID int, limit int) []models.Message { func GetRecentMessages(db *sql.DB, userID int, limit int) []models.Message {
rows, err := db.Query(` rows, err := db.Query(`
SELECT id, sender_id, recipient_id, subject, body, is_read, created_at SELECT id, senderId, recipientId, subject, message, is_read, created_at
FROM user_messages FROM users_messages
WHERE recipient_id = ? WHERE recipientId = ?
ORDER BY created_at DESC ORDER BY created_at DESC
LIMIT ? LIMIT ?
`, userID, limit) `, userID, limit)
@@ -49,9 +49,9 @@ func GetRecentMessages(db *sql.DB, userID int, limit int) []models.Message {
func GetMessageByID(db *sql.DB, userID, messageID int) (*models.Message, error) { func GetMessageByID(db *sql.DB, userID, messageID int) (*models.Message, error) {
row := db.QueryRow(` row := db.QueryRow(`
SELECT id, sender_id, recipient_id, subject, body, is_read, created_at SELECT id, senderId, recipientId, subject, message, is_read, created_at
FROM user_messages FROM users_messages
WHERE id = ? AND recipient_id = ? WHERE id = ? AND recipientId = ?
`, messageID, userID) `, messageID, userID)
var m models.Message var m models.Message
@@ -64,9 +64,9 @@ func GetMessageByID(db *sql.DB, userID, messageID int) (*models.Message, error)
func MarkMessageAsRead(db *sql.DB, messageID, userID int) error { func MarkMessageAsRead(db *sql.DB, messageID, userID int) error {
result, err := db.Exec(` result, err := db.Exec(`
UPDATE user_messages UPDATE users_messages
SET is_read = TRUE SET is_read = TRUE
WHERE id = ? AND recipient_id = ? WHERE id = ? AND recipientId = ?
`, messageID, userID) `, messageID, userID)
if err != nil { if err != nil {
return err return err

View File

@@ -119,7 +119,6 @@ CREATE TABLE IF NOT EXISTS users_messages (
subject TEXT NOT NULL, subject TEXT NOT NULL,
message TEXT, message TEXT,
is_read BOOLEAN DEFAULT FALSE, is_read BOOLEAN DEFAULT FALSE,
type VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);` );`

View File

@@ -4,7 +4,7 @@
<h2>{{ .Message.Subject }}</h2> <h2>{{ .Message.Subject }}</h2>
<p class="text-muted">Received: {{ .Message.CreatedAt.Format "02 Jan 2006 15:04" }}</p> <p class="text-muted">Received: {{ .Message.CreatedAt.Format "02 Jan 2006 15:04" }}</p>
<hr> <hr>
<p>{{ .Message.Body }}</p> <p>{{ .Message.Message }}</p>
<a href="/account/messages" class="btn btn-secondary mt-4">Back to Inbox</a> <a href="/account/messages" class="btn btn-secondary mt-4">Back to Inbox</a>
{{ else }} {{ else }}
<div class="alert alert-danger text-center"> <div class="alert alert-danger text-center">

View File

@@ -16,27 +16,23 @@
<ul class="dropdown-menu dropdown-menu-end dropdown-admin-box shadow-sm dropdown-with-arrow" <ul class="dropdown-menu dropdown-menu-end dropdown-admin-box shadow-sm dropdown-with-arrow"
aria-labelledby="adminDropdown"> aria-labelledby="adminDropdown">
<li class="dropdown-header text-center fw-bold">Admin Menu</li> <li class="dropdown-header text-center fw-bold">Admin Menu</li>
<li> <li><hr class="dropdown-divider"></li>
<hr class="dropdown-divider">
</li>
<li class="text-center"><a href="/admin/dashboard" class="dropdown-item">Tools</a></li> <li class="text-center"><a href="/admin/dashboard" class="dropdown-item">Tools</a></li>
<li class="text-center"><a href="/admin/dashboard" class="dropdown-item">Audit Logs</a></li> <li class="text-center"><a href="/admin/dashboard" class="dropdown-item">Audit Logs</a></li>
<li> <li><hr class="dropdown-divider"></li>
<hr class="dropdown-divider">
</li>
<li class="text-center"><a href="/admin/dashboard" class="dropdown-item">Open Dashboard</a></li> <li class="text-center"><a href="/admin/dashboard" class="dropdown-item">Open Dashboard</a></li>
</ul> </ul>
</div> </div>
{{ end }} {{ end }}
<!-- Notification Dropdown --> <!-- Notification Dropdown -->
<div class="dropdown"> <div class="dropdown">
<a class="nav-link text-dark" href="#" id="notificationDropdown" role="button" data-bs-toggle="dropdown" <a class="nav-link text-dark" href="#" id="notificationDropdown" role="button" data-bs-toggle="dropdown"
aria-expanded="false"> aria-expanded="false">
<i class="bi bi-bell fs-5 position-relative"> <i class="bi bi-bell fs-5 position-relative">
{{ if gt ( .NotificationCount) 0 }} {{ if gt .NotificationCount 0 }}
<span <span class="position-absolute top-0 start-0 translate-middle badge rounded-pill bg-warning text-dark badge-small">
class="position-absolute top-0 start-0 translate-middle badge rounded-pill bg-warning text-dark badge-small"> {{ if gt .NotificationCount 15 }}15+{{ else }}{{ .NotificationCount }}{{ end }}
{{ if gt (.NotificationCount) 15 }}15+{{ else }}{{ .NotificationCount }}{{ end }}
</span> </span>
{{ end }} {{ end }}
</i> </i>
@@ -44,9 +40,7 @@
<ul class="dropdown-menu dropdown-menu-end dropdown-notification-box shadow-sm dropdown-with-arrow" <ul class="dropdown-menu dropdown-menu-end dropdown-notification-box shadow-sm dropdown-with-arrow"
aria-labelledby="notificationDropdown"> aria-labelledby="notificationDropdown">
<li class="dropdown-header text-center fw-bold">Notifications</li> <li class="dropdown-header text-center fw-bold">Notifications</li>
<li> <li><hr class="dropdown-divider"></li>
<hr class="dropdown-divider">
</li>
{{ $total := len .Notifications }} {{ $total := len .Notifications }}
{{ range $i, $n := .Notifications }} {{ range $i, $n := .Notifications }}
@@ -62,17 +56,15 @@
</a> </a>
</li> </li>
{{ if lt (add $i 1) $total }} {{ if lt (add $i 1) $total }}
<li> <li><hr class="dropdown-divider"></li>
<hr class="dropdown-divider">
</li>
{{ end }} {{ end }}
{{ else }} {{ end }}
{{ if not .Notifications }}
<li class="text-center text-muted py-2">No notifications</li> <li class="text-center text-muted py-2">No notifications</li>
{{ end }} {{ end }}
<li> <li><hr class="dropdown-divider"></li>
<hr class="dropdown-divider">
</li>
<li class="text-center"><a href="/account/notifications" class="dropdown-item">View all notifications</a></li> <li class="text-center"><a href="/account/notifications" class="dropdown-item">View all notifications</a></li>
</ul> </ul>
</div> </div>
@@ -97,16 +89,18 @@
{{ if .Messages }} {{ if .Messages }}
{{ range $i, $m := .Messages }} {{ range $i, $m := .Messages }}
<li class="px-3 py-2"> <li class="px-3 py-2">
<a href="/account/messages/read?id={{ $m.ID }}" class="text-decoration-none text-dark d-block">
<div class="d-flex align-items-start"> <div class="d-flex align-items-start">
<i class="bi bi-person-circle me-2 fs-4 text-secondary"></i> <i class="bi bi-person-circle me-2 fs-4 text-secondary"></i>
<div> <div>
<div class="fw-semibold">{{ $m.Subject }}</div> <div class="fw-semibold">{{ $m.Subject }}</div>
<small class="text-muted">{{ truncate $m.Body 40 }}</small> <small class="text-muted">{{ truncate $m.Message 40 }}</small>
</div> </div>
</div> </div>
</a>
</li> </li>
<li><hr class="dropdown-divider"></li>
{{ end }} {{ end }}
<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>
{{ else }} {{ else }}
<li class="text-center text-muted py-2">No messages</li> <li class="text-center text-muted py-2">No messages</li>
@@ -122,5 +116,4 @@
{{ end }} {{ end }}
</div> </div>
</nav> </nav>
{{ end }} {{ end }}