mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-04-30 12:13:20 -04:00
Merge pull request #2978 from owncloud/add-get-single-drive
add GetSingleDrive handler
This commit is contained in:
5
changelog/unreleased/single-space-enpoint.md
Normal file
5
changelog/unreleased/single-space-enpoint.md
Normal file
@@ -0,0 +1,5 @@
|
||||
Enhancement: Add endpoint to retrieve a single space
|
||||
|
||||
We added the endpoint ``/drives/{driveID}`` to get a single space by id from the server.
|
||||
|
||||
https://github.com/owncloud/ocis/pull/2978
|
||||
@@ -43,46 +43,20 @@ func (g Graph) GetDrives(w http.ResponseWriter, r *http.Request) {
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
g.logger.Info().Msg("Calling GetDrives")
|
||||
g.logger.Info().Interface("query", r.URL.Query()).Msg("Calling GetDrives")
|
||||
ctx := r.Context()
|
||||
|
||||
client := g.GetGatewayClient()
|
||||
|
||||
permissions := make(map[string]struct{}, 1)
|
||||
s := sproto.NewPermissionService("com.owncloud.api.settings", grpc.DefaultClient)
|
||||
|
||||
_, err = s.GetPermissionByID(ctx, &sproto.GetPermissionByIDRequest{
|
||||
PermissionId: settingsSvc.ListAllSpacesPermissionID,
|
||||
})
|
||||
|
||||
// No error means the user has the permission
|
||||
if err == nil {
|
||||
permissions[settingsSvc.ListAllSpacesPermissionName] = struct{}{}
|
||||
}
|
||||
value, err := json.Marshal(permissions)
|
||||
if err != nil {
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
filters, err := generateCs3Filters(odataReq)
|
||||
if err != nil {
|
||||
g.logger.Err(err).Interface("query", r.URL.Query()).Msg("query error")
|
||||
errorcode.NotSupported.Render(w, r, http.StatusNotImplemented, err.Error())
|
||||
return
|
||||
}
|
||||
res, err := client.ListStorageSpaces(ctx, &storageprovider.ListStorageSpacesRequest{
|
||||
Opaque: &types.Opaque{Map: map[string]*types.OpaqueEntry{
|
||||
"permissions": {
|
||||
Decoder: "json",
|
||||
Value: value,
|
||||
},
|
||||
}},
|
||||
Filters: filters,
|
||||
})
|
||||
res, err := g.ListStorageSpacesWithFilters(ctx, filters)
|
||||
switch {
|
||||
case err != nil:
|
||||
g.logger.Error().Err(err).Msg("error sending list storage spaces grpc request")
|
||||
errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
g.logger.Error().Err(err).Msg(ListStorageSpacesTransportErr)
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
case res.Status.Code != cs3rpc.Code_CODE_OK:
|
||||
if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND {
|
||||
@@ -91,7 +65,7 @@ func (g Graph) GetDrives(w http.ResponseWriter, r *http.Request) {
|
||||
render.JSON(w, r, &listResponse{})
|
||||
return
|
||||
}
|
||||
g.logger.Error().Err(err).Msg("error sending list storage spaces grpc request")
|
||||
g.logger.Error().Err(err).Msg(ListStorageSpacesReturnsErr)
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message)
|
||||
return
|
||||
}
|
||||
@@ -113,6 +87,75 @@ func (g Graph) GetDrives(w http.ResponseWriter, r *http.Request) {
|
||||
render.JSON(w, r, &listResponse{Value: files})
|
||||
}
|
||||
|
||||
// GetSingleDrive does a lookup of a single space by spaceId
|
||||
func (g Graph) GetSingleDrive(w http.ResponseWriter, r *http.Request) {
|
||||
driveID := chi.URLParam(r, "driveID")
|
||||
if driveID == "" {
|
||||
err := fmt.Errorf("no valid space id retrieved")
|
||||
g.logger.Err(err)
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
g.logger.Info().Str("driveID", driveID).Msg("Calling GetSingleDrive")
|
||||
ctx := r.Context()
|
||||
|
||||
filters := []*storageprovider.ListStorageSpacesRequest_Filter{
|
||||
{
|
||||
Type: storageprovider.ListStorageSpacesRequest_Filter_TYPE_ID,
|
||||
Term: &storageprovider.ListStorageSpacesRequest_Filter_Id{
|
||||
Id: &storageprovider.StorageSpaceId{
|
||||
OpaqueId: driveID,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := g.ListStorageSpacesWithFilters(ctx, filters)
|
||||
switch {
|
||||
case err != nil:
|
||||
g.logger.Error().Err(err).Msg(ListStorageSpacesTransportErr)
|
||||
errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
case res.Status.Code != cs3rpc.Code_CODE_OK:
|
||||
if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND {
|
||||
// the client is doing a lookup for a specific space, therefore we need to return
|
||||
// not found to the caller
|
||||
g.logger.Error().Str("driveID", driveID).Msg(fmt.Sprintf(NoSpaceFoundMessage, driveID))
|
||||
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, fmt.Sprintf(NoSpaceFoundMessage, driveID))
|
||||
return
|
||||
}
|
||||
g.logger.Error().Err(err).Msg(ListStorageSpacesReturnsErr)
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message)
|
||||
return
|
||||
}
|
||||
|
||||
wdu, err := url.Parse(g.config.Spaces.WebDavBase + g.config.Spaces.WebDavPath)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("error parsing url")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
spaces, err := g.formatDrives(ctx, wdu, res.StorageSpaces)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("error encoding response")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
switch num := len(spaces); {
|
||||
case num == 0:
|
||||
g.logger.Error().Str("driveID", driveID).Msg("no space found")
|
||||
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, fmt.Sprintf(NoSpaceFoundMessage, driveID))
|
||||
return
|
||||
case num == 1:
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, spaces[0])
|
||||
default:
|
||||
g.logger.Error().Int("number", num).Msg("expected to find a single space but found more")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "expected to find a single space but found more")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// CreateDrive creates a storage drive (space).
|
||||
func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
|
||||
us, ok := ctxpkg.ContextGetUser(r.Context())
|
||||
@@ -340,6 +383,38 @@ func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, mds []*storag
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
// ListStorageSpacesWithFilters List Storage Spaces using filters
|
||||
func (g Graph) ListStorageSpacesWithFilters(ctx context.Context, filters []*storageprovider.ListStorageSpacesRequest_Filter) (*storageprovider.ListStorageSpacesResponse, error) {
|
||||
client := g.GetGatewayClient()
|
||||
|
||||
permissions := make(map[string]struct{}, 1)
|
||||
s := sproto.NewPermissionService("com.owncloud.api.settings", grpc.DefaultClient)
|
||||
|
||||
_, err := s.GetPermissionByID(ctx, &sproto.GetPermissionByIDRequest{
|
||||
PermissionId: settingsSvc.ListAllSpacesPermissionID,
|
||||
})
|
||||
|
||||
// No error means the user has the permission
|
||||
if err == nil {
|
||||
permissions[settingsSvc.ListAllSpacesPermissionName] = struct{}{}
|
||||
}
|
||||
value, err := json.Marshal(permissions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := client.ListStorageSpaces(ctx, &storageprovider.ListStorageSpacesRequest{
|
||||
Opaque: &types.Opaque{Map: map[string]*types.OpaqueEntry{
|
||||
"permissions": {
|
||||
Decoder: "json",
|
||||
Value: value,
|
||||
},
|
||||
}},
|
||||
Filters: filters,
|
||||
})
|
||||
return res, err
|
||||
}
|
||||
|
||||
func cs3StorageSpaceToDrive(baseURL *url.URL, space *storageprovider.StorageSpace) (*libregraph.Drive, error) {
|
||||
rootID := space.Root.StorageId + "!" + space.Root.OpaqueId
|
||||
if space.Root.StorageId == space.Root.OpaqueId {
|
||||
|
||||
@@ -85,3 +85,9 @@ func (g Graph) GetHTTPClient() HTTPClient {
|
||||
type listResponse struct {
|
||||
Value interface{} `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
NoSpaceFoundMessage = "space with id `%s` not found"
|
||||
ListStorageSpacesTransportErr = "transport error sending list storage spaces grpc request"
|
||||
ListStorageSpacesReturnsErr = "list storage spaces grpc request returns an errorcode in the response"
|
||||
)
|
||||
|
||||
@@ -117,6 +117,7 @@ func NewService(opts ...Option) Service {
|
||||
r.Post("/", svc.CreateDrive)
|
||||
r.Route("/{driveID}", func(r chi.Router) {
|
||||
r.Patch("/", svc.UpdateDrive)
|
||||
r.Get("/", svc.GetSingleDrive)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -79,3 +79,35 @@ Feature: List and create spaces
|
||||
| name | Project Venus |
|
||||
| quota@@@total | 2000 |
|
||||
| root@@@webDavUrl | %base_url%/dav/spaces/%space_id% |
|
||||
|
||||
Scenario: A user can list his personal space via multiple endpoints
|
||||
When user "Alice" lists all available spaces via the GraphApi with query "$filter=driveType eq 'personal'"
|
||||
Then the json responded should contain a space "Alice Hansen" with these key and value pairs:
|
||||
| key | value |
|
||||
| driveType | personal |
|
||||
| name | Alice Hansen |
|
||||
| root@@@webDavUrl | %base_url%/dav/spaces/%space_id% |
|
||||
When user "Alice" looks up the single space "Alice Hansen" via the GraphApi by using its id
|
||||
Then the json responded should contain a space "Alice Hansen" with these key and value pairs:
|
||||
| key | value |
|
||||
| driveType | personal |
|
||||
| name | Alice Hansen |
|
||||
| root@@@webDavUrl | %base_url%/dav/spaces/%space_id% |
|
||||
|
||||
Scenario: A user can list his created spaces via multiple endpoints
|
||||
Given the administrator has given "Alice" the role "Admin" using the settings api
|
||||
When user "Alice" creates a space "Project Venus" of type "project" with quota "2000" using the GraphApi
|
||||
Then the HTTP status code should be "201"
|
||||
And the json responded should contain a space "Project Venus" with these key and value pairs:
|
||||
| key | value |
|
||||
| driveType | project |
|
||||
| name | Project Venus |
|
||||
| quota@@@total | 2000 |
|
||||
| root@@@webDavUrl | %base_url%/dav/spaces/%space_id% |
|
||||
When user "Alice" looks up the single space "Project Venus" via the GraphApi by using its id
|
||||
Then the json responded should contain a space "Project Venus" with these key and value pairs:
|
||||
| key | value |
|
||||
| driveType | project |
|
||||
| name | Project Venus |
|
||||
| quota@@@total | 2000 |
|
||||
| root@@@webDavUrl | %base_url%/dav/spaces/%space_id% |
|
||||
|
||||
@@ -232,7 +232,7 @@ class SpacesContext implements Context {
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Graph List Spaces Request
|
||||
* Send Graph List My Spaces Request
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
@@ -245,7 +245,7 @@ class SpacesContext implements Context {
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function listSpacesRequest(
|
||||
public function listMySpacesRequest(
|
||||
string $user,
|
||||
string $password,
|
||||
string $urlArguments = '',
|
||||
@@ -258,6 +258,34 @@ class SpacesContext implements Context {
|
||||
return HttpRequestHelper::get($fullUrl, $xRequestId, $user, $password, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Graph List Single Space Request
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $urlArguments
|
||||
* @param string $xRequestId
|
||||
* @param array $body
|
||||
* @param array $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function listSingleSpaceRequest(
|
||||
string $user,
|
||||
string $password,
|
||||
string $spaceId,
|
||||
string $urlArguments = '',
|
||||
string $xRequestId = '',
|
||||
array $body = [],
|
||||
array $headers = []
|
||||
): ResponseInterface {
|
||||
$fullUrl = $this->baseUrl . "/graph/v1.0/drives/" . $spaceId . "/" . $urlArguments;
|
||||
|
||||
return HttpRequestHelper::get($fullUrl, $xRequestId, $user, $password, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Graph Create Space Request
|
||||
*
|
||||
@@ -342,7 +370,7 @@ class SpacesContext implements Context {
|
||||
*/
|
||||
public function theUserListsAllHisAvailableSpacesUsingTheGraphApi(string $user): void {
|
||||
$this->featureContext->setResponse(
|
||||
$this->listSpacesRequest(
|
||||
$this->listMySpacesRequest(
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user)
|
||||
)
|
||||
@@ -362,7 +390,7 @@ class SpacesContext implements Context {
|
||||
*/
|
||||
public function theUserListsAllHisAvailableSpacesUsingTheGraphApiWithFilter(string $user, string $query): void {
|
||||
$this->featureContext->setResponse(
|
||||
$this->listSpacesRequest(
|
||||
$this->listMySpacesRequest(
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
"?". $query
|
||||
@@ -370,6 +398,30 @@ class SpacesContext implements Context {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" looks up the single space "([^"]*)" via the GraphApi by using its id$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $query
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function theUserLooksUpTheSingleSpaceUsingTheGraphApiByUsingItsId(string $user, string $spaceName): void {
|
||||
$space = $this->getSpaceByName($user, $spaceName);
|
||||
Assert::assertIsArray($space);
|
||||
Assert::assertNotEmpty($spaceId = $space["id"]);
|
||||
Assert::assertNotEmpty($spaceWebDavUrl = $space["root"]["webDavUrl"]);
|
||||
$this->featureContext->setResponse(
|
||||
$this->listSingleSpaceRequest(
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$spaceId
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" creates a space "([^"]*)" of type "([^"]*)" with the default quota using the GraphApi$/
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user