diff --git a/api/clinicians.go b/api/clinicians.go index a32813ea..ac29bed0 100644 --- a/api/clinicians.go +++ b/api/clinicians.go @@ -42,7 +42,7 @@ func (h *Handler) ListClinicians(ec echo.Context, clinicId ClinicId, params List Role: roleToString(params.Role), } - list, err := h.clinicians.List(ctx, &filter, page) + list, err := h.Clinicians.List(ctx, &filter, page) if err != nil { return err } @@ -57,7 +57,7 @@ func (h *Handler) DeleteClinician(ec echo.Context, clinicId ClinicId, clinicianI return err } - err := h.clinicians.Delete(ctx, string(clinicId), string(clinicianId)) + err := h.Clinicians.Delete(ctx, string(clinicId), string(clinicianId)) if err != nil { return err } @@ -67,7 +67,7 @@ func (h *Handler) DeleteClinician(ec echo.Context, clinicId ClinicId, clinicianI func (h *Handler) GetClinician(ec echo.Context, clinicId ClinicId, clinicianId ClinicianId) error { ctx := ec.Request().Context() - clinician, err := h.clinicians.Get(ctx, string(clinicId), string(clinicianId)) + clinician, err := h.Clinicians.Get(ctx, string(clinicId), string(clinicianId)) if err != nil { return err } @@ -93,7 +93,7 @@ func (h *Handler) CreateClinician(ec echo.Context, clinicId ClinicId) error { clinician := NewClinician(dto) clinician.ClinicId = &clinicObjId - result, err := h.clinicians.Create(ctx, clinician) + result, err := h.Clinicians.Create(ctx, clinician) if err != nil { return err } @@ -127,7 +127,7 @@ func (h *Handler) UpdateClinician(ec echo.Context, clinicId ClinicId, clinicianI Clinician: NewClinicianUpdate(dto), } - result, err := h.cliniciansUpdater.Update(ctx, update) + result, err := h.Clinicians.Update(ctx, update) if err != nil { return err } @@ -146,7 +146,7 @@ func (h *Handler) ListClinicsForClinician(ec echo.Context, userId UserId, params func (h *Handler) GetInvitedClinician(ec echo.Context, clinicId ClinicId, inviteId InviteId) error { ctx := ec.Request().Context() - clinician, err := h.clinicians.GetInvite(ctx, string(clinicId), string(inviteId)) + clinician, err := h.Clinicians.GetInvite(ctx, string(clinicId), string(inviteId)) if err != nil { return err } @@ -161,7 +161,7 @@ func (h *Handler) DeleteInvitedClinician(ec echo.Context, clinicId ClinicId, inv return err } - if err := h.clinicians.DeleteInvite(ctx, string(clinicId), string(inviteId)); err != nil { + if err := h.Clinicians.DeleteInvite(ctx, string(clinicId), string(inviteId)); err != nil { return err } @@ -185,7 +185,7 @@ func (h *Handler) AssociateClinicianToUser(ec echo.Context, clinicId ClinicId, i UserId: dto.UserId, } - clinician, err := h.cliniciansUpdater.AssociateInvite(ctx, associate) + clinician, err := h.Clinicians.AssociateInvite(ctx, associate) if err != nil { return err } @@ -195,7 +195,7 @@ func (h *Handler) AssociateClinicianToUser(ec echo.Context, clinicId ClinicId, i func (h *Handler) EnableNewClinicExperience(ec echo.Context, userId string) error { ctx := ec.Request().Context() - clinic, err := h.clinicsMigrator.CreateEmptyClinic(ctx, userId) + clinic, err := h.ClinicsMigrator.CreateEmptyClinic(ctx, userId) if err != nil { return err } @@ -219,7 +219,7 @@ func (h *Handler) AddServiceAccount(ec echo.Context, clinicId ClinicId) error { return ec.JSON(http.StatusBadRequest, "invalid clinic id") } - acc, err := h.serviceAccountAuthenticator.GetServiceAccount(ctx, dto.ClientId, dto.ClientSecret) + acc, err := h.ServiceAccountAuthenticator.GetServiceAccount(ctx, dto.ClientId, dto.ClientSecret) if err != nil { return fmt.Errorf("unable to get service account for client %s: %w", dto.ClientId, err) } @@ -231,7 +231,7 @@ func (h *Handler) AddServiceAccount(ec echo.Context, clinicId ClinicId) error { Roles: []string{clinicians.RoleClinicMember}, IsServiceAccount: true, } - result, err := h.clinicians.Create(ctx, clinician) + result, err := h.Clinicians.Create(ctx, clinician) if err != nil { return err } @@ -241,7 +241,7 @@ func (h *Handler) AddServiceAccount(ec echo.Context, clinicId ClinicId) error { func (h *Handler) listClinics(ec echo.Context, filter clinicians.Filter, page store.Pagination) error { ctx := ec.Request().Context() - cliniciansList, err := h.clinicians.List(ctx, &filter, page) + cliniciansList, err := h.Clinicians.List(ctx, &filter, page) if err != nil { return err } @@ -251,7 +251,7 @@ func (h *Handler) listClinics(ec echo.Context, filter clinicians.Filter, page st clinicIds = append(clinicIds, clinician.ClinicId.Hex()) } - clinicList, err := h.clinics.List(ctx, &clinics.Filter{Ids: clinicIds}, store.Pagination{}) + clinicList, err := h.Clinics.List(ctx, &clinics.Filter{Ids: clinicIds}, store.Pagination{}) dtos, err := NewClinicianClinicRelationshipsDto(cliniciansList, clinicList) if err != nil { return err @@ -262,7 +262,7 @@ func (h *Handler) listClinics(ec echo.Context, filter clinicians.Filter, page st func (h *Handler) assertClinicMigrated(ctx context.Context, clinicId ClinicId) error { id := string(clinicId) - clinic, err := h.clinics.Get(ctx, id) + clinic, err := h.Clinics.Get(ctx, id) if err != nil { return err } diff --git a/api/clinics.go b/api/clinics.go index 7f437d21..f7359030 100644 --- a/api/clinics.go +++ b/api/clinics.go @@ -38,7 +38,7 @@ func (h *Handler) ListClinics(ec echo.Context, params ListClinicsParams) error { filter.EHREnabled = params.EhrEnabled } - list, err := h.clinics.List(ctx, &filter, page) + list, err := h.Clinics.List(ctx, &filter, page) if err != nil { return err } @@ -73,7 +73,7 @@ func (h *Handler) CreateClinic(ec echo.Context) error { CreateDemoPatient: true, } - result, err := h.clinicsManager.CreateClinic(ctx, &create) + result, err := h.ClinicsManager.CreateClinic(ctx, &create) if err != nil { return err } @@ -83,7 +83,7 @@ func (h *Handler) CreateClinic(ec echo.Context) error { func (h *Handler) GetClinic(ec echo.Context, clinicId ClinicId) error { ctx := ec.Request().Context() - clinic, err := h.clinics.Get(ctx, string(clinicId)) + clinic, err := h.Clinics.Get(ctx, string(clinicId)) if err != nil { return err } @@ -97,14 +97,14 @@ func (h *Handler) UpdateClinic(ec echo.Context, clinicId ClinicId) error { if err := ec.Bind(&dto); err != nil { return err } - result, err := h.clinics.Update(ctx, string(clinicId), NewClinic(dto)) + result, err := h.Clinics.Update(ctx, string(clinicId), NewClinic(dto)) if err != nil { return err } // Update patient count settings if the country has changed if result.UpdatePatientCountSettingsForCountry() { - if err := h.clinics.UpdatePatientCountSettings(ctx, clinicId, result.PatientCountSettings); err != nil { + if err := h.Clinics.UpdatePatientCountSettings(ctx, clinicId, result.PatientCountSettings); err != nil { return err } } @@ -114,7 +114,7 @@ func (h *Handler) UpdateClinic(ec echo.Context, clinicId ClinicId) error { func (h *Handler) DeleteClinic(ec echo.Context, clinicId ClinicId) error { ctx := ec.Request().Context() - err := h.clinicsManager.DeleteClinic(ctx, string(clinicId)) + err := h.ClinicsManager.DeleteClinic(ctx, string(clinicId)) if err != nil { return err } @@ -132,7 +132,7 @@ func (h *Handler) GetClinicByShareCode(ec echo.Context, shareCode string) error ShareCodes: []string{shareCode}, } - list, err := h.clinics.List(ctx, &filter, store.Pagination{Limit: 2}) + list, err := h.Clinics.List(ctx, &filter, store.Pagination{Limit: 2}) if err != nil { return err } @@ -148,7 +148,7 @@ func (h *Handler) GetClinicByShareCode(ec echo.Context, shareCode string) error func (h *Handler) TriggerInitialMigration(ec echo.Context, clinicId string) error { ctx := ec.Request().Context() - migration, err := h.clinicsMigrator.TriggerInitialMigration(ctx, clinicId) + migration, err := h.ClinicsMigrator.TriggerInitialMigration(ctx, clinicId) if err != nil { return err } @@ -158,7 +158,7 @@ func (h *Handler) TriggerInitialMigration(ec echo.Context, clinicId string) erro func (h *Handler) ListMigrations(ec echo.Context, clinicId string) error { ctx := ec.Request().Context() - migrations, err := h.clinicsMigrator.ListMigrations(ctx, clinicId) + migrations, err := h.ClinicsMigrator.ListMigrations(ctx, clinicId) if err != nil { return err } @@ -173,7 +173,7 @@ func (h *Handler) MigrateLegacyClinicianPatients(ec echo.Context, clinicId strin return err } - migration, err := h.clinicsMigrator.MigrateLegacyClinicianPatients(ctx, clinicId, dto.UserId) + migration, err := h.ClinicsMigrator.MigrateLegacyClinicianPatients(ctx, clinicId, dto.UserId) if err != nil { return err } @@ -184,7 +184,7 @@ func (h *Handler) MigrateLegacyClinicianPatients(ec echo.Context, clinicId strin func (h *Handler) GetMigration(ec echo.Context, clinicId Id, userId UserId) error { ctx := ec.Request().Context() - migration, err := h.clinicsMigrator.GetMigration(ctx, string(clinicId), string(userId)) + migration, err := h.ClinicsMigrator.GetMigration(ctx, string(clinicId), string(userId)) if err != nil { return err } @@ -199,7 +199,7 @@ func (h *Handler) UpdateMigration(ec echo.Context, clinicId Id, userId UserId) e return err } - migration, err := h.clinicsMigrator.UpdateMigrationStatus(ctx, string(clinicId), string(userId), string(dto.Status)) + migration, err := h.ClinicsMigrator.UpdateMigrationStatus(ctx, string(clinicId), string(userId), string(dto.Status)) if err != nil { return err } @@ -209,10 +209,10 @@ func (h *Handler) UpdateMigration(ec echo.Context, clinicId Id, userId UserId) e func (h *Handler) DeleteUserFromClinics(ec echo.Context, userId UserId) error { ctx := ec.Request().Context() - if _, err := h.patients.DeleteFromAllClinics(ctx, string(userId)); err != nil { + if _, err := h.Patients.DeleteFromAllClinics(ctx, string(userId)); err != nil { return err } - if err := h.clinicians.DeleteFromAllClinics(ctx, string(userId)); err != nil { + if err := h.Clinicians.DeleteFromAllClinics(ctx, string(userId)); err != nil { return err } @@ -235,12 +235,12 @@ func (h *Handler) UpdateClinicUserDetails(ec echo.Context, userId UserId) error UserId: id, Email: *email, } - if err := h.clinicians.UpdateAll(ctx, update); err != nil { + if err := h.Clinicians.UpdateAll(ctx, update); err != nil { return err } } - if err := h.patients.UpdateEmail(ctx, id, email); err != nil { + if err := h.Patients.UpdateEmail(ctx, id, email); err != nil { return err } @@ -254,7 +254,7 @@ func (h *Handler) UpdateTier(ec echo.Context, clinicId ClinicId) error { return err } - if err := h.clinics.UpdateTier(ctx, string(clinicId), string(dto.Tier)); err != nil { + if err := h.Clinics.UpdateTier(ctx, string(clinicId), string(dto.Tier)); err != nil { return err } @@ -268,7 +268,7 @@ func (h *Handler) UpdateSuppressedNotifications(ec echo.Context, clinicId Clinic return err } - if err := h.clinics.UpdateSuppressedNotifications(ctx, string(clinicId), clinics.SuppressedNotifications(dto.SuppressedNotifications)); err != nil { + if err := h.Clinics.UpdateSuppressedNotifications(ctx, string(clinicId), clinics.SuppressedNotifications(dto.SuppressedNotifications)); err != nil { return err } @@ -282,7 +282,7 @@ func (h *Handler) CreatePatientTag(ec echo.Context, clinicId ClinicId) error { return err } - updated, err := h.clinics.CreatePatientTag(ctx, string(clinicId), dto.Name) + updated, err := h.Clinics.CreatePatientTag(ctx, string(clinicId), dto.Name) if err != nil { return err } @@ -297,7 +297,7 @@ func (h *Handler) UpdatePatientTag(ec echo.Context, clinicId ClinicId, patientTa return err } - updated, err := h.clinics.UpdatePatientTag(ctx, string(clinicId), string(patientTagId), dto.Name) + updated, err := h.Clinics.UpdatePatientTag(ctx, string(clinicId), string(patientTagId), dto.Name) if err != nil { return err } @@ -308,7 +308,7 @@ func (h *Handler) UpdatePatientTag(ec echo.Context, clinicId ClinicId, patientTa func (h *Handler) DeletePatientTag(ec echo.Context, clinicId ClinicId, patientTagId PatientTagId) error { ctx := ec.Request().Context() - updated, err := h.clinics.DeletePatientTag(ctx, string(clinicId), string(patientTagId)) + updated, err := h.Clinics.DeletePatientTag(ctx, string(clinicId), string(patientTagId)) if err != nil { return err } @@ -318,7 +318,7 @@ func (h *Handler) DeletePatientTag(ec echo.Context, clinicId ClinicId, patientTa func (h *Handler) ListMembershipRestrictions(ec echo.Context, clinicId ClinicId) error { ctx := ec.Request().Context() - updated, err := h.clinics.ListMembershipRestrictions(ctx, clinicId) + updated, err := h.Clinics.ListMembershipRestrictions(ctx, clinicId) if err != nil { return err } @@ -333,7 +333,7 @@ func (h *Handler) UpdateMembershipRestrictions(ec echo.Context, clinicId ClinicI return err } - if err := h.clinics.UpdateMembershipRestrictions(ctx, clinicId, NewMembershipRestrictions(dto)); err != nil { + if err := h.Clinics.UpdateMembershipRestrictions(ctx, clinicId, NewMembershipRestrictions(dto)); err != nil { return err } @@ -343,7 +343,7 @@ func (h *Handler) UpdateMembershipRestrictions(ec echo.Context, clinicId ClinicI func (h *Handler) GetEHRSettings(ec echo.Context, clinicId ClinicId) error { ctx := ec.Request().Context() - settings, err := h.clinics.GetEHRSettings(ctx, clinicId) + settings, err := h.Clinics.GetEHRSettings(ctx, clinicId) if err != nil { return err } @@ -364,7 +364,7 @@ func (h *Handler) UpdateEHRSettings(ec echo.Context, clinicId ClinicId) error { } settings := NewEHRSettings(dto) - err := h.clinics.UpdateEHRSettings(ctx, clinicId, settings) + err := h.Clinics.UpdateEHRSettings(ctx, clinicId, settings) if err != nil { return err } @@ -375,7 +375,7 @@ func (h *Handler) UpdateEHRSettings(ec echo.Context, clinicId ClinicId) error { func (h *Handler) GetMRNSettings(ec echo.Context, clinicId ClinicId) error { ctx := ec.Request().Context() - settings, err := h.clinics.GetMRNSettings(ctx, clinicId) + settings, err := h.Clinics.GetMRNSettings(ctx, clinicId) if err != nil { return err } @@ -397,7 +397,7 @@ func (h *Handler) UpdateMRNSettings(ec echo.Context, clinicId ClinicId) error { return err } - err := h.clinics.UpdateMRNSettings(ctx, clinicId, &clinics.MRNSettings{ + err := h.Clinics.UpdateMRNSettings(ctx, clinicId, &clinics.MRNSettings{ Required: dto.Required, Unique: dto.Unique, }) @@ -412,7 +412,7 @@ func (h *Handler) UpdateMRNSettings(ec echo.Context, clinicId ClinicId) error { func (h *Handler) GetPatientCountSettings(ec echo.Context, clinicId ClinicId) error { ctx := ec.Request().Context() - patientCountSettings, err := h.clinics.GetPatientCountSettings(ctx, clinicId) + patientCountSettings, err := h.Clinics.GetPatientCountSettings(ctx, clinicId) if err != nil { return err } else if patientCountSettings == nil { @@ -434,7 +434,7 @@ func (h *Handler) UpdatePatientCountSettings(ec echo.Context, clinicId ClinicId) return errors.BadRequest } - if err := h.clinics.UpdatePatientCountSettings(ctx, clinicId, patientCountSettings); err != nil { + if err := h.Clinics.UpdatePatientCountSettings(ctx, clinicId, patientCountSettings); err != nil { return err } @@ -444,7 +444,7 @@ func (h *Handler) UpdatePatientCountSettings(ec echo.Context, clinicId ClinicId) func (h *Handler) GetPatientCount(ec echo.Context, clinicId ClinicId) error { ctx := ec.Request().Context() - patientCount, err := h.clinicsManager.GetClinicPatientCount(ctx, clinicId) + patientCount, err := h.ClinicsManager.GetClinicPatientCount(ctx, clinicId) if err != nil { return err } @@ -461,7 +461,7 @@ func (h *Handler) GenerateMergeReport(ec echo.Context, clinicId ClinicId) error return err } - planner := merge.NewClinicMergePlanner(h.clinics, h.patients, h.clinicians, *dto.SourceId, clinicId) + planner := merge.NewClinicMergePlanner(h.Clinics, h.Patients, h.Clinicians, *dto.SourceId, clinicId) plan, err := planner.Plan(ctx) if err != nil { return err @@ -477,3 +477,25 @@ func (h *Handler) GenerateMergeReport(ec echo.Context, clinicId ClinicId) error ec.Response().WriteHeader(http.StatusOK) return file.Write(ec.Response()) } + +func (h *Handler) MergeClinic(ec echo.Context, clinicId ClinicId) error { + ctx := ec.Request().Context() + dto := MergeClinic{} + if err := ec.Bind(&dto); err != nil { + return err + } + + + planner := merge.NewClinicMergePlanner(h.Clinics, h.Patients, h.Clinicians, *dto.SourceId, clinicId) + plan, err := planner.Plan(ctx) + if err != nil { + return err + } + + err = h.ClinicMergePlanExecutor.Execute(ctx, plan) + if err != nil { + return err + } + + return ec.NoContent(http.StatusOK) +} diff --git a/api/gen_server.go b/api/gen_server.go index 971c7d73..b1a53e7f 100644 --- a/api/gen_server.go +++ b/api/gen_server.go @@ -73,6 +73,9 @@ type ServerInterface interface { // Update Membership Restrictions // (PUT /v1/clinics/{clinicId}/membership_restrictions) UpdateMembershipRestrictions(ctx echo.Context, clinicId ClinicId) error + // Merge Clinic + // (POST /v1/clinics/{clinicId}/merge) + MergeClinic(ctx echo.Context, clinicId ClinicId) error // Trigger initial migration // (POST /v1/clinics/{clinicId}/migrate) TriggerInitialMigration(ctx echo.Context, clinicId string) error @@ -735,6 +738,24 @@ func (w *ServerInterfaceWrapper) UpdateMembershipRestrictions(ctx echo.Context) return err } +// MergeClinic converts echo context to params. +func (w *ServerInterfaceWrapper) MergeClinic(ctx echo.Context) error { + var err error + // ------------- Path parameter "clinicId" ------------- + var clinicId ClinicId + + err = runtime.BindStyledParameterWithOptions("simple", "clinicId", ctx.Param("clinicId"), &clinicId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter clinicId: %s", err)) + } + + ctx.Set(SessionTokenScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.MergeClinic(ctx, clinicId) + return err +} + // TriggerInitialMigration converts echo context to params. func (w *ServerInterfaceWrapper) TriggerInitialMigration(ctx echo.Context) error { var err error @@ -2613,6 +2634,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.PATCH(baseURL+"/v1/clinics/:clinicId/invites/clinicians/:inviteId/clinician", wrapper.AssociateClinicianToUser) router.GET(baseURL+"/v1/clinics/:clinicId/membership_restrictions", wrapper.ListMembershipRestrictions) router.PUT(baseURL+"/v1/clinics/:clinicId/membership_restrictions", wrapper.UpdateMembershipRestrictions) + router.POST(baseURL+"/v1/clinics/:clinicId/merge", wrapper.MergeClinic) router.POST(baseURL+"/v1/clinics/:clinicId/migrate", wrapper.TriggerInitialMigration) router.GET(baseURL+"/v1/clinics/:clinicId/migrations", wrapper.ListMigrations) router.POST(baseURL+"/v1/clinics/:clinicId/migrations", wrapper.MigrateLegacyClinicianPatients) diff --git a/api/gen_spec.go b/api/gen_spec.go index 9d027107..d6b622d1 100644 --- a/api/gen_spec.go +++ b/api/gen_spec.go @@ -18,290 +18,292 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "AAAA///DXeu6/K8BAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/gen_types.go b/api/gen_types.go index e66dcdc8..1aa69c56 100644 --- a/api/gen_types.go +++ b/api/gen_types.go @@ -950,6 +950,12 @@ type MembershipRestrictions struct { Restrictions *[]MembershipRestriction `json:"restrictions,omitempty"` } +// MergeClinic defines model for MergeClinic. +type MergeClinic struct { + // SourceId Clinic identifier. + SourceId *Id `json:"sourceId,omitempty"` +} + // Meta defines model for Meta. type Meta struct { Count *int `json:"count,omitempty"` @@ -2185,6 +2191,9 @@ type AssociateClinicianToUserJSONRequestBody = AssociateClinicianToUser // UpdateMembershipRestrictionsJSONRequestBody defines body for UpdateMembershipRestrictions for application/json ContentType. type UpdateMembershipRestrictionsJSONRequestBody = MembershipRestrictions +// MergeClinicJSONRequestBody defines body for MergeClinic for application/json ContentType. +type MergeClinicJSONRequestBody = MergeClinic + // TriggerInitialMigrationJSONRequestBody defines body for TriggerInitialMigration for application/json ContentType. type TriggerInitialMigrationJSONRequestBody = TriggerMigration diff --git a/api/handler.go b/api/handler.go index e1374a78..19cac730 100644 --- a/api/handler.go +++ b/api/handler.go @@ -5,6 +5,7 @@ import ( "github.com/tidepool-org/clinic/clinicians" "github.com/tidepool-org/clinic/clinics" "github.com/tidepool-org/clinic/clinics/manager" + "github.com/tidepool-org/clinic/clinics/merge" "github.com/tidepool-org/clinic/clinics/migration" "github.com/tidepool-org/clinic/patients" "github.com/tidepool-org/clinic/redox" @@ -14,49 +15,21 @@ import ( ) type Handler struct { - clinics clinics.Service - clinicsManager manager.Manager - clinicsMigrator migration.Migrator - clinicians clinicians.Service - cliniciansUpdater clinicians.Service - patients patients.Service - redox redox.Redox - xealth xealth.Xealth - serviceAccountAuthenticator *auth.ServiceAccountAuthenticator - users patients.UserService -} - -var _ ServerInterface = &Handler{} - -type Params struct { fx.In + ClinicMergePlanExecutor merge.ClinicPlanExecutor Clinics clinics.Service - ClinicsCreator manager.Manager + ClinicsManager manager.Manager ClinicsMigrator migration.Migrator Clinicians clinicians.Service - CliniciansUpdater clinicians.Service Patients patients.Service Redox redox.Redox - Users patients.UserService - ServiceAccountAuthenticator *auth.ServiceAccountAuthenticator Xealth xealth.Xealth + ServiceAccountAuthenticator *auth.ServiceAccountAuthenticator + Users patients.UserService } -func NewHandler(p Params) *Handler { - return &Handler{ - clinics: p.Clinics, - clinicsManager: p.ClinicsCreator, - clinicsMigrator: p.ClinicsMigrator, - clinicians: p.Clinicians, - cliniciansUpdater: p.CliniciansUpdater, - patients: p.Patients, - redox: p.Redox, - users: p.Users, - serviceAccountAuthenticator: p.ServiceAccountAuthenticator, - xealth: p.Xealth, - } -} +var _ ServerInterface = &Handler{} func pagination(offset *Offset, limit *Limit) store.Pagination { page := store.DefaultPagination() diff --git a/api/main.go b/api/main.go index baff15ec..848d49fc 100644 --- a/api/main.go +++ b/api/main.go @@ -72,7 +72,7 @@ func SetReady(healthCheck *HealthCheck, db *mongo.Database, lifecycle fx.Lifecyc }) } -func NewServer(handler *Handler, healthCheck *HealthCheck, authorizer auth.RequestAuthorizer, authenticator auth.Authenticator, logger *zap.Logger) (*echo.Echo, error) { +func NewServer(handler Handler, healthCheck *HealthCheck, authorizer auth.RequestAuthorizer, authenticator auth.Authenticator, logger *zap.Logger) (*echo.Echo, error) { e := echo.New() logger.Info("Starting Main Loop") swagger, err := GetSwagger() @@ -136,7 +136,7 @@ func NewServer(handler *Handler, healthCheck *HealthCheck, authorizer auth.Reque e.HTTPErrorHandler = errors.CustomHTTPErrorHandler e.GET("/ready", healthCheck.Ready) - RegisterHandlers(e, handler) + RegisterHandlers(e, &handler) return e, nil } @@ -172,7 +172,6 @@ func Dependencies() []fx.Option { auth.NewServiceAccountAuthenticator, auth.NewRequestAuthorizer, NewHealthCheck, - NewHandler, NewServer, ), fx.WithLogger(func(log *zap.Logger) fxevent.Logger { diff --git a/api/patients.go b/api/patients.go index d3e52de7..e2be139b 100644 --- a/api/patients.go +++ b/api/patients.go @@ -54,7 +54,7 @@ func (h *Handler) ListPatients(ec echo.Context, clinicId ClinicId, params ListPa return err } - list, err := h.patients.List(ctx, &filter, page, sorts) + list, err := h.Patients.List(ctx, &filter, page, sorts) if err != nil { return err } @@ -90,7 +90,7 @@ func (h *Handler) CreatePatientAccount(ec echo.Context, clinicId ClinicId) error patient.InvitedBy = &authData.SubjectId } - result, err := h.patients.Create(ctx, patient) + result, err := h.Patients.Create(ctx, patient) if err != nil { return err } @@ -100,7 +100,7 @@ func (h *Handler) CreatePatientAccount(ec echo.Context, clinicId ClinicId) error func (h *Handler) GetPatient(ec echo.Context, clinicId ClinicId, patientId PatientId) error { ctx := ec.Request().Context() - patient, err := h.patients.Get(ctx, string(clinicId), string(patientId)) + patient, err := h.Patients.Get(ctx, string(clinicId), string(patientId)) if err != nil { return err } @@ -124,12 +124,12 @@ func (h *Handler) CreatePatientFromUser(ec echo.Context, clinicId ClinicId, pati patient.UserId = strp(patientId) patient.ClinicId = &clinicObjId - if err = h.users.PopulatePatientDetailsFromExistingUser(ctx, &patient); err != nil { + if err = h.Users.PopulatePatientDetailsFromExistingUser(ctx, &patient); err != nil { return err } patient.Email = pstrToLower(patient.Email) - result, err := h.patients.Create(ctx, patient) + result, err := h.Patients.Create(ctx, patient) if err != nil { return err } @@ -164,7 +164,7 @@ func (h *Handler) UpdatePatient(ec echo.Context, clinicId ClinicId, patientId Pa Patient: NewPatient(dto), UpdatedBy: authData.SubjectId, } - patient, err := h.patients.Update(ctx, update) + patient, err := h.Patients.Update(ctx, update) if err != nil { return err } @@ -194,7 +194,7 @@ func (h *Handler) SendUploadReminder(ec echo.Context, clinicId ClinicId, patient UserId: string(patientId), Time: time.Now(), } - patient, err := h.patients.UpdateLastUploadReminderTime(ctx, &update) + patient, err := h.Patients.UpdateLastUploadReminderTime(ctx, &update) if err != nil { return err } @@ -224,7 +224,7 @@ func (h *Handler) SendDexcomConnectRequest(ec echo.Context, clinicId ClinicId, p Time: time.Now(), UserId: patientId, } - patient, err := h.patients.UpdateLastRequestedDexcomConnectTime(ctx, &update) + patient, err := h.Patients.UpdateLastRequestedDexcomConnectTime(ctx, &update) if err != nil { return err } @@ -239,7 +239,7 @@ func (h *Handler) UpdatePatientPermissions(ec echo.Context, clinicId ClinicId, p return err } - patient, err := h.patients.UpdatePermissions(ctx, string(clinicId), string(patientId), NewPermissions(&dto)) + patient, err := h.Patients.UpdatePermissions(ctx, string(clinicId), string(patientId), NewPermissions(&dto)) if err != nil { return err } @@ -254,7 +254,7 @@ func (h *Handler) UpdatePatientPermissions(ec echo.Context, clinicId ClinicId, p func (h *Handler) DeletePatientPermission(ec echo.Context, clinicId ClinicId, patientId PatientId, permission string) error { ctx := ec.Request().Context() - _, err := h.patients.DeletePermission(ctx, string(clinicId), string(patientId), permission) + _, err := h.Patients.DeletePermission(ctx, string(clinicId), string(patientId), permission) if err != nil { return err } @@ -265,7 +265,7 @@ func (h *Handler) DeletePatientPermission(ec echo.Context, clinicId ClinicId, pa func (h *Handler) ListClinicsForPatient(ec echo.Context, patientId UserId, params ListClinicsForPatientParams) error { ctx := ec.Request().Context() page := pagination(params.Offset, params.Limit) - list, err := h.patients.List(ctx, &patients.Filter{UserId: strp(string(patientId))}, page, nil) + list, err := h.Patients.List(ctx, &patients.Filter{UserId: strp(string(patientId))}, page, nil) if err != nil { return err } @@ -275,7 +275,7 @@ func (h *Handler) ListClinicsForPatient(ec echo.Context, patientId UserId, param clinicIds = append(clinicIds, patient.ClinicId.Hex()) } - clinicList, err := h.clinics.List(ctx, &clinics.Filter{Ids: clinicIds}, store.Pagination{}) + clinicList, err := h.Clinics.List(ctx, &clinics.Filter{Ids: clinicIds}, store.Pagination{}) dtos, err := NewPatientClinicRelationshipsDto(list.Patients, clinicList) if err != nil { return err @@ -286,7 +286,7 @@ func (h *Handler) ListClinicsForPatient(ec echo.Context, patientId UserId, param func (h *Handler) DeletePatient(ec echo.Context, clinicId ClinicId, patientId PatientId) error { ctx := ec.Request().Context() - err := h.patients.Remove(ctx, string(clinicId), string(patientId)) + err := h.Patients.Remove(ctx, string(clinicId), string(patientId)) if err != nil { return err } @@ -304,7 +304,7 @@ func (h *Handler) UpdatePatientSummary(ec echo.Context, patientId PatientId) err } } - err := h.patients.UpdateSummaryInAllClinics(ctx, patientId, NewSummary(dto)) + err := h.Patients.UpdateSummaryInAllClinics(ctx, patientId, NewSummary(dto)) if err != nil { return err } @@ -314,7 +314,7 @@ func (h *Handler) UpdatePatientSummary(ec echo.Context, patientId PatientId) err func (h *Handler) TideReport(ec echo.Context, clinicId ClinicId, params TideReportParams) error { ctx := ec.Request().Context() - tide, err := h.patients.TideReport(ctx, clinicId, patients.TideReportParams(params)) + tide, err := h.Patients.TideReport(ctx, clinicId, patients.TideReportParams(params)) if err != nil { return err } @@ -335,7 +335,7 @@ func (h *Handler) DeletePatientTagFromClinicPatients(ec echo.Context, clinicId C dto = nil } - err := h.patients.DeletePatientTagFromClinicPatients(ctx, string(clinicId), string(patientTagId), dto) + err := h.Patients.DeletePatientTagFromClinicPatients(ctx, string(clinicId), string(patientTagId), dto) if err != nil { return err @@ -352,7 +352,7 @@ func (h *Handler) AssignPatientTagToClinicPatients(ec echo.Context, clinicId Cli return err } - err := h.patients.AssignPatientTagToClinicPatients(ctx, string(clinicId), string(patientTagId), dto) + err := h.Patients.AssignPatientTagToClinicPatients(ctx, string(clinicId), string(patientTagId), dto) if err != nil { return err @@ -368,7 +368,7 @@ func (h *Handler) UpdatePatientDataSources(ec echo.Context, userId UserId) error return err } - err := h.patients.UpdatePatientDataSources(ctx, string(userId), &dto) + err := h.Patients.UpdatePatientDataSources(ctx, string(userId), &dto) if err != nil { return err } @@ -390,7 +390,7 @@ func (h *Handler) FindPatients(ec echo.Context, params FindPatientsParams) error cliniciansFilter := &clinicians.Filter{ UserId: &authData.SubjectId, } - clinicianList, err := h.clinicians.List(ctx, cliniciansFilter, maxClinics) + clinicianList, err := h.Clinicians.List(ctx, cliniciansFilter, maxClinics) if err != nil { return err } @@ -402,7 +402,7 @@ func (h *Handler) FindPatients(ec echo.Context, params FindPatientsParams) error } } - clinicList, err := h.clinics.List(ctx, &clinics.Filter{Ids: clinicIds}, maxClinics) + clinicList, err := h.Clinics.List(ctx, &clinics.Filter{Ids: clinicIds}, maxClinics) if err != nil { return err } @@ -431,7 +431,7 @@ func (h *Handler) FindPatients(ec echo.Context, params FindPatientsParams) error Mrn: params.Mrn, BirthDate: params.BirthDate, } - list, err := h.patients.List(ctx, &filter, page, nil) + list, err := h.Patients.List(ctx, &filter, page, nil) if err != nil { return err } diff --git a/api/redox.go b/api/redox.go index 67bcea93..322eec13 100644 --- a/api/redox.go +++ b/api/redox.go @@ -16,7 +16,7 @@ func (h *Handler) VerifyEndpoint(ec echo.Context) error { if err := ec.Bind(&request); err != nil { return err } - result, err := h.redox.VerifyEndpoint(request) + result, err := h.Redox.VerifyEndpoint(request) if err != nil { return err } @@ -28,7 +28,7 @@ func (h *Handler) ProcessEHRMessage(ec echo.Context) error { ctx := ec.Request().Context() // Make sure the request is initiated by redox - if err := h.redox.AuthorizeRequest(ec.Request()); err != nil { + if err := h.Redox.AuthorizeRequest(ec.Request()); err != nil { return err } @@ -38,7 +38,7 @@ func (h *Handler) ProcessEHRMessage(ec echo.Context) error { return err } - return h.redox.ProcessEHRMessage(ctx, raw) + return h.Redox.ProcessEHRMessage(ctx, raw) } func (h *Handler) MatchClinicAndPatient(ec echo.Context) error { @@ -61,7 +61,7 @@ func (h *Handler) MatchClinicAndPatient(ec echo.Context) error { if request.MessageRef.DataModel != Order || request.MessageRef.EventType != EHRMatchMessageRefEventTypeNew { return fmt.Errorf("%w: only new order messages are supported", errors.BadRequest) } - msg, err := h.redox.FindMessage( + msg, err := h.Redox.FindMessage( ctx, request.MessageRef.DocumentId, string(request.MessageRef.DataModel), @@ -80,13 +80,13 @@ func (h *Handler) MatchClinicAndPatient(ec echo.Context) error { if err != nil { return err } - clinic, err := h.redox.FindMatchingClinic(ctx, criteria) + clinic, err := h.Redox.FindMatchingClinic(ctx, criteria) if err != nil { return err } update := redox.GetUpdateFromNewOrder(*clinic, documentId, *order) - matchedPatients, err := h.redox.MatchNewOrderToPatient(ctx, *clinic, *order, update) + matchedPatients, err := h.Redox.MatchNewOrderToPatient(ctx, *clinic, *order, update) if err != nil { return err } @@ -106,7 +106,7 @@ func (h *Handler) MatchClinicAndPatient(ec echo.Context) error { func (h *Handler) SyncEHRData(ec echo.Context, clinicId ClinicId) error { ctx := ec.Request().Context() - if err := h.redox.RescheduleSubscriptionOrders(ctx, clinicId); err != nil { + if err := h.Redox.RescheduleSubscriptionOrders(ctx, clinicId); err != nil { return err } @@ -115,7 +115,7 @@ func (h *Handler) SyncEHRData(ec echo.Context, clinicId ClinicId) error { func (h *Handler) SyncEHRDataForPatient(ec echo.Context, patientId PatientId) error { ctx := ec.Request().Context() - if err := h.redox.RescheduleSubscriptionOrdersForPatient(ctx, patientId); err != nil { + if err := h.Redox.RescheduleSubscriptionOrdersForPatient(ctx, patientId); err != nil { return err } diff --git a/api/xealth.go b/api/xealth.go index f8c2babd..1fc0a6e7 100644 --- a/api/xealth.go +++ b/api/xealth.go @@ -15,7 +15,7 @@ func (h *Handler) XealthPreorder(ec echo.Context) error { ctx := ec.Request().Context() // Make sure the request is initiated by xealth - if err := h.xealth.AuthorizeRequest(ec.Request()); err != nil { + if err := h.Xealth.AuthorizeRequest(ec.Request()); err != nil { return err } @@ -50,7 +50,7 @@ func (h *Handler) XealthPreorder(ec echo.Context) error { return err } - response, err := h.xealth.ProcessInitialPreorderRequest(ctx, initial) + response, err := h.Xealth.ProcessInitialPreorderRequest(ctx, initial) if err != nil { return err } @@ -61,7 +61,7 @@ func (h *Handler) XealthPreorder(ec echo.Context) error { return err } - response, err := h.xealth.ProcessSubsequentPreorderRequest(ctx, subsequent) + response, err := h.Xealth.ProcessSubsequentPreorderRequest(ctx, subsequent) if err != nil { return err } @@ -75,7 +75,7 @@ func (h *Handler) XealthNotification(ec echo.Context) error { ctx := ec.Request().Context() // Make sure the request is initiated by xealth - if err := h.xealth.AuthorizeRequest(ec.Request()); err != nil { + if err := h.Xealth.AuthorizeRequest(ec.Request()); err != nil { return err } @@ -84,7 +84,7 @@ func (h *Handler) XealthNotification(ec echo.Context) error { return err } - if err := h.xealth.HandleEventNotification(ctx, eventNotification); err != nil { + if err := h.Xealth.HandleEventNotification(ctx, eventNotification); err != nil { return err } @@ -95,7 +95,7 @@ func (h *Handler) XealthGetProgramUrl(ec echo.Context) error { ctx := ec.Request().Context() // Make sure the request is initiated by xealth - if err := h.xealth.AuthorizeRequest(ec.Request()); err != nil { + if err := h.Xealth.AuthorizeRequest(ec.Request()); err != nil { return err } @@ -104,7 +104,7 @@ func (h *Handler) XealthGetProgramUrl(ec echo.Context) error { return err } - response, err := h.xealth.GetProgramUrl(ctx, request) + response, err := h.Xealth.GetProgramUrl(ctx, request) if err != nil { return err } @@ -116,7 +116,7 @@ func (h *Handler) XealthGetPrograms(ec echo.Context) error { ctx := ec.Request().Context() // Make sure the request is initiated by xealth - if err := h.xealth.AuthorizeRequest(ec.Request()); err != nil { + if err := h.Xealth.AuthorizeRequest(ec.Request()); err != nil { return err } @@ -125,7 +125,7 @@ func (h *Handler) XealthGetPrograms(ec echo.Context) error { return err } - response, err := h.xealth.GetPrograms(ctx, request) + response, err := h.Xealth.GetPrograms(ctx, request) if err != nil { return err } @@ -136,7 +136,7 @@ func (h *Handler) XealthGetPrograms(ec echo.Context) error { func (h *Handler) ViewPDFReport(ec echo.Context, params ViewPDFReportParams) error { ctx := ec.Request().Context() - report, err := h.xealth.GetPDFReport(ctx, xealth.PDFReportRequest{ + report, err := h.Xealth.GetPDFReport(ctx, xealth.PDFReportRequest{ ClinicId: params.ClinicId, PatientId: params.PatientId, RestrictedToken: params.RestrictedToken, diff --git a/client/client.go b/client/client.go index 2894719f..6ce7db44 100644 --- a/client/client.go +++ b/client/client.go @@ -162,6 +162,11 @@ type ClientInterface interface { UpdateMembershipRestrictions(ctx context.Context, clinicId ClinicId, body UpdateMembershipRestrictionsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // MergeClinicWithBody request with any body + MergeClinicWithBody(ctx context.Context, clinicId ClinicId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + MergeClinic(ctx context.Context, clinicId ClinicId, body MergeClinicJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // TriggerInitialMigrationWithBody request with any body TriggerInitialMigrationWithBody(ctx context.Context, clinicId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -662,6 +667,30 @@ func (c *Client) UpdateMembershipRestrictions(ctx context.Context, clinicId Clin return c.Client.Do(req) } +func (c *Client) MergeClinicWithBody(ctx context.Context, clinicId ClinicId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewMergeClinicRequestWithBody(c.Server, clinicId, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) MergeClinic(ctx context.Context, clinicId ClinicId, body MergeClinicJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewMergeClinicRequest(c.Server, clinicId, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) TriggerInitialMigrationWithBody(ctx context.Context, clinicId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewTriggerInitialMigrationRequestWithBody(c.Server, clinicId, contentType, body) if err != nil { @@ -2577,6 +2606,53 @@ func NewUpdateMembershipRestrictionsRequestWithBody(server string, clinicId Clin return req, nil } +// NewMergeClinicRequest calls the generic MergeClinic builder with application/json body +func NewMergeClinicRequest(server string, clinicId ClinicId, body MergeClinicJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewMergeClinicRequestWithBody(server, clinicId, "application/json", bodyReader) +} + +// NewMergeClinicRequestWithBody generates requests for MergeClinic with any type of body +func NewMergeClinicRequestWithBody(server string, clinicId ClinicId, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "clinicId", runtime.ParamLocationPath, clinicId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/clinics/%s/merge", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewTriggerInitialMigrationRequest calls the generic TriggerInitialMigration builder with application/json body func NewTriggerInitialMigrationRequest(server string, clinicId string, body TriggerInitialMigrationJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -6745,6 +6821,11 @@ type ClientWithResponsesInterface interface { UpdateMembershipRestrictionsWithResponse(ctx context.Context, clinicId ClinicId, body UpdateMembershipRestrictionsJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateMembershipRestrictionsResponse, error) + // MergeClinicWithBodyWithResponse request with any body + MergeClinicWithBodyWithResponse(ctx context.Context, clinicId ClinicId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MergeClinicResponse, error) + + MergeClinicWithResponse(ctx context.Context, clinicId ClinicId, body MergeClinicJSONRequestBody, reqEditors ...RequestEditorFn) (*MergeClinicResponse, error) + // TriggerInitialMigrationWithBodyWithResponse request with any body TriggerInitialMigrationWithBodyWithResponse(ctx context.Context, clinicId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TriggerInitialMigrationResponse, error) @@ -7371,6 +7452,27 @@ func (r UpdateMembershipRestrictionsResponse) StatusCode() int { return 0 } +type MergeClinicResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r MergeClinicResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r MergeClinicResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type TriggerInitialMigrationResponse struct { Body []byte HTTPResponse *http.Response @@ -8606,6 +8708,23 @@ func (c *ClientWithResponses) UpdateMembershipRestrictionsWithResponse(ctx conte return ParseUpdateMembershipRestrictionsResponse(rsp) } +// MergeClinicWithBodyWithResponse request with arbitrary body returning *MergeClinicResponse +func (c *ClientWithResponses) MergeClinicWithBodyWithResponse(ctx context.Context, clinicId ClinicId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MergeClinicResponse, error) { + rsp, err := c.MergeClinicWithBody(ctx, clinicId, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseMergeClinicResponse(rsp) +} + +func (c *ClientWithResponses) MergeClinicWithResponse(ctx context.Context, clinicId ClinicId, body MergeClinicJSONRequestBody, reqEditors ...RequestEditorFn) (*MergeClinicResponse, error) { + rsp, err := c.MergeClinic(ctx, clinicId, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseMergeClinicResponse(rsp) +} + // TriggerInitialMigrationWithBodyWithResponse request with arbitrary body returning *TriggerInitialMigrationResponse func (c *ClientWithResponses) TriggerInitialMigrationWithBodyWithResponse(ctx context.Context, clinicId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TriggerInitialMigrationResponse, error) { rsp, err := c.TriggerInitialMigrationWithBody(ctx, clinicId, contentType, body, reqEditors...) @@ -9701,6 +9820,22 @@ func ParseUpdateMembershipRestrictionsResponse(rsp *http.Response) (*UpdateMembe return response, nil } +// ParseMergeClinicResponse parses an HTTP response from a MergeClinicWithResponse call +func ParseMergeClinicResponse(rsp *http.Response) (*MergeClinicResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &MergeClinicResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + // ParseTriggerInitialMigrationResponse parses an HTTP response from a TriggerInitialMigrationWithResponse call func ParseTriggerInitialMigrationResponse(rsp *http.Response) (*TriggerInitialMigrationResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/client/mock.go b/client/mock.go index db2e883a..e67c820f 100644 --- a/client/mock.go +++ b/client/mock.go @@ -1054,6 +1054,46 @@ func (mr *MockClientInterfaceMockRecorder) MatchClinicAndPatientWithBody(ctx, co return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MatchClinicAndPatientWithBody", reflect.TypeOf((*MockClientInterface)(nil).MatchClinicAndPatientWithBody), varargs...) } +// MergeClinic mocks base method. +func (m *MockClientInterface) MergeClinic(ctx context.Context, clinicId ClinicId, body MergeClinicJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, clinicId, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "MergeClinic", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MergeClinic indicates an expected call of MergeClinic. +func (mr *MockClientInterfaceMockRecorder) MergeClinic(ctx, clinicId, body interface{}, reqEditors ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, clinicId, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MergeClinic", reflect.TypeOf((*MockClientInterface)(nil).MergeClinic), varargs...) +} + +// MergeClinicWithBody mocks base method. +func (m *MockClientInterface) MergeClinicWithBody(ctx context.Context, clinicId ClinicId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, clinicId, contentType, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "MergeClinicWithBody", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MergeClinicWithBody indicates an expected call of MergeClinicWithBody. +func (mr *MockClientInterfaceMockRecorder) MergeClinicWithBody(ctx, clinicId, contentType, body interface{}, reqEditors ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, clinicId, contentType, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MergeClinicWithBody", reflect.TypeOf((*MockClientInterface)(nil).MergeClinicWithBody), varargs...) +} + // MigrateLegacyClinicianPatients mocks base method. func (m *MockClientInterface) MigrateLegacyClinicianPatients(ctx context.Context, clinicId string, body MigrateLegacyClinicianPatientsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { m.ctrl.T.Helper() @@ -2997,6 +3037,46 @@ func (mr *MockClientWithResponsesInterfaceMockRecorder) MatchClinicAndPatientWit return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MatchClinicAndPatientWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).MatchClinicAndPatientWithResponse), varargs...) } +// MergeClinicWithBodyWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) MergeClinicWithBodyWithResponse(ctx context.Context, clinicId ClinicId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MergeClinicResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, clinicId, contentType, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "MergeClinicWithBodyWithResponse", varargs...) + ret0, _ := ret[0].(*MergeClinicResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MergeClinicWithBodyWithResponse indicates an expected call of MergeClinicWithBodyWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) MergeClinicWithBodyWithResponse(ctx, clinicId, contentType, body interface{}, reqEditors ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, clinicId, contentType, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MergeClinicWithBodyWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).MergeClinicWithBodyWithResponse), varargs...) +} + +// MergeClinicWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) MergeClinicWithResponse(ctx context.Context, clinicId ClinicId, body MergeClinicJSONRequestBody, reqEditors ...RequestEditorFn) (*MergeClinicResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, clinicId, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "MergeClinicWithResponse", varargs...) + ret0, _ := ret[0].(*MergeClinicResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MergeClinicWithResponse indicates an expected call of MergeClinicWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) MergeClinicWithResponse(ctx, clinicId, body interface{}, reqEditors ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, clinicId, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MergeClinicWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).MergeClinicWithResponse), varargs...) +} + // MigrateLegacyClinicianPatientsWithBodyWithResponse mocks base method. func (m *MockClientWithResponsesInterface) MigrateLegacyClinicianPatientsWithBodyWithResponse(ctx context.Context, clinicId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MigrateLegacyClinicianPatientsResponse, error) { m.ctrl.T.Helper() diff --git a/client/types.go b/client/types.go index 281d4116..2b73cea9 100644 --- a/client/types.go +++ b/client/types.go @@ -950,6 +950,12 @@ type MembershipRestrictions struct { Restrictions *[]MembershipRestriction `json:"restrictions,omitempty"` } +// MergeClinic defines model for MergeClinic. +type MergeClinic struct { + // SourceId Clinic identifier. + SourceId *Id `json:"sourceId,omitempty"` +} + // Meta defines model for Meta. type Meta struct { Count *int `json:"count,omitempty"` @@ -2185,6 +2191,9 @@ type AssociateClinicianToUserJSONRequestBody = AssociateClinicianToUser // UpdateMembershipRestrictionsJSONRequestBody defines body for UpdateMembershipRestrictions for application/json ContentType. type UpdateMembershipRestrictionsJSONRequestBody = MembershipRestrictions +// MergeClinicJSONRequestBody defines body for MergeClinic for application/json ContentType. +type MergeClinicJSONRequestBody = MergeClinic + // TriggerInitialMigrationJSONRequestBody defines body for TriggerInitialMigration for application/json ContentType. type TriggerInitialMigrationJSONRequestBody = TriggerMigration diff --git a/clinics/clinics.go b/clinics/clinics.go index ccd12ee4..96eb99b3 100644 --- a/clinics/clinics.go +++ b/clinics/clinics.go @@ -58,6 +58,7 @@ type Service interface { UpdatePatientCountSettings(ctx context.Context, clinicId string, settings *PatientCountSettings) error GetPatientCount(ctx context.Context, clinicId string) (*PatientCount, error) UpdatePatientCount(ctx context.Context, clinicId string, patientCount *PatientCount) error + AppendShareCodes(ctx context.Context, clinicId string, shareCodes []string) error } type Filter struct { diff --git a/clinics/manager/manager.go b/clinics/manager/manager.go index dc9d1835..1b1d049c 100644 --- a/clinics/manager/manager.go +++ b/clinics/manager/manager.go @@ -30,6 +30,7 @@ type Manager interface { CreateClinic(ctx context.Context, create *CreateClinic) (*clinics.Clinic, error) DeleteClinic(ctx context.Context, clinicId string) error GetClinicPatientCount(ctx context.Context, clinicId string) (*clinics.PatientCount, error) + FinalizeMerge(ctx context.Context, sourceId, targetId string) error } type manager struct { @@ -133,32 +134,36 @@ func (c *manager) CreateClinic(ctx context.Context, create *CreateClinic) (*clin func (c *manager) DeleteClinic(ctx context.Context, clinicId string) error { transaction := func(sessionCtx mongo.SessionContext) (interface{}, error) { - filter := patients.Filter{ClinicId: &clinicId} - pagination := store.Pagination{Limit: 2} - res, err := c.patientsService.List(sessionCtx, &filter, pagination, nil) + return nil, c.deleteClinic(sessionCtx, clinicId) + } - if err != nil { - return nil, err - } - if res == nil { - return nil, fmt.Errorf("patient list result not defined") - } - if !c.patientListAllowsClinicDeletion(res.Patients) { - return nil, fmt.Errorf("%w: deletion of non-empty clinics is not allowed", errors.BadRequest) - } + _, err := store.WithTransaction(ctx, c.dbClient, transaction) + return err +} - if err := c.patientsService.Remove(sessionCtx, clinicId, c.config.ClinicDemoPatientUserId); err != nil && !errs.Is(err, errors.NotFound) { - return nil, err - } +func (c *manager) FinalizeMerge(ctx context.Context, sourceId, targetId string) error { + source, err := c.clinics.Get(ctx, sourceId) + if err != nil { + return err + } - if err := c.cliniciansRepository.DeleteAll(sessionCtx, clinicId); err != nil { - return nil, err - } + // Delete clinics if allowed by patient list + err = c.deleteClinic(ctx, sourceId) + if err != nil { + return err + } - return nil, c.clinics.Delete(sessionCtx, clinicId) + // Append share codes of source clinic + if source.ShareCodes != nil { + err = c.clinics.AppendShareCodes(ctx, targetId, *source.ShareCodes) + if err != nil { + return err + } } - _, err := store.WithTransaction(ctx, c.dbClient, transaction) + // Refresh patient count of target clinic + _, err = c.GetClinicPatientCount(ctx, targetId) + return err } @@ -193,7 +198,7 @@ retryLoop: create.Clinic.ShareCodes = &shareCodes clinic, err = c.clinics.Create(sessionCtx, &create.Clinic) - if err == nil || err != clinics.ErrDuplicateShareCode { + if err == nil || !errs.Is(err, clinics.ErrDuplicateShareCode) { break retryLoop } } @@ -205,7 +210,7 @@ func (c *manager) getDemoPatient(ctx context.Context) (*patients.Patient, error) return nil, nil } - perm := make(patients.Permission, 0) + perm := make(patients.Permission) patient := &patients.Patient{ UserId: &c.config.ClinicDemoPatientUserId, IsMigrated: true, // Do not send emails @@ -219,6 +224,32 @@ func (c *manager) getDemoPatient(ctx context.Context) (*patients.Patient, error) return patient, nil } +func (c *manager) deleteClinic(ctx context.Context, clinicId string) error { + filter := patients.Filter{ClinicId: &clinicId} + pagination := store.Pagination{Limit: 2} + res, err := c.patientsService.List(ctx, &filter, pagination, nil) + + if err != nil { + return err + } + if res == nil { + return fmt.Errorf("patient list result not defined") + } + if !c.patientListAllowsClinicDeletion(res.Patients) { + return fmt.Errorf("%w: deletion of non-empty clinics is not allowed", errors.BadRequest) + } + + if err := c.patientsService.Remove(ctx, clinicId, c.config.ClinicDemoPatientUserId); err != nil && !errs.Is(err, errors.NotFound) { + return err + } + + if err := c.cliniciansRepository.DeleteAll(ctx, clinicId); err != nil { + return err + } + + return c.clinics.Delete(ctx, clinicId) +} + func (c *manager) patientListAllowsClinicDeletion(list []*patients.Patient) bool { // No patients, OK to delete if len(list) == 0 { diff --git a/clinics/merge/clinic.go b/clinics/merge/clinic.go index 95459ddc..f627bf50 100644 --- a/clinics/merge/clinic.go +++ b/clinics/merge/clinic.go @@ -5,8 +5,12 @@ import ( "fmt" "github.com/tidepool-org/clinic/clinicians" "github.com/tidepool-org/clinic/clinics" + "github.com/tidepool-org/clinic/clinics/manager" "github.com/tidepool-org/clinic/patients" "github.com/tidepool-org/clinic/store" + "go.mongodb.org/mongo-driver/mongo" + "go.uber.org/fx" + "go.uber.org/zap" "slices" "time" ) @@ -18,10 +22,10 @@ type ClinicMergePlan struct { MembershipRestrictionsMergePlan MembershipRestrictionsMergePlan SourcePatientClusters PatientClusters TargetPatientClusters PatientClusters - SettingsPlan SettingsPlans - TagsPlan TagPlans - CliniciansPlan ClinicianPlans - PatientsPlan PatientPlans + SettingsPlans SettingsPlans + TagsPlans TagPlans + ClinicianPlans ClinicianPlans + PatientPlans PatientPlans CreatedTime time.Time } @@ -31,10 +35,10 @@ func (c ClinicMergePlan) PreventsMerge() bool { c.MembershipRestrictionsMergePlan.PreventsMerge, c.SourcePatientClusters.PreventsMerge, c.TargetPatientClusters.PreventsMerge, - c.SettingsPlan.PreventsMerge, - c.TagsPlan.PreventsMerge, - c.CliniciansPlan.PreventsMerge, - c.PatientsPlan.PreventsMerge, + c.SettingsPlans.PreventsMerge, + c.TagsPlans.PreventsMerge, + c.ClinicianPlans.PreventsMerge, + c.PatientPlans.PreventsMerge, } for _, preventsMerge := range fs { @@ -246,19 +250,19 @@ func (i *intermediatePlanner) Plan(ctx context.Context) (plan ClinicMergePlan, e if err != nil { return } - plan.PatientsPlan, err = i.PatientPlanner.Plan(ctx) + plan.PatientPlans, err = i.PatientPlanner.Plan(ctx) if err != nil { return } - plan.SettingsPlan, err = RunPlanners(ctx, i.SettingsPlanners) + plan.SettingsPlans, err = RunPlanners(ctx, i.SettingsPlanners) if err != nil { return } - plan.TagsPlan, err = RunPlanners(ctx, i.TagPlanners) + plan.TagsPlans, err = RunPlanners(ctx, i.TagPlanners) if err != nil { return } - plan.CliniciansPlan, err = RunPlanners(ctx, i.ClinicianPlanners) + plan.ClinicianPlans, err = RunPlanners(ctx, i.ClinicianPlanners) if err != nil { return } @@ -268,3 +272,56 @@ func (i *intermediatePlanner) Plan(ctx context.Context) (plan ClinicMergePlan, e plan.CreatedTime = time.Now() return } + +type ClinicPlanExecutor struct { + fx.In + + Logger *zap.SugaredLogger + ClinicsService clinics.Service + ClinicManager manager.Manager + DBClient *mongo.Client + DB *mongo.Database +} + +func (c *ClinicPlanExecutor) Execute(ctx context.Context, plan ClinicMergePlan) error { + logger := c.Logger.With("clinicId", plan.Source.Id.Hex(), "targetClinicId", plan.Target.Id.Hex()) + if plan.PreventsMerge() { + err := fmt.Errorf("the merge plan does not allow execution") + logger.Errorw("cannot merge clinics", "error", err) + return err + } + + _, err := store.WithTransaction(ctx, c.DBClient, func(sessionContext mongo.SessionContext) (any, error) { + tpe := NewTagPlanExecutor(logger, c.ClinicsService) + logger.Info("starting tags migration") + for _, p := range plan.TagsPlans { + if err := tpe.Execute(ctx, p); err != nil { + return nil, err + } + } + + logger.Info("starting patients migration") + ppe := NewPatientPlanExecutor(logger, c.DB) + for _, p := range plan.PatientPlans { + if err := ppe.Execute(ctx, p, plan.Source, plan.Target); err != nil { + return nil, err + } + } + + logger.Info("starting clinicians migration") + cpe := NewClinicianPlanExecutor(logger, c.DB) + for _, p := range plan.ClinicianPlans { + if err := cpe.Execute(ctx, p, plan.Target); err != nil { + return nil, err + } + } + + logger.Info("finalizing clinic merge") + if err := c.ClinicManager.FinalizeMerge(ctx, plan.Source.Id.Hex(), plan.Target.Id.Hex()); err != nil { + return nil, err + } + + return nil, nil + }) + return err +} diff --git a/clinics/merge/report.go b/clinics/merge/report.go index 352b89fd..8365e0c7 100644 --- a/clinics/merge/report.go +++ b/clinics/merge/report.go @@ -97,7 +97,7 @@ func (r Report) addSourcePatients(report *xlsx.File) error { currentRow.AddCell().SetValue("Latest Upload ---") sh.AddRow() - for _, plan := range r.plan.PatientsPlan.GetSourcePatientPlans() { + for _, plan := range r.plan.PatientPlans.GetSourcePatientPlans() { addPatientDetails(sh.AddRow(), *plan.SourcePatient, plan.SourceTagNames) } return nil @@ -119,7 +119,7 @@ func (r Report) addTargetPatients(report *xlsx.File) error { currentRow.AddCell().SetValue("Latest Upload ---") sh.AddRow() - for _, plan := range r.plan.PatientsPlan.GetTargetPatientPlans() { + for _, plan := range r.plan.PatientPlans.GetTargetPatientPlans() { addPatientDetails(sh.AddRow(), *plan.TargetPatient, plan.TargetTagNames) } return nil @@ -170,7 +170,7 @@ func (r Report) addDuplicatesInMergedSheet(report *xlsx.File) error { currentRow.AddCell().SetValue("Latest Upload ---") count := 1 - for _, patientPlan := range r.plan.PatientsPlan { + for _, patientPlan := range r.plan.PatientPlans { if patientPlan.SourcePatient == nil || !patientPlan.HasConflicts() { continue } @@ -284,7 +284,7 @@ func (r Report) addSettingsSummary(sh *xlsx.Sheet) error { currentRow.AddCell().SetValue(r.plan.MembershipRestrictionsMergePlan.GetSourceValue()) currentRow.AddCell().SetValue(r.plan.MembershipRestrictionsMergePlan.GetTargetValue()) - for _, s := range r.plan.SettingsPlan { + for _, s := range r.plan.SettingsPlans { currentRow = sh.AddRow() currentRow.AddCell().SetValue(s.Name) if s.ValuesMatch() { @@ -305,7 +305,7 @@ func (r Report) addClinicianSummary(sh *xlsx.Sheet) error { adminTasks := make([]ClinicianPlan, 0) nonAdminTasks := make([]ClinicianPlan, 0) - for _, c := range r.plan.CliniciansPlan { + for _, c := range r.plan.ClinicianPlans { if c.ClinicianAction == ClinicianActionMergeInto { // Results will be reported by the corresponding source merge task continue @@ -346,14 +346,14 @@ func (r Report) addClinicianSummary(sh *xlsx.Sheet) error { sh.AddRow() pendingInvites := 0 - for _, count := range r.plan.CliniciansPlan.PendingInvitesByWorkspace() { + for _, count := range r.plan.ClinicianPlans.PendingInvitesByWorkspace() { pendingInvites += count } currentRow = sh.AddRow() currentRow.AddCell().SetValue(fmt.Sprintf("Pending Invites (%v)", pendingInvites)) currentRow.AddCell().SetValue("Workspace ---") - for workspace, count := range r.plan.CliniciansPlan.PendingInvitesByWorkspace() { + for workspace, count := range r.plan.ClinicianPlans.PendingInvitesByWorkspace() { currentRow = sh.AddRow() currentRow.AddCell().SetValue(count) currentRow.AddCell().SetValue(workspace) @@ -364,14 +364,14 @@ func (r Report) addClinicianSummary(sh *xlsx.Sheet) error { } func (r Report) addTagsSummary(sh *xlsx.Sheet) error { - resultingTagsCount := r.plan.TagsPlan.GetResultingTagsCount() + resultingTagsCount := r.plan.TagsPlans.GetResultingTagsCount() currentRow := sh.AddRow() currentRow.AddCell().SetValue(fmt.Sprintf("Resulting Tags (%v) ---", resultingTagsCount)) currentRow.AddCell().SetValue("Workspace ---") currentRow.AddCell().SetValue("Merge ---") - for _, plan := range r.plan.TagsPlan { + for _, plan := range r.plan.TagsPlans { if plan.TagAction == TagActionSkip { continue } @@ -391,9 +391,9 @@ func (r Report) addTagsSummary(sh *xlsx.Sheet) error { func (r Report) addMeasuresSummary(sh *xlsx.Sheet) error { adminTasks := make([]ClinicianPlan, 0) nonAdminTasks := make([]ClinicianPlan, 0) - membersDowngraded := r.plan.CliniciansPlan.GetDowngradedMembersCount() + membersDowngraded := r.plan.ClinicianPlans.GetDowngradedMembersCount() - for _, plan := range r.plan.CliniciansPlan { + for _, plan := range r.plan.ClinicianPlans { if plan.ClinicianAction == ClinicianActionMergeInto { // Results will be reported by the corresponding source merge task continue @@ -417,8 +417,8 @@ func (r Report) addMeasuresSummary(sh *xlsx.Sheet) error { currentRow.AddCell().SetValue("- Members downgraded from Admin") currentRow.AddCell().SetValue(membersDowngraded) - resultingTagsCount := r.plan.TagsPlan.GetResultingTagsCount() - duplicateTagsCount := r.plan.TagsPlan.GetDuplicateTagsCount() + resultingTagsCount := r.plan.TagsPlans.GetResultingTagsCount() + duplicateTagsCount := r.plan.TagsPlans.GetDuplicateTagsCount() currentRow = sh.AddRow() currentRow.AddCell().SetValue("Resulting Tags") @@ -427,11 +427,11 @@ func (r Report) addMeasuresSummary(sh *xlsx.Sheet) error { currentRow.AddCell().SetValue("- Duplicate tags that will be merged") currentRow.AddCell().SetValue(duplicateTagsCount) - resultingPatientsCount := r.plan.PatientsPlan.GetResultingPatientsCount() - duplicateAccountsCounts := r.plan.PatientsPlan.GetConflictCounts()[PatientConflictCategoryDuplicateAccounts] - likelyDuplicateCount := r.plan.PatientsPlan.GetConflictCounts()[PatientConflictCategoryLikelyDuplicateAccounts] - duplicateMRNsCount := r.plan.PatientsPlan.GetConflictCounts()[PatientConflictCategoryMRNOnlyMatch] - duplicateNamesCount := r.plan.PatientsPlan.GetConflictCounts()[PatientConflictCategoryNameOnlyMatch] + resultingPatientsCount := r.plan.PatientPlans.GetResultingPatientsCount() + duplicateAccountsCounts := r.plan.PatientPlans.GetConflictCounts()[PatientConflictCategoryDuplicateAccounts] + likelyDuplicateCount := r.plan.PatientPlans.GetConflictCounts()[PatientConflictCategoryLikelyDuplicateAccounts] + duplicateMRNsCount := r.plan.PatientPlans.GetConflictCounts()[PatientConflictCategoryMRNOnlyMatch] + duplicateNamesCount := r.plan.PatientPlans.GetConflictCounts()[PatientConflictCategoryNameOnlyMatch] currentRow = sh.AddRow() currentRow.AddCell().SetValue("Resulting Patient Accounts") diff --git a/clinics/merge/settings.go b/clinics/merge/settings.go index d3ddef16..fc49f668 100644 --- a/clinics/merge/settings.go +++ b/clinics/merge/settings.go @@ -146,8 +146,8 @@ func (m MembershipRestrictionsMergePlan) getSerializedValue(restrictions []clini } func (m MembershipRestrictionsMergePlan) PreventsMerge() bool { - sourceMap := m.membershipRestrictionsToMap(m.SourceValue) - targetMap := m.membershipRestrictionsToMap(m.TargetValue) + sourceMap := membershipRestrictionsToMap(m.SourceValue) + targetMap := membershipRestrictionsToMap(m.TargetValue) // Check if the source map is a subset of the target map for sourceDomain, sourceIDP := range sourceMap { @@ -158,7 +158,7 @@ func (m MembershipRestrictionsMergePlan) PreventsMerge() bool { return false } -func (m MembershipRestrictionsMergePlan) membershipRestrictionsToMap(restrictions []clinics.MembershipRestrictions) map[string]string { +func membershipRestrictionsToMap(restrictions []clinics.MembershipRestrictions) map[string]string { result := map[string]string{} for _, r := range restrictions { result[r.EmailDomain] = r.RequiredIdp diff --git a/clinics/repo.go b/clinics/repo.go index 9539e3c2..90fd38b9 100644 --- a/clinics/repo.go +++ b/clinics/repo.go @@ -45,7 +45,6 @@ func (c *repository) Initialize(ctx context.Context) error { {Key: "shareCodes", Value: 1}, }, Options: options.Index(). - SetBackground(true). SetUnique(true). SetName("UniqueShareCodes"), }, @@ -54,7 +53,6 @@ func (c *repository) Initialize(ctx context.Context) error { {Key: "canonicalShareCode", Value: 1}, }, Options: options.Index(). - SetBackground(true). SetUnique(true). SetName("UniqueCanonicalShareCode"), }, @@ -502,6 +500,26 @@ func (c *repository) UpdatePatientCountSettings(ctx context.Context, id string, return err } +func (c *repository) AppendShareCodes(ctx context.Context, id string, shareCodes []string) error { + clinicId, _ := primitive.ObjectIDFromHex(id) + selector := bson.M{"_id": clinicId} + + update := bson.M{ + "$addToSet": bson.M{"shareCodes": shareCodes}, + "$set": bson.M{ + "updatedTime": time.Now(), + }, + } + + err := c.collection.FindOneAndUpdate(ctx, selector, update).Err() + if errors.Is(err, mongo.ErrNoDocuments) { + return ErrNotFound + } + + return err +} + + func (c *repository) GetPatientCount(ctx context.Context, clinicId string) (*PatientCount, error) { clinic, err := c.Get(ctx, clinicId) if err != nil { diff --git a/clinics/test/mock_service.go b/clinics/test/mock_service.go index 2bfecc19..a07d3ce6 100644 --- a/clinics/test/mock_service.go +++ b/clinics/test/mock_service.go @@ -36,6 +36,20 @@ func (m *MockService) EXPECT() *MockServiceMockRecorder { return m.recorder } +// AppendShareCodes mocks base method. +func (m *MockService) AppendShareCodes(ctx context.Context, clinicId string, shareCodes []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AppendShareCodes", ctx, clinicId, shareCodes) + ret0, _ := ret[0].(error) + return ret0 +} + +// AppendShareCodes indicates an expected call of AppendShareCodes. +func (mr *MockServiceMockRecorder) AppendShareCodes(ctx, clinicId, shareCodes interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppendShareCodes", reflect.TypeOf((*MockService)(nil).AppendShareCodes), ctx, clinicId, shareCodes) +} + // Create mocks base method. func (m *MockService) Create(ctx context.Context, clinic *clinics.Clinic) (*clinics.Clinic, error) { m.ctrl.T.Helper() diff --git a/spec/clinic.v1.yaml b/spec/clinic.v1.yaml index 32da8544..b8aaa483 100644 --- a/spec/clinic.v1.yaml +++ b/spec/clinic.v1.yaml @@ -2148,6 +2148,25 @@ paths: schema: $ref: '#/components/schemas/GenerateMergeReport' description: '' + '/v1/clinics/{clinicId}/merge': + parameters: + - $ref: '#/components/parameters/clinicId' + post: + summary: Merge Clinic + operationId: MergeClinic + responses: + '200': + description: OK + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MergeClinic' + tags: + - Clinics + - Internal + x-internal: true + description: 'Merges tags, patients, clinicians, invites and share codes of the source clinic' components: schemas: Error: @@ -4677,6 +4696,14 @@ components: $ref: '#/components/schemas/Id' required: - sourceId + MergeClinic: + title: MergeClinics + x-stoplight: + id: 6hn01uuhln0wx + type: object + properties: + sourceId: + $ref: '#/components/schemas/Id' securitySchemes: sessionToken: name: x-tidepool-session-token diff --git a/store/transaction.go b/store/transaction.go index 19129c53..7aee64f7 100644 --- a/store/transaction.go +++ b/store/transaction.go @@ -18,8 +18,9 @@ func WithTransaction(ctx context.Context, dbClient *mongo.Client, txn Transactio } defer session.EndSession(ctx) - wc := writeconcern.New(writeconcern.WMajority()) - rc := readconcern.Snapshot() - txnOpts := options.Transaction().SetWriteConcern(wc).SetReadConcern(rc) + txnOpts := options. + Transaction(). + SetWriteConcern(writeconcern.Majority()). + SetReadConcern(readconcern.Snapshot()) return session.WithTransaction(ctx, txn, txnOpts) } diff --git a/xealth_client/gen_client.go b/xealth_client/gen_client.go index 9412590a..b8bef8e2 100644 --- a/xealth_client/gen_client.go +++ b/xealth_client/gen_client.go @@ -1,3 +1,4 @@ +// WARNING: You are using an OpenAPI 3.1.x specification, which is not yet supported by oapi-codegen (https://github.com/deepmap/oapi-codegen/issues/373) and so some functionality may not be available. Until oapi-codegen supports OpenAPI 3.1, it is recommended to downgrade your spec to 3.0.x // Package xealth_client provides primitives to interact with the openapi HTTP API. // // Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.3.0 DO NOT EDIT. diff --git a/xealth_client/gen_types.go b/xealth_client/gen_types.go index 459919ab..a96cb782 100644 --- a/xealth_client/gen_types.go +++ b/xealth_client/gen_types.go @@ -1,3 +1,4 @@ +// WARNING: You are using an OpenAPI 3.1.x specification, which is not yet supported by oapi-codegen (https://github.com/deepmap/oapi-codegen/issues/373) and so some functionality may not be available. Until oapi-codegen supports OpenAPI 3.1, it is recommended to downgrade your spec to 3.0.x // Package xealth_client provides primitives to interact with the openapi HTTP API. // // Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.3.0 DO NOT EDIT.