cart and checkout
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.k6n.net/mats/go-cart-actor/pkg/profile"
|
||||
@@ -15,12 +16,18 @@ import (
|
||||
|
||||
// CustomerServer is the UCP REST adapter over the profile grain pool.
|
||||
type CustomerServer struct {
|
||||
applier ProfileApplier
|
||||
applier ProfileApplier
|
||||
deleter CredentialDeleter
|
||||
auditLogPath string
|
||||
}
|
||||
|
||||
// NewCustomerServer builds a UCP REST adapter over a profile grain pool.
|
||||
func NewCustomerServer(applier ProfileApplier) *CustomerServer {
|
||||
return &CustomerServer{applier: applier}
|
||||
func NewCustomerServer(applier ProfileApplier, auditLogPath string, deleter ...CredentialDeleter) *CustomerServer {
|
||||
var del CredentialDeleter
|
||||
if len(deleter) > 0 {
|
||||
del = deleter[0]
|
||||
}
|
||||
return &CustomerServer{applier: applier, deleter: del, auditLogPath: auditLogPath}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -276,6 +283,59 @@ func (s *CustomerServer) handleRemoveAddress(w http.ResponseWriter, r *http.Requ
|
||||
writeJSON(w, http.StatusOK, customerGrainToResponse(r.PathValue("id"), g))
|
||||
}
|
||||
|
||||
// handleDeleteCustomer handles DELETE /customers/{id}.
|
||||
func (s *CustomerServer) handleDeleteCustomer(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := parseCustomerID(r)
|
||||
if !ok {
|
||||
writeError(w, http.StatusBadRequest, "invalid customer id")
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Fetch current profile grain to get the email address.
|
||||
g, err := s.applier.Get(r.Context(), id)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, "customer not found")
|
||||
return
|
||||
}
|
||||
|
||||
email := g.Email
|
||||
|
||||
// 2. Apply the AnonymizeProfile mutation to clear all PII.
|
||||
msg := &messages.AnonymizeProfile{}
|
||||
if _, err := s.applier.Apply(r.Context(), id, msg); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "failed to anonymize customer: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 3. Delete the credentials from the auth store if we have a deleter.
|
||||
if email != "" && s.deleter != nil {
|
||||
if _, err := s.deleter.Delete(r.Context(), email); err != nil {
|
||||
fmt.Printf("ucp: failed to delete credentials for email %s: %v\n", email, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Log audit trail to file (GDPR requirement, append-only, readable, no PII)
|
||||
if s.auditLogPath != "" {
|
||||
logLine := fmt.Sprintf("[%s] ACTION=GDPR_ERASURE PROFILE_ID=%s STATUS=SUCCESS\n",
|
||||
time.Now().UTC().Format(time.RFC3339), r.PathValue("id"))
|
||||
|
||||
// Import "os" package is handled or we can open it directly.
|
||||
// Since we need to write to the file, let's open it in append-only mode.
|
||||
// To ensure directory exists, we write to the file.
|
||||
if f, err := os.OpenFile(s.auditLogPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644); err == nil {
|
||||
_, _ = f.WriteString(logLine)
|
||||
_ = f.Close()
|
||||
} else {
|
||||
fmt.Printf("ucp: failed to open audit log file %s: %v\n", s.auditLogPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Log audit trail to stdout (GDPR requirement)
|
||||
fmt.Printf("AUDIT: GDPR right to erasure executed for customer profile id %d\n", id)
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Identity linking helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user