Download api/v1/mx/handler.go from Mrkiti/Mexa: direct link, hf CLI and curl.
- Browser
- Download file 9.94 kB
-
https://huggingface.co/spaces/Mrkiti/Mexa/resolve/main/api/v1/mx/handler.go
- Command line
-
hf download hf://spaces/Mrkiti/Mexa/api/v1/mx/handler.go
-
curl -L -o handler.go https://huggingface.co/spaces/Mrkiti/Mexa/resolve/main/api/v1/mx/handler.go
9.94 kB
| package mx | |
| import ( | |
| "database/sql" | |
| "net/http" | |
| "time" | |
| "github.com/gin-gonic/gin" | |
| "mexa/api/v1/actiongate" | |
| "mexa/api/v1/constants" | |
| "mexa/api/v1/db" | |
| "mexa/middleware" | |
| ) | |
| func RegisterRoutes(rg *gin.RouterGroup) { | |
| m := rg.Group("/mx", middleware.RequireAuth()) | |
| { | |
| m.POST("/flag", middleware.RequireRole("MX"), Flag) | |
| m.GET("/flags", middleware.RequireRole("MX", "DIR", "XA"), Flags) | |
| m.GET("/reports", middleware.RequireRole("MX"), Reports) | |
| m.GET("/on-behalf", middleware.RequireRole("MX"), OnBehalfGet) | |
| m.POST("/on-behalf", middleware.RequireRole("MX"), OnBehalfPost) | |
| m.GET("/profile", middleware.RequireRole("MX"), Profile) | |
| m.PATCH("/profile", middleware.RequireRole("MX"), UpdateProfile) | |
| m.GET("/history", middleware.RequireRole("MX"), History) | |
| } | |
| } | |
| func Flag(c *gin.Context) { | |
| claims, ok := middleware.GetClaims(c) | |
| if !ok { | |
| c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorised"}) | |
| return | |
| } | |
| var body struct { | |
| FlagType string `json:"flag_type" binding:"required"` | |
| ListingID string `json:"listing_id"` | |
| SellerID string `json:"seller_id"` | |
| Description string `json:"description" binding:"required"` | |
| EvidenceNote string `json:"evidence_note"` | |
| } | |
| if err := c.ShouldBindJSON(&body); err != nil { | |
| c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| ctx := c.Request.Context() | |
| var flagID int | |
| err := db.DB.QueryRowContext(ctx, | |
| `INSERT INTO mx_flags (mx_id, flag_type, listing_id, seller_id, description, evidence_note, status, created_at) | |
| VALUES ($1, $2, NULLIF($3,''), NULLIF($4,''), $5, NULLIF($6,''), 'OPEN', NOW()) | |
| RETURNING id`, | |
| claims.UserID, body.FlagType, body.ListingID, body.SellerID, body.Description, body.EvidenceNote, | |
| ).Scan(&flagID) | |
| if err != nil { | |
| c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to submit flag"}) | |
| return | |
| } | |
| c.JSON(http.StatusCreated, gin.H{"flag_id": flagID, "status": "OPEN"}) | |
| } | |
| func Flags(c *gin.Context) { | |
| ctx := c.Request.Context() | |
| rows, err := db.DB.QueryContext(ctx, | |
| `SELECT id, mx_id, flag_type, COALESCE(listing_id,''), COALESCE(seller_id,''), description, COALESCE(evidence_note,''), status, created_at | |
| FROM mx_flags ORDER BY created_at DESC LIMIT 200`) | |
| if err != nil { | |
| c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load flags"}) | |
| return | |
| } | |
| defer rows.Close() | |
| type Row struct { | |
| ID int `json:"id"` | |
| MxID string `json:"mx_id"` | |
| FlagType string `json:"flag_type"` | |
| ListingID string `json:"listing_id"` | |
| SellerID string `json:"seller_id"` | |
| Description string `json:"description"` | |
| EvidenceNote string `json:"evidence_note"` | |
| Status string `json:"status"` | |
| CreatedAt string `json:"created_at"` | |
| } | |
| results := []Row{} | |
| for rows.Next() { | |
| var r Row | |
| if err := rows.Scan(&r.ID, &r.MxID, &r.FlagType, &r.ListingID, &r.SellerID, &r.Description, &r.EvidenceNote, &r.Status, &r.CreatedAt); err != nil { | |
| continue | |
| } | |
| results = append(results, r) | |
| } | |
| c.JSON(http.StatusOK, results) | |
| } | |
| func Reports(c *gin.Context) { | |
| claims, ok := middleware.GetClaims(c) | |
| if !ok { | |
| c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorised"}) | |
| return | |
| } | |
| ctx := c.Request.Context() | |
| rows, err := db.DB.QueryContext(ctx, | |
| `SELECT id, flag_type, COALESCE(listing_id,''), COALESCE(seller_id,''), description, status, created_at | |
| FROM mx_flags WHERE mx_id = $1 ORDER BY created_at DESC`, | |
| claims.UserID) | |
| if err != nil { | |
| c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load reports"}) | |
| return | |
| } | |
| defer rows.Close() | |
| type Row struct { | |
| ID int `json:"id"` | |
| FlagType string `json:"flag_type"` | |
| ListingID string `json:"listing_id"` | |
| SellerID string `json:"seller_id"` | |
| Description string `json:"description"` | |
| Status string `json:"status"` | |
| CreatedAt string `json:"created_at"` | |
| } | |
| results := []Row{} | |
| for rows.Next() { | |
| var r Row | |
| if err := rows.Scan(&r.ID, &r.FlagType, &r.ListingID, &r.SellerID, &r.Description, &r.Status, &r.CreatedAt); err != nil { | |
| continue | |
| } | |
| results = append(results, r) | |
| } | |
| c.JSON(http.StatusOK, results) | |
| } | |
| func OnBehalfGet(c *gin.Context) { | |
| claims, ok := middleware.GetClaims(c) | |
| if !ok { | |
| c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorised"}) | |
| return | |
| } | |
| ctx := c.Request.Context() | |
| rows, err := db.DB.QueryContext(ctx, | |
| `SELECT id, event_id, initiator_id, on_behalf_of, action_type, status, created_at | |
| FROM custody_consents WHERE initiator_id = $1 ORDER BY created_at DESC LIMIT 100`, | |
| claims.UserID) | |
| if err != nil { | |
| c.JSON(http.StatusOK, []gin.H{}) | |
| return | |
| } | |
| defer rows.Close() | |
| type Row struct { | |
| ID string `json:"id"` | |
| EventID string `json:"event_id"` | |
| InitiatorID string `json:"initiator_id"` | |
| OnBehalfOf string `json:"on_behalf_of"` | |
| ActionType string `json:"action_type"` | |
| Status string `json:"status"` | |
| CreatedAt time.Time `json:"created_at"` | |
| } | |
| results := []Row{} | |
| for rows.Next() { | |
| var r Row | |
| if err := rows.Scan(&r.ID, &r.EventID, &r.InitiatorID, &r.OnBehalfOf, &r.ActionType, &r.Status, &r.CreatedAt); err != nil { | |
| continue | |
| } | |
| results = append(results, r) | |
| } | |
| c.JSON(http.StatusOK, results) | |
| } | |
| func OnBehalfPost(c *gin.Context) { | |
| claims, ok := middleware.GetClaims(c) | |
| if !ok { | |
| c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorised"}) | |
| return | |
| } | |
| var body struct { | |
| EventID string `json:"event_id" binding:"required"` | |
| OnBehalfOf string `json:"on_behalf_of" binding:"required"` | |
| ActionType string `json:"action_type" binding:"required"` | |
| } | |
| if err := c.ShouldBindJSON(&body); err != nil { | |
| c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | |
| return | |
| } | |
| // Rule: No Self-Verification -- carried over from the retired on_behalf_of.go | |
| // design (custody_consents never got this check when it superseded it). | |
| if claims.UserID == body.OnBehalfOf { | |
| c.JSON(http.StatusForbidden, gin.H{"error": "cannot act on behalf of self"}) | |
| return | |
| } | |
| ctx := c.Request.Context() | |
| key, err := actiongate.IdempotencyKey("mx-obo", gin.H{"event_id": body.EventID, "initiator_id": claims.UserID, "on_behalf_of": body.OnBehalfOf, "action_type": body.ActionType}) | |
| if err != nil { | |
| c.JSON(http.StatusBadRequest, gin.H{"error": "invalid action payload"}) | |
| return | |
| } | |
| gate, err := actiongate.CreateOrGet(ctx, actiongate.Request{ | |
| ActionType: body.ActionType, InitiatorID: claims.UserID, SubjectID: body.OnBehalfOf, | |
| ResourceType: "event", ResourceID: body.EventID, IdempotencyKey: key, | |
| Payload: gin.H{"event_id": body.EventID, "action_type": body.ActionType}, | |
| PolicyVersion: "v1", Purpose: "MX action on behalf of the subject", | |
| }) | |
| if err != nil { | |
| c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create action gate"}) | |
| return | |
| } | |
| var consentID string | |
| if !gate.Created { | |
| err = db.DB.QueryRowContext(ctx, `SELECT id::text FROM custody_consents WHERE action_gate_request_id = $1`, gate.ID).Scan(&consentID) | |
| } else { | |
| err = db.DB.QueryRowContext(ctx, | |
| `INSERT INTO custody_consents (event_id, initiator_id, on_behalf_of, action_type, status, payload, action_gate_request_id, created_at, updated_at) | |
| VALUES ($1, $2, $3, $4, $5, '{}', $6, NOW(), NOW()) RETURNING id::text`, | |
| body.EventID, claims.UserID, body.OnBehalfOf, body.ActionType, constants.CustodyStatusPending, gate.ID).Scan(&consentID) | |
| } | |
| if err != nil { | |
| c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to log consent"}) | |
| return | |
| } | |
| c.JSON(http.StatusCreated, gin.H{"message": "consent logged", "consent_id": consentID, "status": constants.CustodyStatusPending}) | |
| } | |
| func Profile(c *gin.Context) { | |
| claims, ok := middleware.GetClaims(c) | |
| if !ok { | |
| c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorised"}) | |
| return | |
| } | |
| ctx := c.Request.Context() | |
| var myMezaID, role, kycTier string | |
| var createdAt time.Time | |
| err := db.DB.QueryRowContext(ctx, | |
| `SELECT mymeza_id, role, kyc_tier, created_at FROM users WHERE mymeza_id = $1`, | |
| claims.UserID).Scan(&myMezaID, &role, &kycTier, &createdAt) | |
| if err == sql.ErrNoRows { | |
| c.JSON(http.StatusNotFound, gin.H{"error": "profile not found"}) | |
| return | |
| } | |
| if err != nil { | |
| c.JSON(http.StatusInternalServerError, gin.H{"error": "profile lookup failed"}) | |
| return | |
| } | |
| c.JSON(http.StatusOK, gin.H{ | |
| "mymeza_id": myMezaID, | |
| "role": role, | |
| "kyc_tier": kycTier, | |
| "created_at": createdAt, | |
| }) | |
| } | |
| func UpdateProfile(c *gin.Context) { | |
| claims, ok := middleware.GetClaims(c) | |
| if !ok { | |
| c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorised"}) | |
| return | |
| } | |
| ctx := c.Request.Context() | |
| _, err := db.DB.ExecContext(ctx, | |
| `UPDATE users SET updated_at = NOW() WHERE mymeza_id = $1`, | |
| claims.UserID) | |
| if err != nil { | |
| c.JSON(http.StatusInternalServerError, gin.H{"error": "profile update failed"}) | |
| return | |
| } | |
| middleware.WriteAuditLog(middleware.AuditEntry{ | |
| EventType: "MX_PROFILE_UPDATE", | |
| ActorMyMezaID: claims.UserID, | |
| ActorRole: claims.Role, | |
| Description: "MX profile updated", | |
| }) | |
| c.JSON(http.StatusOK, gin.H{"message": "profile updated"}) | |
| } | |
| func History(c *gin.Context) { | |
| claims, ok := middleware.GetClaims(c) | |
| if !ok { | |
| c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorised"}) | |
| return | |
| } | |
| ctx := c.Request.Context() | |
| rows, err := db.DB.QueryContext(ctx, | |
| `SELECT id, event_id, action, COALESCE(note,''), created_at | |
| FROM events_log WHERE actor_mymeza_id = $1 ORDER BY created_at DESC LIMIT 100`, | |
| claims.UserID) | |
| if err != nil { | |
| c.JSON(http.StatusOK, []gin.H{}) | |
| return | |
| } | |
| defer rows.Close() | |
| type Row struct { | |
| ID string `json:"id"` | |
| EventID string `json:"event_id"` | |
| Action string `json:"action"` | |
| Note string `json:"note"` | |
| CreatedAt time.Time `json:"created_at"` | |
| } | |
| results := []Row{} | |
| for rows.Next() { | |
| var r Row | |
| if err := rows.Scan(&r.ID, &r.EventID, &r.Action, &r.Note, &r.CreatedAt); err != nil { | |
| continue | |
| } | |
| results = append(results, r) | |
| } | |
| c.JSON(http.StatusOK, results) | |
| } | |