Repository: TBD54566975/ssi-service
Pull request #171 (2022-11-11 14:16:56)

[OSE-166] Scaffolding for /v1/presentations/submissions
This adds the routing for Create, Get, List, and Review endpoints for submissions. None of the business logic nor tests were added. Only simple routing, request types, and response types.

codecov-commenter: # [Codecov](https://codecov.io/gh/TBD54566975/ssi-service/pull/171?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975) Report
> Merging [#171](https://codecov.io/gh/TBD54566975/ssi-service/pull/171?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975) (4820940) into [main](https://codecov.io/gh/TBD54566975/ssi-service/commit/7e576cad977ea87109c4053bc3440dc8c6d277fd?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975) (7e576ca) will **increase** coverage by `1.93%`.
> The diff coverage is `61.29%`.

```diff
@@            Coverage Diff             @@
##             main     #171      +/-   ##
==========================================
+ Coverage   26.16%   28.09%   +1.93%     
==========================================
  Files          20       21       +1     
  Lines        1594     1687      +93     
==========================================
+ Hits          417      474      +57     
- Misses       1106     1135      +29     
- Partials       71       78       +7     
```


| [Impacted Files](https://codecov.io/gh/TBD54566975/ssi-service/pull/171?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975) | Coverage Δ | |
|---|---|---|
| [config/config.go](https://codecov.io/gh/TBD54566975/ssi-service/pull/171/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975#diff-Y29uZmlnL2NvbmZpZy5nbw==) | `8.08% <0.00%> (-0.72%)` | :arrow_down: |
| [pkg/server/router/submission.go](https://codecov.io/gh/TBD54566975/ssi-service/pull/171/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975#diff-cGtnL3NlcnZlci9yb3V0ZXIvc3VibWlzc2lvbi5nbw==) | `66.21% <66.21%> (ø)` | |
| [pkg/server/server.go](https://codecov.io/gh/TBD54566975/ssi-service/pull/171/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975#diff-cGtnL3NlcnZlci9zZXJ2ZXIuZ28=) | `79.45% <72.72%> (-0.55%)` | :arrow_down: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975)


Patch:
diff --git a/doc/swagger.yaml b/doc/swagger.yaml
index 8ee6a7b..228f301 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 a4dfb0b..54741f1 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 ac87b6c..536309f 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/server.go b/pkg/server/server.go
index 049d1f8..a53a1aa 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/service/presentation/model.go b/pkg/service/presentation/model.go
index cfad0ce..784effa 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 c98b6cb..ce6875f 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 9013695..2c1f883 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 5f4c193..28f0205 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 6355225..4b2e25d 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")
 	}


Test patch:
diff --git a/pkg/server/router/presentation_test.go b/pkg/server/router/presentation_test.go
index c9dde92..dc9acb2 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_presentation_test.go b/pkg/server/server_presentation_test.go
index 09f59b8..6e3422f 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() {