From 79b8cccaab14fb84aa1e7339c86cc1e4fae40105 Mon Sep 17 00:00:00 2001 From: Andres Uribe Gonzalez Date: Thu, 22 Dec 2022 14:28:27 -0500 Subject: [PATCH 1/8] Create implementation --- pkg/server/router/issuance.go | 29 ++- pkg/server/server_issuance_test.go | 273 +++++++++++++++++++++++++++++ pkg/service/issuing/model.go | 75 ++------ pkg/service/issuing/service.go | 107 +++++++++++ pkg/service/issuing/storage.go | 25 +++ 5 files changed, 444 insertions(+), 65 deletions(-) create mode 100644 pkg/server/server_issuance_test.go create mode 100644 pkg/service/issuing/service.go create mode 100644 pkg/service/issuing/storage.go diff --git a/pkg/server/router/issuance.go b/pkg/server/router/issuance.go index d81e04b3c..d88361ff6 100644 --- a/pkg/server/router/issuance.go +++ b/pkg/server/router/issuance.go @@ -4,8 +4,11 @@ import ( "context" "net/http" + "github.com/google/uuid" "github.com/pkg/errors" - "github.com/tbd54566975/ssi-service/pkg/service/framework" + "github.com/tbd54566975/ssi-service/internal/util" + "github.com/tbd54566975/ssi-service/pkg/server/framework" + svcframework "github.com/tbd54566975/ssi-service/pkg/service/framework" "github.com/tbd54566975/ssi-service/pkg/service/issuing" ) @@ -13,7 +16,7 @@ type IssuanceRouter struct { service issuing.Service } -func NewIssuanceRouter(svc framework.Service) (*IssuanceRouter, error) { +func NewIssuanceRouter(svc svcframework.Service) (*IssuanceRouter, error) { service, ok := svc.(*issuing.Service) if !ok { return nil, errors.New("could not cast to issuing service type") @@ -39,6 +42,13 @@ type CreateIssuanceTemplateRequest struct { issuing.IssuanceTemplate } +func (r CreateIssuanceTemplateRequest) ToServiceRequest() *issuing.CreateIssuanceTemplateRequest { + return &issuing.CreateIssuanceTemplateRequest{ + ID: uuid.NewString(), + IssuanceTemplate: r.IssuanceTemplate, + } +} + // CreateIssuanceTemplate godoc // @Summary Create issuance template // @Description Create issuance template @@ -51,7 +61,20 @@ type CreateIssuanceTemplateRequest struct { // @Failure 500 {string} string "Internal server error" // @Router /v1/issuancetemplates [put] func (ir IssuanceRouter) CreateIssuanceTemplate(ctx context.Context, w http.ResponseWriter, r *http.Request) error { - return nil + var request CreateIssuanceTemplateRequest + errMsg := "Invalid Issuance Template Request" + if err := framework.Decode(r, &request); err != nil { + return framework.NewRequestError( + util.LoggingErrorMsg(err, errMsg), http.StatusBadRequest) + } + + template, err := ir.service.CreateIssuanceTemplate(request.ToServiceRequest()) + if err != nil { + return framework.NewRequestError( + util.LoggingErrorMsg(err, "creating issuance template"), http.StatusInternalServerError) + } + + return framework.Respond(ctx, w, template, http.StatusCreated) } // DeleteIssuanceTemplate godoc diff --git a/pkg/server/server_issuance_test.go b/pkg/server/server_issuance_test.go new file mode 100644 index 000000000..ca9e7f4e6 --- /dev/null +++ b/pkg/server/server_issuance_test.go @@ -0,0 +1,273 @@ +package server + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/TBD54566975/ssi-sdk/credential/exchange" + manifestsdk "github.com/TBD54566975/ssi-sdk/credential/manifest" + "github.com/TBD54566975/ssi-sdk/crypto" + "github.com/goccy/go-json" + "github.com/stretchr/testify/assert" + "github.com/tbd54566975/ssi-service/config" + "github.com/tbd54566975/ssi-service/pkg/server/router" + "github.com/tbd54566975/ssi-service/pkg/service/did" + "github.com/tbd54566975/ssi-service/pkg/service/issuing" + "github.com/tbd54566975/ssi-service/pkg/service/manifest/model" + "github.com/tbd54566975/ssi-service/pkg/service/schema" +) + +func TestIssuanceRouter(t *testing.T) { + now := time.Now() + duration := 10 * time.Second + t.Run("CreateIssuanceTemplate returns a template with ID", func(t *testing.T) { + issuerResp, createdSchema, manifest, r := setupAllThings(t) + + request := router.CreateIssuanceTemplateRequest{ + IssuanceTemplate: issuing.IssuanceTemplate{ + CredentialManifest: manifest.Manifest.ID, + Issuer: issuerResp.DID.ID, + Credentials: []issuing.CredentialTemplate{ + { + ID: "output_descriptor_1", + Schema: createdSchema.Schema.ID, + Data: issuing.CredentialTemplateData{ + Claims: issuing.ClaimTemplates{ + Data: map[string]any{ + "foo": "bar", + "hello": "$.vcsomething.something", + }, + }, + }, + Expiry: issuing.TimeLike{ + Time: &now, + }, + }, + }, + }, + } + value := newRequestValue(t, request) + req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/issuancetemplates", value) + w := httptest.NewRecorder() + + err := r.CreateIssuanceTemplate(newRequestContext(), w, req) + assert.NoError(t, err) + + var resp issuing.IssuanceTemplate + assert.NoError(t, json.NewDecoder(w.Body).Decode(&resp)) + assert.NotEmpty(t, resp.ID) + }) + + t.Run("CreateIssuanceTemplate returns error", func(t *testing.T) { + issuerResp, createdSchema, manifest, r := setupAllThings(t) + + for _, tc := range []struct { + name string + request router.CreateIssuanceTemplateRequest + expectedError string + }{ + { + name: "when missing output_descriptor_id", + request: router.CreateIssuanceTemplateRequest{ + IssuanceTemplate: issuing.IssuanceTemplate{ + CredentialManifest: manifest.Manifest.ID, + Issuer: issuerResp.DID.ID, + Credentials: []issuing.CredentialTemplate{ + { + ID: "", + Schema: createdSchema.Schema.ID, + Data: issuing.CredentialTemplateData{ + Claims: issuing.ClaimTemplates{ + Data: map[string]any{ + "foo": "bar", + "hello": "$.vcsomething.something", + }, + }, + }, + Expiry: issuing.TimeLike{ + Time: &now, + }, + }, + }, + }, + }, + expectedError: "ID cannot be empty", + }, + { + name: "when both times are set", + request: router.CreateIssuanceTemplateRequest{ + IssuanceTemplate: issuing.IssuanceTemplate{ + CredentialManifest: manifest.Manifest.ID, + Issuer: issuerResp.DID.ID, + Credentials: []issuing.CredentialTemplate{ + { + ID: "output_descriptor_1", + Schema: createdSchema.Schema.ID, + Data: issuing.CredentialTemplateData{ + Claims: issuing.ClaimTemplates{ + Data: map[string]any{ + "foo": "bar", + "hello": "$.vcsomething.something", + }, + }, + }, + Expiry: issuing.TimeLike{ + Time: &now, + Duration: &duration, + }, + }, + }, + }, + }, + expectedError: "Time and Duration cannot be both set simultaneously", + }, + { + name: "when schema does not exist", + request: router.CreateIssuanceTemplateRequest{ + IssuanceTemplate: issuing.IssuanceTemplate{ + CredentialManifest: manifest.Manifest.ID, + Issuer: issuerResp.DID.ID, + Credentials: []issuing.CredentialTemplate{ + { + ID: "output_descriptor_1", + Schema: "fake schema", + Data: issuing.CredentialTemplateData{ + Claims: issuing.ClaimTemplates{ + Data: map[string]any{ + "foo": "bar", + "hello": "$.vcsomething.something", + }, + }, + }, + Expiry: issuing.TimeLike{ + Time: &now, + }, + }, + }, + }, + }, + expectedError: "schema not found", + }, + { + name: "when credential manifest ID is does not exist", + request: router.CreateIssuanceTemplateRequest{ + IssuanceTemplate: issuing.IssuanceTemplate{ + CredentialManifest: "fake manifest id", + Issuer: issuerResp.DID.ID, + Credentials: []issuing.CredentialTemplate{ + { + ID: "output_descriptor_1", + Schema: createdSchema.ID, + Data: issuing.CredentialTemplateData{ + Claims: issuing.ClaimTemplates{ + Data: map[string]any{ + "foo": "bar", + "hello": "$.vcsomething.something", + }, + }, + }, + Expiry: issuing.TimeLike{ + Time: &now, + }, + }, + }, + }, + }, + expectedError: "manifest not found", + }, + { + name: "when issuer is empty", + request: router.CreateIssuanceTemplateRequest{ + IssuanceTemplate: issuing.IssuanceTemplate{ + CredentialManifest: manifest.Manifest.ID, + Credentials: []issuing.CredentialTemplate{ + { + ID: "output_descriptor_1", + Schema: createdSchema.ID, + Data: issuing.CredentialTemplateData{ + Claims: issuing.ClaimTemplates{ + Data: map[string]any{ + "foo": "bar", + "hello": "$.vcsomething.something", + }, + }, + }, + Expiry: issuing.TimeLike{ + Time: &now, + }, + }, + }, + }, + }, + expectedError: "field validation error", + }, + } { + t.Run(tc.name, func(t *testing.T) { + + value := newRequestValue(t, tc.request) + req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/issuancetemplates", value) + w := httptest.NewRecorder() + + err := r.CreateIssuanceTemplate(newRequestContext(), w, req) + assert.Error(t, err) + assert.Contains(t, err.Error(), tc.expectedError) + + }) + + } + }) +} + +func setupAllThings(t *testing.T) (*did.CreateDIDResponse, *schema.CreateSchemaResponse, *model.CreateManifestResponse, *router.IssuanceRouter) { + s := setupTestDB(t) + + _, keyStoreSvc := testKeyStore(t, s) + didSvc := testDIDService(t, s, keyStoreSvc) + schemaSvc := testSchemaService(t, s, keyStoreSvc, didSvc) + credSvc := testCredentialService(t, s, keyStoreSvc, didSvc, schemaSvc) + _, manifestSvc := testManifest(t, s, keyStoreSvc, didSvc, credSvc) + + issuerResp, err := didSvc.CreateDIDByMethod(did.CreateDIDRequest{ + Method: "key", + KeyType: crypto.Ed25519, + }) + assert.NoError(t, err) + + licenseSchema := map[string]any{ + "type": "object", + "properties": map[string]any{ + "licenseType": map[string]any{ + "type": "string", + }, + }, + "additionalProperties": true, + } + createdSchema, err := schemaSvc.CreateSchema(schema.CreateSchemaRequest{Author: issuerResp.DID.ID, Name: "license schema", Schema: licenseSchema, Sign: true}) + assert.NoError(t, err) + sillyName := "some silly name" + manifest, err := manifestSvc.CreateManifest(model.CreateManifestRequest{ + Name: &sillyName, + IssuerDID: issuerResp.DID.ID, + ClaimFormat: &exchange.ClaimFormat{ + JWT: &exchange.JWTType{Alg: []crypto.SignatureAlgorithm{crypto.EdDSA}}, + }, + OutputDescriptors: []manifestsdk.OutputDescriptor{ + { + ID: "output_descriptor_1", + Schema: createdSchema.Schema.ID, + }, + }, + }) + assert.NoError(t, err) + + svc, err := issuing.NewIssuingService(config.IssuingServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{ + Name: "test-issuing", + }}, s) + assert.NoError(t, err) + + r, err := router.NewIssuanceRouter(svc) + assert.NoError(t, err) + return issuerResp, createdSchema, manifest, r +} diff --git a/pkg/service/issuing/model.go b/pkg/service/issuing/model.go index eebe45ca2..5a26ca667 100644 --- a/pkg/service/issuing/model.go +++ b/pkg/service/issuing/model.go @@ -3,10 +3,7 @@ package issuing import ( "time" - "github.com/pkg/errors" - "github.com/tbd54566975/ssi-service/config" - "github.com/tbd54566975/ssi-service/pkg/service/framework" - "github.com/tbd54566975/ssi-service/pkg/storage" + "github.com/TBD54566975/ssi-sdk/util" "go.einride.tech/aip/filtering" ) @@ -15,7 +12,7 @@ type GetIssuanceTemplateRequest struct { } type CredentialTemplateData struct { - // ID of the input descriptor in the application. Correponds to one of the + // Optional. When present, it's the ID of the input descriptor in the application. Corresponds to one of the // PresentationDefinition.InputDescriptors[].ID in the credential manifest. CredentialInputDescriptor string `json:"credentialInputDescriptor"` @@ -25,10 +22,10 @@ type CredentialTemplateData struct { type TimeLike struct { // For fixed time in the future. - *time.Time + Time *time.Time // For a fixed offset from when it was issued. - *time.Duration + Duration *time.Duration } type ClaimTemplates struct { @@ -55,11 +52,14 @@ type CredentialTemplate struct { } type IssuanceTemplate struct { + // ID of this template. + ID string `json:"id"` + // ID of the credential manifest that this template corresponds to. - CredentialManifest string `json:"credentialManifest"` + CredentialManifest string `json:"credentialManifest" validate:"required"` // ID of the issuer that will be issuing the credentials. - Issuer string `json:"issuer"` + Issuer string `json:"issuer" validate:"required"` // Info required to create a credential from a credential application. Credentials []CredentialTemplate `json:"credentials"` @@ -79,6 +79,10 @@ type CreateIssuanceTemplateRequest struct { IssuanceTemplate IssuanceTemplate `json:"issuanceTemplate"` } +func (r CreateIssuanceTemplateRequest) IsValid() bool { + return util.IsValidStruct(r) == nil +} + type DeleteIssuanceTemplateRequest struct { // ID of the template that will be deleted. // Required. @@ -94,56 +98,3 @@ type ListIssuanceTemplatesResponse struct { // The issuance templates that satisfy the query conditions. IssuanceTemplates []IssuanceTemplate `json:"issuanceTemplates"` } - -type Service struct { - config config.IssuingServiceConfig - storage Storage -} - -func (s *Service) Type() framework.Type { - return framework.Issuing -} - -func (s *Service) Status() framework.Status { - return framework.Status{Status: framework.StatusReady} -} - -func NewIssuingService(config config.IssuingServiceConfig, s storage.ServiceStorage) (*Service, error) { - issuingStorage, err := NewIssuingStorage(s) - if err != nil { - return nil, errors.Wrap(err, "creating issuing storage") - } - return &Service{ - storage: *issuingStorage, - config: config, - }, nil -} - -type Storage struct { - db storage.ServiceStorage -} - -func NewIssuingStorage(s storage.ServiceStorage) (*Storage, error) { - if s == nil { - return nil, errors.New("s cannot be nil") - } - return &Storage{ - db: s, - }, nil -} - -func (s *Service) GetIssuanceTemplate(request *GetIssuanceTemplateRequest) (*GetIssuanceTemplateResponse, error) { - return nil, nil -} - -func (s *Service) CreateIssuanceTemplate(request *CreateIssuanceTemplateRequest) (*IssuanceTemplate, error) { - return nil, nil -} - -func (s *Service) DeleteIssuanceTemplate(request *DeleteIssuanceTemplateRequest) error { - return nil -} - -func (s *Service) ListIssuanceTemplates(request *ListIssuanceTemplatesRequest) (*ListIssuanceTemplatesResponse, error) { - return nil, nil -} diff --git a/pkg/service/issuing/service.go b/pkg/service/issuing/service.go new file mode 100644 index 000000000..460b74b9f --- /dev/null +++ b/pkg/service/issuing/service.go @@ -0,0 +1,107 @@ +package issuing + +import ( + "github.com/goccy/go-json" + "github.com/pkg/errors" + "github.com/tbd54566975/ssi-service/config" + "github.com/tbd54566975/ssi-service/pkg/service/framework" + "github.com/tbd54566975/ssi-service/pkg/service/manifest/storage" + "github.com/tbd54566975/ssi-service/pkg/service/schema" + storage2 "github.com/tbd54566975/ssi-service/pkg/storage" +) + +type Service struct { + config config.IssuingServiceConfig + storage Storage + manifestStorage storage.Storage + schemaStorage schema.Storage +} + +func NewIssuingService(config config.IssuingServiceConfig, s storage2.ServiceStorage) (*Service, error) { + issuingStorage, err := NewIssuingStorage(s) + if err != nil { + return nil, errors.Wrap(err, "creating issuing storage") + } + manifestStorage, err := storage.NewManifestStorage(s) + if err != nil { + return nil, errors.Wrap(err, "creating manifest storage") + } + schemaStorage, err := schema.NewSchemaStorage(s) + if err != nil { + return nil, errors.Wrap(err, "creating manifest storage") + } + return &Service{ + storage: *issuingStorage, + config: config, + manifestStorage: *manifestStorage, + schemaStorage: *schemaStorage, + }, nil +} + +func (s Storage) StoreIssuanceTemplate(template StoredIssuanceTemplate) error { + if template.IssuanceTemplate.ID == "" { + return errors.New("cannot store issuance template without an ID") + } + data, err := json.Marshal(template) + if err != nil { + return errors.Wrap(err, "marshalling template") + } + return s.db.Write(namespace, template.IssuanceTemplate.ID, data) +} + +func (s *Service) GetIssuanceTemplate(request *GetIssuanceTemplateRequest) (*GetIssuanceTemplateResponse, error) { + return nil, nil +} + +func (s *Service) CreateIssuanceTemplate(request *CreateIssuanceTemplateRequest) (*IssuanceTemplate, error) { + if !request.IsValid() { + return nil, errors.New("invalid create issuance template request") + } + + for i, c := range request.IssuanceTemplate.Credentials { + if c.Expiry.Time != nil && c.Expiry.Duration != nil { + return nil, errors.Errorf("Time and Duration cannot be both set simultaneously at index %d", i) + } + if c.ID == "" { + return nil, errors.Errorf("ID cannot be empty at index %d", i) + } + if _, err := s.schemaStorage.GetSchema(c.Schema); err != nil { + return nil, errors.Wrapf(err, "getting schema at index %d", i) + } + } + + if _, err := s.manifestStorage.GetManifest(request.IssuanceTemplate.CredentialManifest); err != nil { + return nil, errors.Wrap(err, "getting manifest") + } + + storedTemplate := StoredIssuanceTemplate{ + IssuanceTemplate: request.IssuanceTemplate, + } + storedTemplate.IssuanceTemplate.ID = request.ID + + if err := s.storage.StoreIssuanceTemplate(storedTemplate); err != nil { + return nil, errors.Wrap(err, "storing issuance template") + } + + return serviceModel(storedTemplate), nil +} + +func serviceModel(template StoredIssuanceTemplate) *IssuanceTemplate { + return &template.IssuanceTemplate +} + +func (s *Service) DeleteIssuanceTemplate(request *DeleteIssuanceTemplateRequest) error { + return nil +} + +func (s *Service) ListIssuanceTemplates(request *ListIssuanceTemplatesRequest) (*ListIssuanceTemplatesResponse, error) { + return nil, nil +} + +func (s *Service) Type() framework.Type { + return framework.Issuing +} + +func (s *Service) Status() framework.Status { + return framework.Status{Status: framework.StatusReady} +} diff --git a/pkg/service/issuing/storage.go b/pkg/service/issuing/storage.go new file mode 100644 index 000000000..0ae7af976 --- /dev/null +++ b/pkg/service/issuing/storage.go @@ -0,0 +1,25 @@ +package issuing + +import ( + "github.com/pkg/errors" + "github.com/tbd54566975/ssi-service/pkg/storage" +) + +type Storage struct { + db storage.ServiceStorage +} + +const namespace = "issuance_template" + +func NewIssuingStorage(s storage.ServiceStorage) (*Storage, error) { + if s == nil { + return nil, errors.New("s cannot be nil") + } + return &Storage{ + db: s, + }, nil +} + +type StoredIssuanceTemplate struct { + IssuanceTemplate IssuanceTemplate +} From 3eeb67408bd53f9f19459055aaed9dfef0bf1eec Mon Sep 17 00:00:00 2001 From: Andres Uribe Gonzalez Date: Thu, 22 Dec 2022 16:25:10 -0500 Subject: [PATCH 2/8] Get implementation --- pkg/server/router/issuance.go | 15 +++++- pkg/server/server_issuance_test.go | 81 +++++++++++++++++++++++++++++- pkg/service/issuing/model.go | 2 +- pkg/service/issuing/service.go | 20 +++----- pkg/service/issuing/storage.go | 30 +++++++++++ 5 files changed, 132 insertions(+), 16 deletions(-) diff --git a/pkg/server/router/issuance.go b/pkg/server/router/issuance.go index d88361ff6..cd4ca61de 100644 --- a/pkg/server/router/issuance.go +++ b/pkg/server/router/issuance.go @@ -31,11 +31,22 @@ func NewIssuanceRouter(svc svcframework.Service) (*IssuanceRouter, error) { // @Accept json // @Produce json // @Param id path string true "ID" -// @Success 200 {object} issuing.GetIssuanceTemplateResponse +// @Success 200 {object} issuing.IssuanceTemplate // @Failure 400 {string} string "Bad request" // @Router /v1/issuancetemplates/{id} [get] func (ir IssuanceRouter) GetIssuanceTemplate(ctx context.Context, w http.ResponseWriter, req *http.Request) error { - return nil + id := framework.GetParam(ctx, IDParam) + if id == nil { + return framework.NewRequestError( + util.LoggingNewError("cannot get issuance template without an ID"), http.StatusBadRequest) + } + + issuanceTemplate, err := ir.service.GetIssuanceTemplate(&issuing.GetIssuanceTemplateRequest{ID: *id}) + if err != nil { + return framework.NewRequestError( + util.LoggingErrorMsg(err, "getting issuance template"), http.StatusInternalServerError) + } + return framework.Respond(ctx, w, issuanceTemplate.IssuanceTemplate, http.StatusOK) } type CreateIssuanceTemplateRequest struct { diff --git a/pkg/server/server_issuance_test.go b/pkg/server/server_issuance_test.go index ca9e7f4e6..5fc41e0e6 100644 --- a/pkg/server/server_issuance_test.go +++ b/pkg/server/server_issuance_test.go @@ -10,6 +10,8 @@ import ( manifestsdk "github.com/TBD54566975/ssi-sdk/credential/manifest" "github.com/TBD54566975/ssi-sdk/crypto" "github.com/goccy/go-json" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/assert" "github.com/tbd54566975/ssi-service/config" "github.com/tbd54566975/ssi-service/pkg/server/router" @@ -17,6 +19,7 @@ import ( "github.com/tbd54566975/ssi-service/pkg/service/issuing" "github.com/tbd54566975/ssi-service/pkg/service/manifest/model" "github.com/tbd54566975/ssi-service/pkg/service/schema" + "github.com/tbd54566975/ssi-service/pkg/storage" ) func TestIssuanceRouter(t *testing.T) { @@ -218,6 +221,77 @@ func TestIssuanceRouter(t *testing.T) { } }) + + t.Run("Create, Get, Delete work as expected", func(t *testing.T) { + issuerResp, createdSchema, manifest, r := setupAllThings(t) + + inputTemplate := issuing.IssuanceTemplate{ + CredentialManifest: manifest.Manifest.ID, + Issuer: issuerResp.DID.ID, + Credentials: []issuing.CredentialTemplate{ + { + ID: "output_descriptor_1", + Schema: createdSchema.Schema.ID, + Data: issuing.CredentialTemplateData{ + Claims: issuing.ClaimTemplates{ + Data: map[string]any{ + "foo": "bar", + "hello": "$.vcsomething.something", + }, + }, + }, + Expiry: issuing.TimeLike{ + Time: &now, + }, + }, + }, + } + var issuanceTemplate issuing.IssuanceTemplate + + { + request := router.CreateIssuanceTemplateRequest{ + IssuanceTemplate: inputTemplate, + } + value := newRequestValue(t, request) + req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/issuancetemplates", value) + w := httptest.NewRecorder() + + err := r.CreateIssuanceTemplate(newRequestContext(), w, req) + assert.NoError(t, err) + + assert.NoError(t, json.NewDecoder(w.Body).Decode(&issuanceTemplate)) + if diff := cmp.Diff(inputTemplate, issuanceTemplate, cmpopts.IgnoreFields(issuing.IssuanceTemplate{}, "ID")); diff != "" { + t.Errorf("IssuanceTemplate mismatch (-want +got):\n%s", diff) + } + } + + { + value := newRequestValue(t, nil) + req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/issuancetemplates/"+issuanceTemplate.ID, value) + w := httptest.NewRecorder() + err := r.GetIssuanceTemplate(newRequestContextWithParams(map[string]string{"id": issuanceTemplate.ID}), w, req) + assert.NoError(t, err) + + var getIssuanceTemplate issuing.IssuanceTemplate + assert.NoError(t, json.NewDecoder(w.Body).Decode(&getIssuanceTemplate)) + if diff := cmp.Diff(issuanceTemplate, getIssuanceTemplate); diff != "" { + t.Errorf("IssuanceTemplate mismatch (-want +got):\n%s", diff) + } + } + }) + + t.Run("GetIssuanceTemplate returns error for unknown ID", func(t *testing.T) { + s := setupTestDB(t) + r := testIssuanceRouter(t, s) + + value := newRequestValue(t, nil) + req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/issuancetemplates/where-is-it", value) + w := httptest.NewRecorder() + err := r.GetIssuanceTemplate(newRequestContextWithParams(map[string]string{"id": "where-is-it"}), w, req) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "issuance template not found") + }) } func setupAllThings(t *testing.T) (*did.CreateDIDResponse, *schema.CreateSchemaResponse, *model.CreateManifestResponse, *router.IssuanceRouter) { @@ -262,6 +336,11 @@ func setupAllThings(t *testing.T) (*did.CreateDIDResponse, *schema.CreateSchemaR }) assert.NoError(t, err) + r := testIssuanceRouter(t, s) + return issuerResp, createdSchema, manifest, r +} + +func testIssuanceRouter(t *testing.T, s storage.ServiceStorage) *router.IssuanceRouter { svc, err := issuing.NewIssuingService(config.IssuingServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{ Name: "test-issuing", }}, s) @@ -269,5 +348,5 @@ func setupAllThings(t *testing.T) (*did.CreateDIDResponse, *schema.CreateSchemaR r, err := router.NewIssuanceRouter(svc) assert.NoError(t, err) - return issuerResp, createdSchema, manifest, r + return r } diff --git a/pkg/service/issuing/model.go b/pkg/service/issuing/model.go index 5a26ca667..3fe9e6339 100644 --- a/pkg/service/issuing/model.go +++ b/pkg/service/issuing/model.go @@ -67,7 +67,7 @@ type IssuanceTemplate struct { type GetIssuanceTemplateResponse struct { // The template that was requested. - IssuanceTemplate IssuanceTemplate `json:"issuanceTemplate"` + IssuanceTemplate *IssuanceTemplate `json:"issuanceTemplate"` } type CreateIssuanceTemplateRequest struct { diff --git a/pkg/service/issuing/service.go b/pkg/service/issuing/service.go index 460b74b9f..2162818c5 100644 --- a/pkg/service/issuing/service.go +++ b/pkg/service/issuing/service.go @@ -1,7 +1,6 @@ package issuing import ( - "github.com/goccy/go-json" "github.com/pkg/errors" "github.com/tbd54566975/ssi-service/config" "github.com/tbd54566975/ssi-service/pkg/service/framework" @@ -38,19 +37,16 @@ func NewIssuingService(config config.IssuingServiceConfig, s storage2.ServiceSto }, nil } -func (s Storage) StoreIssuanceTemplate(template StoredIssuanceTemplate) error { - if template.IssuanceTemplate.ID == "" { - return errors.New("cannot store issuance template without an ID") - } - data, err := json.Marshal(template) +func (s *Service) GetIssuanceTemplate(request *GetIssuanceTemplateRequest) (*GetIssuanceTemplateResponse, error) { + storedIssuanceTemplate, err := s.storage.GetIssuanceTemplate(request.ID) if err != nil { - return errors.Wrap(err, "marshalling template") + return nil, errors.Wrapf(err, "getting issuance template with id: %s", request.ID) } - return s.db.Write(namespace, template.IssuanceTemplate.ID, data) -} - -func (s *Service) GetIssuanceTemplate(request *GetIssuanceTemplateRequest) (*GetIssuanceTemplateResponse, error) { - return nil, nil + if storedIssuanceTemplate == nil { + return nil, errors.Errorf("issuance template with id<%s> not be found", request.ID) + } + return &GetIssuanceTemplateResponse{ + IssuanceTemplate: serviceModel(*storedIssuanceTemplate)}, nil } func (s *Service) CreateIssuanceTemplate(request *CreateIssuanceTemplateRequest) (*IssuanceTemplate, error) { diff --git a/pkg/service/issuing/storage.go b/pkg/service/issuing/storage.go index 0ae7af976..cf9a463e1 100644 --- a/pkg/service/issuing/storage.go +++ b/pkg/service/issuing/storage.go @@ -1,6 +1,7 @@ package issuing import ( + "github.com/goccy/go-json" "github.com/pkg/errors" "github.com/tbd54566975/ssi-service/pkg/storage" ) @@ -23,3 +24,32 @@ func NewIssuingStorage(s storage.ServiceStorage) (*Storage, error) { type StoredIssuanceTemplate struct { IssuanceTemplate IssuanceTemplate } + +func (s Storage) StoreIssuanceTemplate(template StoredIssuanceTemplate) error { + if template.IssuanceTemplate.ID == "" { + return errors.New("cannot store issuance template without an ID") + } + data, err := json.Marshal(template) + if err != nil { + return errors.Wrap(err, "marshalling template") + } + return s.db.Write(namespace, template.IssuanceTemplate.ID, data) +} + +func (s Storage) GetIssuanceTemplate(id string) (*StoredIssuanceTemplate, error) { + if id == "" { + return nil, errors.New("cannot fetch issuance template without an ID") + } + data, err := s.db.Read(namespace, id) + if err != nil { + return nil, errors.Wrap(err, "reading from db") + } + if len(data) == 0 { + return nil, errors.Errorf("issuance template not found with id: %s", id) + } + var st StoredIssuanceTemplate + if err = json.Unmarshal(data, &st); err != nil { + return nil, errors.Wrap(err, "unmarshalling template") + } + return &st, nil +} From 3b26ec1a7231559552051d1cdd33a38f092a6d79 Mon Sep 17 00:00:00 2001 From: Andres Uribe Gonzalez Date: Thu, 22 Dec 2022 16:34:29 -0500 Subject: [PATCH 3/8] Delete implementation --- pkg/server/router/issuance.go | 15 +++++++++++++-- pkg/server/server_issuance_test.go | 18 ++++++++++++++++++ pkg/service/issuing/service.go | 3 +++ pkg/service/issuing/storage.go | 10 ++++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/pkg/server/router/issuance.go b/pkg/server/router/issuance.go index cd4ca61de..5b9a560d8 100644 --- a/pkg/server/router/issuance.go +++ b/pkg/server/router/issuance.go @@ -99,8 +99,19 @@ func (ir IssuanceRouter) CreateIssuanceTemplate(ctx context.Context, w http.Resp // @Failure 400 {string} string "Bad request" // @Failure 500 {string} string "Internal server error" // @Router /v1/issuancetemplates/{id} [delete] -func (ir IssuanceRouter) DeleteIssuanceTemplate(ctx context.Context, w http.ResponseWriter, r *http.Request) error { - return nil +func (ir IssuanceRouter) DeleteIssuanceTemplate(ctx context.Context, w http.ResponseWriter, _ *http.Request) error { + id := framework.GetParam(ctx, IDParam) + if id == nil { + return framework.NewRequestError( + util.LoggingNewError("cannot delete a presentation without an ID parameter"), http.StatusBadRequest) + } + + if err := ir.service.DeleteIssuanceTemplate(&issuing.DeleteIssuanceTemplateRequest{ID: *id}); err != nil { + return framework.NewRequestError( + util.LoggingErrorMsgf(err, "could not delete presentation with id: %s", *id), http.StatusInternalServerError) + } + + return framework.Respond(ctx, w, nil, http.StatusOK) } type ListIssuanceTemplatesResponse struct { diff --git a/pkg/server/server_issuance_test.go b/pkg/server/server_issuance_test.go index 5fc41e0e6..ef12a3335 100644 --- a/pkg/server/server_issuance_test.go +++ b/pkg/server/server_issuance_test.go @@ -278,6 +278,24 @@ func TestIssuanceRouter(t *testing.T) { t.Errorf("IssuanceTemplate mismatch (-want +got):\n%s", diff) } } + + { + value := newRequestValue(t, nil) + req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/issuancetemplates/"+issuanceTemplate.ID, value) + w := httptest.NewRecorder() + err := r.DeleteIssuanceTemplate(newRequestContextWithParams(map[string]string{"id": issuanceTemplate.ID}), w, req) + assert.NoError(t, err) + } + + { + value := newRequestValue(t, nil) + req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/issuancetemplates/"+issuanceTemplate.ID, value) + w := httptest.NewRecorder() + err := r.GetIssuanceTemplate(newRequestContextWithParams(map[string]string{"id": issuanceTemplate.ID}), w, req) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "issuance template not found") + } }) t.Run("GetIssuanceTemplate returns error for unknown ID", func(t *testing.T) { diff --git a/pkg/service/issuing/service.go b/pkg/service/issuing/service.go index 2162818c5..2c8950ac0 100644 --- a/pkg/service/issuing/service.go +++ b/pkg/service/issuing/service.go @@ -87,6 +87,9 @@ func serviceModel(template StoredIssuanceTemplate) *IssuanceTemplate { } func (s *Service) DeleteIssuanceTemplate(request *DeleteIssuanceTemplateRequest) error { + if err := s.storage.DeleteIssuanceTemplate(request.ID); err != nil { + return errors.Wrap(err, "deleting template from storage") + } return nil } diff --git a/pkg/service/issuing/storage.go b/pkg/service/issuing/storage.go index cf9a463e1..c5299624a 100644 --- a/pkg/service/issuing/storage.go +++ b/pkg/service/issuing/storage.go @@ -53,3 +53,13 @@ func (s Storage) GetIssuanceTemplate(id string) (*StoredIssuanceTemplate, error) } return &st, nil } + +func (s Storage) DeleteIssuanceTemplate(id string) error { + if id == "" { + return nil + } + if err := s.db.Delete(namespace, id); err != nil { + return errors.Wrap(err, "deleting from db") + } + return nil +} From 7db7306392d1bb0dd84660d47f924d871bea4eff Mon Sep 17 00:00:00 2001 From: Andres Uribe Gonzalez Date: Thu, 22 Dec 2022 16:58:40 -0500 Subject: [PATCH 4/8] ListTemplates implementation --- pkg/server/router/issuance.go | 16 ++++++-- pkg/server/server_issuance_test.go | 66 ++++++++++++++++++++++++++++++ pkg/service/issuing/model.go | 4 ++ pkg/service/issuing/service.go | 14 ++++++- pkg/service/issuing/storage.go | 16 ++++++++ 5 files changed, 111 insertions(+), 5 deletions(-) diff --git a/pkg/server/router/issuance.go b/pkg/server/router/issuance.go index 5b9a560d8..d1bd7268f 100644 --- a/pkg/server/router/issuance.go +++ b/pkg/server/router/issuance.go @@ -34,7 +34,7 @@ func NewIssuanceRouter(svc svcframework.Service) (*IssuanceRouter, error) { // @Success 200 {object} issuing.IssuanceTemplate // @Failure 400 {string} string "Bad request" // @Router /v1/issuancetemplates/{id} [get] -func (ir IssuanceRouter) GetIssuanceTemplate(ctx context.Context, w http.ResponseWriter, req *http.Request) error { +func (ir IssuanceRouter) GetIssuanceTemplate(ctx context.Context, w http.ResponseWriter, _ *http.Request) error { id := framework.GetParam(ctx, IDParam) if id == nil { return framework.NewRequestError( @@ -120,7 +120,7 @@ type ListIssuanceTemplatesResponse struct { // ListIssuanceTemplates godoc // @Summary Lists issuance templates -// @Description Lists all issuangce templates stored in this service. +// @Description Lists all issuance templates stored in this service. // @Tags IssuingAPI // @Accept json // @Produce json @@ -128,6 +128,14 @@ type ListIssuanceTemplatesResponse struct { // @Failure 400 {string} string "Bad request" // @Failure 500 {string} string "Internal server error" // @Router /v1/manifests [get] -func (ir IssuanceRouter) ListIssuanceTemplates(ctx context.Context, w http.ResponseWriter, r *http.Request) error { - return nil +func (ir IssuanceRouter) ListIssuanceTemplates(ctx context.Context, w http.ResponseWriter, _ *http.Request) error { + gotManifests, err := ir.service.ListIssuanceTemplates(&issuing.ListIssuanceTemplatesRequest{}) + + if err != nil { + return framework.NewRequestError( + util.LoggingErrorMsg(err, "could not get manifests"), http.StatusBadRequest) + } + + resp := ListIssuanceTemplatesResponse{IssuanceTemplates: gotManifests.IssuanceTemplates} + return framework.Respond(ctx, w, resp, http.StatusOK) } diff --git a/pkg/server/server_issuance_test.go b/pkg/server/server_issuance_test.go index ef12a3335..9e8dad652 100644 --- a/pkg/server/server_issuance_test.go +++ b/pkg/server/server_issuance_test.go @@ -310,6 +310,72 @@ func TestIssuanceRouter(t *testing.T) { assert.Error(t, err) assert.Contains(t, err.Error(), "issuance template not found") }) + + t.Run("ListIssuanceTemplates returns empty when there aren't templates", func(t *testing.T) { + s := setupTestDB(t) + r := testIssuanceRouter(t, s) + + value := newRequestValue(t, nil) + req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/issuancetemplates", value) + w := httptest.NewRecorder() + err := r.ListIssuanceTemplates(newRequestContext(), w, req) + + assert.NoError(t, err) + var getIssuanceTemplate router.ListIssuanceTemplatesResponse + assert.NoError(t, json.NewDecoder(w.Body).Decode(&getIssuanceTemplate)) + assert.Empty(t, getIssuanceTemplate.IssuanceTemplates) + }) + + t.Run("ListIssuanceTemplates returns all created templates", func(t *testing.T) { + issuerResp, createdSchema, manifest, r := setupAllThings(t) + + createSimpleTemplate(t, manifest, issuerResp, createdSchema, now, r) + createSimpleTemplate(t, manifest, issuerResp, createdSchema, now, r) + + value := newRequestValue(t, nil) + req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/issuancetemplates", value) + w := httptest.NewRecorder() + err := r.ListIssuanceTemplates(newRequestContext(), w, req) + + assert.NoError(t, err) + var getIssuanceTemplate router.ListIssuanceTemplatesResponse + assert.NoError(t, json.NewDecoder(w.Body).Decode(&getIssuanceTemplate)) + assert.Len(t, getIssuanceTemplate.IssuanceTemplates, 2) + }) +} + +func createSimpleTemplate(t *testing.T, manifest *model.CreateManifestResponse, issuerResp *did.CreateDIDResponse, createdSchema *schema.CreateSchemaResponse, now time.Time, r *router.IssuanceRouter) { + { + request := router.CreateIssuanceTemplateRequest{ + IssuanceTemplate: issuing.IssuanceTemplate{ + CredentialManifest: manifest.Manifest.ID, + Issuer: issuerResp.DID.ID, + Credentials: []issuing.CredentialTemplate{ + { + ID: "output_descriptor_1", + Schema: createdSchema.Schema.ID, + Data: issuing.CredentialTemplateData{ + Claims: issuing.ClaimTemplates{ + Data: map[string]any{ + "foo": "bar", + "hello": "$.vcsomething.something", + }, + }, + }, + Expiry: issuing.TimeLike{ + Time: &now, + }, + }, + }, + }, + } + value := newRequestValue(t, request) + req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/issuancetemplates", value) + w := httptest.NewRecorder() + + err := r.CreateIssuanceTemplate(newRequestContext(), w, req) + assert.NoError(t, err) + } } func setupAllThings(t *testing.T) (*did.CreateDIDResponse, *schema.CreateSchemaResponse, *model.CreateManifestResponse, *router.IssuanceRouter) { diff --git a/pkg/service/issuing/model.go b/pkg/service/issuing/model.go index 3fe9e6339..0c1edf6e0 100644 --- a/pkg/service/issuing/model.go +++ b/pkg/service/issuing/model.go @@ -94,6 +94,10 @@ type ListIssuanceTemplatesRequest struct { Filter filtering.Filter } +func (r ListIssuanceTemplatesRequest) Validate() error { + return util.NewValidator().Struct(r) +} + type ListIssuanceTemplatesResponse struct { // The issuance templates that satisfy the query conditions. IssuanceTemplates []IssuanceTemplate `json:"issuanceTemplates"` diff --git a/pkg/service/issuing/service.go b/pkg/service/issuing/service.go index 2c8950ac0..aa17bd6bc 100644 --- a/pkg/service/issuing/service.go +++ b/pkg/service/issuing/service.go @@ -94,7 +94,19 @@ func (s *Service) DeleteIssuanceTemplate(request *DeleteIssuanceTemplateRequest) } func (s *Service) ListIssuanceTemplates(request *ListIssuanceTemplatesRequest) (*ListIssuanceTemplatesResponse, error) { - return nil, nil + if err := request.Validate(); err != nil { + return nil, errors.Wrap(err, "invalid request") + } + + ops, err := s.storage.ListIssuanceTemplates() + if err != nil { + return nil, errors.Wrap(err, "fetching ops from storage") + } + + resp := &ListIssuanceTemplatesResponse{ + IssuanceTemplates: ops, + } + return resp, nil } func (s *Service) Type() framework.Type { diff --git a/pkg/service/issuing/storage.go b/pkg/service/issuing/storage.go index c5299624a..0111992b8 100644 --- a/pkg/service/issuing/storage.go +++ b/pkg/service/issuing/storage.go @@ -63,3 +63,19 @@ func (s Storage) DeleteIssuanceTemplate(id string) error { } return nil } + +func (s Storage) ListIssuanceTemplates() ([]IssuanceTemplate, error) { + m, err := s.db.ReadAll(namespace) + if err != nil { + return nil, errors.Wrap(err, "reading all") + } + ts := make([]IssuanceTemplate, len(m)) + i := 0 + for k, v := range m { + if err = json.Unmarshal(v, &ts[i]); err != nil { + return nil, errors.Wrapf(err, "unmarshalling template with key <%s>", k) + } + i++ + } + return ts, nil +} From ab50a7aafcbd9636c35846557cd1c3d44b811ed0 Mon Sep 17 00:00:00 2001 From: Andres Uribe Gonzalez Date: Thu, 22 Dec 2022 17:21:55 -0500 Subject: [PATCH 5/8] Better name for pkg --- pkg/service/issuing/service.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/service/issuing/service.go b/pkg/service/issuing/service.go index aa17bd6bc..e60d3fe36 100644 --- a/pkg/service/issuing/service.go +++ b/pkg/service/issuing/service.go @@ -4,24 +4,24 @@ import ( "github.com/pkg/errors" "github.com/tbd54566975/ssi-service/config" "github.com/tbd54566975/ssi-service/pkg/service/framework" - "github.com/tbd54566975/ssi-service/pkg/service/manifest/storage" + manifeststg "github.com/tbd54566975/ssi-service/pkg/service/manifest/storage" "github.com/tbd54566975/ssi-service/pkg/service/schema" - storage2 "github.com/tbd54566975/ssi-service/pkg/storage" + "github.com/tbd54566975/ssi-service/pkg/storage" ) type Service struct { config config.IssuingServiceConfig storage Storage - manifestStorage storage.Storage + manifestStorage manifeststg.Storage schemaStorage schema.Storage } -func NewIssuingService(config config.IssuingServiceConfig, s storage2.ServiceStorage) (*Service, error) { +func NewIssuingService(config config.IssuingServiceConfig, s storage.ServiceStorage) (*Service, error) { issuingStorage, err := NewIssuingStorage(s) if err != nil { return nil, errors.Wrap(err, "creating issuing storage") } - manifestStorage, err := storage.NewManifestStorage(s) + manifestStorage, err := manifeststg.NewManifestStorage(s) if err != nil { return nil, errors.Wrap(err, "creating manifest storage") } From 9170423c7437f4f8956737d03def5faab2cfcbe9 Mon Sep 17 00:00:00 2001 From: Andres Uribe Gonzalez Date: Thu, 22 Dec 2022 17:34:10 -0500 Subject: [PATCH 6/8] Schema validation only when present. --- pkg/server/server_issuance_test.go | 90 +++++++++++++++++++++--------- pkg/service/issuing/service.go | 6 +- 2 files changed, 67 insertions(+), 29 deletions(-) diff --git a/pkg/server/server_issuance_test.go b/pkg/server/server_issuance_test.go index 9e8dad652..8eea0f58c 100644 --- a/pkg/server/server_issuance_test.go +++ b/pkg/server/server_issuance_test.go @@ -25,42 +25,78 @@ import ( func TestIssuanceRouter(t *testing.T) { now := time.Now() duration := 10 * time.Second - t.Run("CreateIssuanceTemplate returns a template with ID", func(t *testing.T) { + t.Run("CreateIssuanceTemplate", func(t *testing.T) { issuerResp, createdSchema, manifest, r := setupAllThings(t) - - request := router.CreateIssuanceTemplateRequest{ - IssuanceTemplate: issuing.IssuanceTemplate{ - CredentialManifest: manifest.Manifest.ID, - Issuer: issuerResp.DID.ID, - Credentials: []issuing.CredentialTemplate{ - { - ID: "output_descriptor_1", - Schema: createdSchema.Schema.ID, - Data: issuing.CredentialTemplateData{ - Claims: issuing.ClaimTemplates{ - Data: map[string]any{ - "foo": "bar", - "hello": "$.vcsomething.something", + for _, tc := range []struct { + name string + request router.CreateIssuanceTemplateRequest + }{ + { + name: "returns a template with ID", + request: router.CreateIssuanceTemplateRequest{ + IssuanceTemplate: issuing.IssuanceTemplate{ + CredentialManifest: manifest.Manifest.ID, + Issuer: issuerResp.DID.ID, + Credentials: []issuing.CredentialTemplate{ + { + ID: "output_descriptor_1", + Schema: createdSchema.Schema.ID, + Data: issuing.CredentialTemplateData{ + Claims: issuing.ClaimTemplates{ + Data: map[string]any{ + "foo": "bar", + "hello": "$.vcsomething.something", + }, + }, + }, + Expiry: issuing.TimeLike{ + Time: &now, }, }, }, - Expiry: issuing.TimeLike{ - Time: &now, + }, + }, + }, + { + name: "returns a template with ID when schema is empty", + request: router.CreateIssuanceTemplateRequest{ + IssuanceTemplate: issuing.IssuanceTemplate{ + CredentialManifest: manifest.Manifest.ID, + Issuer: issuerResp.DID.ID, + Credentials: []issuing.CredentialTemplate{ + { + ID: "output_descriptor_1", + Schema: "", + Data: issuing.CredentialTemplateData{ + Claims: issuing.ClaimTemplates{ + Data: map[string]any{ + "foo": "bar", + "hello": "$.vcsomething.something", + }, + }, + }, + Expiry: issuing.TimeLike{ + Time: &now, + }, + }, }, }, }, }, - } - value := newRequestValue(t, request) - req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/issuancetemplates", value) - w := httptest.NewRecorder() + } { + t.Run(tc.name, func(t *testing.T) { + value := newRequestValue(t, tc.request) + req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/issuancetemplates", value) + w := httptest.NewRecorder() - err := r.CreateIssuanceTemplate(newRequestContext(), w, req) - assert.NoError(t, err) + err := r.CreateIssuanceTemplate(newRequestContext(), w, req) + assert.NoError(t, err) - var resp issuing.IssuanceTemplate - assert.NoError(t, json.NewDecoder(w.Body).Decode(&resp)) - assert.NotEmpty(t, resp.ID) + var resp issuing.IssuanceTemplate + assert.NoError(t, json.NewDecoder(w.Body).Decode(&resp)) + assert.NotEmpty(t, resp.ID) + }) + } }) t.Run("CreateIssuanceTemplate returns error", func(t *testing.T) { @@ -127,7 +163,7 @@ func TestIssuanceRouter(t *testing.T) { expectedError: "Time and Duration cannot be both set simultaneously", }, { - name: "when schema does not exist", + name: "when credential schema does not exist", request: router.CreateIssuanceTemplateRequest{ IssuanceTemplate: issuing.IssuanceTemplate{ CredentialManifest: manifest.Manifest.ID, diff --git a/pkg/service/issuing/service.go b/pkg/service/issuing/service.go index e60d3fe36..e63d22d88 100644 --- a/pkg/service/issuing/service.go +++ b/pkg/service/issuing/service.go @@ -61,8 +61,10 @@ func (s *Service) CreateIssuanceTemplate(request *CreateIssuanceTemplateRequest) if c.ID == "" { return nil, errors.Errorf("ID cannot be empty at index %d", i) } - if _, err := s.schemaStorage.GetSchema(c.Schema); err != nil { - return nil, errors.Wrapf(err, "getting schema at index %d", i) + if c.Schema != "" { + if _, err := s.schemaStorage.GetSchema(c.Schema); err != nil { + return nil, errors.Wrapf(err, "getting schema at index %d", i) + } } } From 3d2f409a9988e0556a6c4e06ad61d6204ecc2224 Mon Sep 17 00:00:00 2001 From: Andres Uribe Gonzalez Date: Tue, 3 Jan 2023 14:37:09 -0500 Subject: [PATCH 7/8] typos --- pkg/server/router/issuance.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/server/router/issuance.go b/pkg/server/router/issuance.go index d1bd7268f..594b57c33 100644 --- a/pkg/server/router/issuance.go +++ b/pkg/server/router/issuance.go @@ -103,12 +103,12 @@ func (ir IssuanceRouter) DeleteIssuanceTemplate(ctx context.Context, w http.Resp id := framework.GetParam(ctx, IDParam) if id == nil { return framework.NewRequestError( - util.LoggingNewError("cannot delete a presentation without an ID parameter"), http.StatusBadRequest) + util.LoggingNewError("cannot delete an issuance template without an ID parameter"), http.StatusBadRequest) } if err := ir.service.DeleteIssuanceTemplate(&issuing.DeleteIssuanceTemplateRequest{ID: *id}); err != nil { return framework.NewRequestError( - util.LoggingErrorMsgf(err, "could not delete presentation with id: %s", *id), http.StatusInternalServerError) + util.LoggingErrorMsgf(err, "could not delete issuance template with id: %s", *id), http.StatusInternalServerError) } return framework.Respond(ctx, w, nil, http.StatusOK) @@ -133,7 +133,7 @@ func (ir IssuanceRouter) ListIssuanceTemplates(ctx context.Context, w http.Respo if err != nil { return framework.NewRequestError( - util.LoggingErrorMsg(err, "could not get manifests"), http.StatusBadRequest) + util.LoggingErrorMsg(err, "could not get templates"), http.StatusBadRequest) } resp := ListIssuanceTemplatesResponse{IssuanceTemplates: gotManifests.IssuanceTemplates} From db0bac224b78895aecfd77227b22b32d338df99a Mon Sep 17 00:00:00 2001 From: Andres Uribe Gonzalez Date: Tue, 3 Jan 2023 15:06:29 -0500 Subject: [PATCH 8/8] Moved ID to service layer. --- pkg/server/router/issuance.go | 2 -- pkg/service/issuing/model.go | 4 ---- pkg/service/issuing/service.go | 3 ++- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/pkg/server/router/issuance.go b/pkg/server/router/issuance.go index 594b57c33..10b7c72e1 100644 --- a/pkg/server/router/issuance.go +++ b/pkg/server/router/issuance.go @@ -4,7 +4,6 @@ import ( "context" "net/http" - "github.com/google/uuid" "github.com/pkg/errors" "github.com/tbd54566975/ssi-service/internal/util" "github.com/tbd54566975/ssi-service/pkg/server/framework" @@ -55,7 +54,6 @@ type CreateIssuanceTemplateRequest struct { func (r CreateIssuanceTemplateRequest) ToServiceRequest() *issuing.CreateIssuanceTemplateRequest { return &issuing.CreateIssuanceTemplateRequest{ - ID: uuid.NewString(), IssuanceTemplate: r.IssuanceTemplate, } } diff --git a/pkg/service/issuing/model.go b/pkg/service/issuing/model.go index 0c1edf6e0..af5fb8220 100644 --- a/pkg/service/issuing/model.go +++ b/pkg/service/issuing/model.go @@ -71,10 +71,6 @@ type GetIssuanceTemplateResponse struct { } type CreateIssuanceTemplateRequest struct { - // ID to be used when creating the issuance template. Must be unique. - // Required. - ID string `json:"id" validate:"required"` - // The template to create. IssuanceTemplate IssuanceTemplate `json:"issuanceTemplate"` } diff --git a/pkg/service/issuing/service.go b/pkg/service/issuing/service.go index e63d22d88..2cc223caa 100644 --- a/pkg/service/issuing/service.go +++ b/pkg/service/issuing/service.go @@ -1,6 +1,7 @@ package issuing import ( + "github.com/google/uuid" "github.com/pkg/errors" "github.com/tbd54566975/ssi-service/config" "github.com/tbd54566975/ssi-service/pkg/service/framework" @@ -75,7 +76,7 @@ func (s *Service) CreateIssuanceTemplate(request *CreateIssuanceTemplateRequest) storedTemplate := StoredIssuanceTemplate{ IssuanceTemplate: request.IssuanceTemplate, } - storedTemplate.IssuanceTemplate.ID = request.ID + storedTemplate.IssuanceTemplate.ID = uuid.NewString() if err := s.storage.StoreIssuanceTemplate(storedTemplate); err != nil { return nil, errors.Wrap(err, "storing issuance template")