diff --git a/discoveryservice/api/v1/api.go b/discoveryservice/api/v1/api.go deleted file mode 100644 index 2a90d4c29f..0000000000 --- a/discoveryservice/api/v1/api.go +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2023 Nuts community - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package v1 - -//// lists maps a list name (last path part of use case endpoint) to the list ID -//lists map[string]string -//// name is derived from endpoint: it's the last path part of the definition endpoint -//// It is used to route HTTP GET requests to the correct list. -//pathParts := strings.Split(definition.Endpoint, "/") -//name := pathParts[len(pathParts)-1] -//if name == "" { -//return nil, fmt.Errorf("can't derive list name from definition endpoint: %s", definition.Endpoint) -//} diff --git a/discoveryservice/config.go b/discoveryservice/config.go index d2dd701a96..f6d8647a8a 100644 --- a/discoveryservice/config.go +++ b/discoveryservice/config.go @@ -33,8 +33,6 @@ type DefinitionsConfig struct { type ServerConfig struct { // DefinitionIDs specifies which use case lists the server serves. DefinitionIDs []string `koanf:"definition_ids"` - // Directory is the directory where the server stores the lists. - Directory string `koanf:"directory"` } // DefaultConfig returns the default configuration. diff --git a/discoveryservice/definition.go b/discoveryservice/definition.go index b9af33283b..2919c93e71 100644 --- a/discoveryservice/definition.go +++ b/discoveryservice/definition.go @@ -37,10 +37,11 @@ func init() { panic(err) } const schemaURL = "http://nuts.nl/schemas/discovery-service-v0.json" - if err := v2.Compiler.AddResource(schemaURL, bytes.NewReader(serviceDefinitionSchemaData)); err != nil { + compiler := v2.Compiler() + if err := compiler.AddResource(schemaURL, bytes.NewReader(serviceDefinitionSchemaData)); err != nil { panic(err) } - definitionJsonSchema = v2.Compiler.MustCompile(schemaURL) + definitionJsonSchema = compiler.MustCompile(schemaURL) } // Definition holds the definition of a service. diff --git a/discoveryservice/log/logger.go b/discoveryservice/log/logger.go index 13914edbaf..3a4fcf4b4a 100644 --- a/discoveryservice/log/logger.go +++ b/discoveryservice/log/logger.go @@ -23,7 +23,7 @@ import ( "github.com/sirupsen/logrus" ) -var _logger = logrus.StandardLogger().WithField(core.LogFieldModule, "DiscoveryService") +var _logger = logrus.StandardLogger().WithField(core.LogFieldModule, "Discovery") // Logger returns a logger with the module field set func Logger() *logrus.Entry { diff --git a/discoveryservice/module.go b/discoveryservice/module.go index 93be32ec21..fa12d1ef9d 100644 --- a/discoveryservice/module.go +++ b/discoveryservice/module.go @@ -32,7 +32,7 @@ import ( "time" ) -const ModuleName = "DiscoveryService" +const ModuleName = "Discovery" var ErrServerModeDisabled = errors.New("node is not a discovery server for this service") diff --git a/discoveryservice/module_test.go b/discoveryservice/module_test.go index 416c85258a..0a4af7aae6 100644 --- a/discoveryservice/module_test.go +++ b/discoveryservice/module_test.go @@ -31,7 +31,7 @@ import ( const serviceID = "urn:nuts.nl:usecase:eOverdrachtDev2023" func TestModule_Name(t *testing.T) { - assert.Equal(t, "DiscoveryService", (&Module{}).Name()) + assert.Equal(t, "Discovery", (&Module{}).Name()) } func TestModule_Shutdown(t *testing.T) { diff --git a/discoveryservice/store.go b/discoveryservice/store.go index 98bf3b8c49..2b805c5f20 100644 --- a/discoveryservice/store.go +++ b/discoveryservice/store.go @@ -39,12 +39,12 @@ var ErrServiceNotFound = errors.New("discovery service not found") var ErrPresentationAlreadyExists = errors.New("presentation already exists") type serviceRecord struct { - ID string `gorm:"primaryKey"` - Timestamp uint64 + ID string `gorm:"primaryKey"` + LamportTimestamp uint64 } func (s serviceRecord) TableName() string { - return "discoveryservices" + return "discovery_service" } var _ schema.Tabler = (*presentationRecord)(nil) @@ -52,7 +52,7 @@ var _ schema.Tabler = (*presentationRecord)(nil) type presentationRecord struct { ID string `gorm:"primaryKey"` ServiceID string - Timestamp uint64 + LamportTimestamp uint64 CredentialSubjectID string PresentationID string PresentationRaw string @@ -61,14 +61,14 @@ type presentationRecord struct { } func (s presentationRecord) TableName() string { - return "discoveryservice_presentations" + return "discovery_presentation" } // credentialRecord is a Verifiable Credential, part of a presentation (entry) on a use case list. type credentialRecord struct { // ID is the unique identifier of the entry. ID string `gorm:"primaryKey"` - // PresentationID corresponds to the discoveryservice_presentations record ID (not VerifiablePresentation.ID) this credentialRecord belongs to. + // PresentationID corresponds to the discovery_presentation record ID (not VerifiablePresentation.ID) this credentialRecord belongs to. PresentationID string // CredentialID contains the 'id' property of the Verifiable Credential. CredentialID string @@ -78,18 +78,18 @@ type credentialRecord struct { CredentialSubjectID string // CredentialType contains the 'type' property of the Verifiable Credential (not being 'VerifiableCredential'). CredentialType *string - Properties []credentialPropertyRecord `gorm:"foreignKey:ID;references:ID"` + Properties []credentialPropertyRecord `gorm:"foreignKey:CredentialID;references:ID"` } // TableName returns the table name for this DTO. func (p credentialRecord) TableName() string { - return "discoveryservice_credentials" + return "discovery_credential" } // credentialPropertyRecord is a property of a Verifiable Credential in a Verifiable Presentation in a discovery service. type credentialPropertyRecord struct { - // ID refers to the entry record in discoveryservice_credentials - ID string `gorm:"primaryKey"` + // CredentialID refers to the entry record in discovery_credential + CredentialID string `gorm:"primaryKey"` // Key is JSON path of the property. Key string `gorm:"primaryKey"` // Value is the value of the property. @@ -98,7 +98,7 @@ type credentialPropertyRecord struct { // TableName returns the table name for this DTO. func (l credentialPropertyRecord) TableName() string { - return "discoveryservice_credential_props" + return "discovery_credential_prop" } type sqlStore struct { @@ -183,7 +183,7 @@ func createPresentationRecord(serviceID string, timestamp Timestamp, presentatio newPresentation := presentationRecord{ ID: uuid.NewString(), ServiceID: serviceID, - Timestamp: uint64(timestamp), + LamportTimestamp: uint64(timestamp), CredentialSubjectID: credentialSubjectID.String(), PresentationID: presentation.ID.String(), PresentationRaw: presentation.Raw(), @@ -219,9 +219,9 @@ func createPresentationRecord(serviceID string, timestamp Timestamp, presentatio continue } newCredential.Properties = append(newCredential.Properties, credentialPropertyRecord{ - ID: newCredential.ID, - Key: key, - Value: values[i], + CredentialID: newCredential.ID, + Key: key, + Value: values[i], }) } newPresentation.Credentials = append(newPresentation.Credentials, newCredential) @@ -231,7 +231,7 @@ func createPresentationRecord(serviceID string, timestamp Timestamp, presentatio func (s *sqlStore) get(serviceID string, startAt Timestamp) ([]vc.VerifiablePresentation, *Timestamp, error) { var rows []presentationRecord - err := s.db.Order("timestamp ASC").Find(&rows, "service_id = ? AND timestamp > ?", serviceID, int(startAt)).Error + err := s.db.Order("lamport_timestamp ASC").Find(&rows, "service_id = ? AND lamport_timestamp > ?", serviceID, int(startAt)).Error if err != nil { return nil, nil, fmt.Errorf("query service '%s': %w", serviceID, err) } @@ -243,7 +243,7 @@ func (s *sqlStore) get(serviceID string, startAt Timestamp) ([]vc.VerifiablePres return nil, nil, fmt.Errorf("parse presentation '%s' of service '%s': %w", row.PresentationID, serviceID, err) } presentations = append(presentations, *presentation) - timestamp = Timestamp(row.Timestamp) + timestamp = Timestamp(row.LamportTimestamp) } return presentations, ×tamp, nil } @@ -258,7 +258,7 @@ func (s *sqlStore) search(serviceID string, query map[string]string) ([]vc.Verif stmt := s.db.Model(&presentationRecord{}). Where("service_id = ?", serviceID). - Joins("inner join discoveryservice_credentials cred ON cred.presentation_id = discoveryservice_presentations.id") + Joins("inner join discovery_credential cred ON cred.presentation_id = discovery_presentation.id") numProps := 0 for jsonPath, value := range query { if value == "*" { @@ -281,7 +281,7 @@ func (s *sqlStore) search(serviceID string, query map[string]string) ([]vc.Verif // Multiple (inner) joins to filter on a dynamic number of properties to filter on is not pretty, but it works alias := "p" + strconv.Itoa(numProps) numProps++ - stmt = stmt.Joins("inner join discoveryservice_credential_props "+alias+" ON "+alias+".id = cred.id AND "+alias+".key = ? AND "+alias+".value "+eq+" ?", jsonPath, value) + stmt = stmt.Joins("inner join discovery_credential_prop "+alias+" ON "+alias+".credential_id = cred.id AND "+alias+".key = ? AND "+alias+".value "+eq+" ?", jsonPath, value) } } @@ -305,7 +305,7 @@ func (s *sqlStore) search(serviceID string, query map[string]string) ([]vc.Verif func (s *sqlStore) updateTimestamp(tx *gorm.DB, serviceID string, newTimestamp *Timestamp) (Timestamp, error) { var result serviceRecord - // Lock (SELECT FOR UPDATE) discoveryservices row to prevent concurrent updates to the same list, which could mess up the lamport timestamp. + // Lock (SELECT FOR UPDATE) discoveryservice row to prevent concurrent updates to the same list, which could mess up the lamport timestamp. // But, it is not supported by SQLite. SQLite defaults to table-level write-locks upon the first write action in the transaction. // if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). @@ -317,14 +317,14 @@ func (s *sqlStore) updateTimestamp(tx *gorm.DB, serviceID string, newTimestamp * result.ID = serviceID if newTimestamp == nil { // Increment timestamp - result.Timestamp++ + result.LamportTimestamp++ } else { - result.Timestamp = uint64(*newTimestamp) + result.LamportTimestamp = uint64(*newTimestamp) } if err := tx.Save(&result).Error; err != nil { return 0, err } - return Timestamp(result.Timestamp), nil + return Timestamp(result.LamportTimestamp), nil } func (s *sqlStore) exists(serviceID string, credentialSubjectID string, presentationID string) (bool, error) { diff --git a/discoveryservice/store_test.go b/discoveryservice/store_test.go index afea3c33b7..463f78a646 100644 --- a/discoveryservice/store_test.go +++ b/discoveryservice/store_test.go @@ -343,7 +343,7 @@ func resetStore(t *testing.T, db *gorm.DB) { underlyingDB, err := db.DB() require.NoError(t, err) // related tables are emptied due to on-delete-cascade clause - _, err = underlyingDB.Exec("DELETE FROM discoveryservices") + _, err = underlyingDB.Exec("DELETE FROM discovery_service") require.NoError(t, err) } diff --git a/storage/sql_migrations/2_discoveryservice.up.sql b/storage/sql_migrations/2_discoveryservice.up.sql index bfd78e9937..4f7dc05414 100644 --- a/storage/sql_migrations/2_discoveryservice.up.sql +++ b/storage/sql_migrations/2_discoveryservice.up.sql @@ -1,49 +1,49 @@ --- discoveryservices contains the known discovery services and the highest timestamp -create table discoveryservices +-- discovery contains the known discovery services and the highest timestamp +create table discovery_service ( - id text not null primary key, - timestamp integer not null + id varchar(36) not null primary key, + lamport_timestamp integer not null ); --- discoveryservice_presentations contains the presentations of the discovery services -create table discoveryservice_presentations +-- discovery_presentation contains the presentations of the discovery services +create table discovery_presentation ( - id text not null primary key, - service_id text not null, - timestamp integer not null, - credential_subject_id text not null, - presentation_id text not null, - presentation_raw text not null, - presentation_expiration integer not null, + id varchar(36) not null primary key, + service_id varchar(36) not null, + lamport_timestamp integer not null, + credential_subject_id varchar not null, + presentation_id varchar not null, + presentation_raw varchar not null, + presentation_expiration integer not null, unique (service_id, credential_subject_id), - constraint fk_discovery_presentation_service_id foreign key (service_id) references discoveryservices (id) on delete cascade + constraint fk_discovery_presentation_service_id foreign key (service_id) references discovery_service (id) on delete cascade ); --- discoveryservice_credentials is a credential in a presentation of the discovery service. +-- discovery_credential is a credential in a presentation of the discovery service. -- We could do without the table, but having it allows to have a normalized index for credential properties that appear on every credential. -- Then we don't need rows in the properties table for them (having a column for those is faster than having a row in the properties table which needs to be joined). -create table discoveryservice_credentials +create table discovery_credential ( - id text not null primary key, - presentation_id text not null, - credential_id text not null, - credential_issuer text not null, - credential_subject_id text not null, + id varchar(36) not null primary key, + presentation_id varchar(36) not null, + credential_id varchar not null, + credential_issuer varchar not null, + credential_subject_id varchar not null, -- for now, credentials with at most 2 types are supported. -- The type stored in the type column will be the 'other' type, not being 'VerifiableCredential'. -- When credentials with 3 or more types appear, we could have to use a separate table for the types. - credential_type text, - constraint fk_discoveryservice_credential_presentation foreign key (presentation_id) references discoveryservice_presentations (id) on delete cascade + credential_type varchar, + constraint fk_discoveryservice_credential_presentation foreign key (presentation_id) references discovery_presentation (id) on delete cascade ); --- discoveryservice_credential_props contains the credentialSubject properties of a credential in a presentation of the discovery service. +-- discovery_credential_prop contains the credentialSubject properties of a credential in a presentation of the discovery service. -- It is used by clients to search for presentations. -create table discoveryservice_credential_props +create table discovery_credential_prop ( - id text not null, - key text not null, - value text, - PRIMARY KEY (id, key), + credential_id varchar(36) not null, + key varchar not null, + value varchar, + PRIMARY KEY (credential_id, key), -- cascading delete: if the presentation gets deleted, the properties get deleted as well - constraint fk_discoveryservice_credential_id foreign key (id) references discoveryservice_credentials (id) on delete cascade + constraint fk_discoveryservice_credential_id foreign key (credential_id) references discovery_credential (id) on delete cascade ); \ No newline at end of file diff --git a/vcr/pe/schema/v2/schema.go b/vcr/pe/schema/v2/schema.go index 8c3da1a2bc..dd4c1ce59a 100644 --- a/vcr/pe/schema/v2/schema.go +++ b/vcr/pe/schema/v2/schema.go @@ -51,8 +51,17 @@ var PresentationDefinition *jsonschema.Schema // PresentationSubmission is the JSON schema for a presentation submission. var PresentationSubmission *jsonschema.Schema -// Compiler is the JSON schema compiler. -var Compiler = jsonschema.NewCompiler() +// Compiler returns a JSON schema compiler with the Presentation Exchange schemas loaded. +func Compiler() *jsonschema.Compiler { + compiler := jsonschema.NewCompiler() + compiler.Draft = jsonschema.Draft7 + if err := loadSchemas(schemaFiles, compiler); err != nil { + panic(err) + } + PresentationDefinition = compiler.MustCompile(presentationDefinition) + PresentationSubmission = compiler.MustCompile(presentationSubmission) + return compiler +} func init() { // By default, it loads from filesystem, but that sounds unsafe. @@ -60,12 +69,9 @@ func init() { loader.Load = func(url string) (io.ReadCloser, error) { return nil, fmt.Errorf("refusing to load unknown schema: %s", url) } - Compiler.Draft = jsonschema.Draft7 - if err := loadSchemas(schemaFiles, Compiler); err != nil { - panic(err) - } - PresentationDefinition = Compiler.MustCompile(presentationDefinition) - PresentationSubmission = Compiler.MustCompile(presentationSubmission) + compiler := Compiler() + PresentationDefinition = compiler.MustCompile(presentationDefinition) + PresentationSubmission = compiler.MustCompile(presentationSubmission) } func loadSchemas(reader fs.ReadFileFS, compiler *jsonschema.Compiler) error {