Skip to content

Commit 94b8287

Browse files
committed
feat: added new endpoints to better see skipped Ids and expose emails
1 parent 1288dc1 commit 94b8287

7 files changed

Lines changed: 114 additions & 1 deletion

File tree

casbin/casbin_auth_policy.csv

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ p, PARTICIPANT, /api/participantdata,
3232
p, GUEST, /api/participantdata, POST
3333
p, ADMIN, /api/participantdata/{studyId}/{taskOrder}, GET
3434
p, ORGANIZATION_MEMBER, /api/participantdata/{studyId}/{taskOrder}, GET
35+
p, ADMIN, /api/participantdata/{studyId}, GET
36+
p, ORGANIZATION_MEMBER, /api/participantdata/{studyId}, GET
3537

3638
p, ADMIN, /api/tasks, GET
3739
p, ORGANIZATION_MEMBER, /api/tasks, GET

src/controllers/ParticipantData.controller.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,31 @@ func (s *ParticipantDataController) GetParticipantDataByStudyIdAndTaskOrder(e ec
3939
}
4040
return common.SendHTTPOkWithBody(e, taskData)
4141
}
42+
43+
// GetParticipantDataByStudyIdWithFilters gets participant data for a study with optional metadata filters via query parameters
44+
func (s *ParticipantDataController) GetParticipantDataByStudyIdWithFilters(e echo.Context) error {
45+
axonlogger.InfoLogger.Println("============= PARTICIPANTDATA CONTROLLER: GetParticipantDataByStudyIdWithFilters() =============")
46+
studyId := e.Param("studyId")
47+
48+
userId, ok := e.Get("id").(string)
49+
if !ok {
50+
return common.SendHTTPBadRequest(e)
51+
}
52+
53+
// Extract all query parameters as filters
54+
// This makes the endpoint extensible - any query param will be treated as a metadata filter
55+
filters := make(map[string]string)
56+
queryParams := e.QueryParams()
57+
for key, values := range queryParams {
58+
if len(values) > 0 {
59+
// Use the first value if multiple values are provided for the same key
60+
filters[key] = values[0]
61+
}
62+
}
63+
64+
participantData, httpStatus := participantDataServiceImpl.GetParticipantDataByStudyIdWithFilters(studyId, userId, filters)
65+
if !common.HTTPRequestIsSuccessful(httpStatus.Status) {
66+
return e.JSON(httpStatus.Status, httpStatus)
67+
}
68+
return common.SendHTTPOkWithBody(e, participantData)
69+
}

src/database/ParticipantData.database.go

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ func (s *ParticipantDataRepository) CreateParticipantData(participantData models
7878
axonlogger.ErrorLogger.Println("could not save participant data", err)
7979
return models.HTTPStatus{Status: http.StatusInternalServerError, Message: http.StatusText(http.StatusInternalServerError)}
8080
}
81-
8281
}
8382

8483
axonlogger.InfoLogger.Println("Successfully uploaded task data [studyId, userId, taskOrder]:", participantData.StudyID, participantData.UserID, participantData.TaskOrder)
@@ -143,3 +142,46 @@ func (s *ParticipantDataRepository) GetAllParticipantDataByStudyIdAndTaskOrder(s
143142

144143
return participantData, models.HTTPStatus{Status: http.StatusOK, Message: http.StatusText(http.StatusOK)}
145144
}
145+
146+
// GetParticipantDataByStudyIdWithFilters gets all participant data for the given study id with optional metadata filters
147+
// It returns a 200 or 500 status code
148+
// The filters parameter is a map of JSON path keys to values (e.g., map["wasSkipped"]="true")
149+
func (s *ParticipantDataRepository) GetParticipantDataByStudyIdWithFilters(studyId uint, filters map[string]string) ([]models.ParticipantData, models.HTTPStatus) {
150+
axonlogger.InfoLogger.Println("PARTICIPANTDATA DATABASE: GetParticipantDataByStudyIdWithFilters()")
151+
152+
defer func() {
153+
if err := recover(); err != nil {
154+
axonlogger.ErrorLogger.Println("there was an error getting the participant data list with filters", err)
155+
}
156+
}()
157+
158+
participantData := []models.ParticipantData{}
159+
160+
// Build the base query
161+
query := `
162+
SELECT user_id, study_id, task_order, participant_type, submitted_at, metadata, data
163+
FROM participant_data
164+
WHERE study_id = ?`
165+
166+
// Create slice to hold query arguments
167+
args := []interface{}{studyId}
168+
169+
// Handle specific filter: wasSkipped
170+
if skippedValue, exists := filters["wasSkipped"]; exists {
171+
// Use CAST(... AS CHAR) to convert JSON boolean to string for comparison
172+
query += ` AND metadata->>'$.wasSkipped' = ?`
173+
args = append(args, skippedValue)
174+
}
175+
176+
query += ";"
177+
178+
if httpStatus := baseRepositoryImpl.GetAllBy(
179+
&participantData,
180+
query,
181+
args...,
182+
); !common.HTTPRequestIsSuccessful(httpStatus.Status) {
183+
return participantData, httpStatus
184+
}
185+
186+
return participantData, models.HTTPStatus{Status: http.StatusOK, Message: http.StatusText(http.StatusOK)}
187+
}

