diff --git a/CHANGELOG.md b/CHANGELOG.md index 96a6bea..fb56461 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Consolidate the information, and make it accessible with a single API call [#79](https://github.com/rokwire/surveys-building-block/issues/79) + ## [1.10.3] - 2024-08-21 ### Fixed - Fix GET surveys time filtering [#75](https://github.com/rokwire/surveys-building-block/issues/75) diff --git a/core/app_client.go b/core/app_client.go index 6b889c2..0179f04 100644 --- a/core/app_client.go +++ b/core/app_client.go @@ -182,6 +182,11 @@ func (a appClient) CreateSurveyAlert(surveyAlert model.SurveyAlert) error { return nil } +// GetUserData returns surveys matching the provided query +func (a appClient) GetUserData(orgID string, appID string, userID *string) (*model.UserData, error) { + return a.app.shared.getUserData(orgID, appID, userID) +} + // newAppClient creates new appClient func newAppClient(app *Application) appClient { return appClient{app: app} diff --git a/core/app_shared.go b/core/app_shared.go index 90097a5..5f7ed32 100644 --- a/core/app_shared.go +++ b/core/app_shared.go @@ -152,6 +152,72 @@ func (a appShared) hasAttendedEvent(orgID string, appID string, eventID string, return false, nil } +func (a appShared) getUserData(orgID string, appID string, userID *string) (*model.UserData, error) { + var serveyUserData []model.SurveysUserData + var surveyResponseUserData []model.SurveysResponseUserData + + // Create channels for data and error handling + surveysChan := make(chan []model.Survey, 1) + surveysErrChan := make(chan error, 1) + surveyResponsesChan := make(chan []model.SurveyResponse, 1) + surveyResponsesErrChan := make(chan error, 1) + + // Fetch surveys asynchronously + go func() { + surveys, err := a.app.storage.GetSurveysLight(orgID, appID, userID) + if err != nil { + surveysErrChan <- err + return + } + surveysChan <- surveys + }() + + // Fetch survey responses asynchronously + go func() { + surveysResponses, err := a.app.storage.GetSurveyResponses(&orgID, &appID, userID, nil, nil, nil, nil, nil, nil) + if err != nil { + surveyResponsesErrChan <- err + return + } + surveyResponsesChan <- surveysResponses + }() + + // Wait for both operations to complete or return an error + var surveys []model.Survey + var surveysResponses []model.SurveyResponse + + for i := 0; i < 2; i++ { + select { + case err := <-surveysErrChan: + return nil, err + case err := <-surveyResponsesErrChan: + return nil, err + case surveys = <-surveysChan: + // Handle the surveys data when received + case surveysResponses = <-surveyResponsesChan: + // Handle the survey responses data when received + } + } + + // Process the surveys data + for _, s := range surveys { + survey := model.SurveysUserData{ID: s.ID, CreatorID: s.CreatorID, AppID: s.AppID, AccountID: s.CreatorID, + OrgID: s.OrgID, Title: s.Title, Type: s.Type} + serveyUserData = append(serveyUserData, survey) + } + + // Process the survey responses data + for _, sr := range surveysResponses { + surveyResponse := model.SurveysResponseUserData{ID: sr.ID, UserID: sr.UserID, AppID: sr.AppID, AccountID: sr.UserID, + OrgID: sr.OrgID, Title: sr.Survey.Title} + surveyResponseUserData = append(surveyResponseUserData, surveyResponse) + } + + // Return the user data after all data has been fetched and processed + userData := model.UserData{SurveyUserData: &serveyUserData, SurveyResponseUserData: &surveyResponseUserData} + return &userData, nil +} + // newAppShared creates new appShared func newAppShared(app *Application) appShared { return appShared{app: app} diff --git a/core/interfaces.go b/core/interfaces.go index 091800b..c29d7fd 100644 --- a/core/interfaces.go +++ b/core/interfaces.go @@ -27,6 +27,8 @@ type Shared interface { isEventAdmin(orgID string, appID string, eventID string, userID string, externalIDs map[string]string) (bool, error) hasAttendedEvent(orgID string, appID string, eventID string, userID string, externalIDs map[string]string) (bool, error) + + getUserData(orgID string, appID string, userID *string) (*model.UserData, error) } // Core exposes Core APIs for the driver adapters diff --git a/core/interfaces/core.go b/core/interfaces/core.go index 7c6521a..866a0c6 100644 --- a/core/interfaces/core.go +++ b/core/interfaces/core.go @@ -46,6 +46,9 @@ type Client interface { // Survey Alerts CreateSurveyAlert(surveyAlert model.SurveyAlert) error + + // User data + GetUserData(orgID string, appID string, userID *string) (*model.UserData, error) } // Admin exposes administrative APIs for the driver adapters diff --git a/core/interfaces/driven.go b/core/interfaces/driven.go index b7f8a8c..9302d0d 100644 --- a/core/interfaces/driven.go +++ b/core/interfaces/driven.go @@ -34,6 +34,8 @@ type Storage interface { GetSurvey(id string, orgID string, appID string) (*model.Survey, error) GetSurveys(orgID string, appID string, creatorID *string, surveyIDs []string, surveyTypes []string, calendarEventID string, limit *int, offset *int, filter *model.SurveyTimeFilter, public *bool, archived *bool, completed *bool) ([]model.Survey, error) + GetSurveysLight(orgID string, appID string, creatorID *string) ([]model.Survey, error) + CreateSurvey(survey model.Survey) (*model.Survey, error) UpdateSurvey(survey model.Survey, admin bool) error DeleteSurvey(id string, orgID string, appID string, creatorID string, admin bool) error diff --git a/core/interfaces/mocks/Storage.go b/core/interfaces/mocks/Storage.go index 8286942..426d0d1 100644 --- a/core/interfaces/mocks/Storage.go +++ b/core/interfaces/mocks/Storage.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. +// Code generated by mockery v2.46.1. DO NOT EDIT. package mocks @@ -572,6 +572,36 @@ func (_m *Storage) GetSurveysAndSurveyResponses(orgID string, appID string, crea return r0, r1, r2 } +// GetSurveysLight provides a mock function with given fields: orgID, appID, creatorID +func (_m *Storage) GetSurveysLight(orgID string, appID string, creatorID *string) ([]model.Survey, error) { + ret := _m.Called(orgID, appID, creatorID) + + if len(ret) == 0 { + panic("no return value specified for GetSurveysLight") + } + + var r0 []model.Survey + var r1 error + if rf, ok := ret.Get(0).(func(string, string, *string) ([]model.Survey, error)); ok { + return rf(orgID, appID, creatorID) + } + if rf, ok := ret.Get(0).(func(string, string, *string) []model.Survey); ok { + r0 = rf(orgID, appID, creatorID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]model.Survey) + } + } + + if rf, ok := ret.Get(1).(func(string, string, *string) error); ok { + r1 = rf(orgID, appID, creatorID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // InsertConfig provides a mock function with given fields: config func (_m *Storage) InsertConfig(config model.Config) error { ret := _m.Called(config) diff --git a/core/model/surveys.go b/core/model/surveys.go index de78538..af00446 100644 --- a/core/model/surveys.go +++ b/core/model/surveys.go @@ -252,3 +252,30 @@ type SurveyTimeFilterRequest struct { EndTimeAfter *string `json:"end_time_after"` EndTimeBefore *string `json:"end_time_before"` } + +// SurveysUserData represents user data for surveys +type SurveysUserData struct { + ID string `json:"id" bson:"_id"` + CreatorID string `json:"creator_id" bson:"creator_id"` + AppID string `json:"app_id"` + AccountID string `json:"account_id"` + OrgID string `json:"org_id"` + Title string `json:"title"` + Type string `json:"type"` +} + +// SurveysResponseUserData represents user data for surveys responses +type SurveysResponseUserData struct { + ID string `json:"id"` + UserID string `json:"user_id"` + AppID string `json:"app_id"` + AccountID string `json:"account_id"` + OrgID string `json:"org_id"` + Title string `json:"title"` +} + +// UserData represents user data +type UserData struct { + SurveyUserData *[]SurveysUserData `json:"survey"` + SurveyResponseUserData *[]SurveysResponseUserData `json:"survey_responses"` +} diff --git a/driven/storage/adapter_surveys.go b/driven/storage/adapter_surveys.go index 6ac8af9..1e413b3 100644 --- a/driven/storage/adapter_surveys.go +++ b/driven/storage/adapter_surveys.go @@ -124,6 +124,26 @@ func (a *Adapter) GetSurveys(orgID string, appID string, creatorID *string, surv return results, nil } +// GetSurveysLight gets matching surveys +func (a *Adapter) GetSurveysLight(orgID string, appID string, creatorID *string) ([]model.Survey, error) { + filter := bson.D{ + {Key: "org_id", Value: orgID}, + {Key: "app_id", Value: appID}, + } + + if creatorID != nil { + filter = append(filter, bson.E{Key: "creator_id", Value: *creatorID}) + } + + var results []model.Survey + err := a.db.surveys.Find(a.context, filter, &results, nil) + if err != nil { + return nil, err + } + + return results, nil +} + // CreateSurvey creates a poll func (a *Adapter) CreateSurvey(survey model.Survey) (*model.Survey, error) { _, err := a.db.surveys.InsertOne(a.context, survey) diff --git a/driver/web/adapter.go b/driver/web/adapter.go index 8ef5f00..3030a8a 100644 --- a/driver/web/adapter.go +++ b/driver/web/adapter.go @@ -87,6 +87,7 @@ func (a Adapter) Start() { mainRouter.HandleFunc("/survey-responses", a.wrapFunc(a.clientAPIsHandler.deleteSurveyResponses, a.auth.client.User)).Methods("DELETE") mainRouter.HandleFunc("/survey-alerts", a.wrapFunc(a.clientAPIsHandler.createSurveyAlert, a.auth.client.User)).Methods("POST") mainRouter.HandleFunc("/creator/surveys", a.wrapFunc(a.clientAPIsHandler.getCreatorSurveys, a.auth.client.User)).Methods("GET") + mainRouter.HandleFunc("/user-data", a.wrapFunc(a.clientAPIsHandler.getUserData, a.auth.client.User)).Methods("GET") // Admin APIs adminRouter := mainRouter.PathPrefix("/admin").Subrouter() diff --git a/driver/web/apis_client.go b/driver/web/apis_client.go index 81d9593..e108757 100644 --- a/driver/web/apis_client.go +++ b/driver/web/apis_client.go @@ -587,6 +587,20 @@ func (h ClientAPIsHandler) getCreatorSurveys(l *logs.Log, r *http.Request, claim return l.HTTPResponseSuccessJSON(data) } +func (h ClientAPIsHandler) getUserData(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + resData, err := h.app.Client.GetUserData(claims.OrgID, claims.AppID, &claims.Subject) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeSurvey, nil, err, http.StatusInternalServerError, true) + } + + data, err := json.Marshal(resData) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResponseBody, nil, err, http.StatusInternalServerError, false) + } + + return l.HTTPResponseSuccessJSON(data) +} + // NewClientAPIsHandler creates new client API handler instance func NewClientAPIsHandler(app *core.Application) ClientAPIsHandler { return ClientAPIsHandler{app: app} diff --git a/driver/web/docs/gen/def.yaml b/driver/web/docs/gen/def.yaml index a11b071..1c24da2 100644 --- a/driver/web/docs/gen/def.yaml +++ b/driver/web/docs/gen/def.yaml @@ -712,6 +712,30 @@ paths: description: Unauthorized '500': description: Internal error + /api/user-data: + get: + tags: + - Client + summary: Retrieves surveys + description: | + Retrieves surveys matching the provided query + security: + - bearerAuth: [] + responses: + '200': + description: Success + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/UserData' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error /api/admin/configs: get: tags: @@ -1855,6 +1879,49 @@ components: type: string params: type: object + UserData: + type: object + properties: + survey: + type: array + items: + type: object + properties: + id: + type: string + org_id: + type: string + app_id: + type: string + creator_id: + type: string + account_id: + type: string + context: + type: object + title: + type: string + type: + type: string + survey_responses: + type: array + items: + type: object + properties: + id: + type: string + org_id: + type: string + app_id: + type: string + creator_id: + type: string + account_id: + type: string + context: + type: object + title: + type: string _admin_req_update-configs: required: - type diff --git a/driver/web/docs/index.yaml b/driver/web/docs/index.yaml index d54a722..4e2aa6d 100644 --- a/driver/web/docs/index.yaml +++ b/driver/web/docs/index.yaml @@ -45,6 +45,8 @@ paths: $ref: "./resources/client/survey-alerts.yaml" /api/creator/surveys: $ref: "./resources/client/creator/surveys.yaml" + /api/user-data: + $ref: "./resources/client/user-data.yaml" # Admin /api/admin/configs: diff --git a/driver/web/docs/resources/client/user-data.yaml b/driver/web/docs/resources/client/user-data.yaml new file mode 100644 index 0000000..333967c --- /dev/null +++ b/driver/web/docs/resources/client/user-data.yaml @@ -0,0 +1,23 @@ +get: + tags: + - Client + summary: Retrieves surveys + description: | + Retrieves surveys matching the provided query + security: + - bearerAuth: [] + responses: + 200: + description: Success + content: + application/json: + schema: + type: array + items: + $ref: "../../schemas/surveys/UserData.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/schemas/index.yaml b/driver/web/docs/schemas/index.yaml index 7dbd28b..574eb02 100644 --- a/driver/web/docs/schemas/index.yaml +++ b/driver/web/docs/schemas/index.yaml @@ -19,6 +19,8 @@ SurveyResponseAnonymous: $ref: "./surveys/SurveyResponseAnonymous.yaml" AlertContact: $ref: "./surveys/AlertContact.yaml" +UserData: + $ref: "./surveys/UserData.yaml" # ADMIN section diff --git a/driver/web/docs/schemas/surveys/SurveyResponseUserData.yaml b/driver/web/docs/schemas/surveys/SurveyResponseUserData.yaml new file mode 100644 index 0000000..14555b1 --- /dev/null +++ b/driver/web/docs/schemas/surveys/SurveyResponseUserData.yaml @@ -0,0 +1,17 @@ +type: object +properties: + id: + type: string + org_id: + type: string + app_id: + type: string + creator_id: + type: string + account_id: + type: string + context: + type: object + title: + type: string + diff --git a/driver/web/docs/schemas/surveys/SurveysUserData.yaml b/driver/web/docs/schemas/surveys/SurveysUserData.yaml new file mode 100644 index 0000000..8546e20 --- /dev/null +++ b/driver/web/docs/schemas/surveys/SurveysUserData.yaml @@ -0,0 +1,19 @@ +type: object +properties: + id: + type: string + org_id: + type: string + app_id: + type: string + creator_id: + type: string + account_id: + type: string + context: + type: object + title: + type: string + type: + type: string + diff --git a/driver/web/docs/schemas/surveys/UserData.yaml b/driver/web/docs/schemas/surveys/UserData.yaml new file mode 100644 index 0000000..f24811a --- /dev/null +++ b/driver/web/docs/schemas/surveys/UserData.yaml @@ -0,0 +1,10 @@ +type: object +properties: + survey: + type: array + items: + $ref: "./SurveysUserData.yaml" + survey_responses: + type: array + items: + $ref: "./SurveyResponseUserData.yaml"