package handler import ( "errors" "net/http" "time" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/yuxingu/digital-psychology/apps/api/internal/middleware" "github.com/yuxingu/digital-psychology/apps/api/internal/service/profile" "github.com/yuxingu/digital-psychology/apps/api/pkg/response" ) // ProfileHandler exposes personal archive APIs. type ProfileHandler struct { Svc *profile.Service } // Register mounts profile routes (requires device auth on group). func (h *ProfileHandler) Register(rg *gin.RouterGroup) { rg.GET("/profiles", h.List) rg.POST("/profiles", h.Create) rg.PATCH("/profiles/:id", h.Update) rg.DELETE("/profiles/:id", h.Delete) } type createProfileReq struct { Relation string `json:"relation" binding:"required"` DisplayName string `json:"display_name"` BirthDate string `json:"birth_date" binding:"required"` RelationType *string `json:"relation_type"` BirthTime *string `json:"birth_time"` BirthPlace *string `json:"birth_place"` } // Create handles POST /profiles. func (h *ProfileHandler) Create(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } var req createProfileReq if err := c.ShouldBindJSON(&req); err != nil { response.Fail(c, http.StatusBadRequest, 10000, "invalid request") return } birth, err := time.Parse("2006-01-02", req.BirthDate) if err != nil { response.Fail(c, http.StatusBadRequest, 10000, "birth_date must be YYYY-MM-DD") return } p, err := h.Svc.Create(c.Request.Context(), userID, profile.CreateInput{ Relation: req.Relation, DisplayName: req.DisplayName, BirthDate: birth, RelationType: req.RelationType, BirthTime: req.BirthTime, BirthPlace: req.BirthPlace, }) if err != nil { if errors.Is(err, profile.ErrSelfExists) { response.Fail(c, http.StatusConflict, 40902, err.Error()) return } response.Fail(c, http.StatusBadRequest, 30001, err.Error()) return } response.OK(c, p) } // List handles GET /profiles. func (h *ProfileHandler) List(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } list, err := h.Svc.List(c.Request.Context(), userID) if err != nil { response.Fail(c, http.StatusInternalServerError, 50002, "list failed") return } response.OK(c, gin.H{"items": list}) } // Update handles PATCH /profiles/:id. func (h *ProfileHandler) Update(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } pid, err := uuid.Parse(c.Param("id")) if err != nil { response.Fail(c, http.StatusBadRequest, 10000, "invalid id") return } var req struct { DisplayName string `json:"display_name"` BirthDate string `json:"birth_date"` RelationType *string `json:"relation_type"` BirthTime *string `json:"birth_time"` BirthPlace *string `json:"birth_place"` GeoLat *float64 `json:"geo_lat"` GeoLng *float64 `json:"geo_lng"` GeoVisible *bool `json:"geo_visible"` } if err := c.ShouldBindJSON(&req); err != nil { response.Fail(c, http.StatusBadRequest, 10000, "invalid request") return } var birth time.Time if req.BirthDate != "" { birth, err = time.Parse("2006-01-02", req.BirthDate) if err != nil { response.Fail(c, http.StatusBadRequest, 10000, "birth_date must be YYYY-MM-DD") return } } p, err := h.Svc.Update(c.Request.Context(), userID, pid, profile.UpdateInput{ DisplayName: req.DisplayName, BirthDate: birth, RelationType: req.RelationType, BirthTime: req.BirthTime, BirthPlace: req.BirthPlace, GeoLat: req.GeoLat, GeoLng: req.GeoLng, GeoVisible: req.GeoVisible, }) if err != nil { response.Fail(c, http.StatusNotFound, 40401, err.Error()) return } response.OK(c, p) } // Delete handles DELETE /profiles/:id. func (h *ProfileHandler) Delete(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } pid, err := uuid.Parse(c.Param("id")) if err != nil { response.Fail(c, http.StatusBadRequest, 10000, "invalid id") return } if err := h.Svc.Delete(c.Request.Context(), userID, pid); err != nil { response.Fail(c, http.StatusNotFound, 40401, err.Error()) return } response.OK(c, gin.H{"deleted": true}) }