Created
April 25, 2023 10:46
-
-
Save ianaz/3b58b93982e76bb12f9e594a10488c43 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/main.go b/main.go | |
index 5eeaadeb..012c19dd 100644 | |
--- a/main.go | |
+++ b/main.go | |
@@ -339,9 +339,7 @@ func initFactory(ctx context.Context) (deps endpoints.AppDependencies, err error | |
} | |
settingService := setting.NewSettingService(config, db, repository.NewSqlSettingRepository(db), organizationService, fileObjectStorageService, featureService) | |
settingService.AddObserver(observers.NewOrganization2faSettingObserver(organizationService, roleRepository, auth0Client)) | |
- if config.FF_ConfigurableDeletionEnabled { | |
- settingService.AddObserver(observers.NewOrganizationDeletionTimelineObserver(dbTransactionManager, signatureRepository)) | |
- } | |
+ settingService.AddObserver(observers.NewOrganizationDeletionTimelineObserver(dbTransactionManager, signatureRepository, config.FF_ConfigurableDeletionEnabled)) | |
fileService := file.NewFileService(config, fileRepository, organizationService, settingService, shortenerRepository) | |
diff --git a/service/observers/organization_deletion_timeline_observer.go b/service/observers/organization_deletion_timeline_observer.go | |
index 21e06957..02b09f2b 100644 | |
--- a/service/observers/organization_deletion_timeline_observer.go | |
+++ b/service/observers/organization_deletion_timeline_observer.go | |
@@ -17,187 +17,98 @@ import ( | |
type organizationDeletionTimelineObserver struct { | |
transactionManager dbRepository.DBTransactionManager | |
signatureRepo signaturesRepository.Repository | |
- getNowTime func() time.Time | |
+ active bool | |
} | |
-func NewOrganizationDeletionTimelineObserver(transactionManager dbRepository.DBTransactionManager, signatureRepo signaturesRepository.Repository) *organizationDeletionTimelineObserver { | |
+func NewOrganizationDeletionTimelineObserver( | |
+ transactionManager dbRepository.DBTransactionManager, | |
+ signatureRepo signaturesRepository.Repository, | |
+ active bool, | |
+) *organizationDeletionTimelineObserver { | |
return &organizationDeletionTimelineObserver{ | |
transactionManager: transactionManager, | |
signatureRepo: signatureRepo, | |
- getNowTime: func() time.Time { | |
- return time.Now() | |
- }, | |
+ active: active, | |
} | |
} | |
var neverExpireTime = time.Date(9999, 12, 31, 0, 0, 0, 0, time.UTC) | |
-func (me *organizationDeletionTimelineObserver) Notify(ctx context.Context, event observable.Event) error { | |
+func (me *organizationDeletionTimelineObserver) Notify(ctx context.Context, Event observable.Event) error { | |
- newSetting, ok := event.GetContent().(model.EntitySetting) | |
+ if !me.active { | |
+ return nil | |
+ } | |
+ | |
+ event, ok := Event.GetContent().(model.EntitySetting) | |
if !ok { | |
log.ErrorCtx(ctx, "Deletion Timeline Observer Unable to convert event content") | |
return errors.New("invalid event content passed") | |
} | |
- if newSetting.Setting.ID != model.SettingDeletionTimeline { | |
- log.DebugfCtx(ctx, "Skipping Deletion Timeline update for the setting: %s", newSetting.Setting.ID) | |
+ if event.Setting.ID != model.SettingDeletionTimeline { | |
+ log.DebugfCtx(ctx, "Skipping Deletion Timeline update for the setting: %s", event.Setting.ID) | |
return nil | |
} | |
- newDurationDays, err := strconv.Atoi(newSetting.GetValue()) | |
+ deletionTimeline, err := strconv.Atoi(event.GetValue()) | |
if err != nil { | |
log.ErrorCtx(ctx, "Unable to convert file expiry into integer") | |
return err | |
} | |
- if newDurationDays == timelineNeverDelete { | |
+ if deletionTimeline < 0 { | |
log.DebugfCtx(ctx, "Skipping deleting previous files. Timeline is set to never delete") | |
return nil | |
} | |
- previousSetting, ok := event.GetBeforeContent().(model.EntitySetting) | |
- if !ok { | |
- log.ErrorCtx(ctx, "Encountered a different type than EntitySetting: %+v", event.GetBeforeContent()) | |
- return err | |
- } | |
- | |
- previousDurationDays, err := strconv.Atoi(previousSetting.GetValue()) | |
- if err != nil { | |
- log.ErrorfCtx(ctx, "Unable to convert previous setting file expiry into integer: %s", err, previousSetting.GetValue()) | |
- return err | |
- } | |
+ fileExpiryDate := time.Now().AddDate(0, 0, deletionTimeline) | |
ctx, err = me.transactionManager.BeginTransaction(ctx, sql.LevelReadCommitted) | |
if err != nil { | |
return err | |
} | |
- err = me.updateAsNeeded(ctx, durationChange{previousDurationDays, newDurationDays}, newSetting.EntityID) | |
+ activeOrganizationSignatures, err := me.signatureRepo.FindActiveOrganizationSignatures(ctx, event.EntityID) | |
if err != nil { | |
me.transactionManager.Rollback(ctx) | |
return err | |
} | |
- ctx, err = me.transactionManager.Commit(ctx) | |
- if err != nil { | |
- return err | |
- } | |
- | |
- return nil | |
-} | |
- | |
-func (me *organizationDeletionTimelineObserver) updateAsNeeded(ctx context.Context, change durationChange, entityID string) error { | |
- if change.IsTimelineIncreased() || change.IsFromDeleteInstantlyToTimeline() || change.IsFromDeleteInstantlyToNeverDelete() { | |
- // Do nothing, only takes effect going forward | |
- return nil | |
- } | |
- | |
- newExpiryDate := me.getNowTime().AddDate(0, 0, change.newDurationDays) | |
- if change.IsToNeverDelete() { | |
- newExpiryDate = neverExpireTime | |
- } | |
- if change.IsToDeleteInstantly() { | |
- newExpiryDate = me.getNowTime() | |
- } | |
- | |
- var signatureRequestsToUpdate []model.SignatureRequest | |
- var err error | |
- | |
- signatureRequestsToUpdate, err = me.signatureRepo.FindCompletedForOrganization(ctx, entityID) | |
- if err != nil { | |
- return err | |
- } | |
- | |
- return me.clearThemUp(ctx, signatureRequestsToUpdate, newExpiryDate) | |
-} | |
- | |
-func (me *organizationDeletionTimelineObserver) clearThemUp(ctx context.Context, signatureRequests []model.SignatureRequest, newExpiryDate time.Time) error { | |
- for _, originalSignatureRequest := range signatureRequests { | |
- | |
- signatureRequest := originalSignatureRequest | |
- | |
- signatureRequest.FileURLExpiry = newExpiryDate | |
- if me.getNowTime().After(newExpiryDate) || me.getNowTime().Equal(newExpiryDate) { | |
- signatureRequest.FileURL = "" | |
- } | |
+ for _, activeOrganizationSignature := range activeOrganizationSignatures { | |
- for i, envelopeItem := range originalSignatureRequest.EnvelopeItems { | |
- envelopeItem.FileURLExpiry = newExpiryDate | |
- if me.getNowTime().After(newExpiryDate) || me.getNowTime().Equal(newExpiryDate) { | |
- envelopeItem.FileURL = "" | |
+ for i, envelopItem := range activeOrganizationSignature.EnvelopeItems { | |
+ // If the file expiry comes after the new file Expiry Date delete the name and URl | |
+ if envelopItem.FileURLExpiry.Equal(neverExpireTime) { | |
+ envelopItem.FileURLExpiry = fileExpiryDate | |
+ } else if envelopItem.FileURLExpiry.After(fileExpiryDate) { | |
+ // If the expiry is before the new fileExpiry set the new expiry date | |
+ envelopItem.FileURL = "" | |
} | |
- signatureRequest.EnvelopeItems[i] = envelopeItem | |
+ activeOrganizationSignature.EnvelopeItems[i] = envelopItem | |
} | |
- sr := signatureRequest | |
- err := me.signatureRepo.Update(ctx, &sr) | |
+ // If the file expiry comes after the new file Expiry Date delete the name and URl | |
+ if activeOrganizationSignature.FileURLExpiry.Equal(neverExpireTime) { | |
+ activeOrganizationSignature.FileURLExpiry = fileExpiryDate | |
+ } else if activeOrganizationSignature.FileURLExpiry.After(fileExpiryDate) { | |
+ // If the expiry is before the new fileExpiry set the new expiry date | |
+ activeOrganizationSignature.Filename = "" | |
+ activeOrganizationSignature.FileURL = "" | |
+ } | |
+ err = me.signatureRepo.Update(ctx, &activeOrganizationSignature) | |
if err != nil { | |
log.ErrorfCtx(ctx, "Update signature request file url: %s", err) | |
+ me.transactionManager.Rollback(ctx) | |
return err | |
} | |
} | |
- return nil | |
-} | |
- | |
-func (me *organizationDeletionTimelineObserver) setNowTime(getNowTime func() time.Time) { | |
- me.getNowTime = getNowTime | |
-} | |
- | |
-const ( | |
- timelineNeverDelete = -1 | |
- timelineDeleteInstantly = 0 | |
-) | |
- | |
-type durationChange struct { | |
- previousDurationDays, newDurationDays int | |
-} | |
- | |
-func (me durationChange) NewDurationChange(previous, new int) durationChange { | |
- return durationChange{ | |
- previousDurationDays: previous, | |
- newDurationDays: new, | |
+ ctx, err = me.transactionManager.Commit(ctx) | |
+ if err != nil { | |
+ return err | |
} | |
-} | |
- | |
-func (me durationChange) IsTimelineIncreased() bool { | |
- return me.previousDurationDays > 0 && me.newDurationDays > me.previousDurationDays | |
-} | |
-func (me durationChange) IsFromDeleteInstantlyToTimeline() bool { | |
- return me.previousDurationDays == timelineDeleteInstantly && me.newDurationDays > 0 | |
-} | |
- | |
-func (me durationChange) IsFromDeleteInstantlyToNeverDelete() bool { | |
- return me.previousDurationDays == timelineDeleteInstantly && me.newDurationDays == timelineNeverDelete | |
-} | |
- | |
-func (me durationChange) IsToNeverDelete() bool { | |
- return me.newDurationDays == timelineNeverDelete | |
-} | |
- | |
-func (me durationChange) IsToDeleteInstantly() bool { | |
- return me.newDurationDays == timelineDeleteInstantly | |
-} | |
- | |
-func (me durationChange) IsFromNeverDeleteToDeleteInstantly() bool { | |
- return me.previousDurationDays == timelineNeverDelete && me.newDurationDays == timelineDeleteInstantly | |
-} | |
- | |
-func (me durationChange) IsFromTimelineToDeleteInstantly() bool { | |
- return me.previousDurationDays > 0 && me.newDurationDays == timelineDeleteInstantly | |
-} | |
- | |
-func (me durationChange) IsFromNeverDeleteToTimeline() bool { | |
- return me.previousDurationDays == timelineNeverDelete && me.newDurationDays > 0 | |
-} | |
- | |
-func (me durationChange) IsFromTimelineToNeverDelete() bool { | |
- return me.previousDurationDays > 0 && me.newDurationDays == timelineNeverDelete | |
-} | |
- | |
-func (me durationChange) IsTimelineDecreased() bool { | |
- return me.previousDurationDays > 0 && me.newDurationDays > 0 && me.newDurationDays < me.previousDurationDays | |
+ return nil | |
} | |
diff --git a/service/observers/organization_deletion_timeline_observer_integration_test.go b/service/observers/organization_deletion_timeline_observer_integration_test.go | |
deleted file mode 100644 | |
index f488ed70..00000000 | |
--- a/service/observers/organization_deletion_timeline_observer_integration_test.go | |
+++ /dev/null | |
@@ -1,327 +0,0 @@ | |
-//go:build integration | |
- | |
-package observers | |
- | |
-import ( | |
- "context" | |
- "database/sql" | |
- "math" | |
- "strconv" | |
- "testing" | |
- "time" | |
- | |
- "github.com/certifaction/api/observable" | |
- | |
- identitydb "github.com/certifaction/api/identity/repository/db" | |
- "github.com/certifaction/api/model" | |
- "github.com/certifaction/api/repository/user" | |
- "github.com/certifaction/api/service/configuration" | |
- signatureRepository "github.com/certifaction/api/signature_requests/repository" | |
- "github.com/certifaction/base-service/connection" | |
- "github.com/stretchr/testify/assert" | |
- "github.com/stretchr/testify/require" | |
-) | |
- | |
-var ( | |
- observer *organizationDeletionTimelineObserver | |
- signaturesRepo signatureRepository.Repository | |
- txContext context.Context | |
- user1 = model.User{UID: "uid-2000"} | |
- user2 = model.User{UID: "uid-2001"} | |
-) | |
- | |
-func setupTestSuite(t *testing.T) func(t *testing.T) { | |
- config, err := configuration.NewConfiguration(nil) | |
- assert.NoError(t, err) | |
- | |
- db, err := connection.NewMySQLDBConnection(config.DbUsername, config.DbPassword, config.DbUrl, config.DbName) | |
- assert.NoError(t, err) | |
- | |
- txContext, err = db.BeginTransaction(context.Background(), sql.LevelSerializable) // Start a new transaction | |
- assert.NoError(t, err) | |
- | |
- identityStore := identitydb.NewDbIdentityStore(db) | |
- userRepository := user.NewSQLUserRepository(db, identityStore) | |
- signaturesRepo = signatureRepository.NewDefaultRepository(db, userRepository, nil) | |
- | |
- insertTestDataQuery := ` | |
-INSERT INTO users (id, uid, email, name, verified, address, hash, randomhash, secret_auth_token) | |
-VALUES | |
- (1005, 'uid-2000', 'integration-test-cleanup@certifaction.com', 'Integration Test', '1', '', '', '', ''), | |
- (1006, 'uid-2001', 'integration-test-cleanup@certifaction.com', 'Integration Test', '1', '', '', '', ''); | |
- | |
-INSERT INTO organizations (id, name) VALUES ('org-001', 'Test Org'); | |
- | |
-INSERT INTO organizations_users (user_uid, organization_id, role, status, invite_email, verification_hash, role_id_after_invite) | |
-VALUES ('uid-2000', 'org-001', 'user', 'joined', null, null, DEFAULT); | |
- | |
-INSERT INTO roles (id, organization_id, name, admin, default_role) | |
-VALUES ('entity-001', 'org-001', 'Admin', 1, 0); | |
- | |
-INSERT INTO users_roles (user_uid,role_id) VALUES ('uid-2000', 'entity-001'); | |
-` | |
- _, err = db.Exec(txContext, insertTestDataQuery) | |
- assert.NoError(t, err) | |
- | |
- err = signaturesRepo.Create(txContext, &model.SignatureRequest{ | |
- EnvelopeItems: []model.EnvelopeItem{{ | |
- FileID: "111", | |
- FileURLExpiry: time.Now().Add(5 * time.Second), | |
- FileURL: "https://...", | |
- Filename: "fileName111", | |
- LegalWeight: model.StandardLevel, | |
- }}, | |
- FileID: "111", | |
- FileURLExpiry: time.Now().Add(5 * time.Second), | |
- FileURL: "https://...", | |
- Filename: "fileName111", | |
- Signer: &user1, | |
- Requester: &user1, | |
- Signed: true, | |
- }) | |
- assert.NoError(t, err) | |
- | |
- err = signaturesRepo.Create(txContext, &model.SignatureRequest{ | |
- EnvelopeItems: []model.EnvelopeItem{{ | |
- FileID: "112", | |
- FileURLExpiry: time.Now().Add((24 * 15) * time.Hour), | |
- FileURL: "https://...", | |
- Filename: "fileName112", | |
- LegalWeight: model.StandardLevel, | |
- }}, | |
- FileID: "112", | |
- FileURLExpiry: time.Now().Add((24 * 15) * time.Hour), | |
- FileURL: "https://...", | |
- Filename: "fileName112", | |
- Signer: &user1, | |
- Requester: &user1, | |
- Signed: true, | |
- }) | |
- assert.NoError(t, err) | |
- | |
- observer = NewOrganizationDeletionTimelineObserver(db, signaturesRepo) | |
- | |
- return func(t *testing.T) { | |
- txContext = db.Rollback(txContext) | |
- } | |
-} | |
-func TestOrganizationDeletionTimelineObserver_Notify(t *testing.T) { | |
- teardown := setupTestSuite(t) | |
- t.Cleanup(func() { | |
- teardown(t) | |
- }) | |
- | |
- tests := []struct { | |
- name string | |
- timelineDaysFrom int | |
- timelineDaysTo int | |
- beforeTest func() | |
- expected func() | |
- }{ | |
- { | |
- name: "Timeline → Never delete", | |
- timelineDaysFrom: 5, | |
- timelineDaysTo: timelineNeverDelete, | |
- expected: func() { | |
- signatureRequests, err := signaturesRepo.FindByFileID(txContext, []string{"111", "112", "113"}, true) | |
- require.NoError(t, err) | |
- assert.Len(t, signatureRequests, 2) | |
- | |
- var cleared []model.SignatureRequest | |
- for _, signatureRequest := range signatureRequests { | |
- if signatureRequest.FileURL == "" { | |
- cleared = append(cleared, signatureRequest) | |
- } | |
- } | |
- | |
- assert.Len(t, cleared, 0) | |
- }, | |
- }, | |
- { | |
- name: "Never delete → Timeline", | |
- timelineDaysFrom: timelineNeverDelete, | |
- timelineDaysTo: 13, | |
- expected: func() { | |
- signatureRequests, err := signaturesRepo.FindByFileID(txContext, []string{"111", "112", "113"}, true) | |
- require.NoError(t, err) | |
- assert.Len(t, signatureRequests, 2) | |
- require.Equal(t, "https://...", signatureRequests[0].FileURL, "should not delete any file URLs as the date have been set to the future") | |
- | |
- diffDates := signatureRequests[0].FileURLExpiry.Sub(time.Now().AddDate(0, 0, 13)) | |
- dateTolerance := 500 * time.Millisecond | |
- assert.InDelta(t, float64(diffDates.Milliseconds()), 0, float64(dateTolerance)) | |
- }, | |
- }, | |
- { | |
- name: "Timeline increased", | |
- timelineDaysFrom: 3, | |
- timelineDaysTo: 6, | |
- expected: func() { | |
- signatureRequests, err := signaturesRepo.FindByFileID(txContext, []string{"111"}, true) | |
- require.NoError(t, err) | |
- | |
- request := signatureRequests[0] | |
- assert.Equal(t, "111", request.FileID) | |
- assert.Equal(t, "https://...", request.FileURL) | |
- }, | |
- }, | |
- { | |
- name: "Never delete → Delete instantly", | |
- timelineDaysFrom: timelineNeverDelete, | |
- timelineDaysTo: timelineDeleteInstantly, | |
- beforeTest: func() { | |
- err := signaturesRepo.Create(txContext, &model.SignatureRequest{ | |
- EnvelopeItems: []model.EnvelopeItem{{ | |
- FileID: "113", | |
- FileURLExpiry: neverExpireTime, | |
- Filename: "fileName113", | |
- FileURL: "https://...", | |
- LegalWeight: model.StandardLevel, | |
- }}, | |
- FileID: "113", | |
- FileURLExpiry: neverExpireTime, | |
- Filename: "fileName113", | |
- FileURL: "https://...", | |
- Signer: &user1, | |
- Requester: &user1, | |
- Signed: true, | |
- }) | |
- assert.NoError(t, err) | |
- | |
- err = signaturesRepo.Create(txContext, &model.SignatureRequest{ | |
- EnvelopeItems: []model.EnvelopeItem{{ | |
- FileID: "113", | |
- FileURLExpiry: neverExpireTime, | |
- FileURL: "https://...", | |
- Filename: "fileName113", | |
- LegalWeight: model.StandardLevel, | |
- }}, | |
- FileID: "113", | |
- FileURLExpiry: neverExpireTime, | |
- FileURL: "https://...", | |
- Filename: "fileName113", | |
- Signer: &user2, | |
- Requester: &user1, | |
- Signed: true, | |
- }) | |
- | |
- assert.NoError(t, err) | |
- }, | |
- expected: func() { | |
- signatureRequests, err := signaturesRepo.FindByFileID(txContext, []string{"113"}, true) | |
- require.NoError(t, err) | |
- | |
- signatureRequest := signatureRequests[0] | |
- changeTimeline := signatureRequest.FileURLExpiry.Sub(time.Now()).Hours() | |
- changeTimelineDays := int(math.Round(changeTimeline / 24)) | |
- assert.Equal(t, 0, changeTimelineDays) | |
- | |
- envelopeItem := signatureRequest.EnvelopeItems[0] | |
- envelopeItemTimeChange := envelopeItem.FileURLExpiry.Sub(time.Now()).Hours() | |
- envelopeItemChangeInDays := int(math.Round(envelopeItemTimeChange / 24)) | |
- assert.Equal(t, 0, envelopeItemChangeInDays) | |
- | |
- assert.Empty(t, signatureRequest.FileURL) | |
- assert.Empty(t, signatureRequest.EnvelopeItems[0].FileURL) | |
- }, | |
- }, | |
- { | |
- name: "Timeline decreased", | |
- timelineDaysFrom: 9, | |
- timelineDaysTo: 3, | |
- beforeTest: func() { | |
- manyDaysFromNow := time.Now().AddDate(0, 0, 300) | |
- err := signaturesRepo.Create(txContext, &model.SignatureRequest{ | |
- EnvelopeItems: []model.EnvelopeItem{{ | |
- FileID: "114", | |
- FileURLExpiry: manyDaysFromNow, | |
- FileURL: "https://...", | |
- Filename: "fileName113", | |
- LegalWeight: model.StandardLevel, | |
- }}, | |
- FileID: "114", | |
- FileURLExpiry: manyDaysFromNow, | |
- FileURL: "https://...", | |
- Filename: "fileName114", | |
- Signer: &user2, | |
- Requester: &user1, | |
- Signed: true, | |
- }) | |
- assert.NoError(t, err) | |
- }, | |
- expected: func() { | |
- signatureRequests, err := signaturesRepo.FindByFileID(txContext, []string{"114"}, true) | |
- require.NoError(t, err) | |
- request := signatureRequests[0] | |
- assert.Equal(t, "114", request.FileID) | |
- assert.Equal(t, "https://...", request.FileURL) | |
- assert.Equal(t, "https://...", request.EnvelopeItems[0].FileURL) | |
- | |
- assert.WithinDuration(t, request.FileURLExpiry, time.Now().AddDate(0, 0, 3), 500*time.Millisecond) | |
- }, | |
- }, | |
- { | |
- name: "Timeline → Delete instantly", | |
- timelineDaysFrom: 5, | |
- timelineDaysTo: timelineDeleteInstantly, | |
- beforeTest: func() { | |
- manyDaysFromNow := time.Now().AddDate(0, 0, 300) | |
- err := signaturesRepo.Create(txContext, &model.SignatureRequest{ | |
- EnvelopeItems: []model.EnvelopeItem{{ | |
- FileID: "115", | |
- FileURLExpiry: manyDaysFromNow, | |
- FileURL: "https://...", | |
- Filename: "fileName113", | |
- LegalWeight: model.StandardLevel, | |
- }}, | |
- FileID: "115", | |
- FileURLExpiry: manyDaysFromNow, | |
- FileURL: "https://...", | |
- Filename: "fileName114", | |
- Signer: &user2, | |
- Requester: &user1, | |
- Signed: true, | |
- }) | |
- assert.NoError(t, err) | |
- }, | |
- expected: func() { | |
- signatureRequests, err := signaturesRepo.FindByFileID(txContext, []string{"115"}, true) | |
- require.NoError(t, err) | |
- request := signatureRequests[0] | |
- assert.Equal(t, "115", request.FileID) | |
- assert.Equal(t, "", request.FileURL, "should remove file URL") | |
- assert.Equal(t, "", request.EnvelopeItems[0].FileURL, "should remove file URL") | |
- | |
- assert.WithinDuration(t, request.FileURLExpiry, time.Now(), 500*time.Millisecond) | |
- }, | |
- }, | |
- } | |
- | |
- for _, tt := range tests { | |
- t.Run(tt.name, func(t *testing.T) { | |
- if tt.beforeTest != nil { | |
- tt.beforeTest() | |
- } | |
- | |
- err := observer.Notify(txContext, observable.ObserverEvent{ | |
- Type: model.NotificationEventTypeDeletionTimelineChange, | |
- BeforeContent: model.EntitySetting{ | |
- EntityID: "entity-001", | |
- Setting: model.Setting{ | |
- ID: model.SettingDeletionTimeline, | |
- }, | |
- Value: strconv.Itoa(tt.timelineDaysFrom), | |
- }, | |
- Content: model.EntitySetting{ | |
- EntityID: "entity-001", | |
- Setting: model.Setting{ | |
- ID: model.SettingDeletionTimeline, | |
- }, | |
- Value: strconv.Itoa(tt.timelineDaysTo), | |
- }, | |
- }) | |
- assert.NoError(t, err) | |
- tt.expected() | |
- }) | |
- } | |
-} | |
diff --git a/service/observers/organization_deletion_timeline_observer_test.go b/service/observers/organization_deletion_timeline_observer_test.go | |
index 252095b9..8519b168 100644 | |
--- a/service/observers/organization_deletion_timeline_observer_test.go | |
+++ b/service/observers/organization_deletion_timeline_observer_test.go | |
@@ -1,251 +1,265 @@ | |
+//go:build integration | |
+ | |
package observers | |
import ( | |
"context" | |
+ "database/sql" | |
+ "fmt" | |
+ "math" | |
"testing" | |
"time" | |
+ identitydb "github.com/certifaction/api/identity/repository/db" | |
"github.com/certifaction/api/model" | |
- repositoryfakes2 "github.com/certifaction/api/repository/repositoryfakes" | |
- "github.com/certifaction/api/signature_requests/repository/repositoryfakes" | |
- "github.com/google/go-cmp/cmp" | |
- "github.com/google/go-cmp/cmp/cmpopts" | |
+ "github.com/certifaction/api/observable" | |
+ "github.com/certifaction/api/repository/user" | |
+ "github.com/certifaction/api/service/configuration" | |
+ signatureRepository "github.com/certifaction/api/signature_requests/repository" | |
+ "github.com/certifaction/base-service/connection" | |
"github.com/stretchr/testify/assert" | |
"github.com/stretchr/testify/require" | |
) | |
-func TestUpdateAsNeeded(t *testing.T) { | |
- testStartTime := time.Now() | |
- | |
- testCases := []struct { | |
- name string | |
- previousDurationDays int | |
- newDurationDays int | |
- signatureRequests []model.SignatureRequest | |
- expectedSignatureRequests []model.SignatureRequest | |
- expectedFindCompleted bool | |
- }{ | |
- { | |
- name: "From timelineNeverDelete to timelineDeleteInstantly", | |
- previousDurationDays: timelineNeverDelete, | |
- newDurationDays: timelineDeleteInstantly, | |
- expectedFindCompleted: true, | |
- signatureRequests: []model.SignatureRequest{ | |
- { | |
- ID: "abc", | |
- FileURL: "https://da.certifaction.com/abc.pdf", | |
- FileURLExpiry: neverExpireTime, | |
- EnvelopeItems: []model.EnvelopeItem{ | |
- {FileURL: "https://da.certifaction.com/abc.pdf", Filename: "abc.pdf", FileURLExpiry: neverExpireTime}, | |
- }, | |
- }, | |
- { | |
- ID: "def", | |
- FileURL: "", | |
- FileURLExpiry: neverExpireTime, | |
- EnvelopeItems: []model.EnvelopeItem{ | |
- {FileURL: "https://da.certifaction.com/def.pdf", Filename: "def.pdf", FileURLExpiry: neverExpireTime}, | |
- }, | |
- }, | |
- }, | |
- expectedSignatureRequests: []model.SignatureRequest{ | |
- { | |
- ID: "abc", | |
- FileURL: "", | |
- FileURLExpiry: testStartTime, | |
- EnvelopeItems: []model.EnvelopeItem{ | |
- {FileURL: "", Filename: "abc.pdf", FileURLExpiry: testStartTime}, | |
- }, | |
- }, | |
- { | |
- ID: "def", | |
- FileURL: "", | |
- FileURLExpiry: testStartTime, | |
- EnvelopeItems: []model.EnvelopeItem{ | |
- {FileURL: "", Filename: "def.pdf", FileURLExpiry: testStartTime}, | |
- }, | |
- }, | |
- }, | |
- }, | |
- { | |
- name: "From timelineNeverDelete to timeline value", | |
- previousDurationDays: timelineNeverDelete, | |
- newDurationDays: 10, | |
- expectedFindCompleted: false, | |
- signatureRequests: []model.SignatureRequest{ | |
- { | |
- ID: "abc", | |
- FileURL: "https://da.certifaction.com/abc.pdf", | |
- FileURLExpiry: neverExpireTime, | |
- EnvelopeItems: []model.EnvelopeItem{ | |
- {FileURL: "https://da.certifaction.com/abc.pdf", Filename: "abc.pdf", FileURLExpiry: neverExpireTime}, | |
- }, | |
- }, | |
- { | |
- ID: "def", | |
- FileURL: "https://da.certifaction.com/def.pdf", | |
- FileURLExpiry: neverExpireTime, | |
- EnvelopeItems: []model.EnvelopeItem{ | |
- {FileURL: "https://da.certifaction.com/def.pdf", Filename: "def.pdf", FileURLExpiry: neverExpireTime}, | |
- }, | |
- }, | |
- }, | |
- expectedSignatureRequests: []model.SignatureRequest{ | |
- { | |
- ID: "abc", | |
- FileURL: "https://da.certifaction.com/abc.pdf", | |
- FileURLExpiry: testStartTime.AddDate(0, 0, 10), | |
- EnvelopeItems: []model.EnvelopeItem{ | |
- {FileURL: "https://da.certifaction.com/abc.pdf", Filename: "abc.pdf", FileURLExpiry: testStartTime.AddDate(0, 0, 10)}, | |
- }, | |
- }, | |
- { | |
- ID: "def", | |
- FileURL: "https://da.certifaction.com/def.pdf", | |
- FileURLExpiry: testStartTime.AddDate(0, 0, 10), | |
- EnvelopeItems: []model.EnvelopeItem{ | |
- {FileURL: "https://da.certifaction.com/def.pdf", Filename: "def.pdf", FileURLExpiry: testStartTime.AddDate(0, 0, 10)}, | |
- }, | |
- }, | |
- }, | |
- }, | |
- { | |
- name: "From timelineDeleteInstantly to timelineNeverDelete", | |
- previousDurationDays: timelineDeleteInstantly, | |
- newDurationDays: timelineNeverDelete, | |
- expectedFindCompleted: false, | |
- signatureRequests: []model.SignatureRequest{ | |
- { | |
- ID: "abc", | |
- FileURL: "", | |
- FileURLExpiry: testStartTime.AddDate(0, 0, -5), | |
- EnvelopeItems: []model.EnvelopeItem{ | |
- {FileURL: "", Filename: "abc.pdf", FileURLExpiry: testStartTime.AddDate(0, 0, -5)}, | |
- }, | |
- }, | |
- }, | |
- }, | |
- { | |
- name: "From timelineDeleteInstantly to timeline value", | |
- previousDurationDays: timelineDeleteInstantly, | |
- newDurationDays: 5, | |
- expectedFindCompleted: false, | |
- }, | |
- { | |
- name: "From timeline value to another timeline value", | |
- previousDurationDays: 5, | |
- newDurationDays: 10, | |
- expectedFindCompleted: false, | |
- }, | |
- { | |
- name: "From a higher timeline value to a lower timeline value", | |
- previousDurationDays: 10, | |
- newDurationDays: 5, | |
- expectedFindCompleted: true, | |
- signatureRequests: []model.SignatureRequest{ | |
- { | |
- ID: "abc", | |
- FileURL: "https://da.certifaction.com/abc.pdf", | |
- FileURLExpiry: testStartTime.AddDate(0, 0, 7), | |
- EnvelopeItems: []model.EnvelopeItem{ | |
- {FileURL: "https://da.certifaction.com/abc.pdf", Filename: "abc.pdf", FileURLExpiry: testStartTime.AddDate(0, 0, 7)}, | |
- }, | |
- }, | |
- }, | |
- expectedSignatureRequests: []model.SignatureRequest{ | |
- { | |
- ID: "abc", | |
- FileURL: "https://da.certifaction.com/abc.pdf", | |
- FileURLExpiry: testStartTime.AddDate(0, 0, 5), | |
- EnvelopeItems: []model.EnvelopeItem{ | |
- {FileURL: "https://da.certifaction.com/abc.pdf", Filename: "abc.pdf", FileURLExpiry: testStartTime.AddDate(0, 0, 5)}, | |
- }, | |
- }, | |
- }, | |
- }, | |
- { | |
- name: "From a timeline value to timelineNeverDelete", | |
- previousDurationDays: 5, | |
- newDurationDays: timelineNeverDelete, | |
- expectedFindCompleted: true, | |
- signatureRequests: []model.SignatureRequest{ | |
- { | |
- ID: "def", | |
- FileURL: "https://da.certifaction.com/def.pdf", | |
- FileURLExpiry: testStartTime.AddDate(0, 0, 6), | |
- EnvelopeItems: []model.EnvelopeItem{ | |
- {FileURL: "https://da.certifaction.com/def.pdf", Filename: "def.pdf", FileURLExpiry: testStartTime.AddDate(0, 0, 6)}, | |
- }, | |
- }, | |
- }, | |
- expectedSignatureRequests: []model.SignatureRequest{ | |
- { | |
- ID: "def", | |
- FileURL: "https://da.certifaction.com/def.pdf", | |
- FileURLExpiry: neverExpireTime, | |
- EnvelopeItems: []model.EnvelopeItem{ | |
- {FileURL: "https://da.certifaction.com/def.pdf", Filename: "def.pdf", FileURLExpiry: neverExpireTime}, | |
- }, | |
- }, | |
- }, | |
- }, | |
- { | |
- name: "From a timeline value to timelineDeleteInstantly", | |
- previousDurationDays: 5, | |
- newDurationDays: timelineDeleteInstantly, | |
- expectedFindCompleted: true, | |
- signatureRequests: []model.SignatureRequest{ | |
- { | |
- ID: "def", | |
- FileURL: "https://da.certifaction.com/def.pdf", | |
- FileURLExpiry: testStartTime.AddDate(0, 0, 6), | |
- EnvelopeItems: []model.EnvelopeItem{ | |
- {FileURL: "https://da.certifaction.com/def.pdf", Filename: "def.pdf", FileURLExpiry: testStartTime.AddDate(0, 0, 6)}, | |
- }, | |
- }, | |
- }, | |
- expectedSignatureRequests: []model.SignatureRequest{ | |
- { | |
- ID: "def", | |
- FileURL: "", | |
- FileURLExpiry: testStartTime, | |
- EnvelopeItems: []model.EnvelopeItem{ | |
- {FileURL: "", Filename: "def.pdf", FileURLExpiry: testStartTime}, | |
- }, | |
- }, | |
- }, | |
- }, | |
+var ( | |
+ observer *organizationDeletionTimelineObserver | |
+ signaturesRepo signatureRepository.Repository | |
+ txContext context.Context | |
+ user1 = model.User{UID: "uid-2000"} | |
+ user2 = model.User{UID: "uid-2001"} | |
+) | |
+ | |
+func setupTestSuite(t *testing.T) func(t *testing.T) { | |
+ config, err := configuration.NewConfiguration(nil) | |
+ assert.NoError(t, err) | |
+ | |
+ db, err := connection.NewMySQLDBConnection(config.DbUsername, config.DbPassword, config.DbUrl, config.DbName) | |
+ assert.NoError(t, err) | |
+ | |
+ txContext, err = db.BeginTransaction(context.Background(), sql.LevelSerializable) // Start a new transaction | |
+ assert.NoError(t, err) | |
+ | |
+ identityStore := identitydb.NewDbIdentityStore(db) | |
+ userRepository := user.NewSQLUserRepository(db, identityStore) | |
+ signaturesRepo = signatureRepository.NewDefaultRepository(db, userRepository, nil) | |
+ | |
+ insertTestDataQuery := ` | |
+INSERT INTO users (id, uid, email, name, verified, address, hash, randomhash, secret_auth_token) | |
+VALUES | |
+ (1005, 'uid-2000', 'integration-test-cleanup@certifaction.com', 'Integration Test', '1', '', '', '', ''), | |
+ (1006, 'uid-2001', 'integration-test-cleanup@certifaction.com', 'Integration Test', '1', '', '', '', ''); | |
+ | |
+INSERT INTO organizations (id, name) VALUES ('org-001', 'Test Org'); | |
+ | |
+INSERT INTO organizations_users (user_uid, organization_id, role, status, invite_email, verification_hash, role_id_after_invite) | |
+VALUES ('uid-2000', 'org-001', 'user', 'joined', null, null, DEFAULT); | |
+ | |
+INSERT INTO roles (id, organization_id, name, admin, default_role) | |
+VALUES ('entity-001', 'org-001', 'Admin', 1, 0); | |
+ | |
+INSERT INTO users_roles (user_uid,role_id) VALUES ('uid-2000', 'entity-001'); | |
+` | |
+ _, err = db.Exec(txContext, insertTestDataQuery) | |
+ assert.NoError(t, err) | |
+ | |
+ err = signaturesRepo.Create(txContext, &model.SignatureRequest{ | |
+ EnvelopeItems: []model.EnvelopeItem{{ | |
+ FileID: "111", | |
+ FileURLExpiry: time.Now().Add(5 * time.Second), | |
+ FileURL: "https://...", | |
+ Filename: "fileName111", | |
+ LegalWeight: model.StandardLevel, | |
+ }}, | |
+ FileID: "111", | |
+ FileURLExpiry: time.Now().Add(5 * time.Second), | |
+ FileURL: "https://...", | |
+ Filename: "fileName111", | |
+ Signer: &user1, | |
+ Requester: &user1, | |
+ Signed: true, | |
+ }) | |
+ assert.NoError(t, err) | |
+ | |
+ err = signaturesRepo.Create(txContext, &model.SignatureRequest{ | |
+ EnvelopeItems: []model.EnvelopeItem{{ | |
+ FileID: "112", | |
+ FileURLExpiry: time.Now().Add((24 * 15) * time.Hour), | |
+ FileURL: "https://...", | |
+ Filename: "fileName112", | |
+ LegalWeight: model.StandardLevel, | |
+ }}, | |
+ FileID: "112", | |
+ FileURLExpiry: time.Now().Add((24 * 15) * time.Hour), | |
+ FileURL: "https://...", | |
+ Filename: "fileName112", | |
+ Signer: &user1, | |
+ Requester: &user1, | |
+ Signed: true, | |
+ }) | |
+ assert.NoError(t, err) | |
+ | |
+ observer = NewOrganizationDeletionTimelineObserver(db, signaturesRepo, true) | |
+ | |
+ return func(t *testing.T) { | |
+ txContext = db.Rollback(txContext) | |
} | |
+} | |
+ | |
+func TestOrganizationDeletionTimelineObserver_Notify(t *testing.T) { | |
+ teardown := setupTestSuite(t) | |
+ t.Cleanup(func() { | |
+ teardown(t) | |
+ }) | |
- for _, tc := range testCases { | |
- t.Run(tc.name, func(t *testing.T) { | |
- fakeTransactionManager := new(repositoryfakes2.FakeDBTransactionManager) | |
- fakeSignatureRepository := new(repositoryfakes.FakeRepository) | |
- fakeSignatureRepository.FindCompletedForOrganizationStub = func(ctx context.Context, entityID string) ([]model.SignatureRequest, error) { | |
- return tc.signatureRequests, nil | |
+ t.Run("No changes are made if deletion timeline is set to never delete", func(t *testing.T) { | |
+ setting := model.EntitySetting{ | |
+ EntityID: "entity-001", | |
+ Setting: model.Setting{ | |
+ ID: model.SettingDeletionTimeline, | |
+ }, | |
+ Value: "-1", | |
+ } | |
+ | |
+ err := observer.Notify(txContext, observable.ObserverEvent{Type: model.NotificationEventTypeDeletionTimelineChange, Content: setting}) | |
+ | |
+ assert.NoError(t, err) | |
+ | |
+ signatureRequests, err := signaturesRepo.FindByFileID(txContext, []string{"111", "112", "113"}, true) | |
+ require.NoError(t, err) | |
+ assert.Len(t, signatureRequests, 2) | |
+ | |
+ var cleared []model.SignatureRequest | |
+ for _, signatureRequest := range signatureRequests { | |
+ if signatureRequest.FileURL == "" { | |
+ cleared = append(cleared, signatureRequest) | |
} | |
+ } | |
+ | |
+ assert.Len(t, cleared, 0) | |
+ | |
+ }) | |
- obs := NewOrganizationDeletionTimelineObserver(fakeTransactionManager, fakeSignatureRepository) | |
- obs.setNowTime(func() time.Time { | |
- return testStartTime | |
- }) | |
+ t.Run("Files with expiry after the new deletion timeline are delete", func(t *testing.T) { | |
- err := obs.updateAsNeeded(context.Background(), durationChange{ | |
- tc.previousDurationDays, | |
- tc.newDurationDays}, | |
- "someEntityID") | |
+ setting := model.EntitySetting{ | |
+ EntityID: "entity-001", | |
+ Setting: model.Setting{ | |
+ ID: model.SettingDeletionTimeline, | |
+ }, | |
+ Value: "13", | |
+ } | |
+ | |
+ err := observer.Notify(txContext, observable.ObserverEvent{Type: model.NotificationEventTypeDeletionTimelineChange, Content: setting}) | |
- assert.Nil(t, err) | |
+ assert.NoError(t, err) | |
- require.Equal(t, len(tc.expectedSignatureRequests), fakeSignatureRepository.UpdateCallCount()) | |
- var updatedSignatureRequests []model.SignatureRequest | |
- for i := 0; i < len(tc.expectedSignatureRequests); i++ { | |
- _, sr := fakeSignatureRepository.UpdateArgsForCall(i) | |
- updatedSignatureRequests = append(updatedSignatureRequests, *sr) | |
+ signatureRequests, err := signaturesRepo.FindByFileID(txContext, []string{"111", "112", "113"}, true) | |
+ require.NoError(t, err) | |
+ assert.Len(t, signatureRequests, 2) | |
+ | |
+ var cleared []model.SignatureRequest | |
+ for _, signatureRequest := range signatureRequests { | |
+ if signatureRequest.FileURL == "" { | |
+ cleared = append(cleared, signatureRequest) | |
} | |
+ } | |
+ | |
+ assert.Len(t, cleared, 1) | |
+ clearedRequest := cleared[0] | |
+ assert.Equal(t, "112", clearedRequest.FileID) | |
+ assert.Equal(t, "", clearedRequest.FileURL) | |
+ | |
+ envelopItem := clearedRequest.EnvelopeItems[0] | |
+ assert.NotEmpty(t, envelopItem.Filename) | |
+ assert.Equal(t, "", envelopItem.FileURL) | |
+ }) | |
+ | |
+ t.Run("Files with expiry before the new deletion timeline don't change", func(t *testing.T) { | |
+ | |
+ setting := model.EntitySetting{ | |
+ EntityID: "entity-001", | |
+ Setting: model.Setting{ | |
+ ID: model.SettingDeletionTimeline, | |
+ }, | |
+ Value: "9", | |
+ } | |
+ | |
+ err := observer.Notify(txContext, observable.ObserverEvent{Type: model.NotificationEventTypeDeletionTimelineChange, Content: setting}) | |
+ | |
+ assert.NoError(t, err) | |
+ | |
+ signatureRequests, err := signaturesRepo.FindByFileID(txContext, []string{"111"}, true) | |
+ require.NoError(t, err) | |
+ request := signatureRequests[0] | |
+ assert.Equal(t, "111", request.FileID) | |
+ assert.Equal(t, "https://...", request.FileURL) | |
+ assert.Equal(t, "fileName111", request.Filename) | |
- require.Empty(t, cmp.Diff(tc.expectedSignatureRequests, updatedSignatureRequests), | |
- cmpopts.EquateApproxTime(100*time.Millisecond)) | |
+ envelopItem := request.EnvelopeItems[0] | |
+ assert.Equal(t, "fileName111", envelopItem.Filename) | |
+ assert.Equal(t, "https://...", envelopItem.FileURL) | |
+ }) | |
+ | |
+ t.Run("Files set to an newer expiry date are updated to the new deletion date", func(t *testing.T) { | |
+ err := signaturesRepo.Create(txContext, &model.SignatureRequest{ | |
+ EnvelopeItems: []model.EnvelopeItem{{ | |
+ FileID: "113", | |
+ FileURLExpiry: neverExpireTime, | |
+ Filename: "fileName113", | |
+ FileURL: "https://...", | |
+ LegalWeight: model.StandardLevel, | |
+ }}, | |
+ FileID: "113", | |
+ FileURLExpiry: neverExpireTime, | |
+ Filename: "fileName113", | |
+ FileURL: "https://...", | |
+ Signer: &user1, | |
+ Requester: &user1, | |
+ Signed: true, | |
}) | |
- } | |
+ assert.NoError(t, err) | |
+ | |
+ err = signaturesRepo.Create(txContext, &model.SignatureRequest{ | |
+ EnvelopeItems: []model.EnvelopeItem{{ | |
+ FileID: "113", | |
+ FileURLExpiry: neverExpireTime, | |
+ FileURL: "https://...", | |
+ Filename: "fileName113", | |
+ LegalWeight: model.StandardLevel, | |
+ }}, | |
+ FileID: "113", | |
+ FileURLExpiry: neverExpireTime, | |
+ FileURL: "https://...", | |
+ Filename: "fileName113", | |
+ Signer: &user2, | |
+ Requester: &user1, | |
+ Signed: true, | |
+ }) | |
+ | |
+ assert.NoError(t, err) | |
+ expiryTimeline := 5 | |
+ setting := model.EntitySetting{ | |
+ EntityID: "entity-001", | |
+ Setting: model.Setting{ | |
+ ID: model.SettingDeletionTimeline, | |
+ }, | |
+ Value: fmt.Sprintf("%d", expiryTimeline), | |
+ } | |
+ | |
+ err = observer.Notify(txContext, observable.ObserverEvent{Type: model.NotificationEventTypeDeletionTimelineChange, Content: setting}) | |
+ | |
+ assert.NoError(t, err) | |
+ signatureRequests, err := signaturesRepo.FindByFileID(txContext, []string{"113"}, true) | |
+ require.NoError(t, err) | |
+ | |
+ signatureRequest := signatureRequests[0] | |
+ changeTimeline := signatureRequest.FileURLExpiry.Sub(time.Now()).Hours() | |
+ changeTimelineDays := int(math.Round(changeTimeline / 24)) | |
+ assert.Equal(t, changeTimelineDays, expiryTimeline) | |
+ | |
+ envelopItem := signatureRequest.EnvelopeItems[0] | |
+ envelopItemTimeChange := envelopItem.FileURLExpiry.Sub(time.Now()).Hours() | |
+ envelopItemChangeInDays := int(math.Round(envelopItemTimeChange / 24)) | |
+ assert.Equal(t, envelopItemChangeInDays, expiryTimeline) | |
+ }) | |
} | |
diff --git a/signature_requests/repository/repository.go b/signature_requests/repository/repository.go | |
index 3aa8dd5c..65f28d52 100644 | |
--- a/signature_requests/repository/repository.go | |
+++ b/signature_requests/repository/repository.go | |
@@ -35,6 +35,35 @@ type defaultRepository struct { | |
userRepoFindByUIDCache cache.Cache | |
} | |
+func (me *defaultRepository) FindByDocumentIDAndSignerUID(ctx context.Context, documentID string, signerUID string) (*model.SignatureRequest, error) { | |
+ log.DebugCtx(ctx, "Attempt to retrieve signature request found for documentID %s and signerUID %s", documentID, signerUID) | |
+ sqlQuery := fmt.Sprintf( | |
+ `%s JOIN files f on s.file_id = f.id WHERE f.document_id = ? and s.signer_uid = ?`, | |
+ me.selectSQLAsString(), | |
+ ) | |
+ | |
+ rows, err := me.db.Query(ctx, sqlQuery, documentID, signerUID) | |
+ if err != nil { | |
+ if err == sql.ErrNoRows { | |
+ return nil, nil | |
+ } | |
+ | |
+ return nil, err | |
+ } | |
+ | |
+ signature, err := me.singleRowToSignatureRequest(ctx, rows) | |
+ if err == sql.ErrNoRows { | |
+ log.DebugfCtx(ctx, "No signature request found for documentID %s and signerUID %s", documentID, signerUID) | |
+ | |
+ } else if err != nil { | |
+ log.ErrorCtx(ctx, "Scanning row to SignatureRequest", err) | |
+ return nil, err | |
+ } | |
+ | |
+ log.DebugCtx(ctx, "Successfully retrieved signature request found for documentID %s and signerUID %s", documentID, signerUID) | |
+ return signature, nil | |
+} | |
+ | |
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . Repository | |
type Repository interface { | |
Create(ctx context.Context, signature *model.SignatureRequest) error | |
@@ -59,7 +88,7 @@ type Repository interface { | |
DeleteTokens(ctx context.Context, id string) error | |
FindByRequestorCreatedBeforeNotAnonymizedForUpdate(ctx context.Context, orgID string, createdBefore time.Time, limit int) ([]model.SignatureRequest, error) | |
FindByDocumentIDAndSignerUID(ctx context.Context, documentID string, signerUID string) (*model.SignatureRequest, error) | |
- FindCompletedForOrganization(ctx context.Context, entityID string) ([]model.SignatureRequest, error) | |
+ FindActiveOrganizationSignatures(ctx context.Context, entityID string) ([]model.SignatureRequest, error) | |
} | |
var _ Repository = &defaultRepository{} | |
@@ -752,7 +781,7 @@ func (me *defaultRepository) FindByHashedSignerToken(ctx context.Context, hashed | |
return &signatureRequests[0], err | |
} | |
-func (me *defaultRepository) UpdateEnvelopeItems(ctx context.Context, envelopeItems []model.EnvelopeItem) error { | |
+func (me *defaultRepository) UpdateEnvelopItems(ctx context.Context, envelopeItems []model.EnvelopeItem) error { | |
log.InfoCtx(ctx, "Updating Envelop Items") | |
for _, item := range envelopeItems { | |
@@ -804,7 +833,7 @@ func (me *defaultRepository) Update(ctx context.Context, signature *model.Signat | |
return err | |
} | |
- err = me.UpdateEnvelopeItems(ctx, signature.EnvelopeItems) | |
+ err = me.UpdateEnvelopItems(ctx, signature.EnvelopeItems) | |
if err != nil { | |
log.ErrorCtx(ctx, "Failed to update envelop Items") | |
me.db.Rollback(ctx) | |
@@ -957,36 +986,7 @@ func (me *defaultRepository) FindExpiredBefore(ctx context.Context, t time.Time) | |
return signatureRequests, err | |
} | |
-func (me *defaultRepository) FindByDocumentIDAndSignerUID(ctx context.Context, documentID string, signerUID string) (*model.SignatureRequest, error) { | |
- log.DebugCtx(ctx, "Attempt to retrieve signature request found for documentID %s and signerUID %s", documentID, signerUID) | |
- sqlQuery := fmt.Sprintf( | |
- `%s JOIN files f on s.file_id = f.id WHERE f.document_id = ? and s.signer_uid = ?`, | |
- me.selectSQLAsString(), | |
- ) | |
- | |
- rows, err := me.db.Query(ctx, sqlQuery, documentID, signerUID) | |
- if err != nil { | |
- if err == sql.ErrNoRows { | |
- return nil, nil | |
- } | |
- | |
- return nil, err | |
- } | |
- | |
- signature, err := me.singleRowToSignatureRequest(ctx, rows) | |
- if err == sql.ErrNoRows { | |
- log.DebugfCtx(ctx, "No signature request found for documentID %s and signerUID %s", documentID, signerUID) | |
- | |
- } else if err != nil { | |
- log.ErrorCtx(ctx, "Scanning row to SignatureRequest", err) | |
- return nil, err | |
- } | |
- | |
- log.DebugCtx(ctx, "Successfully retrieved signature request found for documentID %s and signerUID %s", documentID, signerUID) | |
- return signature, nil | |
-} | |
- | |
-func (me *defaultRepository) FindCompletedForOrganization(ctx context.Context, entityID string) ([]model.SignatureRequest, error) { | |
+func (me *defaultRepository) FindActiveOrganizationSignatures(ctx context.Context, entityID string) ([]model.SignatureRequest, error) { | |
sqlQuery := me.selectSQLAsString() + ` | |
WHERE file_id IN( | |
SELECT file_id FROM signature_requests WHERE file_url <> '' AND cancelled = 0 AND | |
diff --git a/signature_requests/repository/repositoryfakes/fake_repository.go b/signature_requests/repository/repositoryfakes/fake_repository.go | |
index a4ec569c..5cb82a14 100644 | |
--- a/signature_requests/repository/repositoryfakes/fake_repository.go | |
+++ b/signature_requests/repository/repositoryfakes/fake_repository.go | |
@@ -86,6 +86,20 @@ type FakeRepository struct { | |
deleteTokensReturnsOnCall map[int]struct { | |
result1 error | |
} | |
+ FindActiveOrganizationSignaturesStub func(context.Context, string) ([]model.SignatureRequest, error) | |
+ findActiveOrganizationSignaturesMutex sync.RWMutex | |
+ findActiveOrganizationSignaturesArgsForCall []struct { | |
+ arg1 context.Context | |
+ arg2 string | |
+ } | |
+ findActiveOrganizationSignaturesReturns struct { | |
+ result1 []model.SignatureRequest | |
+ result2 error | |
+ } | |
+ findActiveOrganizationSignaturesReturnsOnCall map[int]struct { | |
+ result1 []model.SignatureRequest | |
+ result2 error | |
+ } | |
FindAllForSignerStub func(context.Context, string) ([]model.SignatureRequest, error) | |
findAllForSignerMutex sync.RWMutex | |
findAllForSignerArgsForCall []struct { | |
@@ -264,20 +278,6 @@ type FakeRepository struct { | |
result1 []model.SignatureRequest | |
result2 error | |
} | |
- FindCompletedForOrganizationStub func(context.Context, string) ([]model.SignatureRequest, error) | |
- findCompletedForOrganizationMutex sync.RWMutex | |
- findCompletedForOrganizationArgsForCall []struct { | |
- arg1 context.Context | |
- arg2 string | |
- } | |
- findCompletedForOrganizationReturns struct { | |
- result1 []model.SignatureRequest | |
- result2 error | |
- } | |
- findCompletedForOrganizationReturnsOnCall map[int]struct { | |
- result1 []model.SignatureRequest | |
- result2 error | |
- } | |
FindExpiredBeforeStub func(context.Context, time.Time) ([]model.SignatureRequest, error) | |
findExpiredBeforeMutex sync.RWMutex | |
findExpiredBeforeArgsForCall []struct { | |
@@ -712,6 +712,71 @@ func (fake *FakeRepository) DeleteTokensReturnsOnCall(i int, result1 error) { | |
}{result1} | |
} | |
+func (fake *FakeRepository) FindActiveOrganizationSignatures(arg1 context.Context, arg2 string) ([]model.SignatureRequest, error) { | |
+ fake.findActiveOrganizationSignaturesMutex.Lock() | |
+ ret, specificReturn := fake.findActiveOrganizationSignaturesReturnsOnCall[len(fake.findActiveOrganizationSignaturesArgsForCall)] | |
+ fake.findActiveOrganizationSignaturesArgsForCall = append(fake.findActiveOrganizationSignaturesArgsForCall, struct { | |
+ arg1 context.Context | |
+ arg2 string | |
+ }{arg1, arg2}) | |
+ stub := fake.FindActiveOrganizationSignaturesStub | |
+ fakeReturns := fake.findActiveOrganizationSignaturesReturns | |
+ fake.recordInvocation("FindActiveOrganizationSignatures", []interface{}{arg1, arg2}) | |
+ fake.findActiveOrganizationSignaturesMutex.Unlock() | |
+ if stub != nil { | |
+ return stub(arg1, arg2) | |
+ } | |
+ if specificReturn { | |
+ return ret.result1, ret.result2 | |
+ } | |
+ return fakeReturns.result1, fakeReturns.result2 | |
+} | |
+ | |
+func (fake *FakeRepository) FindActiveOrganizationSignaturesCallCount() int { | |
+ fake.findActiveOrganizationSignaturesMutex.RLock() | |
+ defer fake.findActiveOrganizationSignaturesMutex.RUnlock() | |
+ return len(fake.findActiveOrganizationSignaturesArgsForCall) | |
+} | |
+ | |
+func (fake *FakeRepository) FindActiveOrganizationSignaturesCalls(stub func(context.Context, string) ([]model.SignatureRequest, error)) { | |
+ fake.findActiveOrganizationSignaturesMutex.Lock() | |
+ defer fake.findActiveOrganizationSignaturesMutex.Unlock() | |
+ fake.FindActiveOrganizationSignaturesStub = stub | |
+} | |
+ | |
+func (fake *FakeRepository) FindActiveOrganizationSignaturesArgsForCall(i int) (context.Context, string) { | |
+ fake.findActiveOrganizationSignaturesMutex.RLock() | |
+ defer fake.findActiveOrganizationSignaturesMutex.RUnlock() | |
+ argsForCall := fake.findActiveOrganizationSignaturesArgsForCall[i] | |
+ return argsForCall.arg1, argsForCall.arg2 | |
+} | |
+ | |
+func (fake *FakeRepository) FindActiveOrganizationSignaturesReturns(result1 []model.SignatureRequest, result2 error) { | |
+ fake.findActiveOrganizationSignaturesMutex.Lock() | |
+ defer fake.findActiveOrganizationSignaturesMutex.Unlock() | |
+ fake.FindActiveOrganizationSignaturesStub = nil | |
+ fake.findActiveOrganizationSignaturesReturns = struct { | |
+ result1 []model.SignatureRequest | |
+ result2 error | |
+ }{result1, result2} | |
+} | |
+ | |
+func (fake *FakeRepository) FindActiveOrganizationSignaturesReturnsOnCall(i int, result1 []model.SignatureRequest, result2 error) { | |
+ fake.findActiveOrganizationSignaturesMutex.Lock() | |
+ defer fake.findActiveOrganizationSignaturesMutex.Unlock() | |
+ fake.FindActiveOrganizationSignaturesStub = nil | |
+ if fake.findActiveOrganizationSignaturesReturnsOnCall == nil { | |
+ fake.findActiveOrganizationSignaturesReturnsOnCall = make(map[int]struct { | |
+ result1 []model.SignatureRequest | |
+ result2 error | |
+ }) | |
+ } | |
+ fake.findActiveOrganizationSignaturesReturnsOnCall[i] = struct { | |
+ result1 []model.SignatureRequest | |
+ result2 error | |
+ }{result1, result2} | |
+} | |
+ | |
func (fake *FakeRepository) FindAllForSigner(arg1 context.Context, arg2 string) ([]model.SignatureRequest, error) { | |
fake.findAllForSignerMutex.Lock() | |
ret, specificReturn := fake.findAllForSignerReturnsOnCall[len(fake.findAllForSignerArgsForCall)] | |
@@ -1512,71 +1577,6 @@ func (fake *FakeRepository) FindBySignerWithNameOrEmailLikeReturnsOnCall(i int, | |
}{result1, result2} | |
} | |
-func (fake *FakeRepository) FindCompletedForOrganization(arg1 context.Context, arg2 string) ([]model.SignatureRequest, error) { | |
- fake.findCompletedForOrganizationMutex.Lock() | |
- ret, specificReturn := fake.findCompletedForOrganizationReturnsOnCall[len(fake.findCompletedForOrganizationArgsForCall)] | |
- fake.findCompletedForOrganizationArgsForCall = append(fake.findCompletedForOrganizationArgsForCall, struct { | |
- arg1 context.Context | |
- arg2 string | |
- }{arg1, arg2}) | |
- stub := fake.FindCompletedForOrganizationStub | |
- fakeReturns := fake.findCompletedForOrganizationReturns | |
- fake.recordInvocation("FindCompletedForOrganization", []interface{}{arg1, arg2}) | |
- fake.findCompletedForOrganizationMutex.Unlock() | |
- if stub != nil { | |
- return stub(arg1, arg2) | |
- } | |
- if specificReturn { | |
- return ret.result1, ret.result2 | |
- } | |
- return fakeReturns.result1, fakeReturns.result2 | |
-} | |
- | |
-func (fake *FakeRepository) FindCompletedForOrganizationCallCount() int { | |
- fake.findCompletedForOrganizationMutex.RLock() | |
- defer fake.findCompletedForOrganizationMutex.RUnlock() | |
- return len(fake.findCompletedForOrganizationArgsForCall) | |
-} | |
- | |
-func (fake *FakeRepository) FindCompletedForOrganizationCalls(stub func(context.Context, string) ([]model.SignatureRequest, error)) { | |
- fake.findCompletedForOrganizationMutex.Lock() | |
- defer fake.findCompletedForOrganizationMutex.Unlock() | |
- fake.FindCompletedForOrganizationStub = stub | |
-} | |
- | |
-func (fake *FakeRepository) FindCompletedForOrganizationArgsForCall(i int) (context.Context, string) { | |
- fake.findCompletedForOrganizationMutex.RLock() | |
- defer fake.findCompletedForOrganizationMutex.RUnlock() | |
- argsForCall := fake.findCompletedForOrganizationArgsForCall[i] | |
- return argsForCall.arg1, argsForCall.arg2 | |
-} | |
- | |
-func (fake *FakeRepository) FindCompletedForOrganizationReturns(result1 []model.SignatureRequest, result2 error) { | |
- fake.findCompletedForOrganizationMutex.Lock() | |
- defer fake.findCompletedForOrganizationMutex.Unlock() | |
- fake.FindCompletedForOrganizationStub = nil | |
- fake.findCompletedForOrganizationReturns = struct { | |
- result1 []model.SignatureRequest | |
- result2 error | |
- }{result1, result2} | |
-} | |
- | |
-func (fake *FakeRepository) FindCompletedForOrganizationReturnsOnCall(i int, result1 []model.SignatureRequest, result2 error) { | |
- fake.findCompletedForOrganizationMutex.Lock() | |
- defer fake.findCompletedForOrganizationMutex.Unlock() | |
- fake.FindCompletedForOrganizationStub = nil | |
- if fake.findCompletedForOrganizationReturnsOnCall == nil { | |
- fake.findCompletedForOrganizationReturnsOnCall = make(map[int]struct { | |
- result1 []model.SignatureRequest | |
- result2 error | |
- }) | |
- } | |
- fake.findCompletedForOrganizationReturnsOnCall[i] = struct { | |
- result1 []model.SignatureRequest | |
- result2 error | |
- }{result1, result2} | |
-} | |
- | |
func (fake *FakeRepository) FindExpiredBefore(arg1 context.Context, arg2 time.Time) ([]model.SignatureRequest, error) { | |
fake.findExpiredBeforeMutex.Lock() | |
ret, specificReturn := fake.findExpiredBeforeReturnsOnCall[len(fake.findExpiredBeforeArgsForCall)] | |
@@ -1850,6 +1850,8 @@ func (fake *FakeRepository) Invocations() map[string][][]interface{} { | |
defer fake.deleteByIDMutex.RUnlock() | |
fake.deleteTokensMutex.RLock() | |
defer fake.deleteTokensMutex.RUnlock() | |
+ fake.findActiveOrganizationSignaturesMutex.RLock() | |
+ defer fake.findActiveOrganizationSignaturesMutex.RUnlock() | |
fake.findAllForSignerMutex.RLock() | |
defer fake.findAllForSignerMutex.RUnlock() | |
fake.findByDocumentIDAndSignerUIDMutex.RLock() | |
@@ -1874,8 +1876,6 @@ func (fake *FakeRepository) Invocations() map[string][][]interface{} { | |
defer fake.findBySignerUIDMutex.RUnlock() | |
fake.findBySignerWithNameOrEmailLikeMutex.RLock() | |
defer fake.findBySignerWithNameOrEmailLikeMutex.RUnlock() | |
- fake.findCompletedForOrganizationMutex.RLock() | |
- defer fake.findCompletedForOrganizationMutex.RUnlock() | |
fake.findExpiredBeforeMutex.RLock() | |
defer fake.findExpiredBeforeMutex.RUnlock() | |
fake.findExpiredByFileIdMutex.RLock() | |
diff --git a/signature_requests/service.go b/signature_requests/service.go | |
index cb12885c..954ccaa5 100644 | |
--- a/signature_requests/service.go | |
+++ b/signature_requests/service.go | |
@@ -74,66 +74,6 @@ type defaultService struct { | |
var _ Service = &defaultService{} | |
-var EventTypeDeleteSignature model.EventType = "delete_signature_request" | |
-var EventTypeSignatureRequestCreated model.EventType = "signature_request_created" | |
- | |
-type SignatureRequestCreated = struct { | |
- Request model.SignatureRequest | |
- Token string | |
-} | |
- | |
-func NewDefaultService( | |
- config *configuration.Configuration, | |
- repository repository.Repository, | |
- signingProcessWriteRepo signingprocess.Repository, | |
- transactionManager dbrepository.DBTransactionManager, | |
- mailService mail.MailService, | |
- identityService identity.IdentityService, | |
- userService user.UserService, | |
- featureService feature.FeatureService, | |
- fileService file.FileService, | |
- organizationService organization.Service, | |
- balanceService balance.BalanceService, | |
- templateService template.TemplateService, | |
- claimMetadataService claim.ClaimMetadataService, | |
- signatureService signatures.Service, | |
- notificationService notification.NotificationService, | |
- storageService storage.Service, | |
- settingService setting.SettingService) *defaultService { | |
- | |
- return &defaultService{ | |
- config: config, | |
- repository: repository, | |
- signingProcessRepo: signingProcessWriteRepo, | |
- transactionManager: transactionManager, | |
- mailService: mailService, | |
- userService: userService, | |
- identityService: identityService, | |
- featureService: featureService, | |
- fileService: fileService, | |
- organizationService: organizationService, | |
- balanceService: balanceService, | |
- templateService: templateService, | |
- claimMetadataService: claimMetadataService, | |
- fileExpiryInDays: config.FileURLExpiryInDays, | |
- anonymizeOlderThanInDays: config.SRAnonymizationOlderThanInDays, | |
- anonymizationBatchCountLimit: config.SRAnonymizationBatchCountLimit, | |
- anonymizationBatchSize: config.SRAnonymizationBatchSize, | |
- signatureService: signatureService, | |
- notificationService: notificationService, | |
- storageService: storageService, | |
- settingService: settingService, | |
- } | |
-} | |
- | |
-type SignaturesRequestResult struct { | |
- Err error | |
- SignerToken string | |
- CreditTransactionId string //contains the signatureRequest.ID | |
- Prepaid bool | |
- SendEmail bool // is true if sendEmail is true and if this user is next to sign (depending on signOrder) | |
-} | |
- | |
func (me *defaultService) DocumentUploaded(ctx context.Context, event callback.Event) error { | |
signatureRequest, err := me.repository.FindByDocumentIDAndSignerUID(ctx, event.DocumentID, event.Author) | |
if err != nil { | |
@@ -261,6 +201,68 @@ func (me *defaultService) anonymizeSignatureRequest(ctx context.Context, request | |
return nil | |
} | |
+var _ Service = &defaultService{} | |
+ | |
+var EventTypeDeleteSignature model.EventType = "delete_signature_request" | |
+var EventTypeSignatureRequestCreated model.EventType = "signature_request_created" | |
+ | |
+type SignatureRequestCreated = struct { | |
+ Request model.SignatureRequest | |
+ Token string | |
+} | |
+ | |
+func NewDefaultService( | |
+ config *configuration.Configuration, | |
+ repository repository.Repository, | |
+ signingProcessWriteRepo signingprocess.Repository, | |
+ transactionManager dbrepository.DBTransactionManager, | |
+ mailService mail.MailService, | |
+ identityService identity.IdentityService, | |
+ userService user.UserService, | |
+ featureService feature.FeatureService, | |
+ fileService file.FileService, | |
+ organizationService organization.Service, | |
+ balanceService balance.BalanceService, | |
+ templateService template.TemplateService, | |
+ claimMetadataService claim.ClaimMetadataService, | |
+ signatureService signatures.Service, | |
+ notificationService notification.NotificationService, | |
+ storageService storage.Service, | |
+ settingService setting.SettingService) *defaultService { | |
+ | |
+ return &defaultService{ | |
+ config: config, | |
+ repository: repository, | |
+ signingProcessRepo: signingProcessWriteRepo, | |
+ transactionManager: transactionManager, | |
+ mailService: mailService, | |
+ userService: userService, | |
+ identityService: identityService, | |
+ featureService: featureService, | |
+ fileService: fileService, | |
+ organizationService: organizationService, | |
+ balanceService: balanceService, | |
+ templateService: templateService, | |
+ claimMetadataService: claimMetadataService, | |
+ fileExpiryInDays: config.FileURLExpiryInDays, | |
+ anonymizeOlderThanInDays: config.SRAnonymizationOlderThanInDays, | |
+ anonymizationBatchCountLimit: config.SRAnonymizationBatchCountLimit, | |
+ anonymizationBatchSize: config.SRAnonymizationBatchSize, | |
+ signatureService: signatureService, | |
+ notificationService: notificationService, | |
+ storageService: storageService, | |
+ settingService: settingService, | |
+ } | |
+} | |
+ | |
+type SignaturesRequestResult struct { | |
+ Err error | |
+ SignerToken string | |
+ CreditTransactionId string //contains the signatureRequest.ID | |
+ Prepaid bool | |
+ SendEmail bool // is true if sendEmail is true and if this user is next to sign (depending on signOrder) | |
+} | |
+ | |
func (me *defaultService) GetByID(ctx context.Context, id string) (*model.SignatureRequest, error) { | |
return me.repository.FindByID(ctx, id) | |
} | |
@@ -1702,9 +1704,6 @@ func (me *defaultService) ClearExpired(ctx context.Context) error { | |
for _, expiredSignatureRequest := range expiredSignatureRequests { | |
expiredSignatureRequest.FileURL = "" | |
- for i := range expiredSignatureRequest.GetEnvelopeItems() { | |
- expiredSignatureRequest.EnvelopeItems[i].FileURL = "" | |
- } | |
err = me.repository.Update(ctx, &expiredSignatureRequest) | |
if err != nil { | |
log.ErrorfCtx(ctx, "Update signature request file url: %s", err) | |
@@ -2114,7 +2113,7 @@ func (me *defaultService) sendNextEmailsIfNeeded(ctx context.Context, signatureR | |
return nil | |
} | |
-func (me *defaultService) minSignOrder(signaturesRequest model.SignaturesRequest) int { | |
+func (me defaultService) minSignOrder(signaturesRequest model.SignaturesRequest) int { | |
minSignOrder := math.MaxInt | |
for _, signer := range signaturesRequest.Signers { | |
if signer.SignOrder < minSignOrder { |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment