diff --git a/CHANGELOG.md b/CHANGELOG.md index 42a6d73..467828c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,43 @@ 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] +## [1.9.0] - 2024-007-26 +### Added +- Add "complete" field to show if the survey is completed [#61](https://github.com/rokwire/surveys-building-block/issues/61) +## [1.8.2] - 2024-007-26 +### Fixed +- Fix Get /surveys [#58](https://github.com/rokwire/surveys-building-block/issues/58) + +## [1.8.1] - 2024-007-26 +### Fixed +- Fix "public" and "archived" [#55](https://github.com/rokwire/surveys-building-block/issues/55) + +## [1.8.0] - 2024-007-25 +### Added +- Fix "start_date" and "end_date" timestamp, "archived", "public" and set "complete" in the result [#52](https://github.com/rokwire/surveys-building-block/issues/52) + +## [1.7.0] - 2024-007-24 +### Added +- Add Estimated_completion_time [#49](https://github.com/rokwire/surveys-building-block/issues/49) + +## [1.6.0] - 2024-007-23 +### Added +- Archived flag [#40](https://github.com/rokwire/surveys-building-block/issues/40) +### Added +- Public flag [#37](https://github.com/rokwire/surveys-building-block/issues/37) +### Added +- Add extras field to survey data [#39](https://github.com/rokwire/surveys-building-block/issues/39) +### Fixed +- Fix "start_date" and "end_date" [#43](https://github.com/rokwire/surveys-building-block/issues/43) + +## [1.5.0] - 2024-007-22 +### Added +- Start/end date [#38](https://github.com/rokwire/surveys-building-block/issues/38) + +## [1.4.0] - 2024-007-11 +### Added +- Remove user data [#34](https://github.com/rokwire/surveys-building-block/issues/34) + ## [1.3.0] - 2023-09-20 ### Added - Reintroduce survey responses admin API [#27](https://github.com/rokwire/surveys-building-block/issues/27) diff --git a/Dockerfile b/Dockerfile index fcae686..9ce058a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,8 @@ WORKDIR /app COPY . . RUN make -FROM alpine:3.17.2 +FROM alpine:3.20 + #we need timezone database RUN apk add --no-cache --update tzdata diff --git a/SECURITY.md b/SECURITY.md index 698ea19..f1feef5 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,8 +6,8 @@ Patches for **Surveys Building Block** in this repository will only be applied t | Version | Supported | | ------- | ------------------ | -| 1.3.0 | :white_check_mark: | -| < 1.3.0 | :x: | +| 1.9.0 | :white_check_mark: | +| < 1.9.0 | :x: | ## Reporting a Bug or Vulnerability diff --git a/core/app_admin.go b/core/app_admin.go index b7c7121..63b64c1 100644 --- a/core/app_admin.go +++ b/core/app_admin.go @@ -37,8 +37,8 @@ func (a appAdmin) GetSurvey(id string, orgID string, appID string) (*model.Surve } // GetSurvey returns surveys matching the provided query -func (a appAdmin) GetSurveys(orgID string, appID string, creatorID *string, surveyIDs []string, surveyTypes []string, calendarEventID string, limit *int, offset *int) ([]model.Survey, error) { - return a.app.shared.getSurveys(orgID, appID, creatorID, surveyIDs, surveyTypes, calendarEventID, limit, offset) +func (a appAdmin) 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, []model.SurveyResponse, error) { + return a.app.shared.getSurveys(orgID, appID, creatorID, surveyIDs, surveyTypes, calendarEventID, limit, offset, filter, public, archived, completed) } // GetAllSurveyResponses returns survey responses matching the provided query diff --git a/core/app_client.go b/core/app_client.go index ff3b3fc..06e80a4 100644 --- a/core/app_client.go +++ b/core/app_client.go @@ -35,8 +35,9 @@ func (a appClient) GetSurvey(id string, orgID string, appID string) (*model.Surv } // GetSurvey returns surveys matching the provided query -func (a appClient) GetSurveys(orgID string, appID string, creatorID *string, surveyIDs []string, surveyTypes []string, calendarEventID string, limit *int, offset *int) ([]model.Survey, error) { - return a.app.shared.getSurveys(orgID, appID, creatorID, surveyIDs, surveyTypes, calendarEventID, limit, offset) +func (a appClient) 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, []model.SurveyResponse, error) { + return a.app.shared.getSurveys(orgID, appID, creatorID, surveyIDs, surveyTypes, calendarEventID, limit, offset, filter, public, archived, completed) } // CreateSurvey creates a new survey diff --git a/core/app_delete_data_logic.go b/core/app_delete_data_logic.go new file mode 100644 index 0000000..20cecb3 --- /dev/null +++ b/core/app_delete_data_logic.go @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2020 Board of Trustees of the University of Illinois. + * All rights reserved. + + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package core + +import ( + "fmt" + + "application/core/interfaces" + "application/core/model" + corebb "application/driven/core" + "time" + + "github.com/rokwire/logging-library-go/v2/logs" +) + +type deleteDataLogic struct { + logger logs.Logger + + serviceID string + core *corebb.Adapter + + storage interfaces.Storage + + //delete data timer + dailyDeleteTimer *time.Timer + timerDone chan bool +} + +func (d deleteDataLogic) start() error { + + //2. set up web tools timer + go d.setupTimerForDelete() + + return nil +} + +func (d deleteDataLogic) setupTimerForDelete() { + d.logger.Info("Delete data timer") + + //cancel if active + if d.dailyDeleteTimer != nil { + d.logger.Info("setupTimerForDelete -> there is active timer, so cancel it") + + d.timerDone <- true + d.dailyDeleteTimer.Stop() + } + + //wait until it is the correct moment from the day + location, err := time.LoadLocation("America/Chicago") + if err != nil { + d.logger.Errorf("Error getting location:%s\n", err.Error()) + } + now := time.Now().In(location) + d.logger.Infof("setupTimerForDelete -> now - hours:%d minutes:%d seconds:%d\n", now.Hour(), now.Minute(), now.Second()) + + nowSecondsInDay := 60*60*now.Hour() + 60*now.Minute() + now.Second() + desiredMoment := 14400 //4 AM + + var durationInSeconds int + d.logger.Infof("setupTimerForDelete -> nowSecondsInDay:%d desiredMoment:%d\n", nowSecondsInDay, desiredMoment) + if nowSecondsInDay <= desiredMoment { + d.logger.Infof("setupTimerForDelete -> not delete process today, so the first process will be today") + durationInSeconds = desiredMoment - nowSecondsInDay + } else { + d.logger.Infof("setupTimerForDelete -> the delete process has already been processed today, so the first process will be tomorrow") + leftToday := 86400 - nowSecondsInDay + durationInSeconds = leftToday + desiredMoment // the time which left today + desired moment from tomorrow + } + //log.Println(durationInSeconds) + //duration := time.Second * time.Duration(3) + duration := time.Second * time.Duration(durationInSeconds) + d.logger.Infof("setupTimerForDelete -> first call after %s", duration) + + d.dailyDeleteTimer = time.NewTimer(duration) + select { + case <-d.dailyDeleteTimer.C: + d.logger.Info("setupTimerForDelete -> delete timer expired") + d.dailyDeleteTimer = nil + + d.process() + case <-d.timerDone: + // timer aborted + d.logger.Info("setupTimerForDelete -> delete timer aborted") + d.dailyDeleteTimer = nil + } +} + +func (d deleteDataLogic) process() { + d.logger.Info("Deleting data process") + + //process work + d.processDelete() + + //generate new processing after 24 hours + duration := time.Hour * 24 + d.logger.Infof("Deleting data process -> next call after %s", duration) + d.dailyDeleteTimer = time.NewTimer(duration) + select { + case <-d.dailyDeleteTimer.C: + d.logger.Info("Deleting data process -> timer expired") + d.dailyDeleteTimer = nil + + d.process() + case <-d.timerDone: + // timer aborted + d.logger.Info("Deleting data process -> timer aborted") + d.dailyDeleteTimer = nil + } +} + +func (d deleteDataLogic) processDelete() { + //load deleted accounts + deletedMemberships, err := d.core.LoadDeletedMemberships() + if err != nil { + d.logger.Errorf("error on loading deleted accounts - %s", err) + return + } + fmt.Print(deletedMemberships) + //process by app org + for _, appOrgSection := range deletedMemberships { + d.logger.Infof("delete - [app-id:%s org-id:%s]", appOrgSection.AppID, appOrgSection.OrgID) + + accountsIDs := d.getAccountsIDs(appOrgSection.Memberships) + if len(accountsIDs) == 0 { + d.logger.Info("no accounts for deletion") + continue + } + + d.logger.Infof("accounts for deletion - %s", accountsIDs) + + //delete the data + d.deleteAppOrgUsersData(appOrgSection.AppID, appOrgSection.OrgID, accountsIDs) + } +} + +func (d deleteDataLogic) deleteAppOrgUsersData(appID string, orgID string, accountsIDs []string) { + // delete survey responses + err := d.storage.DeleteSurveyResponsesWithIDs(appID, orgID, accountsIDs) + if err != nil { + d.logger.Errorf("error deleting the survey responses - %s", err) + return + } + + // delete surveys + err = d.storage.DeleteSurveysWithIDs(appID, orgID, accountsIDs) + if err != nil { + d.logger.Errorf("error deleting the surveys - %s", err) + return + } + +} + +func (d deleteDataLogic) getAccountsIDs(memberships []model.DeletedMembership) []string { + res := make([]string, len(memberships)) + for i, item := range memberships { + res[i] = item.AccountID + } + return res +} + +// deleteLogic creates new deleteLogic +func deleteLogic(coreAdapter corebb.Adapter, logger logs.Logger) deleteDataLogic { + timerDone := make(chan bool) + return deleteDataLogic{core: &coreAdapter, timerDone: timerDone, logger: logger} +} diff --git a/core/app_shared.go b/core/app_shared.go index b47e393..8f002de 100644 --- a/core/app_shared.go +++ b/core/app_shared.go @@ -34,8 +34,18 @@ func (a appShared) getSurvey(id string, orgID string, appID string) (*model.Surv return a.app.storage.GetSurvey(id, orgID, appID) } -func (a appShared) getSurveys(orgID string, appID string, creatorID *string, surveyIDs []string, surveyTypes []string, calendarEventID string, limit *int, offset *int) ([]model.Survey, error) { - return a.app.storage.GetSurveys(orgID, appID, creatorID, surveyIDs, surveyTypes, calendarEventID, limit, offset) +func (a appShared) 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, []model.SurveyResponse, error) { + surveys, err := a.app.storage.GetSurveys(orgID, appID, creatorID, surveyIDs, surveyTypes, calendarEventID, limit, offset, filter, public, archived, nil) + if err != nil { + return nil, nil, err + } + + surveysResponse, err := a.app.storage.GetSurveyResponses(nil, nil, nil, surveyIDs, surveyTypes, nil, nil, nil, nil) + if err != nil { + return nil, nil, err + } + + return surveys, surveysResponse, nil } func (a appShared) createSurvey(survey model.Survey, externalIDs map[string]string) (*model.Survey, error) { diff --git a/core/application.go b/core/application.go index 49ab66d..d9352ae 100644 --- a/core/application.go +++ b/core/application.go @@ -17,6 +17,7 @@ package core import ( "application/core/interfaces" "application/core/model" + corebb "application/driven/core" "github.com/rokwire/core-auth-library-go/v3/authutils" "github.com/rokwire/logging-library-go/v2/errors" @@ -52,9 +53,11 @@ type Application struct { logger *logs.Logger - storage interfaces.Storage - notifications interfaces.Notifications - calendar interfaces.Calendar + storage interfaces.Storage + notifications interfaces.Notifications + calendar interfaces.Calendar + corebb *corebb.Adapter + deleteDataLogic deleteDataLogic } // Start starts the core part of the application @@ -62,6 +65,7 @@ func (a *Application) Start() { //set storage listener storageListener := storageListener{app: a} a.storage.RegisterStorageListener(&storageListener) + a.deleteDataLogic.start() } // GetEnvConfigs retrieves the cached database env configs @@ -78,8 +82,12 @@ func (a *Application) GetEnvConfigs() (*model.EnvConfigData, error) { } // NewApplication creates new Application -func NewApplication(version string, build string, storage interfaces.Storage, notifications interfaces.Notifications, calendar interfaces.Calendar, logger *logs.Logger) *Application { - application := Application{version: version, build: build, storage: storage, notifications: notifications, calendar: calendar, logger: logger} +func NewApplication(version string, build string, storage interfaces.Storage, notifications interfaces.Notifications, calendar interfaces.Calendar, + coreBB *corebb.Adapter, serviceID string, logger *logs.Logger) *Application { + deleteDataLogic := deleteDataLogic{logger: *logger, core: coreBB, serviceID: serviceID, storage: storage} + + application := Application{version: version, build: build, storage: storage, notifications: notifications, + calendar: calendar, deleteDataLogic: deleteDataLogic, logger: logger} //add the drivers ports/interfaces application.Default = newAppDefault(&application) diff --git a/core/application_test.go b/core/application_test.go index 13b288a..77b7bfd 100644 --- a/core/application_test.go +++ b/core/application_test.go @@ -37,7 +37,7 @@ const ( func buildTestApplication(storage interfaces.Storage) *core.Application { loggerOpts := logs.LoggerOpts{SuppressRequests: logs.NewStandardHealthCheckHTTPRequestProperties(serviceID + "/version")} logger := logs.NewLogger(serviceID, &loggerOpts) - return core.NewApplication("1.1.1", "build", storage, nil, nil, logger) + return core.NewApplication("1.1.1", "build", storage, nil, nil, nil, "", logger) } func TestApplication_Start(t *testing.T) { diff --git a/core/interfaces.go b/core/interfaces.go index 6423854..b03186d 100644 --- a/core/interfaces.go +++ b/core/interfaces.go @@ -20,7 +20,7 @@ import "application/core/model" type Shared interface { // Surveys 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) ([]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, []model.SurveyResponse, error) createSurvey(survey model.Survey, externalIDs map[string]string) (*model.Survey, error) updateSurvey(survey model.Survey, userID string, externalIDs map[string]string, admin bool) error deleteSurvey(id string, orgID string, appID string, userID string, externalIDs map[string]string, admin bool) error @@ -28,3 +28,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) } + +// Core exposes Core APIs for the driver adapters +type Core interface { + LoadDeletedMemberships() ([]model.DeletedUserData, error) +} diff --git a/core/interfaces/core.go b/core/interfaces/core.go index 8772d2d..8a09e29 100644 --- a/core/interfaces/core.go +++ b/core/interfaces/core.go @@ -30,7 +30,7 @@ type Default interface { type Client interface { // Surveys 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) ([]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, []model.SurveyResponse, error) CreateSurvey(survey model.Survey, externalIDs map[string]string) (*model.Survey, error) UpdateSurvey(survey model.Survey, userID string, externalIDs map[string]string) error DeleteSurvey(id string, orgID string, appID string, userID string, externalIDs map[string]string) error @@ -59,7 +59,7 @@ type Admin interface { // Surveys 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) ([]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, []model.SurveyResponse, error) CreateSurvey(survey model.Survey, externalIDs map[string]string) (*model.Survey, error) UpdateSurvey(survey model.Survey, userID string, externalIDs map[string]string) error DeleteSurvey(id string, orgID string, appID string, userID string, externalIDs map[string]string) error diff --git a/core/interfaces/driven.go b/core/interfaces/driven.go index b3d0f88..9c51a0a 100644 --- a/core/interfaces/driven.go +++ b/core/interfaces/driven.go @@ -33,10 +33,11 @@ type Storage interface { DeleteConfig(id string) error 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) ([]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) 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 + DeleteSurveysWithIDs(orgID string, appID string, accountsIDs []string) error GetSurveyResponse(id string, orgID string, appID string, userID string) (*model.SurveyResponse, error) GetSurveyResponses(orgID *string, appID *string, userID *string, surveyIDs []string, surveyTypes []string, startDate *time.Time, endDate *time.Time, limit *int, offset *int) ([]model.SurveyResponse, error) @@ -44,6 +45,7 @@ type Storage interface { UpdateSurveyResponse(surveyResponse model.SurveyResponse) error DeleteSurveyResponse(id string, orgID string, appID string, userID string) error DeleteSurveyResponses(orgID string, appID string, userID string, surveyIDs []string, surveyTypes []string, startDate *time.Time, endDate *time.Time) error + DeleteSurveyResponsesWithIDs(orgID string, appID string, accountsIDs []string) error GetAlertContacts(orgID string, appID string) ([]model.AlertContact, error) GetAlertContact(id string, orgID string, appID string) (*model.AlertContact, error) diff --git a/core/interfaces/mocks/Storage.go b/core/interfaces/mocks/Storage.go index ced2628..9753360 100644 --- a/core/interfaces/mocks/Storage.go +++ b/core/interfaces/mocks/Storage.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.33.2. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks @@ -21,6 +21,10 @@ type Storage struct { func (_m *Storage) CreateAlertContact(alertContact model.AlertContact) (*model.AlertContact, error) { ret := _m.Called(alertContact) + if len(ret) == 0 { + panic("no return value specified for CreateAlertContact") + } + var r0 *model.AlertContact var r1 error if rf, ok := ret.Get(0).(func(model.AlertContact) (*model.AlertContact, error)); ok { @@ -47,6 +51,10 @@ func (_m *Storage) CreateAlertContact(alertContact model.AlertContact) (*model.A func (_m *Storage) CreateSurvey(survey model.Survey) (*model.Survey, error) { ret := _m.Called(survey) + if len(ret) == 0 { + panic("no return value specified for CreateSurvey") + } + var r0 *model.Survey var r1 error if rf, ok := ret.Get(0).(func(model.Survey) (*model.Survey, error)); ok { @@ -73,6 +81,10 @@ func (_m *Storage) CreateSurvey(survey model.Survey) (*model.Survey, error) { func (_m *Storage) CreateSurveyResponse(surveyResponse model.SurveyResponse) (*model.SurveyResponse, error) { ret := _m.Called(surveyResponse) + if len(ret) == 0 { + panic("no return value specified for CreateSurveyResponse") + } + var r0 *model.SurveyResponse var r1 error if rf, ok := ret.Get(0).(func(model.SurveyResponse) (*model.SurveyResponse, error)); ok { @@ -99,6 +111,10 @@ func (_m *Storage) CreateSurveyResponse(surveyResponse model.SurveyResponse) (*m func (_m *Storage) DeleteAlertContact(id string, orgID string, appID string) error { ret := _m.Called(id, orgID, appID) + if len(ret) == 0 { + panic("no return value specified for DeleteAlertContact") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string, string) error); ok { r0 = rf(id, orgID, appID) @@ -113,6 +129,10 @@ func (_m *Storage) DeleteAlertContact(id string, orgID string, appID string) err func (_m *Storage) DeleteConfig(id string) error { ret := _m.Called(id) + if len(ret) == 0 { + panic("no return value specified for DeleteConfig") + } + var r0 error if rf, ok := ret.Get(0).(func(string) error); ok { r0 = rf(id) @@ -127,6 +147,10 @@ func (_m *Storage) DeleteConfig(id string) error { func (_m *Storage) DeleteSurvey(id string, orgID string, appID string, creatorID string, admin bool) error { ret := _m.Called(id, orgID, appID, creatorID, admin) + if len(ret) == 0 { + panic("no return value specified for DeleteSurvey") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string, string, string, bool) error); ok { r0 = rf(id, orgID, appID, creatorID, admin) @@ -141,6 +165,10 @@ func (_m *Storage) DeleteSurvey(id string, orgID string, appID string, creatorID func (_m *Storage) DeleteSurveyResponse(id string, orgID string, appID string, userID string) error { ret := _m.Called(id, orgID, appID, userID) + if len(ret) == 0 { + panic("no return value specified for DeleteSurveyResponse") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string, string, string) error); ok { r0 = rf(id, orgID, appID, userID) @@ -155,6 +183,10 @@ func (_m *Storage) DeleteSurveyResponse(id string, orgID string, appID string, u func (_m *Storage) DeleteSurveyResponses(orgID string, appID string, userID string, surveyIDs []string, surveyTypes []string, startDate *time.Time, endDate *time.Time) error { ret := _m.Called(orgID, appID, userID, surveyIDs, surveyTypes, startDate, endDate) + if len(ret) == 0 { + panic("no return value specified for DeleteSurveyResponses") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string, string, []string, []string, *time.Time, *time.Time) error); ok { r0 = rf(orgID, appID, userID, surveyIDs, surveyTypes, startDate, endDate) @@ -165,10 +197,50 @@ func (_m *Storage) DeleteSurveyResponses(orgID string, appID string, userID stri return r0 } +// DeleteSurveyResponsesWithIDs provides a mock function with given fields: orgID, appID, accountsIDs +func (_m *Storage) DeleteSurveyResponsesWithIDs(orgID string, appID string, accountsIDs []string) error { + ret := _m.Called(orgID, appID, accountsIDs) + + if len(ret) == 0 { + panic("no return value specified for DeleteSurveyResponsesWithIDs") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string, string, []string) error); ok { + r0 = rf(orgID, appID, accountsIDs) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DeleteSurveysWithIDs provides a mock function with given fields: orgID, appID, accountsIDs +func (_m *Storage) DeleteSurveysWithIDs(orgID string, appID string, accountsIDs []string) error { + ret := _m.Called(orgID, appID, accountsIDs) + + if len(ret) == 0 { + panic("no return value specified for DeleteSurveysWithIDs") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string, string, []string) error); ok { + r0 = rf(orgID, appID, accountsIDs) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // FindConfig provides a mock function with given fields: configType, appID, orgID func (_m *Storage) FindConfig(configType string, appID string, orgID string) (*model.Config, error) { ret := _m.Called(configType, appID, orgID) + if len(ret) == 0 { + panic("no return value specified for FindConfig") + } + var r0 *model.Config var r1 error if rf, ok := ret.Get(0).(func(string, string, string) (*model.Config, error)); ok { @@ -195,6 +267,10 @@ func (_m *Storage) FindConfig(configType string, appID string, orgID string) (*m func (_m *Storage) FindConfigByID(id string) (*model.Config, error) { ret := _m.Called(id) + if len(ret) == 0 { + panic("no return value specified for FindConfigByID") + } + var r0 *model.Config var r1 error if rf, ok := ret.Get(0).(func(string) (*model.Config, error)); ok { @@ -221,6 +297,10 @@ func (_m *Storage) FindConfigByID(id string) (*model.Config, error) { func (_m *Storage) FindConfigs(configType *string) ([]model.Config, error) { ret := _m.Called(configType) + if len(ret) == 0 { + panic("no return value specified for FindConfigs") + } + var r0 []model.Config var r1 error if rf, ok := ret.Get(0).(func(*string) ([]model.Config, error)); ok { @@ -247,6 +327,10 @@ func (_m *Storage) FindConfigs(configType *string) ([]model.Config, error) { func (_m *Storage) GetAlertContact(id string, orgID string, appID string) (*model.AlertContact, error) { ret := _m.Called(id, orgID, appID) + if len(ret) == 0 { + panic("no return value specified for GetAlertContact") + } + var r0 *model.AlertContact var r1 error if rf, ok := ret.Get(0).(func(string, string, string) (*model.AlertContact, error)); ok { @@ -273,6 +357,10 @@ func (_m *Storage) GetAlertContact(id string, orgID string, appID string) (*mode func (_m *Storage) GetAlertContacts(orgID string, appID string) ([]model.AlertContact, error) { ret := _m.Called(orgID, appID) + if len(ret) == 0 { + panic("no return value specified for GetAlertContacts") + } + var r0 []model.AlertContact var r1 error if rf, ok := ret.Get(0).(func(string, string) ([]model.AlertContact, error)); ok { @@ -299,6 +387,10 @@ func (_m *Storage) GetAlertContacts(orgID string, appID string) ([]model.AlertCo func (_m *Storage) GetAlertContactsByKey(key string, orgID string, appID string) ([]model.AlertContact, error) { ret := _m.Called(key, orgID, appID) + if len(ret) == 0 { + panic("no return value specified for GetAlertContactsByKey") + } + var r0 []model.AlertContact var r1 error if rf, ok := ret.Get(0).(func(string, string, string) ([]model.AlertContact, error)); ok { @@ -325,6 +417,10 @@ func (_m *Storage) GetAlertContactsByKey(key string, orgID string, appID string) func (_m *Storage) GetSurvey(id string, orgID string, appID string) (*model.Survey, error) { ret := _m.Called(id, orgID, appID) + if len(ret) == 0 { + panic("no return value specified for GetSurvey") + } + var r0 *model.Survey var r1 error if rf, ok := ret.Get(0).(func(string, string, string) (*model.Survey, error)); ok { @@ -351,6 +447,10 @@ func (_m *Storage) GetSurvey(id string, orgID string, appID string) (*model.Surv func (_m *Storage) GetSurveyResponse(id string, orgID string, appID string, userID string) (*model.SurveyResponse, error) { ret := _m.Called(id, orgID, appID, userID) + if len(ret) == 0 { + panic("no return value specified for GetSurveyResponse") + } + var r0 *model.SurveyResponse var r1 error if rf, ok := ret.Get(0).(func(string, string, string, string) (*model.SurveyResponse, error)); ok { @@ -377,6 +477,10 @@ func (_m *Storage) GetSurveyResponse(id string, orgID string, appID string, user func (_m *Storage) GetSurveyResponses(orgID *string, appID *string, userID *string, surveyIDs []string, surveyTypes []string, startDate *time.Time, endDate *time.Time, limit *int, offset *int) ([]model.SurveyResponse, error) { ret := _m.Called(orgID, appID, userID, surveyIDs, surveyTypes, startDate, endDate, limit, offset) + if len(ret) == 0 { + panic("no return value specified for GetSurveyResponses") + } + var r0 []model.SurveyResponse var r1 error if rf, ok := ret.Get(0).(func(*string, *string, *string, []string, []string, *time.Time, *time.Time, *int, *int) ([]model.SurveyResponse, error)); ok { @@ -399,25 +503,29 @@ func (_m *Storage) GetSurveyResponses(orgID *string, appID *string, userID *stri return r0, r1 } -// GetSurveys provides a mock function with given fields: orgID, appID, creatorID, surveyIDs, surveyTypes, calendarEventID, limit, offset -func (_m *Storage) GetSurveys(orgID string, appID string, creatorID *string, surveyIDs []string, surveyTypes []string, calendarEventID string, limit *int, offset *int) ([]model.Survey, error) { - ret := _m.Called(orgID, appID, creatorID, surveyIDs, surveyTypes, calendarEventID, limit, offset) +// GetSurveys provides a mock function with given fields: orgID, appID, creatorID, surveyIDs, surveyTypes, calendarEventID, limit, offset, filter, public, archived, completed +func (_m *Storage) 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) { + ret := _m.Called(orgID, appID, creatorID, surveyIDs, surveyTypes, calendarEventID, limit, offset, filter, public, archived, completed) + + if len(ret) == 0 { + panic("no return value specified for GetSurveys") + } var r0 []model.Survey var r1 error - if rf, ok := ret.Get(0).(func(string, string, *string, []string, []string, string, *int, *int) ([]model.Survey, error)); ok { - return rf(orgID, appID, creatorID, surveyIDs, surveyTypes, calendarEventID, limit, offset) + if rf, ok := ret.Get(0).(func(string, string, *string, []string, []string, string, *int, *int, *model.SurveyTimeFilter, *bool, *bool, *bool) ([]model.Survey, error)); ok { + return rf(orgID, appID, creatorID, surveyIDs, surveyTypes, calendarEventID, limit, offset, filter, public, archived, completed) } - if rf, ok := ret.Get(0).(func(string, string, *string, []string, []string, string, *int, *int) []model.Survey); ok { - r0 = rf(orgID, appID, creatorID, surveyIDs, surveyTypes, calendarEventID, limit, offset) + if rf, ok := ret.Get(0).(func(string, string, *string, []string, []string, string, *int, *int, *model.SurveyTimeFilter, *bool, *bool, *bool) []model.Survey); ok { + r0 = rf(orgID, appID, creatorID, surveyIDs, surveyTypes, calendarEventID, limit, offset, filter, public, archived, completed) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]model.Survey) } } - if rf, ok := ret.Get(1).(func(string, string, *string, []string, []string, string, *int, *int) error); ok { - r1 = rf(orgID, appID, creatorID, surveyIDs, surveyTypes, calendarEventID, limit, offset) + if rf, ok := ret.Get(1).(func(string, string, *string, []string, []string, string, *int, *int, *model.SurveyTimeFilter, *bool, *bool, *bool) error); ok { + r1 = rf(orgID, appID, creatorID, surveyIDs, surveyTypes, calendarEventID, limit, offset, filter, public, archived, completed) } else { r1 = ret.Error(1) } @@ -429,6 +537,10 @@ func (_m *Storage) GetSurveys(orgID string, appID string, creatorID *string, sur func (_m *Storage) InsertConfig(config model.Config) error { ret := _m.Called(config) + if len(ret) == 0 { + panic("no return value specified for InsertConfig") + } + var r0 error if rf, ok := ret.Get(0).(func(model.Config) error); ok { r0 = rf(config) @@ -443,6 +555,10 @@ func (_m *Storage) InsertConfig(config model.Config) error { func (_m *Storage) PerformTransaction(_a0 func(interfaces.Storage) error) error { ret := _m.Called(_a0) + if len(ret) == 0 { + panic("no return value specified for PerformTransaction") + } + var r0 error if rf, ok := ret.Get(0).(func(func(interfaces.Storage) error) error); ok { r0 = rf(_a0) @@ -462,6 +578,10 @@ func (_m *Storage) RegisterStorageListener(listener interfaces.StorageListener) func (_m *Storage) UpdateAlertContact(alertContact model.AlertContact) error { ret := _m.Called(alertContact) + if len(ret) == 0 { + panic("no return value specified for UpdateAlertContact") + } + var r0 error if rf, ok := ret.Get(0).(func(model.AlertContact) error); ok { r0 = rf(alertContact) @@ -476,6 +596,10 @@ func (_m *Storage) UpdateAlertContact(alertContact model.AlertContact) error { func (_m *Storage) UpdateConfig(config model.Config) error { ret := _m.Called(config) + if len(ret) == 0 { + panic("no return value specified for UpdateConfig") + } + var r0 error if rf, ok := ret.Get(0).(func(model.Config) error); ok { r0 = rf(config) @@ -490,6 +614,10 @@ func (_m *Storage) UpdateConfig(config model.Config) error { func (_m *Storage) UpdateSurvey(survey model.Survey, admin bool) error { ret := _m.Called(survey, admin) + if len(ret) == 0 { + panic("no return value specified for UpdateSurvey") + } + var r0 error if rf, ok := ret.Get(0).(func(model.Survey, bool) error); ok { r0 = rf(survey, admin) @@ -504,6 +632,10 @@ func (_m *Storage) UpdateSurvey(survey model.Survey, admin bool) error { func (_m *Storage) UpdateSurveyResponse(surveyResponse model.SurveyResponse) error { ret := _m.Called(surveyResponse) + if len(ret) == 0 { + panic("no return value specified for UpdateSurveyResponse") + } + var r0 error if rf, ok := ret.Get(0).(func(model.SurveyResponse) error); ok { r0 = rf(surveyResponse) diff --git a/core/model/surveys.go b/core/model/surveys.go index 63b853f..17820bb 100644 --- a/core/model/surveys.go +++ b/core/model/surveys.go @@ -40,29 +40,34 @@ type SurveyResponse struct { // Survey wraps the entire record type Survey struct { - ID string `json:"id" bson:"_id"` - CreatorID string `json:"creator_id" bson:"creator_id"` - OrgID string `json:"org_id" bson:"org_id"` - AppID string `json:"app_id" bson:"app_id"` - Title string `json:"title" bson:"title"` - MoreInfo *string `json:"more_info" bson:"more_info"` - Data map[string]SurveyData `json:"data" bson:"data"` - Scored bool `json:"scored" bson:"scored"` - ResultRules string `json:"result_rules" bson:"result_rules"` - ResultJSON string `json:"result_json" bson:"result_json"` - Type string `json:"type" bson:"type"` - SurveyStats *SurveyStats `json:"stats" bson:"stats"` - Sensitive bool `json:"sensitive" bson:"sensitive"` - Anonymous bool `json:"anonymous" bson:"anonymous"` - DefaultDataKey *string `json:"default_data_key" bson:"default_data_key"` - DefaultDataKeyRule *string `json:"default_data_key_rule" bson:"default_data_key_rule"` - Constants map[string]interface{} `json:"constants" bson:"constants"` - Strings map[string]interface{} `json:"strings" bson:"strings"` - SubRules map[string]interface{} `json:"sub_rules" bson:"sub_rules"` - ResponseKeys []string `json:"response_keys" bson:"response_keys"` - DateCreated time.Time `json:"date_created" bson:"date_created"` - DateUpdated *time.Time `json:"date_updated" bson:"date_updated"` - CalendarEventID string `json:"calendar_event_id" bson:"calendar_event_id"` + ID string `json:"id" bson:"_id"` + CreatorID string `json:"creator_id" bson:"creator_id"` + OrgID string `json:"org_id" bson:"org_id"` + AppID string `json:"app_id" bson:"app_id"` + Title string `json:"title" bson:"title"` + MoreInfo *string `json:"more_info" bson:"more_info"` + Data map[string]SurveyData `json:"data" bson:"data"` + Scored bool `json:"scored" bson:"scored"` + ResultRules string `json:"result_rules" bson:"result_rules"` + ResultJSON string `json:"result_json" bson:"result_json"` + Type string `json:"type" bson:"type"` + SurveyStats *SurveyStats `json:"stats" bson:"stats"` + Sensitive bool `json:"sensitive" bson:"sensitive"` + Anonymous bool `json:"anonymous" bson:"anonymous"` + DefaultDataKey *string `json:"default_data_key" bson:"default_data_key"` + DefaultDataKeyRule *string `json:"default_data_key_rule" bson:"default_data_key_rule"` + Constants map[string]interface{} `json:"constants" bson:"constants"` + Strings map[string]interface{} `json:"strings" bson:"strings"` + SubRules map[string]interface{} `json:"sub_rules" bson:"sub_rules"` + ResponseKeys []string `json:"response_keys" bson:"response_keys"` + DateCreated time.Time `json:"date_created" bson:"date_created"` + DateUpdated *time.Time `json:"date_updated" bson:"date_updated"` + CalendarEventID string `json:"calendar_event_id" bson:"calendar_event_id"` + StartDate *time.Time `json:"start_date" bson:"start_date"` + EndDate *time.Time `json:"end_date" bson:"end_date"` + Public *bool `json:"public" bson:"public"` + Archived *bool `json:"archived" bson:"archived"` + EstimatedCompletionTime *int `json:"estimated_completion_time" bson:"estimated_completion_time"` } // SurveyResponseAnonymous represents an anonymized survey response @@ -90,17 +95,18 @@ type SurveyStats struct { // SurveyData is data stored for a Survey type SurveyData struct { - Section *string `json:"section,omitempty" bson:"section,omitempty"` - Sections []string `json:"sections,omitempty" bson:"sections,omitempty"` - AllowSkip bool `json:"allow_skip" bson:"allow_skip"` - Text string `json:"text" bson:"text"` - MoreInfo string `json:"more_info" bson:"more_info"` - DefaultFollowUpKey *string `json:"default_follow_up_key" bson:"default_follow_up_key"` - DefaultResponseRule *string `json:"default_response_rule" bson:"default_response_rule"` - FollowUpRule *string `json:"follow_up_rule" bson:"follow_up_rule"` - ScoreRule *string `json:"score_rule" bson:"score_rule"` - Replace bool `json:"replace" bson:"replace"` - Response interface{} `json:"response" bson:"response"` + Section *string `json:"section,omitempty" bson:"section,omitempty"` + Sections []string `json:"sections,omitempty" bson:"sections,omitempty"` + AllowSkip bool `json:"allow_skip" bson:"allow_skip"` + Text string `json:"text" bson:"text"` + MoreInfo string `json:"more_info" bson:"more_info"` + DefaultFollowUpKey *string `json:"default_follow_up_key" bson:"default_follow_up_key"` + DefaultResponseRule *string `json:"default_response_rule" bson:"default_response_rule"` + FollowUpRule *string `json:"follow_up_rule" bson:"follow_up_rule"` + ScoreRule *string `json:"score_rule" bson:"score_rule"` + Replace bool `json:"replace" bson:"replace"` + Response interface{} `json:"response" bson:"response"` + Extras *map[string]interface{} `json:"extras" bson:"extras"` Type string `json:"type" bson:"type"` @@ -152,3 +158,97 @@ type OptionData struct { Score *float64 `json:"score" bson:"score"` Selected bool `json:"selected" bson:"selected"` } + +// DeletedUserData represents a user-deleted +type DeletedUserData struct { + AppID string `json:"app_id"` + Memberships []DeletedMembership `json:"memberships"` + OrgID string `json:"org_id"` +} + +// DeletedMembership defines model for DeletedMembership. +type DeletedMembership struct { + AccountID string `json:"account_id"` + Context *map[string]interface{} `json:"context,omitempty"` +} + +// SurveyRequest wraps the wraps the request for the surveys +type SurveyRequest struct { + ID string `json:"id" bson:"_id"` + CreatorID string `json:"creator_id" bson:"creator_id"` + OrgID string `json:"org_id" bson:"org_id"` + AppID string `json:"app_id" bson:"app_id"` + Title string `json:"title" bson:"title"` + MoreInfo *string `json:"more_info" bson:"more_info"` + Data map[string]SurveyData `json:"data" bson:"data"` + Scored bool `json:"scored" bson:"scored"` + ResultRules string `json:"result_rules" bson:"result_rules"` + ResultJSON string `json:"result_json" bson:"result_json"` + Type string `json:"type" bson:"type"` + SurveyStats *SurveyStats `json:"stats" bson:"stats"` + Sensitive bool `json:"sensitive" bson:"sensitive"` + Anonymous bool `json:"anonymous" bson:"anonymous"` + DefaultDataKey *string `json:"default_data_key" bson:"default_data_key"` + DefaultDataKeyRule *string `json:"default_data_key_rule" bson:"default_data_key_rule"` + Constants map[string]interface{} `json:"constants" bson:"constants"` + Strings map[string]interface{} `json:"strings" bson:"strings"` + SubRules map[string]interface{} `json:"sub_rules" bson:"sub_rules"` + ResponseKeys []string `json:"response_keys" bson:"response_keys"` + DateCreated time.Time `json:"date_created" bson:"date_created"` + DateUpdated *time.Time `json:"date_updated" bson:"date_updated"` + CalendarEventID string `json:"calendar_event_id" bson:"calendar_event_id"` + StartDate *string `json:"start_date" bson:"start_date"` + EndDate *string `json:"end_date" bson:"end_date"` + Public *bool `json:"public" bson:"public"` + Archived *bool `json:"archived" bson:"archived"` + EstimatedCompletionTime *int `json:"estimated_completion_time" bson:"estimated_completion_time"` +} + +// SurveyTimeFilter wraps the time filter for surveys +type SurveyTimeFilter struct { + StartTimeAfter *time.Time `json:"start_time_after"` + StartTimeBefore *time.Time `json:"start_time_before"` + EndTimeAfter *time.Time `json:"end_time_after"` + EndTimeBefore *time.Time `json:"end_time_before"` +} + +// SurveyTimeFilterRequest wraps the time filter for surveys +type SurveyTimeFilterRequest struct { + StartTimeAfter *string `json:"start_time_after"` + StartTimeBefore *string `json:"start_time_before"` + EndTimeAfter *string `json:"end_time_after"` + EndTimeBefore *string `json:"end_time_before"` +} + +// SurveysResponseData wraps the entire record +type SurveysResponseData struct { + ID string `json:"id"` + CreatorID string `json:"creator_id"` + OrgID string `json:"org_id"` + AppID string `json:"app_id"` + Title string `json:"title"` + MoreInfo *string `json:"more_info"` + Data map[string]SurveyData `json:"data"` + Scored bool `json:"scored"` + ResultRules string `json:"result_rules"` + ResultJSON string `json:"result_json"` + Type string `json:"type"` + SurveyStats *SurveyStats `json:"stats"` + Sensitive bool `json:"sensitive"` + Anonymous bool `json:"anonymous"` + DefaultDataKey *string `json:"default_data_key"` + DefaultDataKeyRule *string `json:"default_data_key_rule"` + Constants map[string]interface{} `json:"constants"` + Strings map[string]interface{} `json:"strings"` + SubRules map[string]interface{} `json:"sub_rules"` + ResponseKeys []string `json:"response_keys"` + DateCreated time.Time `json:"date_created"` + DateUpdated *time.Time `json:"date_updated"` + CalendarEventID string `json:"calendar_event_id"` + StartDate *time.Time `json:"start_date"` + EndDate *time.Time `json:"end_date"` + Public *bool `json:"public"` + Archived *bool `json:"archived"` + EstimatedCompletionTime *int `json:"estimated_completion_time"` + Completed *bool `json:"completed"` +} diff --git a/driven/core/adapter.go b/driven/core/adapter.go new file mode 100644 index 0000000..7baeb7b --- /dev/null +++ b/driven/core/adapter.go @@ -0,0 +1,86 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package corebb + +import ( + "application/core/model" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "net/http" + + "github.com/rokwire/core-auth-library-go/v3/authservice" + "github.com/rokwire/logging-library-go/v2/logs" +) + +// Adapter implements the Core interface +type Adapter struct { + logger logs.Logger + coreURL string + serviceAccountManager *authservice.ServiceAccountManager +} + +// NewCoreAdapter creates a new adapter for Core API +func NewCoreAdapter(coreURL string, serviceAccountManager *authservice.ServiceAccountManager) *Adapter { + return &Adapter{coreURL: coreURL, serviceAccountManager: serviceAccountManager} +} + +// LoadDeletedMemberships loads deleted memberships +func (a *Adapter) LoadDeletedMemberships() ([]model.DeletedUserData, error) { + + if a.serviceAccountManager == nil { + log.Println("LoadDeletedMemberships: service account manager is nil") + return nil, errors.New("service account manager is nil") + } + + url := fmt.Sprintf("%s/bbs/deleted-memberships?service_id=%s", a.coreURL, a.serviceAccountManager.AuthService.ServiceID) + + // Create a new HTTP request + req, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Printf("delete membership: error creating request - %s", err) + return nil, err + } + req.Header.Add("Content-Type", "application/json") + + resp, err := a.serviceAccountManager.MakeRequest(req, "all", "all") + if err != nil { + log.Printf("LoadDeletedMemberships: error sending request - %s", err) + return nil, err + } + + defer resp.Body.Close() + if resp.StatusCode != 200 { + log.Printf("LoadDeletedMemberships: error with response code - %d", resp.StatusCode) + return nil, fmt.Errorf("LoadDeletedMemberships: error with response code != 200") + } + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Printf("LoadDeletedMemberships: unable to read json: %s", err) + return nil, fmt.Errorf("LoadDeletedMemberships: unable to parse json: %s", err) + } + + var deletedMemberships []model.DeletedUserData + err = json.Unmarshal(data, &deletedMemberships) + if err != nil { + log.Printf("LoadDeletedMemberships: unable to parse json: %s", err) + return nil, fmt.Errorf("LoadDeletedMemberships: unable to parse json: %s", err) + } + + return deletedMemberships, nil +} diff --git a/driven/storage/adapter.go b/driven/storage/adapter.go index 810c487..abc28d2 100644 --- a/driven/storage/adapter.go +++ b/driven/storage/adapter.go @@ -251,6 +251,36 @@ func (a *Adapter) DeleteConfig(id string) error { return nil } +// DeleteSurveyResponsesWithIDs Deletes survey responses +func (a Adapter) DeleteSurveyResponsesWithIDs(orgID string, appID string, accountsIDs []string) error { + filter := bson.D{ + primitive.E{Key: "app_id", Value: appID}, + primitive.E{Key: "org_id", Value: orgID}, + primitive.E{Key: "user_id", Value: bson.M{"$in": accountsIDs}}, + } + + _, err := a.db.surveyResponses.DeleteMany(nil, filter, nil) + if err != nil { + return errors.WrapErrorAction(logutils.ActionDelete, "user", nil, err) + } + return nil +} + +// DeleteSurveysWithIDs Deletes surveys +func (a Adapter) DeleteSurveysWithIDs(orgID string, appID string, accountsIDs []string) error { + filter := bson.D{ + primitive.E{Key: "app_id", Value: appID}, + primitive.E{Key: "org_id", Value: orgID}, + primitive.E{Key: "creator_id", Value: bson.M{"$in": accountsIDs}}, + } + + _, err := a.db.surveys.DeleteMany(nil, filter, nil) + if err != nil { + return errors.WrapErrorAction(logutils.ActionDelete, "user", nil, err) + } + return nil +} + // PerformTransaction performs a transaction func (a *Adapter) PerformTransaction(transaction func(storage interfaces.Storage) error) error { // transaction diff --git a/driven/storage/adapter_surveys.go b/driven/storage/adapter_surveys.go index 511721d..0015331 100644 --- a/driven/storage/adapter_surveys.go +++ b/driven/storage/adapter_surveys.go @@ -21,6 +21,7 @@ import ( "github.com/rokwire/logging-library-go/v2/errors" "github.com/rokwire/logging-library-go/v2/logutils" "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo/options" ) @@ -36,19 +37,61 @@ func (a *Adapter) GetSurvey(id string, orgID string, appID string) (*model.Surve } // GetSurveys gets matching surveys -func (a *Adapter) GetSurveys(orgID string, appID string, creatorID *string, surveyIDs []string, surveyTypes []string, calendarEventID string, limit *int, offset *int) ([]model.Survey, error) { - filter := bson.M{"org_id": orgID, "app_id": appID} +func (a *Adapter) GetSurveys(orgID string, appID string, creatorID *string, surveyIDs []string, surveyTypes []string, calendarEventID string, limit *int, offset *int, timeFilter *model.SurveyTimeFilter, public *bool, archived *bool, completed *bool) ([]model.Survey, error) { + filter := bson.D{ + {Key: "org_id", Value: orgID}, + {Key: "app_id", Value: appID}, + } + if creatorID != nil { - filter["creator_id"] = *creatorID + filter = append(filter, bson.E{Key: "creator_id", Value: *creatorID}) } if len(surveyIDs) > 0 { - filter["_id"] = bson.M{"$in": surveyIDs} + filter = append(filter, bson.E{Key: "_id", Value: bson.M{"$in": surveyIDs}}) } if len(surveyTypes) > 0 { - filter["type"] = bson.M{"$in": surveyTypes} + filter = append(filter, bson.E{Key: "type", Value: bson.M{"$in": surveyTypes}}) + } + if calendarEventID != "" { + filter = append(filter, bson.E{Key: "calendar_event_id", Value: calendarEventID}) + } + + if timeFilter.StartTimeAfter != nil { + filter = append(filter, primitive.E{Key: "start_date", Value: primitive.M{"$gte": *timeFilter.StartTimeAfter}}) + } + if timeFilter.StartTimeBefore != nil { + filter = append(filter, primitive.E{Key: "start_date", Value: primitive.M{"$lte": *timeFilter.StartTimeBefore}}) } - if len(calendarEventID) > 0 { - filter["calendar_event_id"] = calendarEventID + + if timeFilter.EndTimeAfter != nil { + filter = append(filter, primitive.E{Key: "end_date", Value: primitive.M{"$gte": *timeFilter.EndTimeAfter}}) + } + if timeFilter.EndTimeBefore != nil { + filter = append(filter, primitive.E{Key: "end_date", Value: primitive.M{"$lte": *timeFilter.EndTimeBefore}}) + } + + if public != nil { + if *public == true { + filter = append(filter, bson.E{Key: "public", Value: true}) + } else { + filter = append(filter, bson.E{Key: "$or", Value: bson.A{ + bson.M{"public": false}, + bson.M{"public": bson.M{"$exists": false}}, + bson.M{"public": nil}, + }}) + } + } + + if archived != nil { + if *archived == true { + filter = append(filter, bson.E{Key: "archived", Value: true}) + } else { + filter = append(filter, bson.E{Key: "$or", Value: bson.A{ + bson.M{"archived": false}, + bson.M{"archived": bson.M{"$exists": false}}, + bson.M{"archived": nil}, + }}) + } } opts := options.Find() @@ -58,11 +101,25 @@ func (a *Adapter) GetSurveys(orgID string, appID string, creatorID *string, surv if offset != nil { opts.SetSkip(int64(*offset)) } + if timeFilter.StartTimeBefore != nil { + opts.SetSort(bson.D{{Key: "start_date", Value: -1}}) + } else if timeFilter.StartTimeAfter != nil { + opts.SetSort(bson.D{{Key: "start_date", Value: 1}}) + } + + if timeFilter.EndTimeBefore != nil { + opts.SetSort(bson.D{{Key: "end_date", Value: -1}}) + } else if timeFilter.EndTimeAfter != nil { + opts.SetSort(bson.D{{Key: "end_date", Value: 1}}) + + } + var results []model.Survey err := a.db.surveys.Find(a.context, filter, &results, opts) if err != nil { - return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeSurvey, filterArgs(filter), err) + return nil, err } + return results, nil } @@ -85,19 +142,24 @@ func (a *Adapter) UpdateSurvey(survey model.Survey, admin bool) error { filter["creator_id"] = survey.CreatorID } update := bson.M{"$set": bson.M{ - "title": survey.Title, - "more_info": survey.MoreInfo, - "data": survey.Data, - "scored": survey.Scored, - "result_rules": survey.ResultRules, - "type": survey.Type, - "stats": survey.SurveyStats, - "default_data_key": survey.DefaultDataKey, - "default_data_key_rule": survey.DefaultDataKeyRule, - "constants": survey.Constants, - "strings": survey.Strings, - "sub_rules": survey.SubRules, - "date_updated": now, + "title": survey.Title, + "more_info": survey.MoreInfo, + "data": survey.Data, + "scored": survey.Scored, + "result_rules": survey.ResultRules, + "type": survey.Type, + "stats": survey.SurveyStats, + "default_data_key": survey.DefaultDataKey, + "default_data_key_rule": survey.DefaultDataKeyRule, + "constants": survey.Constants, + "strings": survey.Strings, + "sub_rules": survey.SubRules, + "start_date": survey.StartDate, + "end_date": survey.EndDate, + "public": survey.Public, + "archived": survey.Archived, + "estimated_completion_time": survey.EstimatedCompletionTime, + "date_updated": now, }} res, err := a.db.surveys.UpdateOne(a.context, filter, update, nil) diff --git a/driver/web/apis_admin.go b/driver/web/apis_admin.go index 17a2111..22d567c 100644 --- a/driver/web/apis_admin.go +++ b/driver/web/apis_admin.go @@ -18,7 +18,10 @@ import ( "application/core" "application/core/model" "encoding/json" + "io/ioutil" + "log" "net/http" + "sort" "strconv" "strings" "time" @@ -180,6 +183,26 @@ func (h AdminAPIsHandler) getSurvey(l *logs.Log, r *http.Request, claims *tokena } func (h AdminAPIsHandler) getSurveys(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + data, err := ioutil.ReadAll(r.Body) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionRead, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false) + } + + var items *model.SurveyTimeFilterRequest + // If the body is empty or only contains whitespace, treat it as nil + if len(data) == 0 { + log.Println("Request body is empty, proceeding with default behavior.") + items = &model.SurveyTimeFilterRequest{StartTimeBefore: nil, StartTimeAfter: nil, EndTimeAfter: nil, EndTimeBefore: nil} + } else { + // Unmarshal the data into the items struct + err = json.Unmarshal(data, &items) + if err != nil { + log.Printf("Error unmarshaling request body: %v", err) + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeSurvey, nil, err, http.StatusInternalServerError, true) + } + } + filter := surveyTimeFilter(items) + surveyIDsRaw := r.URL.Query().Get("ids") var surveyIDs []string if len(surveyIDsRaw) > 0 { @@ -194,7 +217,7 @@ func (h AdminAPIsHandler) getSurveys(l *logs.Log, r *http.Request, claims *token calendarEventID := r.URL.Query().Get("calendar_event_id") limitRaw := r.URL.Query().Get("limit") - limit := 20 + limit := 0 if len(limitRaw) > 0 { intParsed, err := strconv.Atoi(limitRaw) if err != nil { @@ -211,31 +234,73 @@ func (h AdminAPIsHandler) getSurveys(l *logs.Log, r *http.Request, claims *token } offset = intParsed } + publicStr := r.URL.Query().Get("public") + + var public *bool + + if publicStr != "" { + valuePublic, err := strconv.ParseBool(publicStr) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeSurvey, nil, err, http.StatusInternalServerError, true) + } + public = &valuePublic + } + + archivedStr := r.URL.Query().Get("archived") + + var archived *bool + + if archivedStr != "" { + valueArchived, err := strconv.ParseBool(archivedStr) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeSurvey, nil, err, http.StatusInternalServerError, true) + } + archived = &valueArchived + } + + completedStr := r.URL.Query().Get("completed") + + var completed *bool - resData, err := h.app.Admin.GetSurveys(claims.OrgID, claims.AppID, nil, surveyIDs, surveyTypes, calendarEventID, &limit, &offset) + if completedStr != "" { + valueCompleted, err := strconv.ParseBool(completedStr) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeSurvey, nil, err, http.StatusInternalServerError, true) + } + completed = &valueCompleted + } + + surveys, surverysRsponse, err := h.app.Admin.GetSurveys(claims.OrgID, claims.AppID, nil, surveyIDs, surveyTypes, calendarEventID, + &limit, &offset, filter, public, archived, completed) if err != nil { return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeSurvey, nil, err, http.StatusInternalServerError, true) } - data, err := json.Marshal(resData) + resData := getSurveysResData(surveys, surverysRsponse, completed) + sort.Slice(resData, func(i, j int) bool { + return resData[i].DateCreated.After(resData[j].DateCreated) + }) + + rdata, err := json.Marshal(resData) if err != nil { return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResponseBody, nil, err, http.StatusInternalServerError, false) } - return l.HTTPResponseSuccessJSON(data) + return l.HTTPResponseSuccessJSON(rdata) } func (h AdminAPIsHandler) createSurvey(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { - var item model.Survey - err := json.NewDecoder(r.Body).Decode(&item) + var items model.SurveyRequest + err := json.NewDecoder(r.Body).Decode(&items) if err != nil { return l.HTTPResponseErrorAction(logutils.ActionDecode, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, true) } + items.CreatorID = claims.Subject + items.OrgID = claims.OrgID + items.AppID = claims.AppID + items.Type = "user" - item.OrgID = claims.OrgID - item.AppID = claims.AppID - item.CreatorID = claims.Subject - item.Type = "admin" + item := surveyRequestToSurvey(items) createdItem, err := h.app.Admin.CreateSurvey(item, claims.ExternalIDs) if err != nil { @@ -258,16 +323,18 @@ func (h AdminAPIsHandler) updateSurvey(l *logs.Log, r *http.Request, claims *tok return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypePathParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) } - var item model.Survey - err := json.NewDecoder(r.Body).Decode(&item) + var items model.SurveyRequest + err := json.NewDecoder(r.Body).Decode(&items) if err != nil { return l.HTTPResponseErrorAction(logutils.ActionDecode, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, true) } - item.ID = id - item.OrgID = claims.OrgID - item.AppID = claims.AppID - item.Type = "admin" + items.CreatorID = claims.Subject + items.OrgID = claims.OrgID + items.AppID = claims.AppID + items.Type = "user" + + item := updateSurveyRequestToSurvey(items, id) err = h.app.Admin.UpdateSurvey(item, claims.Subject, claims.ExternalIDs) if err != nil { diff --git a/driver/web/apis_client.go b/driver/web/apis_client.go index bb6dcda..45a6657 100644 --- a/driver/web/apis_client.go +++ b/driver/web/apis_client.go @@ -18,7 +18,10 @@ import ( "application/core" "application/core/model" "encoding/json" + "io/ioutil" + "log" "net/http" + "sort" "strconv" "strings" "time" @@ -55,6 +58,25 @@ func (h ClientAPIsHandler) getSurvey(l *logs.Log, r *http.Request, claims *token } func (h ClientAPIsHandler) getSurveys(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + data, err := ioutil.ReadAll(r.Body) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionRead, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false) + } + + var items *model.SurveyTimeFilterRequest + // If the body is empty or only contains whitespace, treat it as nil + if len(data) == 0 { + log.Println("Request body is empty, proceeding with default behavior.") + items = &model.SurveyTimeFilterRequest{StartTimeBefore: nil, StartTimeAfter: nil, EndTimeAfter: nil, EndTimeBefore: nil} + } else { + // Unmarshal the data into the items struct + err = json.Unmarshal(data, &items) + if err != nil { + log.Printf("Error unmarshaling request body: %v", err) + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeSurvey, nil, err, http.StatusInternalServerError, true) + } + } + filter := surveyTimeFilter(items) surveyIDsRaw := r.URL.Query().Get("ids") var surveyIDs []string if len(surveyIDsRaw) > 0 { @@ -69,7 +91,7 @@ func (h ClientAPIsHandler) getSurveys(l *logs.Log, r *http.Request, claims *toke calendarEventID := r.URL.Query().Get("calendar_event_id") limitRaw := r.URL.Query().Get("limit") - limit := 20 + limit := 0 if len(limitRaw) > 0 { intParsed, err := strconv.Atoi(limitRaw) if err != nil { @@ -87,30 +109,72 @@ func (h ClientAPIsHandler) getSurveys(l *logs.Log, r *http.Request, claims *toke offset = intParsed } - resData, err := h.app.Client.GetSurveys(claims.OrgID, claims.AppID, nil, surveyIDs, surveyTypes, calendarEventID, &limit, &offset) + publicStr := r.URL.Query().Get("public") + + var public *bool + + if publicStr != "" { + valuePublic, err := strconv.ParseBool(publicStr) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeSurvey, nil, err, http.StatusInternalServerError, true) + } + public = &valuePublic + } + + archivedStr := r.URL.Query().Get("archived") + + var archived *bool + + if archivedStr != "" { + valueArchived, err := strconv.ParseBool(archivedStr) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeSurvey, nil, err, http.StatusInternalServerError, true) + } + archived = &valueArchived + } + + completedStr := r.URL.Query().Get("completed") + + var completed *bool + + if completedStr != "" { + valueCompleted, err := strconv.ParseBool(completedStr) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeSurvey, nil, err, http.StatusInternalServerError, true) + } + completed = &valueCompleted + } + + surveys, surverysRsponse, err := h.app.Client.GetSurveys(claims.OrgID, claims.AppID, nil, surveyIDs, surveyTypes, calendarEventID, + &limit, &offset, filter, public, archived, completed) if err != nil { return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeSurvey, nil, err, http.StatusInternalServerError, true) } - data, err := json.Marshal(resData) + resData := getSurveysResData(surveys, surverysRsponse, completed) + sort.Slice(resData, func(i, j int) bool { + return resData[i].DateCreated.After(resData[j].DateCreated) + }) + + rdata, err := json.Marshal(resData) if err != nil { return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResponseBody, nil, err, http.StatusInternalServerError, false) } - return l.HTTPResponseSuccessJSON(data) + return l.HTTPResponseSuccessJSON(rdata) } func (h ClientAPIsHandler) createSurvey(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { - var item model.Survey - err := json.NewDecoder(r.Body).Decode(&item) + var items model.SurveyRequest + err := json.NewDecoder(r.Body).Decode(&items) if err != nil { return l.HTTPResponseErrorAction(logutils.ActionDecode, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, true) } - - item.OrgID = claims.OrgID - item.AppID = claims.AppID - item.CreatorID = claims.Subject - item.Type = "user" + items.CreatorID = claims.Subject + items.OrgID = claims.OrgID + items.AppID = claims.AppID + items.Type = "user" + item := surveyRequestToSurvey(items) createdItem, err := h.app.Client.CreateSurvey(item, claims.ExternalIDs) if err != nil { @@ -133,16 +197,17 @@ func (h ClientAPIsHandler) updateSurvey(l *logs.Log, r *http.Request, claims *to return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypePathParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) } - var item model.Survey - err := json.NewDecoder(r.Body).Decode(&item) + var items model.SurveyRequest + err := json.NewDecoder(r.Body).Decode(&items) if err != nil { return l.HTTPResponseErrorAction(logutils.ActionDecode, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, true) } + items.CreatorID = claims.Subject + items.OrgID = claims.OrgID + items.AppID = claims.AppID + items.Type = "user" - item.ID = id - item.OrgID = claims.OrgID - item.AppID = claims.AppID - item.Type = "user" + item := updateSurveyRequestToSurvey(items, id) err = h.app.Client.UpdateSurvey(item, claims.Subject, claims.ExternalIDs) if err != nil { @@ -458,7 +523,7 @@ func (h ClientAPIsHandler) getCreatorSurveys(l *logs.Log, r *http.Request, claim offset = intParsed } - resData, err := h.app.Client.GetSurveys(claims.OrgID, claims.AppID, &claims.Subject, surveyIDs, surveyTypes, "", &limit, &offset) + resData, _, err := h.app.Client.GetSurveys(claims.OrgID, claims.AppID, &claims.Subject, surveyIDs, surveyTypes, "", &limit, &offset, nil, nil, nil, nil) if err != nil { return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeSurvey, nil, err, http.StatusInternalServerError, true) } diff --git a/driver/web/convertions_surveys.go b/driver/web/convertions_surveys.go new file mode 100644 index 0000000..b75c3f9 --- /dev/null +++ b/driver/web/convertions_surveys.go @@ -0,0 +1,147 @@ +package web + +import ( + "application/core/model" + "time" +) + +func surveyRequestToSurvey(item model.SurveyRequest) model.Survey { + //start + var startValue *time.Time + if item.StartDate != nil { + startValueValue, _ := time.Parse(time.RFC3339, *item.StartDate) + startValue = &startValueValue + } + //end + var endValue *time.Time + if item.EndDate != nil { + endValueTime, _ := time.Parse(time.RFC3339, *item.EndDate) + endValue = &endValueTime + } + + return model.Survey{CreatorID: item.CreatorID, OrgID: item.OrgID, AppID: item.AppID, Type: item.Type, Title: item.Title, + MoreInfo: item.MoreInfo, Data: item.Data, Scored: item.Scored, ResultRules: item.ResultRules, ResultJSON: item.ResultJSON, + SurveyStats: item.SurveyStats, Sensitive: item.Sensitive, Anonymous: item.Anonymous, DefaultDataKey: item.DefaultDataKey, + DefaultDataKeyRule: item.DefaultDataKeyRule, Constants: item.Constants, Strings: item.Strings, SubRules: item.SubRules, + ResponseKeys: item.ResponseKeys, CalendarEventID: item.CalendarEventID, StartDate: startValue, EndDate: endValue, + Public: item.Public, Archived: item.Archived, EstimatedCompletionTime: item.EstimatedCompletionTime} +} + +func getSurvey(item model.Survey) model.Survey { + + return model.Survey{CreatorID: item.CreatorID, OrgID: item.OrgID, AppID: item.AppID, Type: item.Type, Title: item.Title, + MoreInfo: item.MoreInfo, Data: item.Data, Scored: item.Scored, ResultRules: item.ResultRules, ResultJSON: item.ResultJSON, + SurveyStats: item.SurveyStats, Sensitive: item.Sensitive, Anonymous: item.Anonymous, DefaultDataKey: item.DefaultDataKey, + DefaultDataKeyRule: item.DefaultDataKeyRule, Constants: item.Constants, Strings: item.Strings, SubRules: item.SubRules, + ResponseKeys: item.ResponseKeys, CalendarEventID: item.CalendarEventID, StartDate: item.StartDate, EndDate: item.EndDate, + Public: item.Public, Archived: item.Archived, EstimatedCompletionTime: item.EstimatedCompletionTime} +} + +func getSurveys(items []model.Survey) []model.Survey { + list := make([]model.Survey, len(items)) + for index := range items { + list[index] = getSurvey(items[index]) + } + return list +} + +func updateSurveyRequestToSurvey(item model.SurveyRequest, id string) model.Survey { + + // start + var startValue *time.Time + if item.StartDate != nil { + startValueValue, _ := time.Parse(time.RFC3339, *item.StartDate) + startValue = &startValueValue + } + // end + var endValue *time.Time + if item.EndDate != nil { + endValueTime, _ := time.Parse(time.RFC3339, *item.EndDate) + endValue = &endValueTime + } + + return model.Survey{ID: id, CreatorID: item.CreatorID, OrgID: item.OrgID, AppID: item.AppID, Type: item.Type, Title: item.Title, + MoreInfo: item.MoreInfo, Data: item.Data, Scored: item.Scored, ResultRules: item.ResultRules, ResultJSON: item.ResultJSON, + SurveyStats: item.SurveyStats, Sensitive: item.Sensitive, Anonymous: item.Anonymous, DefaultDataKey: item.DefaultDataKey, + DefaultDataKeyRule: item.DefaultDataKeyRule, Constants: item.Constants, Strings: item.Strings, SubRules: item.SubRules, + ResponseKeys: item.ResponseKeys, CalendarEventID: item.CalendarEventID, StartDate: startValue, EndDate: endValue, + Public: item.Public, Archived: item.Archived, EstimatedCompletionTime: item.EstimatedCompletionTime} +} + +func surveyTimeFilter(item *model.SurveyTimeFilterRequest) *model.SurveyTimeFilter { + + filter := model.SurveyTimeFilter{} + + if item.StartTimeBefore != nil { + beforeStartTime, _ := time.Parse(time.RFC3339, *item.StartTimeBefore) + filter.StartTimeBefore = &beforeStartTime + } + if item.StartTimeAfter != nil { + afterStartTime, _ := time.Parse(time.RFC3339, *item.StartTimeAfter) + filter.StartTimeAfter = &afterStartTime + } + + if item.EndTimeBefore != nil { + beforeEndTime, _ := time.Parse(time.RFC3339, *item.EndTimeBefore) + filter.EndTimeBefore = &beforeEndTime + } + if item.EndTimeAfter != nil { + afterEndTime, _ := time.Parse(time.RFC3339, *item.EndTimeAfter) + filter.EndTimeAfter = &afterEndTime + } + + return &model.SurveyTimeFilter{ + StartTimeAfter: filter.StartTimeAfter, + StartTimeBefore: filter.StartTimeBefore, + EndTimeAfter: filter.EndTimeAfter, + EndTimeBefore: filter.EndTimeBefore} +} + +func getSurveysResData(items []model.Survey, surveyResponses []model.SurveyResponse, completed *bool) []model.SurveysResponseData { + var list []model.SurveysResponseData + + for _, item := range items { + var isCompleted bool + + for _, surveyResponse := range surveyResponses { + if item.ID == surveyResponse.Survey.ID { + isCompleted = true + break + } + } + + if completed == nil || *completed == isCompleted { + list = append(list, model.SurveysResponseData{ + ID: item.ID, + CreatorID: item.CreatorID, + OrgID: item.OrgID, + AppID: item.AppID, + Type: item.Type, + Title: item.Title, + MoreInfo: item.MoreInfo, + Data: item.Data, + Scored: item.Scored, + ResultRules: item.ResultRules, + ResultJSON: item.ResultJSON, + SurveyStats: item.SurveyStats, + Sensitive: item.Sensitive, + Anonymous: item.Anonymous, + DefaultDataKey: item.DefaultDataKey, + DefaultDataKeyRule: item.DefaultDataKeyRule, + Constants: item.Constants, + Strings: item.Strings, + SubRules: item.SubRules, + ResponseKeys: item.ResponseKeys, + CalendarEventID: item.CalendarEventID, + StartDate: item.StartDate, + EndDate: item.EndDate, + Public: item.Public, + Archived: item.Archived, + EstimatedCompletionTime: item.EstimatedCompletionTime, + Completed: &isCompleted, + }) + } + } + + return list +} diff --git a/driver/web/docs/gen/def.yaml b/driver/web/docs/gen/def.yaml index eacd68d..ae05c03 100644 --- a/driver/web/docs/gen/def.yaml +++ b/driver/web/docs/gen/def.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: Rokwire Surveys Building Block API description: Surveys Building Block API Documentation - version: 1.3.0 + version: 1.9.0 servers: - url: 'https://api.rokwire.illinois.edu/surveys' description: Production server @@ -92,6 +92,58 @@ paths: explode: false schema: type: number + - name: public + in: query + description: Shows if the survery is public or not + required: false + style: simple + explode: false + schema: + type: boolean + - name: archived + in: query + description: Shows if the survery is archived or not + required: false + style: simple + explode: false + schema: + type: boolean + - name: completed + in: query + description: Shows if the survery is completed or not + required: false + style: simple + explode: false + schema: + type: boolean + requestBody: + description: Get survey time filter request body + required: false + content: + application/json: + schema: + type: object + properties: + start_time_before: + type: integer + nullable: true + format: int64 + description: UNIX UTC timestamp in seconds + start_time_after: + type: integer + nullable: true + format: int64 + description: UNIX UTC timestamp in seconds + end_time_before: + type: integer + nullable: true + format: int64 + description: UNIX UTC timestamp in seconds + end_time_after: + type: integer + nullable: true + format: int64 + description: UNIX UTC timestamp in seconds responses: '200': description: Success @@ -183,7 +235,7 @@ paths: schema: type: string requestBody: - description: Data body model.Survey + description: Data body model.SurveyRequest content: application/json: schema: @@ -915,6 +967,37 @@ paths: explode: false schema: type: number + - name: public + in: query + description: Shows if the survery is public or not + required: false + style: simple + explode: false + schema: + type: boolean + - name: archived + in: query + description: Shows if the survery is archived or not + required: false + style: simple + explode: false + schema: + type: boolean + - name: completed + in: query + description: Shows if the survery is completed or not + required: false + style: simple + explode: false + schema: + type: boolean + requestBody: + description: Get survey time filter request body + required: false + content: + application/json: + schema: + $ref: '#/paths/~1api~1surveys/get/requestBody/content/application~1json/schema' responses: '200': description: Success @@ -1437,6 +1520,22 @@ components: nullable: true calendar_event_id: type: string + start_date: + type: string + nullable: true + end_date: + type: string + nullable: true + public: + type: boolean + nullable: true + archived: + type: boolean + nullable: true + estimated_completion_time: + type: integer + format: int64 + nullable: true SurveyData: type: object properties: @@ -1460,6 +1559,9 @@ components: type: boolean response: type: object + extras: + type: object + nullable: true type: type: string correct_answer: diff --git a/driver/web/docs/index.yaml b/driver/web/docs/index.yaml index 96e7d42..abfc26c 100644 --- a/driver/web/docs/index.yaml +++ b/driver/web/docs/index.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: Rokwire Surveys Building Block API description: Surveys Building Block API Documentation - version: 1.3.0 + version: 1.9.0 servers: - url: 'https://api.rokwire.illinois.edu/surveys' description: Production server diff --git a/driver/web/docs/resources/admin/surveys.yaml b/driver/web/docs/resources/admin/surveys.yaml index fb68dfc..793bdbd 100644 --- a/driver/web/docs/resources/admin/surveys.yaml +++ b/driver/web/docs/resources/admin/surveys.yaml @@ -48,6 +48,37 @@ get: explode: false schema: type: number + - name: public + in: query + description: Shows if the survery is public or not + required: false + style: simple + explode: false + schema: + type: boolean + - name: archived + in: query + description: Shows if the survery is archived or not + required: false + style: simple + explode: false + schema: + type: boolean + - name: completed + in: query + description: Shows if the survery is completed or not + required: false + style: simple + explode: false + schema: + type: boolean + requestBody: + description: Get survey time filter request body + required: false + content: + application/json: + schema: + $ref: "../../schemas/surveys/SurveyTimeFilter.yaml" responses: 200: description: Success diff --git a/driver/web/docs/resources/client/surveys.yaml b/driver/web/docs/resources/client/surveys.yaml index 0cf84ad..b5e4503 100644 --- a/driver/web/docs/resources/client/surveys.yaml +++ b/driver/web/docs/resources/client/surveys.yaml @@ -46,6 +46,37 @@ get: explode: false schema: type: number + - name: public + in: query + description: Shows if the survery is public or not + required: false + style: simple + explode: false + schema: + type: boolean + - name: archived + in: query + description: Shows if the survery is archived or not + required: false + style: simple + explode: false + schema: + type: boolean + - name: completed + in: query + description: Shows if the survery is completed or not + required: false + style: simple + explode: false + schema: + type: boolean + requestBody: + description: Get survey time filter request body + required: false + content: + application/json: + schema: + $ref: "../../schemas/surveys/SurveyTimeFilter.yaml" responses: 200: description: Success diff --git a/driver/web/docs/resources/client/surveysid.yaml b/driver/web/docs/resources/client/surveysid.yaml index 51e0b57..8f274ce 100644 --- a/driver/web/docs/resources/client/surveysid.yaml +++ b/driver/web/docs/resources/client/surveysid.yaml @@ -46,7 +46,7 @@ put: schema: type: string requestBody: - description: Data body model.Survey + description: Data body model.SurveyRequest content: application/json: schema: diff --git a/driver/web/docs/schemas/surveys/Survey.yaml b/driver/web/docs/schemas/surveys/Survey.yaml index cd36b25..cecc2d3 100644 --- a/driver/web/docs/schemas/surveys/Survey.yaml +++ b/driver/web/docs/schemas/surveys/Survey.yaml @@ -56,4 +56,20 @@ properties: readOnly: true nullable: true calendar_event_id: - type: string \ No newline at end of file + type: string + start_date: + type: string + nullable: true + end_date: + type: string + nullable: true + public: + type: boolean + nullable: true + archived: + type: boolean + nullable: true + estimated_completion_time: + type: integer + format: int64 + nullable: true \ No newline at end of file diff --git a/driver/web/docs/schemas/surveys/SurveyData.yaml b/driver/web/docs/schemas/surveys/SurveyData.yaml index f904639..2e44a5f 100644 --- a/driver/web/docs/schemas/surveys/SurveyData.yaml +++ b/driver/web/docs/schemas/surveys/SurveyData.yaml @@ -20,6 +20,9 @@ properties: type: boolean response: type: object + extras: + type: object + nullable: true type: type: string correct_answer: diff --git a/driver/web/docs/schemas/surveys/SurveyTimeFilter.yaml b/driver/web/docs/schemas/surveys/SurveyTimeFilter.yaml new file mode 100644 index 0000000..7d53d45 --- /dev/null +++ b/driver/web/docs/schemas/surveys/SurveyTimeFilter.yaml @@ -0,0 +1,22 @@ +type: object +properties: + start_time_before: + type: integer + nullable: true + format: int64 + description: "UNIX UTC timestamp in seconds" + start_time_after: + type: integer + nullable: true + format: int64 + description: "UNIX UTC timestamp in seconds" + end_time_before: + type: integer + nullable: true + format: int64 + description: "UNIX UTC timestamp in seconds" + end_time_after: + type: integer + nullable: true + format: int64 + description: "UNIX UTC timestamp in seconds" \ No newline at end of file diff --git a/go.sum b/go.sum index b7da366..8468d17 100644 --- a/go.sum +++ b/go.sum @@ -128,6 +128,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M= gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index 0fefc5c..a5e0f89 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ package main import ( "application/core" "application/driven/calendar" + corebb "application/driven/core" "application/driven/notifications" "application/driven/storage" "application/driver/web" @@ -139,8 +140,12 @@ func main() { logger.Fatalf("Error initializing calendar adapter: %v", err) } + //core adapter + coreAdapter := corebb.NewCoreAdapter(coreBBBaseURL, serviceAccountManager) + // Application - application := core.NewApplication(Version, Build, storageAdapter, notificationsAdapter, calendarAdapter, logger) + application := core.NewApplication(Version, Build, storageAdapter, notificationsAdapter, + calendarAdapter, coreAdapter, serviceID, logger) application.Start() // Web adapter