From c9ef4dce518a166767e4ee7e32a2fc444af3bd59 Mon Sep 17 00:00:00 2001 From: Nicholas Lee Date: Mon, 25 Mar 2024 16:20:44 -0400 Subject: [PATCH 1/2] feat: added support for snapshots --- casbin/casbin_auth_policy.csv | 2 ++ src/controllers/Study.controller.go | 14 ++++++++ src/database/BaseRepository.database.go | 2 +- src/database/Study.Database.go | 8 +++-- src/models/ParticipantData.model.go | 4 +-- src/models/Study.model.go | 3 ++ src/services/Study.service.go | 44 +++++++++++++++++++++++++ src/setup/Router.go | 1 + 8 files changed, 72 insertions(+), 6 deletions(-) diff --git a/casbin/casbin_auth_policy.csv b/casbin/casbin_auth_policy.csv index 1f28947..380c4a8 100644 --- a/casbin/casbin_auth_policy.csv +++ b/casbin/casbin_auth_policy.csv @@ -44,6 +44,8 @@ p, GUEST, /api/tasks/{taskId}, p, *, /api/studies/{studyId}, GET p, ADMIN, /api/studies/{studyId}, PATCH p, ORGANIZATION_MEMBER, /api/studies/{studyId}, PATCH +p, ADMIN, /api/studies/{studyId}/snapshot, PATCH +p, ORGANIZATION_MEMBER, /api/studies/{studyId}/snapshot, PATCH p, ADMIN, /api/studies/{studyId}, DELETE p, ORGANIZATION_MEMBER, /api/studies/{studyId}, DELETE p, ADMIN, /api/studies/{studyId}/studyusers, GET diff --git a/src/controllers/Study.controller.go b/src/controllers/Study.controller.go index 278bf1e..8183fd7 100644 --- a/src/controllers/Study.controller.go +++ b/src/controllers/Study.controller.go @@ -138,3 +138,17 @@ func (s *StudyController) GetStudyUsersByStudyId(e echo.Context) error { return common.SendHTTPOkWithBody(e, results) } + +// SnapshotStudyByStudyId +func (s *StudyController) SnapshotStudyByStudyId(e echo.Context) error { + axonlogger.InfoLogger.Println("============= STUDY CONTROLLER: SnapshotStudyByStudyId() =============") + + studyParamId := e.Param("studyId") + httpStatus := studyServiceImpl.SnapshotStudyByStudyId(studyParamId) + if !common.HTTPRequestIsSuccessful(httpStatus.Status) { + axonlogger.ErrorLogger.Println("GetStudyUsersByStudyId() failed") + return e.JSON(httpStatus.Status, httpStatus) + } + + return common.SendHTTPOk(e) +} diff --git a/src/database/BaseRepository.database.go b/src/database/BaseRepository.database.go index 0ed3fb9..67acb18 100644 --- a/src/database/BaseRepository.database.go +++ b/src/database/BaseRepository.database.go @@ -133,7 +133,7 @@ func (b *BaseRepository) GetAllBy(argStructSlice interface{}, query string, args rowToStructErr := rowsToStructs(rows, argStructSlice) if rowToStructErr != nil { axonlogger.ErrorLogger.Println("Attempted Query: " + query) - axonlogger.ErrorLogger.Println("Error scanning rows", err) + axonlogger.ErrorLogger.Println("Error scanning rows", rowToStructErr) return models.HTTPStatus{Status: http.StatusInternalServerError, Message: http.StatusText(http.StatusInternalServerError)} } return models.HTTPStatus{Status: http.StatusOK, Message: http.StatusText(http.StatusOK)} diff --git a/src/database/Study.Database.go b/src/database/Study.Database.go index 45966e9..bd9a66d 100644 --- a/src/database/Study.Database.go +++ b/src/database/Study.Database.go @@ -105,7 +105,7 @@ func (s *StudyRepository) GetStudyById(studyId uint) (models.Study, models.HTTPS httpStatus := baseRepositoryImpl.GetOneBy( &dbStudy, ` - SELECT id, owner_id, created_at, deleted_at, internal_name, external_name, started, can_edit, consent, description, config + SELECT id, owner_id, created_at, deleted_at, internal_name, external_name, started, can_edit, consent, description, config, snapshots FROM studies WHERE id = ? LIMIT 1; @@ -148,6 +148,7 @@ func (s *StudyRepository) GetStudyById(studyId uint) (models.Study, models.HTTPS study.Description = dbStudy.Description study.Config = dbStudy.Config study.StudyTasks = studyTasks + study.Snapshots = dbStudy.Snapshots return study, models.HTTPStatus{Status: http.StatusOK, Message: http.StatusText(http.StatusOK)} } @@ -168,7 +169,7 @@ func (s *StudyRepository) GetStudiesByOrganizationId(organizationId uint) ([]mod if httpStatus := baseRepositoryImpl.GetAllBy( &dbStudies, ` - SELECT studies.id, owner_id, studies.created_at, deleted_at, internal_name, external_name, started, can_edit, consent, description, config + SELECT studies.id, owner_id, studies.created_at, deleted_at, internal_name, external_name, started, can_edit, consent, description, config, snapshots FROM studies JOIN users ON studies.owner_id = users.id WHERE users.organization_id = ? AND deleted_at IS NULL ORDER BY created_at DESC; @@ -262,7 +263,7 @@ func (s *StudyRepository) UpdateStudyWithoutTaskUpdate(study *models.Study) mode if _, err := db.Exec( ` UPDATE studies - SET deleted_at = ?, internal_name = ?, external_name = ?, started = ?, description = ?, can_edit = ?, consent = ?, config = ? + SET deleted_at = ?, internal_name = ?, external_name = ?, started = ?, description = ?, can_edit = ?, consent = ?, config = ?, snapshots = ? WHERE id = ?; `, study.DeletedAt, @@ -273,6 +274,7 @@ func (s *StudyRepository) UpdateStudyWithoutTaskUpdate(study *models.Study) mode study.CanEdit, study.Consent.ID, study.Config, + study.Snapshots, study.ID, ); err != nil { if err == sql.ErrNoRows { diff --git a/src/models/ParticipantData.model.go b/src/models/ParticipantData.model.go index c0819e5..3a3e372 100644 --- a/src/models/ParticipantData.model.go +++ b/src/models/ParticipantData.model.go @@ -11,7 +11,7 @@ import ( * This file defines structs that outline the parameters collected for each task that is administered to participants */ -type SliceMapStringInterface []map[string]interface{} +type SliceMapStringInterface []MapStringInterface // ParticipantDataSchema defines the SQL table schema for this model var ParticipantDataSchema = ` @@ -44,7 +44,7 @@ type ParticipantData struct { // The data stored in a JSON field is returned as a []uint8 func (s *SliceMapStringInterface) Scan(src interface{}) error { var source []byte - var tempMapSlice []map[string]interface{} + var tempMapSlice SliceMapStringInterface switch src := src.(type) { case []uint8: diff --git a/src/models/Study.model.go b/src/models/Study.model.go index e7430cf..82a40f0 100644 --- a/src/models/Study.model.go +++ b/src/models/Study.model.go @@ -19,6 +19,7 @@ var StudySchema = ` consent INT UNSIGNED DEFAULT(NULL), description VARCHAR(300), config JSON NOT NULL DEFAULT (JSON_OBJECT()), + snapshots JSON NOT NULL DEFAULT (JSON_ARRAY()), FOREIGN KEY (consent) REFERENCES tasks(id), FOREIGN KEY (owner_id) REFERENCES users(id), PRIMARY KEY (id) @@ -39,6 +40,7 @@ type Study struct { Description string `json:"description"` Config MapStringInterface `json:"config"` StudyTasks []StudyTask `json:"studyTasks"` + Snapshots MapStringInterface `json:"snapshots"` } // DBStudy is the database representation of a study @@ -54,4 +56,5 @@ type DBStudy struct { ConsentId uint `json:"consentId"` Description string `json:"description"` Config MapStringInterface `json:"config"` + Snapshots MapStringInterface `json:"snapshots"` } diff --git a/src/services/Study.service.go b/src/services/Study.service.go index db26bf6..03bc862 100644 --- a/src/services/Study.service.go +++ b/src/services/Study.service.go @@ -168,3 +168,47 @@ func (s *StudyService) ArchiveStudyById(studyId string, loggedInUserId string, l return studyRepositoryImpl.UpdateStudyWithoutTaskUpdate(&study) } + +func (s *StudyService) SnapshotStudyByStudyId(studyId string) models.HTTPStatus { + axonlogger.InfoLogger.Println("STUDY SERVICE: SnapshotStudyByStudyId()") + + parsedStudyId, parsedStudyIdErr := convertStringToUint8(studyId) + if parsedStudyIdErr != nil { + axonlogger.WarningLogger.Println("Could not convert id to uint", parsedStudyIdErr) + return models.HTTPStatus{Status: http.StatusInternalServerError, Message: http.StatusText(http.StatusInternalServerError)} + } + + retrievedStudy, getStudyHttpStatus := studyRepositoryImpl.GetStudyById(parsedStudyId) + if !common.HTTPRequestIsSuccessful(getStudyHttpStatus.Status) { + return getStudyHttpStatus + } + + updatedSnapshot := retrievedStudy.Snapshots + snapshotList := make(models.SliceMapStringInterface, 0) + currTime := time.Now().UTC().Format(time.RFC3339) + + for _, studyTask := range retrievedStudy.StudyTasks { + taskSnapshot := make(models.MapStringInterface) + taskSnapshot["taskOrder"] = studyTask.TaskOrder + taskSnapshot["fromPlatform"] = studyTask.Task.FromPlatform + taskSnapshot["description"] = studyTask.Task.Description + taskSnapshot["externalURL"] = studyTask.Task.ExternalURL + taskSnapshot["taskType"] = studyTask.Task.TaskType + taskSnapshot["name"] = studyTask.Task.Name + taskSnapshot["id"] = studyTask.Task.ID + + if len(studyTask.Config) == 0 { + taskSnapshot["config"] = studyTask.Task.Config + } else { + taskSnapshot["config"] = studyTask.Config + } + + snapshotList = append(snapshotList, taskSnapshot) + } + updatedSnapshot[currTime] = snapshotList + studyRepositoryImpl.UpdateStudyWithoutTaskUpdate(&retrievedStudy) + return models.HTTPStatus{ + Status: http.StatusOK, + Message: http.StatusText(http.StatusOK), + } +} diff --git a/src/setup/Router.go b/src/setup/Router.go index 06c92e7..b298416 100644 --- a/src/setup/Router.go +++ b/src/setup/Router.go @@ -61,6 +61,7 @@ func setUpStudyRoutes(group *echo.Group) { studies.GET("/:studyId", studiesControllerImpl.GetStudyById) studies.GET("/:studyId/crowdsourcedusers", studiesControllerImpl.GetCrowdSourcedUsersByStudyId) studies.GET("/:studyId/studyusers", studiesControllerImpl.GetStudyUsersByStudyId) + studies.PATCH("/:studyId/snapshot", studiesControllerImpl.SnapshotStudyByStudyId) studies.PATCH("/:studyId", studiesControllerImpl.UpdateStudy) // takes a query param (updateTasks) studies.DELETE("/:studyId", studiesControllerImpl.ArchiveStudyById) } From 12ca2f7998ef1a64cdf0087245cbc9af1b1f9088 Mon Sep 17 00:00:00 2001 From: Nicholas Lee Date: Tue, 9 Apr 2024 12:46:19 -0400 Subject: [PATCH 2/2] feat: chnage to object --- src/models/Study.model.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/Study.model.go b/src/models/Study.model.go index 82a40f0..26a3e7e 100644 --- a/src/models/Study.model.go +++ b/src/models/Study.model.go @@ -19,7 +19,7 @@ var StudySchema = ` consent INT UNSIGNED DEFAULT(NULL), description VARCHAR(300), config JSON NOT NULL DEFAULT (JSON_OBJECT()), - snapshots JSON NOT NULL DEFAULT (JSON_ARRAY()), + snapshots JSON NOT NULL DEFAULT (JSON_OBJECT()), FOREIGN KEY (consent) REFERENCES tasks(id), FOREIGN KEY (owner_id) REFERENCES users(id), PRIMARY KEY (id)