From a46fede63c9c391809ce4d98798475e34a3dcf53 Mon Sep 17 00:00:00 2001 From: brandon Date: Sat, 7 Jun 2025 21:06:10 -0700 Subject: [PATCH] added read reciepts --- bang.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/bang.go b/bang.go index c6bebcf..ca4dc42 100644 --- a/bang.go +++ b/bang.go @@ -24,6 +24,8 @@ type Email struct { Body string `json:"body"` Timestamp time.Time `json:"timestamp"` Domain string `json:"domain"` + // Add Read field to track if the email has been opened + Read bool `json:"read"` } // User struct for account management @@ -248,6 +250,70 @@ func listMailboxHandler(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(emails) } +func markEmailOpenedHandler(w http.ResponseWriter, r *http.Request) { + username, ok := authenticate(r) + if !ok { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + var req struct { + User string `json:"user"` + ID string `json:"id"` + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid JSON", http.StatusBadRequest) + return + } + if req.User == "" || req.ID == "" { + http.Error(w, "Missing user or id", http.StatusBadRequest) + return + } + if req.User != username { + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + userDir := filepath.Join(mailboxDir, req.User) + emailPath := filepath.Join(userDir, req.ID+".json") + mu.Lock() + defer mu.Unlock() + f, err := os.OpenFile(emailPath, os.O_RDWR, 0644) + if err != nil { + http.Error(w, "Email not found", http.StatusNotFound) + return + } + defer f.Close() + var email Email + if err := json.NewDecoder(f).Decode(&email); err != nil { + http.Error(w, "Failed to decode email", http.StatusInternalServerError) + return + } + email.Read = true + // Write back the updated email + f.Seek(0, 0) + f.Truncate(0) + if err := json.NewEncoder(f).Encode(email); err != nil { + http.Error(w, "Failed to update email", http.StatusInternalServerError) + return + } + // Also update the sender's copy if it exists + senderDir := filepath.Join(mailboxDir, email.From) + senderEmailPath := filepath.Join(senderDir, email.ID+".json") + if senderEmailPath != emailPath { // avoid double update if sender==recipient + if sf, err := os.OpenFile(senderEmailPath, os.O_RDWR, 0644); err == nil { + defer sf.Close() + var senderEmail Email + if err := json.NewDecoder(sf).Decode(&senderEmail); err == nil { + senderEmail.Read = true + sf.Seek(0, 0) + sf.Truncate(0) + json.NewEncoder(sf).Encode(senderEmail) + } + } + } + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]string{"status": "opened"}) +} + // CORS middleware func withCORS(handler http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -266,6 +332,7 @@ func main() { http.HandleFunc("/users", withCORS(createUserHandler)) http.HandleFunc("/email", withCORS(receiveEmailHandler)) http.HandleFunc("/mailbox", withCORS(listMailboxHandler)) + http.HandleFunc("/mailbox/open", withCORS(markEmailOpenedHandler)) log.Println("Email server running on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) }