From 114818bba8793e5300edde88a77b45b87244bbcd Mon Sep 17 00:00:00 2001 From: Andres Uribe Date: Mon, 14 Nov 2022 19:30:05 -0500 Subject: [PATCH] [OSE-166] Scaffolding for /v1/presentations/submissions (#171) * Initial create, delete, get for submissions * Basic tests for the router * Added the rest of the scaffolding * Added List and Review * Removed the delete method * Simplified for scaffolding * spec * zero structs * Consolidate under the same API * mage spec * Minor updates * [OSE-167] Scaffolding for /v1/operations (#172) * The simplest scaffolding possible * rename Co-authored-by: Gabe * Fix lint and compilation * PR fixes * rename * regen * spec * Removed unused config * reviews Co-authored-by: Gabe --- doc/swagger.yaml | 216 ++++++++++++++++++-- pkg/server/router/operation.go | 3 - pkg/server/router/presentation.go | 116 ++++++++++- pkg/server/router/presentation_test.go | 6 +- pkg/server/server.go | 10 +- pkg/server/server_presentation_test.go | 4 +- pkg/service/presentation/model.go | 24 +++ pkg/service/presentation/service.go | 43 +++- pkg/service/presentation/storage/bolt.go | 49 +++-- pkg/service/presentation/storage/storage.go | 14 ++ pkg/service/service.go | 6 +- 11 files changed, 440 insertions(+), 51 deletions(-) diff --git a/doc/swagger.yaml b/doc/swagger.yaml index 8ee6a7b18..228f301cb 100644 --- a/doc/swagger.yaml +++ b/doc/swagger.yaml @@ -597,6 +597,13 @@ definitions: schemaJwt: type: string type: object + github.com_tbd54566975_ssi-service_pkg_server_router.CreateSubmissionRequest: + properties: + submissionJwt: + type: string + required: + - submissionJwt + type: object github.com_tbd54566975_ssi-service_pkg_server_router.GetApplicationResponse: properties: application: @@ -725,7 +732,38 @@ definitions: $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetSchemaResponse' type: array type: object + github.com_tbd54566975_ssi-service_pkg_server_router.GetSubmissionResponse: + properties: + submission: + $ref: '#/definitions/exchange.PresentationSubmission' + type: object + github.com_tbd54566975_ssi-service_pkg_server_router.ListSubmissionRequest: + properties: + filter: + type: string + type: object + github.com_tbd54566975_ssi-service_pkg_server_router.ListSubmissionResponse: + properties: + submissions: + items: + $ref: '#/definitions/exchange.PresentationSubmission' + type: array + type: object github.com_tbd54566975_ssi-service_pkg_server_router.Operation: + properties: + done: + type: boolean + id: + type: string + result: + $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.OperationResult' + type: object + github.com_tbd54566975_ssi-service_pkg_server_router.OperationResult: + properties: + error: + type: string + response: + $ref: '#/definitions/exchange.PresentationSubmission' type: object github.com_tbd54566975_ssi-service_pkg_server_router.ResolveDIDResponse: properties: @@ -736,6 +774,20 @@ definitions: didResolutionMetadata: $ref: '#/definitions/did.DIDResolutionMetadata' type: object + github.com_tbd54566975_ssi-service_pkg_server_router.ReviewSubmissionRequest: + properties: + approved: + type: boolean + reason: + type: string + required: + - approved + type: object + github.com_tbd54566975_ssi-service_pkg_server_router.ReviewSubmissionResponse: + properties: + submission: + $ref: '#/definitions/exchange.PresentationSubmission' + type: object github.com_tbd54566975_ssi-service_pkg_server_router.StoreKeyRequest: properties: base58PrivateKey: @@ -1041,6 +1093,13 @@ definitions: schemaJwt: type: string type: object + pkg_server_router.CreateSubmissionRequest: + properties: + submissionJwt: + type: string + required: + - submissionJwt + type: object pkg_server_router.GetApplicationResponse: properties: application: @@ -1169,7 +1228,38 @@ definitions: $ref: '#/definitions/pkg_server_router.GetSchemaResponse' type: array type: object + pkg_server_router.GetSubmissionResponse: + properties: + submission: + $ref: '#/definitions/exchange.PresentationSubmission' + type: object + pkg_server_router.ListSubmissionRequest: + properties: + filter: + type: string + type: object + pkg_server_router.ListSubmissionResponse: + properties: + submissions: + items: + $ref: '#/definitions/exchange.PresentationSubmission' + type: array + type: object pkg_server_router.Operation: + properties: + done: + type: boolean + id: + type: string + result: + $ref: '#/definitions/pkg_server_router.OperationResult' + type: object + pkg_server_router.OperationResult: + properties: + error: + type: string + response: + $ref: '#/definitions/exchange.PresentationSubmission' type: object pkg_server_router.ResolveDIDResponse: properties: @@ -1180,6 +1270,20 @@ definitions: didResolutionMetadata: $ref: '#/definitions/did.DIDResolutionMetadata' type: object + pkg_server_router.ReviewSubmissionRequest: + properties: + approved: + type: boolean + reason: + type: string + required: + - approved + type: object + pkg_server_router.ReviewSubmissionResponse: + properties: + submission: + $ref: '#/definitions/exchange.PresentationSubmission' + type: object pkg_server_router.StoreKeyRequest: properties: base58PrivateKey: @@ -1757,7 +1861,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetManifestsResponse' + $ref: '#/definitions/pkg_server_router.GetManifestsResponse' "400": description: Bad request schema: @@ -1779,14 +1883,14 @@ paths: name: request required: true schema: - $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.CreateManifestRequest' + $ref: '#/definitions/pkg_server_router.CreateManifestRequest' produces: - application/json responses: "201": description: Created schema: - $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.CreateManifestResponse' + $ref: '#/definitions/pkg_server_router.CreateManifestResponse' "400": description: Bad request schema: @@ -1843,7 +1947,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetManifestResponse' + $ref: '#/definitions/pkg_server_router.GetManifestResponse' "400": description: Bad request schema: @@ -1863,7 +1967,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetApplicationsResponse' + $ref: '#/definitions/pkg_server_router.GetApplicationsResponse' "400": description: Bad request schema: @@ -1886,14 +1990,14 @@ paths: name: request required: true schema: - $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.SubmitApplicationRequest' + $ref: '#/definitions/pkg_server_router.SubmitApplicationRequest' produces: - application/json responses: "201": description: Created schema: - $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.SubmitApplicationResponse' + $ref: '#/definitions/pkg_server_router.SubmitApplicationResponse' "400": description: Bad request schema: @@ -1950,7 +2054,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetApplicationResponse' + $ref: '#/definitions/pkg_server_router.GetApplicationResponse' "400": description: Bad request schema: @@ -1970,7 +2074,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetResponsesResponse' + $ref: '#/definitions/pkg_server_router.GetResponsesResponse' "400": description: Bad request schema: @@ -2027,7 +2131,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetResponseResponse' + $ref: '#/definitions/pkg_server_router.GetResponseResponse' "400": description: Bad request schema: @@ -2104,14 +2208,14 @@ paths: name: request required: true schema: - $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.CreatePresentationDefinitionRequest' + $ref: '#/definitions/pkg_server_router.CreatePresentationDefinitionRequest' produces: - application/json responses: "201": description: Created schema: - $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.CreatePresentationDefinitionResponse' + $ref: '#/definitions/pkg_server_router.CreatePresentationDefinitionResponse' "400": description: Bad request schema: @@ -2168,7 +2272,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetPresentationDefinitionResponse' + $ref: '#/definitions/pkg_server_router.GetPresentationDefinitionResponse' "400": description: Bad request schema: @@ -2176,6 +2280,92 @@ paths: summary: Get PresentationDefinition tags: - PresentationDefinitionAPI + /v1/presentations/submission/{id}: + get: + consumes: + - application/json + description: Get a submission by its ID + parameters: + - description: ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/pkg_server_router.GetSubmissionResponse' + "400": + description: Bad request + schema: + type: string + summary: Get Submission + tags: + - SubmissionAPI + /v1/presentations/submissions: + get: + consumes: + - application/json + description: Reviews a pending submission. After this method is called, the + operation with `id==presentations/submissions/{submission_id}` will be updated + with the result of this invocation. + parameters: + - description: request body + in: body + name: request + required: true + schema: + $ref: '#/definitions/pkg_server_router.ReviewSubmissionRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/pkg_server_router.ReviewSubmissionResponse' + "400": + description: Bad request + schema: + type: string + "500": + description: Internal server error + schema: + type: string + summary: Review a pending submissions + tags: + - SubmissionAPI + put: + consumes: + - application/json + description: Creates a submission in this server ready to be reviewed. + parameters: + - description: request body + in: body + name: request + required: true + schema: + $ref: '#/definitions/pkg_server_router.CreateSubmissionRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/pkg_server_router.Operation' + "400": + description: Bad request + schema: + type: string + "500": + description: Internal server error + schema: + type: string + summary: Create Submission + tags: + - SubmissionAPI /v1/schemas: get: consumes: diff --git a/pkg/server/router/operation.go b/pkg/server/router/operation.go index a4dfb0bca..54741f134 100644 --- a/pkg/server/router/operation.go +++ b/pkg/server/router/operation.go @@ -24,9 +24,6 @@ func NewOperationRouter(s svcframework.Service) (*OperationRouter, error) { return &OperationRouter{service: service}, nil } -type Operation struct { -} - // GetOperation godoc // @Summary Get an operation // @Description Get operation by its ID diff --git a/pkg/server/router/presentation.go b/pkg/server/router/presentation.go index ac87b6c81..536309f22 100644 --- a/pkg/server/router/presentation.go +++ b/pkg/server/router/presentation.go @@ -6,17 +6,18 @@ import ( "github.com/TBD54566975/ssi-sdk/credential/exchange" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "github.com/tbd54566975/ssi-service/internal/keyaccess" "github.com/tbd54566975/ssi-service/pkg/server/framework" svcframework "github.com/tbd54566975/ssi-service/pkg/service/framework" "github.com/tbd54566975/ssi-service/pkg/service/presentation" "net/http" ) -type PresentationDefinitionRouter struct { +type PresentationRouter struct { service *presentation.Service } -func NewPresentationDefinitionRouter(s svcframework.Service) (*PresentationDefinitionRouter, error) { +func NewPresentationRouter(s svcframework.Service) (*PresentationRouter, error) { if s == nil { return nil, errors.New("service cannot be nil") } @@ -24,7 +25,7 @@ func NewPresentationDefinitionRouter(s svcframework.Service) (*PresentationDefin if !ok { return nil, fmt.Errorf("could not create presentation router with service type: %s", s.Type()) } - return &PresentationDefinitionRouter{service: service}, nil + return &PresentationRouter{service: service}, nil } type CreatePresentationDefinitionRequest struct { @@ -50,7 +51,7 @@ type CreatePresentationDefinitionResponse struct { // @Failure 400 {string} string "Bad request" // @Failure 500 {string} string "Internal server error" // @Router /v1/presentation/definition [put] -func (pdr PresentationDefinitionRouter) CreatePresentationDefinition(ctx context.Context, w http.ResponseWriter, r *http.Request) error { +func (pr PresentationRouter) CreatePresentationDefinition(ctx context.Context, w http.ResponseWriter, r *http.Request) error { var request CreatePresentationDefinitionRequest errMsg := "Invalid Presentation Definition Request" if err := framework.Decode(r, &request); err != nil { @@ -68,7 +69,7 @@ func (pdr PresentationDefinitionRouter) CreatePresentationDefinition(ctx context logrus.WithError(err).Error(errMsg) return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest) } - serviceResp, err := pdr.service.CreatePresentationDefinition(presentation.CreatePresentationDefinitionRequest{PresentationDefinition: *def}) + serviceResp, err := pr.service.CreatePresentationDefinition(presentation.CreatePresentationDefinitionRequest{PresentationDefinition: *def}) if err != nil { logrus.WithError(err).Error(errMsg) return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusInternalServerError) @@ -125,7 +126,7 @@ type GetPresentationDefinitionResponse struct { // @Success 200 {object} GetPresentationDefinitionResponse // @Failure 400 {string} string "Bad request" // @Router /v1/presentation/definition/{id} [get] -func (pdr PresentationDefinitionRouter) GetPresentationDefinition(ctx context.Context, w http.ResponseWriter, r *http.Request) error { +func (pr PresentationRouter) GetPresentationDefinition(ctx context.Context, w http.ResponseWriter, r *http.Request) error { id := framework.GetParam(ctx, IDParam) if id == nil { errMsg := "cannot get presentation without ID parameter" @@ -133,7 +134,7 @@ func (pdr PresentationDefinitionRouter) GetPresentationDefinition(ctx context.Co return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest) } - def, err := pdr.service.GetPresentationDefinition(presentation.GetPresentationDefinitionRequest{ID: *id}) + def, err := pr.service.GetPresentationDefinition(presentation.GetPresentationDefinitionRequest{ID: *id}) if err != nil { errMsg := fmt.Sprintf("could not get presentation with id: %s", *id) logrus.WithError(err).Error(errMsg) @@ -157,7 +158,7 @@ func (pdr PresentationDefinitionRouter) GetPresentationDefinition(ctx context.Co // @Failure 400 {string} string "Bad request" // @Failure 500 {string} string "Internal server error" // @Router /v1/presentation/definition/{id} [delete] -func (pdr PresentationDefinitionRouter) DeletePresentationDefinition(ctx context.Context, w http.ResponseWriter, _ *http.Request) error { +func (pr PresentationRouter) DeletePresentationDefinition(ctx context.Context, w http.ResponseWriter, _ *http.Request) error { id := framework.GetParam(ctx, IDParam) if id == nil { errMsg := "cannot delete a presentation without an ID parameter" @@ -165,7 +166,7 @@ func (pdr PresentationDefinitionRouter) DeletePresentationDefinition(ctx context return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest) } - if err := pdr.service.DeletePresentationDefinition(presentation.DeletePresentationDefinitionRequest{ID: *id}); err != nil { + if err := pr.service.DeletePresentationDefinition(presentation.DeletePresentationDefinitionRequest{ID: *id}); err != nil { errMsg := fmt.Sprintf("could not delete presentation with id: %s", *id) logrus.WithError(err).Error(errMsg) return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusInternalServerError) @@ -173,3 +174,100 @@ func (pdr PresentationDefinitionRouter) DeletePresentationDefinition(ctx context return framework.Respond(ctx, w, nil, http.StatusOK) } + +type CreateSubmissionRequest struct { + SubmissionJWT keyaccess.JWT `json:"submissionJwt" validate:"required"` +} + +type Operation struct { + ID string `json:"id"` + Done bool `json:"done"` + Result OperationResult `json:"result,omitempty"` +} + +type OperationResult struct { + Error string `json:"error,omitempty"` + Response exchange.PresentationSubmission `json:"response,omitempty"` +} + +// CreateSubmission godoc +// @Summary Create Submission +// @Description Creates a submission in this server ready to be reviewed. +// @Tags SubmissionAPI +// @Accept json +// @Produce json +// @Param request body CreateSubmissionRequest true "request body" +// @Success 201 {object} Operation +// @Failure 400 {string} string "Bad request" +// @Failure 500 {string} string "Internal server error" +// @Router /v1/presentations/submissions [put] +func (pr PresentationRouter) CreateSubmission(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + var resp Operation + return framework.Respond(ctx, w, resp, http.StatusCreated) +} + +type GetSubmissionResponse struct { + Submission exchange.PresentationSubmission `json:"submission"` +} + +// GetSubmission godoc +// @Summary Get Submission +// @Description Get a submission by its ID +// @Tags SubmissionAPI +// @Accept json +// @Produce json +// @Param id path string true "ID" +// @Success 200 {object} GetSubmissionResponse +// @Failure 400 {string} string "Bad request" +// @Router /v1/presentations/submission/{id} [get] +func (pr PresentationRouter) GetSubmission(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + var resp GetSubmissionResponse + return framework.Respond(ctx, w, resp, http.StatusOK) +} + +type ListSubmissionRequest struct { + Filter string `json:"filter"` +} + +type ListSubmissionResponse struct { + Submissions []exchange.PresentationSubmission `json:"submissions"` +} + +// ListSubmissions godoc +// @Summary List Submissions +// @Description List existing submissions according to a filtering query. The `filter` field follows the syntax described in https://google.aip.dev/160. +// @Tags SubmissionAPI +// @Accept json +// @Produce json +// @Param request body ListSubmissionRequest true "request body" +// @Success 200 {object} ListSubmissionResponse +// @Failure 400 {string} string "Bad request" +// @Failure 500 {string} string "Internal server error" +// @Router /v1/presentations/submissions [get] +func (pr PresentationRouter) ListSubmissions(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + return framework.Respond(ctx, w, ListSubmissionResponse{}, http.StatusOK) +} + +type ReviewSubmissionRequest struct { + Approved bool `json:"approved" validate:"required"` + Reason string `json:"reason"` +} + +type ReviewSubmissionResponse struct { + Submission exchange.PresentationSubmission `json:"submission"` +} + +// ReviewSubmission godoc +// @Summary Review a pending submissions +// @Description Reviews a pending submission. After this method is called, the operation with `id==presentations/submissions/{submission_id}` will be updated with the result of this invocation. +// @Tags SubmissionAPI +// @Accept json +// @Produce json +// @Param request body ReviewSubmissionRequest true "request body" +// @Success 200 {object} ReviewSubmissionResponse +// @Failure 400 {string} string "Bad request" +// @Failure 500 {string} string "Internal server error" +// @Router /v1/presentations/submissions [get] +func (pr PresentationRouter) ReviewSubmission(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + return framework.Respond(ctx, w, ReviewSubmissionResponse{}, http.StatusOK) +} diff --git a/pkg/server/router/presentation_test.go b/pkg/server/router/presentation_test.go index c9dde9201..dc9acb2b5 100644 --- a/pkg/server/router/presentation_test.go +++ b/pkg/server/router/presentation_test.go @@ -13,14 +13,14 @@ import ( func TestPresentationDefinitionRouter(t *testing.T) { t.Run("Nil Service", func(tt *testing.T) { - pdRouter, err := NewPresentationDefinitionRouter(nil) + pdRouter, err := NewPresentationRouter(nil) assert.Error(tt, err) assert.Empty(tt, pdRouter) assert.Contains(tt, err.Error(), "service cannot be nil") }) t.Run("Bad Service", func(tt *testing.T) { - pdRouter, err := NewPresentationDefinitionRouter(&testService{}) + pdRouter, err := NewPresentationRouter(&testService{}) assert.Error(tt, err) assert.Empty(tt, pdRouter) assert.Contains(tt, err.Error(), "could not create presentation router with service type: test") @@ -35,7 +35,7 @@ func TestPresentationDefinitionService(t *testing.T) { s, err := storage.NewStorage(storage.Bolt) assert.NoError(t, err) - service, err := presentation.NewPresentationDefinitionService(config.PresentationServiceConfig{}, s) + service, err := presentation.NewPresentationService(config.PresentationServiceConfig{}, s) assert.NoError(t, err) t.Run("Create returns the created definition", func(t *testing.T) { diff --git a/pkg/server/server.go b/pkg/server/server.go index 049d1f862..a53a1aaea 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -29,6 +29,7 @@ const ( CredentialsPrefix = "/credentials" PresentationsPrefix = "/presentations" DefinitionsPrefix = "/definitions" + SubmissionsPrefix = "/submissions" ManifestsPrefix = "/manifests" ApplicationsPrefix = "/applications" ResponsesPrefix = "/responses" @@ -159,7 +160,7 @@ func (s *SSIServer) CredentialAPI(service svcframework.Service) (err error) { } func (s *SSIServer) PresentationAPI(service svcframework.Service) (err error) { - pRouter, err := router.NewPresentationDefinitionRouter(service) + pRouter, err := router.NewPresentationRouter(service) if err != nil { return util.LoggingErrorMsg(err, "could not create credential router") } @@ -169,6 +170,13 @@ func (s *SSIServer) PresentationAPI(service svcframework.Service) (err error) { s.Handle(http.MethodPut, handlerPath, pRouter.CreatePresentationDefinition) s.Handle(http.MethodGet, path.Join(handlerPath, "/:id"), pRouter.GetPresentationDefinition) s.Handle(http.MethodDelete, path.Join(handlerPath, "/:id"), pRouter.DeletePresentationDefinition) + + submissionHandlerPath := V1Prefix + PresentationsPrefix + SubmissionsPrefix + + s.Handle(http.MethodPut, submissionHandlerPath, pRouter.CreateSubmission) + s.Handle(http.MethodGet, path.Join(submissionHandlerPath, "/:id"), pRouter.GetSubmission) + s.Handle(http.MethodGet, submissionHandlerPath, pRouter.ListSubmissions) + s.Handle(http.MethodPut, path.Join(submissionHandlerPath, "/:id", "/review"), pRouter.ReviewSubmission) return } diff --git a/pkg/server/server_presentation_test.go b/pkg/server/server_presentation_test.go index 09f59b883..6e3422fc0 100644 --- a/pkg/server/server_presentation_test.go +++ b/pkg/server/server_presentation_test.go @@ -40,10 +40,10 @@ func TestPresentationAPI(t *testing.T) { s, err := storage.NewStorage(storage.Bolt) assert.NoError(t, err) - service, err := presentation.NewPresentationDefinitionService(config.PresentationServiceConfig{}, s) + service, err := presentation.NewPresentationService(config.PresentationServiceConfig{}, s) assert.NoError(t, err) - pRouter, err := router.NewPresentationDefinitionRouter(service) + pRouter, err := router.NewPresentationRouter(service) assert.NoError(t, err) t.Cleanup(func() { diff --git a/pkg/service/presentation/model.go b/pkg/service/presentation/model.go index cfad0ce06..784effa94 100644 --- a/pkg/service/presentation/model.go +++ b/pkg/service/presentation/model.go @@ -29,3 +29,27 @@ type GetPresentationDefinitionResponse struct { type DeletePresentationDefinitionRequest struct { ID string `json:"id" validate:"required"` } + +type CreateSubmissionRequest struct { + Submission exchange.PresentationSubmission `json:"submission" validate:"required"` +} + +func (csr CreateSubmissionRequest) IsValid() bool { + return util.IsValidStruct(csr) == nil +} + +type CreateSubmissionResponse struct { + Submission exchange.PresentationSubmission `json:"submission"` +} + +type GetSubmissionRequest struct { + ID string `json:"id" validate:"required"` +} + +type GetSubmissionResponse struct { + Submission exchange.PresentationSubmission `json:"submission"` +} + +type DeleteSubmissionRequest struct { + ID string `json:"id" validate:"required"` +} diff --git a/pkg/service/presentation/service.go b/pkg/service/presentation/service.go index c98b6cba9..ce6875fd3 100644 --- a/pkg/service/presentation/service.go +++ b/pkg/service/presentation/service.go @@ -6,7 +6,6 @@ import ( sdkutil "github.com/TBD54566975/ssi-sdk/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/tbd54566975/ssi-service/config" "github.com/tbd54566975/ssi-service/internal/util" "github.com/tbd54566975/ssi-service/pkg/service/framework" @@ -41,11 +40,10 @@ func (s Service) Config() config.PresentationServiceConfig { return s.config } -func NewPresentationDefinitionService(config config.PresentationServiceConfig, s storage.ServiceStorage) (*Service, error) { +func NewPresentationService(config config.PresentationServiceConfig, s storage.ServiceStorage) (*Service, error) { presentationStorage, err := presentationstorage.NewPresentationStorage(s) if err != nil { - errMsg := "could not instantiate storage for the presentation definition service" - return nil, util.LoggingErrorMsg(err, errMsg) + return nil, util.LoggingErrorMsg(err, "could not instantiate definition storage for the presentation service") } service := Service{ storage: presentationStorage, @@ -103,3 +101,40 @@ func (s Service) DeletePresentationDefinition(request DeletePresentationDefiniti return nil } + +// CreateSubmission houses the main service logic for presentation submission creation. It validates the input, and +// produces a presentation submission value that conforms with the Submission specification. +func (s Service) CreateSubmission(request CreateSubmissionRequest) (*CreateSubmissionResponse, error) { + logrus.Debugf("creating presentation submission: %+v", request) + + if !request.IsValid() { + return nil, util.LoggingNewErrorf("invalid create presentation submission request: %+v", request) + } + + if err := exchange.IsValidPresentationSubmission(request.Submission); err != nil { + return nil, util.LoggingErrorMsg(err, "provided value is not a valid presentation submission") + } + + storedSubmission := presentationstorage.StoredSubmission{Submission: request.Submission} + + if err := s.storage.StoreSubmission(storedSubmission); err != nil { + return nil, util.LoggingErrorMsg(err, "could not store presentation") + } + + return &CreateSubmissionResponse{ + Submission: storedSubmission.Submission, + }, nil +} + +func (s Service) GetSubmission(request GetSubmissionRequest) (*GetSubmissionResponse, error) { + logrus.Debugf("getting presentation submission: %s", request.ID) + + storedSubmission, err := s.storage.GetSubmission(request.ID) + if err != nil { + return nil, util.LoggingNewErrorf("error getting presentation submission: %s", request.ID) + } + if storedSubmission == nil { + return nil, util.LoggingNewErrorf("presentation submission with id<%s> could not be found", request.ID) + } + return &GetSubmissionResponse{Submission: storedSubmission.Submission}, nil +} diff --git a/pkg/service/presentation/storage/bolt.go b/pkg/service/presentation/storage/bolt.go index 9013695e7..2c1f88371 100644 --- a/pkg/service/presentation/storage/bolt.go +++ b/pkg/service/presentation/storage/bolt.go @@ -2,6 +2,7 @@ package storage import ( "fmt" + "github.com/tbd54566975/ssi-service/internal/util" "github.com/goccy/go-json" "github.com/pkg/errors" @@ -11,7 +12,8 @@ import ( ) const ( - namespace = "presentation_definition" + namespace = "presentation_definition" + submissionNamespace = "presentation_submission" ) type BoltPresentationStorage struct { @@ -44,29 +46,50 @@ func (b BoltPresentationStorage) StorePresentation(presentation StoredPresentati func (b BoltPresentationStorage) GetPresentation(id string) (*StoredPresentation, error) { jsonBytes, err := b.db.Read(namespace, id) if err != nil { - errMsg := fmt.Sprintf("could not get presentation definition: %s", id) - logrus.WithError(err).Error(errMsg) - return nil, errors.Wrapf(err, errMsg) + return nil, util.LoggingErrorMsgf(err, "could not get presentation definition: %s", id) } if len(jsonBytes) == 0 { - err := fmt.Errorf("presentation definition not found with id: %s", id) - logrus.WithError(err).Error("could not get presentation definition from storage") - return nil, err + return nil, util.LoggingNewErrorf("presentation definition not found with id: %s", id) } var stored StoredPresentation if err := json.Unmarshal(jsonBytes, &stored); err != nil { - errMsg := fmt.Sprintf("could not unmarshal stored presentation definition: %s", id) - logrus.WithError(err).Error(errMsg) - return nil, errors.Wrapf(err, errMsg) + return nil, util.LoggingErrorMsgf(err, "could not unmarshal stored presentation definition: %s", id) } return &stored, nil } func (b BoltPresentationStorage) DeletePresentation(id string) error { if err := b.db.Delete(namespace, id); err != nil { - errMsg := fmt.Sprintf("could not delete presentation definition: %s", id) - logrus.WithError(err).Error(errMsg) - return errors.Wrapf(err, errMsg) + return util.LoggingNewErrorf("could not delete presentation definition: %s", id) } return nil } + +func (b BoltPresentationStorage) StoreSubmission(submission StoredSubmission) error { + id := submission.Submission.ID + if id == "" { + err := errors.New("could not store submission definition without an ID") + logrus.WithError(err).Error() + return err + } + jsonBytes, err := json.Marshal(submission) + if err != nil { + return util.LoggingNewErrorf("could not store submission definition: %s", id) + } + return b.db.Write(submissionNamespace, id, jsonBytes) +} + +func (b BoltPresentationStorage) GetSubmission(id string) (*StoredSubmission, error) { + jsonBytes, err := b.db.Read(submissionNamespace, id) + if err != nil { + return nil, util.LoggingNewErrorf("could not get submission definition: %s", id) + } + if len(jsonBytes) == 0 { + return nil, util.LoggingNewErrorf("submission definition not found with id: %s", id) + } + var stored StoredSubmission + if err := json.Unmarshal(jsonBytes, &stored); err != nil { + return nil, util.LoggingErrorMsgf(err, "could not unmarshal stored submission definition: %s", id) + } + return &stored, nil +} diff --git a/pkg/service/presentation/storage/storage.go b/pkg/service/presentation/storage/storage.go index 5f4c19367..28f02053b 100644 --- a/pkg/service/presentation/storage/storage.go +++ b/pkg/service/presentation/storage/storage.go @@ -12,6 +12,11 @@ type StoredPresentation struct { } type Storage interface { + DefinitionStorage + SubmissionStorage +} + +type DefinitionStorage interface { StorePresentation(schema StoredPresentation) error GetPresentation(id string) (*StoredPresentation, error) DeletePresentation(id string) error @@ -34,3 +39,12 @@ func NewPresentationStorage(s storage.ServiceStorage) (Storage, error) { return nil, util.LoggingNewErrorf("unsupported storage type: %s", s.Type()) } } + +type StoredSubmission struct { + Submission exchange.PresentationSubmission `json:"submission"` +} + +type SubmissionStorage interface { + StoreSubmission(schema StoredSubmission) error + GetSubmission(id string) (*StoredSubmission, error) +} diff --git a/pkg/service/service.go b/pkg/service/service.go index 635522558..4b2e25d72 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -2,8 +2,6 @@ package service import ( "fmt" - "github.com/tbd54566975/ssi-service/pkg/service/operation" - "github.com/tbd54566975/ssi-service/pkg/service/presentation" "github.com/tbd54566975/ssi-service/config" "github.com/tbd54566975/ssi-service/internal/util" @@ -12,6 +10,8 @@ import ( "github.com/tbd54566975/ssi-service/pkg/service/framework" "github.com/tbd54566975/ssi-service/pkg/service/keystore" "github.com/tbd54566975/ssi-service/pkg/service/manifest" + "github.com/tbd54566975/ssi-service/pkg/service/operation" + "github.com/tbd54566975/ssi-service/pkg/service/presentation" "github.com/tbd54566975/ssi-service/pkg/service/schema" "github.com/tbd54566975/ssi-service/pkg/storage" ) @@ -97,7 +97,7 @@ func instantiateServices(config config.ServicesConfig) ([]framework.Service, err return nil, util.LoggingErrorMsg(err, "could not instantiate the manifest service") } - presentationService, err := presentation.NewPresentationDefinitionService(config.PresentationConfig, storageProvider) + presentationService, err := presentation.NewPresentationService(config.PresentationConfig, storageProvider) if err != nil { return nil, util.LoggingErrorMsg(err, "could not instantiate the presentation service") }