src/models/StudyUser.model.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type StudyUser struct {
3939
// StudyUserSummary
4040
type StudyUserSummary struct {
4141
UserId uint `json:"userId"`
42+
Email string `json:"email"`
4243
Studies []uint `json:"studies"`
4344
}
4445

src/services/ParticipantData.service.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,36 @@ func (s *ParticipantDataService) GetParticipantDataByStudyIdAndTaskOrder(studyId
5151
return participantDataRepositoryImpl.GetAllParticipantDataByStudyIdAndTaskOrder(studyId, taskOrder)
5252

5353
}
54+
55+
// GetParticipantDataByStudyIdWithFilters gets participant data for a given study id with optional metadata filters.
56+
// It returns a 200, 403, or 500 status code
57+
func (s *ParticipantDataService) GetParticipantDataByStudyIdWithFilters(studyIdFromPath string, userId string, filters map[string]string) ([]models.ParticipantData, models.HTTPStatus) {
58+
axonlogger.InfoLogger.Println("PARTICIPANTDATA SERVICE: GetParticipantDataByStudyIdWithFilters()")
59+
studyId, convertStudyIdErr := convertStringToUint8(studyIdFromPath)
60+
if convertStudyIdErr != nil {
61+
axonlogger.WarningLogger.Println(convertStudyIdErr)
62+
return []models.ParticipantData{}, models.HTTPStatus{Status: http.StatusInternalServerError, Message: http.StatusText(http.StatusInternalServerError)}
63+
}
64+
65+
parsedUserId, getUserIdError := convertStringToUint8(userId)
66+
if getUserIdError != nil {
67+
axonlogger.WarningLogger.Println(getUserIdError)
68+
return []models.ParticipantData{}, models.HTTPStatus{Status: http.StatusInternalServerError, Message: http.StatusText(http.StatusInternalServerError)}
69+
}
70+
71+
user, getUserHttpStatus := userRepositoryImpl.GetUserById(parsedUserId)
72+
if !common.HTTPRequestIsSuccessful(getUserHttpStatus.Status) {
73+
return []models.ParticipantData{}, getUserHttpStatus
74+
}
75+
76+
study, getStudyHttpStatus := studyRepositoryImpl.GetStudyById(studyId)
77+
if !common.HTTPRequestIsSuccessful(getStudyHttpStatus.Status) {
78+
return []models.ParticipantData{}, getStudyHttpStatus
79+
}
80+
81+
if user.Organization.ID != study.Owner.Organization.ID && (user.Role != common.ADMIN && user.Role != common.ORGANIZATION_MEMBER) {
82+
return []models.ParticipantData{}, models.HTTPStatus{Status: http.StatusForbidden, Message: http.StatusText(http.StatusForbidden)}
83+
}
84+
85+
return participantDataRepositoryImpl.GetParticipantDataByStudyIdWithFilters(studyId, filters)
86+
}

src/services/StudyUser.service.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,13 @@ func (s *StudyUserService) GetStudyUserSummary() ([]models.StudyUserSummary, mod
149149
var studyUserSummary = make([]models.StudyUserSummary, len(studyUsersMap))
150150
index := 0
151151
for key, studiesSlice := range studyUsersMap {
152+
user, httpStatus := userRepositoryImpl.GetUserById(key)
153+
if !common.HTTPRequestIsSuccessful(httpStatus.Status) {
154+
axonlogger.ErrorLogger.Println("encountered an error while getting user by id", httpStatus)
155+
continue
156+
}
152157
studyUserSummary[index].UserId = key
158+
studyUserSummary[index].Email = user.Email
153159
studyUserSummary[index].Studies = studiesSlice
154160
index++
155161
}

src/setup/Router.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ func setUpParticipantDataRoutes(group *echo.Group) {
7979
studyData := group.Group("/participantdata")
8080
studyData.POST("", participantDataControllerImpl.CreateParticipantData)
8181
studyData.GET("/:studyId/:taskOrder", participantDataControllerImpl.GetParticipantDataByStudyIdAndTaskOrder)
82+
studyData.GET("/:studyId", participantDataControllerImpl.GetParticipantDataByStudyIdWithFilters)
8283
}
8384

8485
func setUpSummaryRoutes(group *echo.Group) {

0 commit comments

Comments
 (0)