diff --git a/cmd/api/wire.go b/cmd/api/wire.go index cc7b6f09..3c319afc 100644 --- a/cmd/api/wire.go +++ b/cmd/api/wire.go @@ -54,7 +54,7 @@ var messageServiceProvider = wire.NewSet(message.NewService, message.NewReposito var associationServiceProvider = wire.NewSet(association.NewService, association.NewRepository, SetupTimelineService, SetupMessageService, SetupKeyService, SetupSchemaService) // Lv6 -var storeServiceProvider = wire.NewSet(store.NewService, SetupKeyService, SetupMessageService) +var storeServiceProvider = wire.NewSet(store.NewService, SetupKeyService, SetupMessageService, SetupAssociationService, SetupProfileService) func SetupJwtService(rdb *redis.Client) jwt.Service { wire.Build(jwtServiceProvider) diff --git a/x/profile/handler.go b/x/profile/handler.go index a4fad96b..f99f4e90 100644 --- a/x/profile/handler.go +++ b/x/profile/handler.go @@ -18,8 +18,6 @@ var tracer = otel.Tracer("profile") type Handler interface { Get(c echo.Context) error Query(c echo.Context) error - Put(c echo.Context) error - Delete(c echo.Context) error } type handler struct { @@ -82,52 +80,3 @@ func (h handler) Query(c echo.Context) error { return c.JSON(http.StatusOK, echo.Map{"status": "ok", "content": profiles}) } - -// Put updates a profile -func (h handler) Put(c echo.Context) error { - ctx, span := tracer.Start(c.Request().Context(), "HandlerPut") - defer span.End() - - var request postRequest - err := c.Bind(&request) - if err != nil { - return c.JSON(http.StatusBadRequest, echo.Map{"error": "Invalid request", "message": err.Error()}) - } - - updated, err := h.service.Put(ctx, request.SignedObject, request.Signature, request.ID) - if err != nil { - return c.JSON(http.StatusBadRequest, echo.Map{"error": "Invalid request", "message": err.Error()}) - } - return c.JSON(http.StatusOK, echo.Map{"status": "ok", "content": updated}) -} - -func (h handler) Delete(c echo.Context) error { - ctx, span := tracer.Start(c.Request().Context(), "HandlerDelete") - defer span.End() - - id := c.Param("id") - if id == "" { - return c.JSON(http.StatusBadRequest, echo.Map{"error": "Invalid request", "message": "id is required"}) - } - - requester, ok := c.Get(core.RequesterIdCtxKey).(string) - if !ok { - return c.JSON(http.StatusForbidden, echo.Map{"status": "error", "message": "requester not found"}) - } - - target, err := h.service.Get(ctx, id) - if err != nil { - return c.JSON(http.StatusNotFound, echo.Map{"error": "Profile not found"}) - } - - if target.Author != requester { - return c.JSON(http.StatusForbidden, echo.Map{"error": "you are not authorized to perform this action"}) - } - - deleted, err := h.service.Delete(ctx, id) - if err != nil { - return c.JSON(http.StatusBadRequest, echo.Map{"error": "Invalid request", "message": err.Error()}) - } - - return c.JSON(http.StatusOK, echo.Map{"status": "ok", "content": deleted}) -} diff --git a/x/profile/model.go b/x/profile/model.go deleted file mode 100644 index a4d3b94b..00000000 --- a/x/profile/model.go +++ /dev/null @@ -1,20 +0,0 @@ -package profile - -import ( - "time" -) - -type postRequest struct { - SignedObject string `json:"signedObject"` - Signature string `json:"signature"` - ID string `json:"id"` -} - -type signedObject struct { - Signer string `json:"signer"` - Type string `json:"type"` - Schema string `json:"schema"` - Body interface{} `json:"body"` - Meta interface{} `json:"meta"` - SignedAt time.Time `json:"signedAt"` -} diff --git a/x/profile/service.go b/x/profile/service.go index eb31e594..d5ead502 100644 --- a/x/profile/service.go +++ b/x/profile/service.go @@ -3,19 +3,22 @@ package profile import ( "context" "encoding/json" + "errors" "github.com/totegamma/concurrent/x/core" "github.com/totegamma/concurrent/x/key" ) // Service is the interface for profile service type Service interface { - Put(ctx context.Context, objectStr string, signature string, id string) (core.Profile, error) + Create(ctx context.Context, objectStr string, signature string) (core.Profile, error) + Update(ctx context.Context, objectStr string, signature string) (core.Profile, error) + Delete(ctx context.Context, document string) (core.Profile, error) + Count(ctx context.Context) (int64, error) Get(ctx context.Context, id string) (core.Profile, error) GetByAuthorAndSchema(ctx context.Context, owner string, schema string) ([]core.Profile, error) GetByAuthor(ctx context.Context, owner string) ([]core.Profile, error) GetBySchema(ctx context.Context, schema string) ([]core.Profile, error) - Delete(ctx context.Context, id string) (core.Profile, error) } type service struct { @@ -61,11 +64,11 @@ func (s *service) GetBySchema(ctx context.Context, schema string) ([]core.Profil } // PutProfile creates new profile if the signature is valid -func (s *service) Put(ctx context.Context, objectStr string, signature string, id string) (core.Profile, error) { +func (s *service) Create(ctx context.Context, objectStr string, signature string) (core.Profile, error) { ctx, span := tracer.Start(ctx, "ServicePutProfile") defer span.End() - var object signedObject + var object core.CreateProfile[any] err := json.Unmarshal([]byte(objectStr), &object) if err != nil { span.RecordError(err) @@ -79,7 +82,42 @@ func (s *service) Put(ctx context.Context, objectStr string, signature string, i } profile := core.Profile{ - ID: id, + ID: object.ID, + Author: object.Signer, + Schema: object.Schema, + Payload: objectStr, + Signature: signature, + } + + err = s.repo.Upsert(ctx, profile) + if err != nil { + span.RecordError(err) + return core.Profile{}, err + } + + return profile, nil +} + +// PutProfile creates new profile if the signature is valid +func (s *service) Update(ctx context.Context, objectStr string, signature string) (core.Profile, error) { + ctx, span := tracer.Start(ctx, "ServicePutProfile") + defer span.End() + + var object core.UpdateProfile[any] + err := json.Unmarshal([]byte(objectStr), &object) + if err != nil { + span.RecordError(err) + return core.Profile{}, err + } + + err = s.key.ValidateSignedObject(ctx, objectStr, signature) + if err != nil { + span.RecordError(err) + return core.Profile{}, err + } + + profile := core.Profile{ + ID: object.ID, Author: object.Signer, Schema: object.Schema, Payload: objectStr, @@ -96,11 +134,30 @@ func (s *service) Put(ctx context.Context, objectStr string, signature string, i } // Delete deletes profile -func (s *service) Delete(ctx context.Context, id string) (core.Profile, error) { +func (s *service) Delete(ctx context.Context, documentStr string) (core.Profile, error) { ctx, span := tracer.Start(ctx, "ServiceDelete") defer span.End() - return s.repo.Delete(ctx, id) + var document core.DeleteProfile + err := json.Unmarshal([]byte(documentStr), &document) + if err != nil { + span.RecordError(err) + return core.Profile{}, err + } + + deleteTarget, err := s.Get(ctx, document.Body.TargetID) + if err != nil { + span.RecordError(err) + return core.Profile{}, err + } + + if deleteTarget.Author != document.Signer { + err = errors.New("unauthorized") + span.RecordError(err) + return core.Profile{}, err + } + + return s.repo.Delete(ctx, document.Body.TargetID) } func (s *service) Get(ctx context.Context, id string) (core.Profile, error) { diff --git a/x/store/service.go b/x/store/service.go index 72bbf39f..118d1f10 100644 --- a/x/store/service.go +++ b/x/store/service.go @@ -9,6 +9,7 @@ import ( "github.com/totegamma/concurrent/x/core" "github.com/totegamma/concurrent/x/key" "github.com/totegamma/concurrent/x/message" + "github.com/totegamma/concurrent/x/profile" ) type Service interface { @@ -19,17 +20,20 @@ type service struct { key key.Service message message.Service association association.Service + profile profile.Service } func NewService( key key.Service, message message.Service, association association.Service, + profile profile.Service, ) Service { return &service{ key: key, message: message, association: association, + profile: profile, } } @@ -58,6 +62,12 @@ func (s *service) Commit(ctx context.Context, document string, signature string) return s.association.Create(ctx, document, signature) case "association.delete": return s.association.Delete(ctx, document) + case "profile.create": + return s.profile.Create(ctx, document, signature) + case "profile.update": + return s.profile.Update(ctx, document, signature) + case "profile.delete": + return s.profile.Delete(ctx, document) } return nil, fmt.Errorf("unknown document type: %s", base.Type)