From 3402390b6998cbb7e08f24b41aa797faf5ea1f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Mon, 9 Mar 2026 11:22:27 +0100 Subject: [PATCH] Add endpoints for following/unfollowing drive items to the graph API --- services/graph/pkg/service/v0/follow.go | 129 +++++++++++++++++++++++ services/graph/pkg/service/v0/service.go | 2 + 2 files changed, 131 insertions(+) create mode 100644 services/graph/pkg/service/v0/follow.go diff --git a/services/graph/pkg/service/v0/follow.go b/services/graph/pkg/service/v0/follow.go new file mode 100644 index 0000000000..3372c56fa2 --- /dev/null +++ b/services/graph/pkg/service/v0/follow.go @@ -0,0 +1,129 @@ +package svc + +import ( + "net/http" + + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode" + revactx "github.com/opencloud-eu/reva/v2/pkg/ctx" +) + +// FollowDriveItem marks a drive item as favorite. +func (g Graph) FollowDriveItem(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + itemID, err := parseIDParam(r, "itemID") + if err != nil { + g.logger.Debug().Err(err).Msg("could not parse itemID") + return + } + + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + g.logger.Error().Err(err).Msg("could not select next gateway client") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusServiceUnavailable, "could not select next gateway client") + return + } + + ref := &provider.Reference{ + ResourceId: &itemID, + } + + u, ok := revactx.ContextGetUser(ctx) + if !ok { + errorcode.GeneralException.Render(w, r, http.StatusUnauthorized, "User not found in context") + return + } + + statReq := &provider.StatRequest{ + Ref: ref, + } + statRes, err := gatewayClient.Stat(ctx, statReq) + if err != nil { + g.logger.Error().Err(err).Msg("could not stat resource") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not stat resource") + return + } + switch statRes.GetStatus().GetCode() { + case rpc.Code_CODE_OK: + // continue + case rpc.Code_CODE_NOT_FOUND: + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "resource not found") + return + default: + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not stat resource") + return + } + + req := &provider.AddFavoriteRequest{ + Ref: ref, + UserId: u.Id, + } + + res, err := gatewayClient.AddFavorite(ctx, req) + if err != nil { + g.logger.Error().Err(err).Msg("could not add favorite") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not add favorite") + return + } + + if res.Status.Code != rpc.Code_CODE_OK { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not add favorite") + return + } + + w.WriteHeader(http.StatusCreated) +} + +// UnfollowDriveItem unmarks a drive item as favorite. +func (g Graph) UnfollowDriveItem(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + itemID, err := parseIDParam(r, "itemID") + if err != nil { + g.logger.Debug().Err(err).Msg("could not parse itemID") + return + } + + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + g.logger.Error().Err(err).Msg("could not select next gateway client") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusServiceUnavailable, "could not select next gateway client") + return + } + + ref := &provider.Reference{ + ResourceId: &itemID, + } + + u, ok := revactx.ContextGetUser(ctx) + if !ok { + errorcode.GeneralException.Render(w, r, http.StatusUnauthorized, "User not found in context") + return + } + + req := &provider.RemoveFavoriteRequest{ + Ref: ref, + UserId: u.Id, + } + + res, err := gatewayClient.RemoveFavorite(ctx, req) + if err != nil { + g.logger.Error().Err(err).Msg("could not remove favorite") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not remove favorite") + return + } + + switch res.Status.Code { + case rpc.Code_CODE_OK: + // continue + case rpc.Code_CODE_NOT_FOUND: + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "favorite not found") + return + default: + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not remove favorite") + return + } + + w.WriteHeader(http.StatusNoContent) +} diff --git a/services/graph/pkg/service/v0/service.go b/services/graph/pkg/service/v0/service.go index f130de6dc1..ba87c73164 100644 --- a/services/graph/pkg/service/v0/service.go +++ b/services/graph/pkg/service/v0/service.go @@ -309,6 +309,8 @@ func NewService(opts ...Option) (Graph, error) { //nolint:maintidx r.Route("/drive", func(r chi.Router) { r.Get("/", svc.GetUserDrive) r.Get("/root/children", svc.GetRootDriveChildren) + r.Post("/items/{itemID}/follow", svc.FollowDriveItem) + r.Delete("/following/{itemID}", svc.UnfollowDriveItem) }) r.Get("/drives", svc.GetDrives(APIVersion_1)) r.Post("/changePassword", svc.ChangeOwnPassword)