adding ability to delete user account completely
This commit is contained in:
parent
f1c120cbb2
commit
86b336e489
|
@ -107,6 +107,66 @@ func (gr *GormRepository) GetCurrentUser(ctx context.Context) (*models.User, err
|
||||||
return ¤tUser, nil
|
return ¤tUser, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SECURITY: this should only be called after the user has confirmed they want to delete their account.
|
||||||
|
func (gr *GormRepository) DeleteCurrentUser(ctx context.Context) error {
|
||||||
|
currentUser, err := gr.GetCurrentUser(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//delete all records associated with this user.
|
||||||
|
// - background jobs
|
||||||
|
// - FHIR Resources
|
||||||
|
// - source credentials
|
||||||
|
// - related resources
|
||||||
|
// - user settings
|
||||||
|
// - user
|
||||||
|
|
||||||
|
//delete background jobs
|
||||||
|
err = gr.GormClient.
|
||||||
|
Where(models.BackgroundJob{UserID: currentUser.ID}).
|
||||||
|
Delete(&models.BackgroundJob{}).Error
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not delete background jobs for user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//delete FHIR Resources & sources
|
||||||
|
sources, err := gr.GetSources(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not get sources: %w", err)
|
||||||
|
}
|
||||||
|
for _, source := range sources {
|
||||||
|
_, err = gr.DeleteSource(ctx, source.ID.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not delete source (%s) & resources for user: %w", source.ID.String(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//delete related resources
|
||||||
|
err = gr.GormClient.
|
||||||
|
Where(models.RelatedResource{ResourceBaseUserID: currentUser.ID}).
|
||||||
|
Delete(&models.RelatedResource{}).Error
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not delete related resources for user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//delete user settings
|
||||||
|
err = gr.GormClient.
|
||||||
|
Where(models.UserSettingEntry{UserID: currentUser.ID}).
|
||||||
|
Delete(&models.UserSettingEntry{}).Error
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not delete user settings for user: %w", err)
|
||||||
|
}
|
||||||
|
//delete user
|
||||||
|
err = gr.GormClient.
|
||||||
|
Where(models.User{ModelBase: models.ModelBase{ID: currentUser.ID}}).
|
||||||
|
Delete(&models.User{}).Error
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not delete user: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
//</editor-fold>
|
//</editor-fold>
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -17,6 +17,7 @@ type DatabaseRepository interface {
|
||||||
|
|
||||||
GetUserByUsername(context.Context, string) (*models.User, error)
|
GetUserByUsername(context.Context, string) (*models.User, error)
|
||||||
GetCurrentUser(ctx context.Context) (*models.User, error)
|
GetCurrentUser(ctx context.Context) (*models.User, error)
|
||||||
|
DeleteCurrentUser(ctx context.Context) error
|
||||||
|
|
||||||
GetSummary(ctx context.Context) (*models.Summary, error)
|
GetSummary(ctx context.Context) (*models.Summary, error)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fastenhealth/fasten-onprem/backend/pkg"
|
||||||
|
"github.com/fastenhealth/fasten-onprem/backend/pkg/database"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SECURITY: this is a secure endpoint, and should only be called after a double confirmation
|
||||||
|
func DeleteAccount(c *gin.Context) {
|
||||||
|
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
|
||||||
|
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
|
||||||
|
|
||||||
|
err := databaseRepo.DeleteCurrentUser(c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorln("An error occurred while deleting current user", err)
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||||
|
}
|
|
@ -62,6 +62,8 @@ func (ae *AppEngine) Setup() (*gin.RouterGroup, *gin.Engine) {
|
||||||
|
|
||||||
secure := api.Group("/secure").Use(middleware.RequireAuth())
|
secure := api.Group("/secure").Use(middleware.RequireAuth())
|
||||||
{
|
{
|
||||||
|
secure.DELETE("/account/me", handler.DeleteAccount)
|
||||||
|
|
||||||
secure.GET("/summary", handler.GetSummary)
|
secure.GET("/summary", handler.GetSummary)
|
||||||
|
|
||||||
secure.POST("/source", handler.CreateReconnectSource)
|
secure.POST("/source", handler.CreateReconnectSource)
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<!-- Header Row -->
|
<!-- Header Row -->
|
||||||
<report-header [reportHeaderTitle]="'Patient Profile'"></report-header>
|
<report-header [reportHeaderTitle]="'Patient Profile'"></report-header>
|
||||||
|
|
||||||
<div *ngIf="!loading else isLoadingTemplate" class="pl-3 pr-3">
|
<div *ngIf="!loading['page'] else isLoadingTemplate" class="pl-3 pr-3">
|
||||||
<!-- Patient Name Row -->
|
<!-- Patient Name Row -->
|
||||||
<div class="row mt-5 mb-3">
|
<div class="row mt-5 mb-3">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
|
@ -173,6 +173,18 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Account Actions -->
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<div class="row" style="padding-bottom:20px">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<button (click)="openModal(manageModalRef)" type="button" class="btn btn-outline-danger">Delete Account</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #isLoadingTemplate>
|
<ng-template #isLoadingTemplate>
|
||||||
|
@ -186,3 +198,30 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ng-template #manageModalRef let-modal>
|
||||||
|
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="modal-basic-title">Delete Account</h4>
|
||||||
|
<button type="button" class="btn close" aria-label="Close" (click)="modal.dismiss('Cross click')">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Are you sure you would like to delete your Fasten Health Account?</p>
|
||||||
|
<p>
|
||||||
|
All personal and medical data on this device will be deleted. You will not be able to recover this data.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" (click)="deleteAccount()" class="btn btn-danger">
|
||||||
|
<span *ngIf="loading['delete']" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
<button (click)="modal.dismiss('Close click')" type="button" class="btn btn-outline-light">Close</button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {fhirModelFactory} from '../../../lib/models/factory';
|
||||||
import {ResourceType} from '../../../lib/models/constants';
|
import {ResourceType} from '../../../lib/models/constants';
|
||||||
import {ImmunizationModel} from '../../../lib/models/resources/immunization-model';
|
import {ImmunizationModel} from '../../../lib/models/resources/immunization-model';
|
||||||
import {AllergyIntoleranceModel} from '../../../lib/models/resources/allergy-intolerance-model';
|
import {AllergyIntoleranceModel} from '../../../lib/models/resources/allergy-intolerance-model';
|
||||||
|
import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-patient-profile',
|
selector: 'app-patient-profile',
|
||||||
|
@ -13,24 +14,27 @@ import {AllergyIntoleranceModel} from '../../../lib/models/resources/allergy-int
|
||||||
styleUrls: ['./patient-profile.component.scss']
|
styleUrls: ['./patient-profile.component.scss']
|
||||||
})
|
})
|
||||||
export class PatientProfileComponent implements OnInit {
|
export class PatientProfileComponent implements OnInit {
|
||||||
loading: boolean = false
|
loading: {[name: string]: boolean} = {page: false, delete: false}
|
||||||
|
|
||||||
|
modalCloseResult = '';
|
||||||
|
|
||||||
patient: ResourceFhir = null
|
patient: ResourceFhir = null
|
||||||
immunizations: ImmunizationModel[] = []
|
immunizations: ImmunizationModel[] = []
|
||||||
allergyIntolerances: AllergyIntoleranceModel[] = []
|
allergyIntolerances: AllergyIntoleranceModel[] = []
|
||||||
constructor(
|
constructor(
|
||||||
private fastenApi: FastenApiService,
|
private fastenApi: FastenApiService,
|
||||||
|
private modalService: NgbModal,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.loading = true
|
this.loading['page'] = true
|
||||||
|
|
||||||
forkJoin([
|
forkJoin([
|
||||||
this.fastenApi.getResources("Patient"),
|
this.fastenApi.getResources("Patient"),
|
||||||
this.fastenApi.getResources("Immunization"),
|
this.fastenApi.getResources("Immunization"),
|
||||||
this.fastenApi.getResources("AllergyIntolerance")
|
this.fastenApi.getResources("AllergyIntolerance")
|
||||||
]).subscribe(results => {
|
]).subscribe(results => {
|
||||||
this.loading = false
|
this.loading['page'] = false
|
||||||
console.log(results)
|
console.log(results)
|
||||||
this.patient = results[0][0]
|
this.patient = results[0][0]
|
||||||
this.immunizations = results[1].map((immunization) => {
|
this.immunizations = results[1].map((immunization) => {
|
||||||
|
@ -40,8 +44,27 @@ export class PatientProfileComponent implements OnInit {
|
||||||
return fhirModelFactory(allergy.source_resource_type as ResourceType, allergy) as AllergyIntoleranceModel
|
return fhirModelFactory(allergy.source_resource_type as ResourceType, allergy) as AllergyIntoleranceModel
|
||||||
})
|
})
|
||||||
}, error => {
|
}, error => {
|
||||||
this.loading = false
|
this.loading['page'] = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteAccount() {
|
||||||
|
this.loading['delete'] = true
|
||||||
|
this.fastenApi.deleteAccount().subscribe(result => {
|
||||||
|
this.loading['delete'] = false
|
||||||
|
console.log(result)
|
||||||
|
}, error => {
|
||||||
|
this.loading['delete'] = false
|
||||||
|
console.log(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
openModal(contentModalRef) {
|
||||||
|
this.modalService.open(contentModalRef, {ariaLabelledBy: 'modal-basic-title'}).result.then((result) => {
|
||||||
|
this.modalCloseResult = `Closed with: ${result}`;
|
||||||
|
}, (reason) => {
|
||||||
|
this.modalCloseResult = `Dismissed ${reason}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,21 @@ export class FastenApiService {
|
||||||
SECURE ENDPOINTS
|
SECURE ENDPOINTS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
deleteAccount(): Observable<boolean> {
|
||||||
|
return this._httpClient.delete<any>(`${GetEndpointAbsolutePath(globalThis.location, environment.fasten_api_endpoint_base)}/secure/account/me`)
|
||||||
|
.pipe(
|
||||||
|
map((response: ResponseWrapper) => {
|
||||||
|
console.log("DELETE ACCOUNT RESPONSE", response)
|
||||||
|
if(response.success) {
|
||||||
|
this.authService.Logout().then(() => {
|
||||||
|
this.router.navigateByUrl('/auth/signup')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return response.success
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: Any significant API changes here should also be reflected in EventBusService
|
//TODO: Any significant API changes here should also be reflected in EventBusService
|
||||||
|
|
||||||
getDashboards(): Observable<DashboardConfig[]> {
|
getDashboards(): Observable<DashboardConfig[]> {
|
||||||
|
|
Loading…
Reference in New Issue