Coffee journaling on ATProto (alpha) alpha.arabica.social
coffee
17
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: fix join/invite audit log entry

+77 -9
+20
internal/database/boltstore/join_store.go
··· 39 39 }) 40 40 } 41 41 42 + // GetRequest returns a join request by ID. 43 + func (s *JoinStore) GetRequest(id string) (*JoinRequest, error) { 44 + var req JoinRequest 45 + err := s.db.View(func(tx *bolt.Tx) error { 46 + bucket := tx.Bucket(BucketJoinRequests) 47 + if bucket == nil { 48 + return fmt.Errorf("bucket not found: %s", BucketJoinRequests) 49 + } 50 + data := bucket.Get([]byte(id)) 51 + if data == nil { 52 + return fmt.Errorf("join request not found: %s", id) 53 + } 54 + return json.Unmarshal(data, &req) 55 + }) 56 + if err != nil { 57 + return nil, err 58 + } 59 + return &req, nil 60 + } 61 + 42 62 // DeleteRequest removes a join request by ID. 43 63 func (s *JoinStore) DeleteRequest(id string) error { 44 64 return s.db.Update(func(tx *bolt.Tx) error {
+19 -2
internal/handlers/join.go
··· 170 170 171 171 // Log the action 172 172 if h.moderationStore != nil { 173 + details := map[string]string{"email": reqEmail} 174 + if h.joinStore != nil { 175 + if joinReq, err := h.joinStore.GetRequest(reqID); err == nil { 176 + details["ip"] = joinReq.IP 177 + details["message"] = joinReq.Message 178 + } 179 + } 173 180 auditEntry := moderation.AuditEntry{ 174 181 ID: generateTID(), 175 182 Action: moderation.AuditActionCreateInvite, 176 183 ActorDID: userDID, 177 - TargetURI: reqEmail, 184 + Details: details, 178 185 Timestamp: time.Now(), 179 186 } 180 187 if err := h.moderationStore.LogAction(r.Context(), auditEntry); err != nil { ··· 215 222 return 216 223 } 217 224 225 + // Fetch request details before deleting so we can log them 226 + var details map[string]string 218 227 if h.joinStore != nil { 228 + if joinReq, err := h.joinStore.GetRequest(reqID); err == nil { 229 + details = map[string]string{ 230 + "email": joinReq.Email, 231 + "ip": joinReq.IP, 232 + "message": joinReq.Message, 233 + } 234 + } 235 + 219 236 if err := h.joinStore.DeleteRequest(reqID); err != nil { 220 237 log.Error().Err(err).Str("id", reqID).Msg("Failed to delete join request") 221 238 http.Error(w, "Failed to dismiss request", http.StatusInternalServerError) ··· 229 246 ID: generateTID(), 230 247 Action: moderation.AuditActionDismissJoinRequest, 231 248 ActorDID: userDID, 232 - TargetURI: reqID, 249 + Details: details, 233 250 Timestamp: time.Now(), 234 251 } 235 252 if err := h.moderationStore.LogAction(r.Context(), auditEntry); err != nil {
+8 -7
internal/moderation/models.go
··· 156 156 157 157 // AuditEntry represents a logged moderation action 158 158 type AuditEntry struct { 159 - ID string `json:"id"` 160 - Action AuditAction `json:"action"` 161 - ActorDID string `json:"actor_did"` // DID of moderator/admin or "automod" 162 - TargetURI string `json:"target_uri"` // AT-URI or DID being acted upon 163 - Reason string `json:"reason"` 164 - Timestamp time.Time `json:"timestamp"` 165 - AutoMod bool `json:"auto_mod"` // true if action was automatic 159 + ID string `json:"id"` 160 + Action AuditAction `json:"action"` 161 + ActorDID string `json:"actor_did"` // DID of moderator/admin or "automod" 162 + TargetURI string `json:"target_uri"` // AT-URI or DID being acted upon 163 + Reason string `json:"reason"` 164 + Details map[string]string `json:"details,omitempty"` // Structured metadata (e.g. email, ip, message) 165 + Timestamp time.Time `json:"timestamp"` 166 + AutoMod bool `json:"auto_mod"` // true if action was automatic 166 167 }
+30
internal/web/pages/admin.templ
··· 539 539 @AuditActionBadge(entry.Action) 540 540 <span class="text-sm text-brown-500">{ entry.Timestamp.Format("Jan 2, 2006 15:04") }</span> 541 541 </div> 542 + <!-- Join request details (for dismiss/invite actions) --> 543 + if len(entry.Details) > 0 { 544 + if entry.Details["email"] != "" { 545 + <div class="flex items-center justify-between"> 546 + <span class="font-medium text-brown-900">{ entry.Details["email"] }</span> 547 + </div> 548 + } 549 + <div class="flex flex-wrap gap-x-6 gap-y-2 text-sm"> 550 + if entry.Details["ip"] != "" { 551 + <div> 552 + <span class="text-brown-500">IP:</span> 553 + <code class="text-brown-700 ml-1 text-xs">{ entry.Details["ip"] }</code> 554 + </div> 555 + } 556 + </div> 557 + if entry.Details["message"] != "" { 558 + <div> 559 + <span class="text-xs font-medium text-brown-500 uppercase tracking-wide">Message</span> 560 + <p class="mt-1 text-sm text-brown-700">{ entry.Details["message"] }</p> 561 + </div> 562 + } 563 + } 542 564 <!-- Target URI with copy button --> 543 565 if entry.TargetURI != "" { 544 566 <div> ··· 601 623 case moderation.AuditActionUnblacklistUser: 602 624 <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800"> 603 625 Unblock User 626 + </span> 627 + case moderation.AuditActionDismissJoinRequest: 628 + <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-800"> 629 + Dismiss Join Request 630 + </span> 631 + case moderation.AuditActionCreateInvite: 632 + <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800"> 633 + Send Invite 604 634 </span> 605 635 default: 606 636 <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-brown-100 text-brown-800">