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
|
||||
}
|
||||
|
||||
// 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>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -17,6 +17,7 @@ type DatabaseRepository interface {
|
|||
|
||||
GetUserByUsername(context.Context, string) (*models.User, error)
|
||||
GetCurrentUser(ctx context.Context) (*models.User, error)
|
||||
DeleteCurrentUser(ctx context.Context) 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.DELETE("/account/me", handler.DeleteAccount)
|
||||
|
||||
secure.GET("/summary", handler.GetSummary)
|
||||
|
||||
secure.POST("/source", handler.CreateReconnectSource)
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<!-- Header Row -->
|
||||
<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 -->
|
||||
<div class="row mt-5 mb-3">
|
||||
<div class="col-6">
|
||||
|
@ -173,6 +173,18 @@
|
|||
</p>
|
||||
</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>
|
||||
|
||||
<ng-template #isLoadingTemplate>
|
||||
|
@ -186,3 +198,30 @@
|
|||
</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 {ImmunizationModel} from '../../../lib/models/resources/immunization-model';
|
||||
import {AllergyIntoleranceModel} from '../../../lib/models/resources/allergy-intolerance-model';
|
||||
import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
@Component({
|
||||
selector: 'app-patient-profile',
|
||||
|
@ -13,24 +14,27 @@ import {AllergyIntoleranceModel} from '../../../lib/models/resources/allergy-int
|
|||
styleUrls: ['./patient-profile.component.scss']
|
||||
})
|
||||
export class PatientProfileComponent implements OnInit {
|
||||
loading: boolean = false
|
||||
loading: {[name: string]: boolean} = {page: false, delete: false}
|
||||
|
||||
modalCloseResult = '';
|
||||
|
||||
patient: ResourceFhir = null
|
||||
immunizations: ImmunizationModel[] = []
|
||||
allergyIntolerances: AllergyIntoleranceModel[] = []
|
||||
constructor(
|
||||
private fastenApi: FastenApiService,
|
||||
private modalService: NgbModal,
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loading = true
|
||||
this.loading['page'] = true
|
||||
|
||||
forkJoin([
|
||||
this.fastenApi.getResources("Patient"),
|
||||
this.fastenApi.getResources("Immunization"),
|
||||
this.fastenApi.getResources("AllergyIntolerance")
|
||||
]).subscribe(results => {
|
||||
this.loading = false
|
||||
this.loading['page'] = false
|
||||
console.log(results)
|
||||
this.patient = results[0][0]
|
||||
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
|
||||
})
|
||||
}, 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
|
||||
*/
|
||||
|
||||
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
|
||||
|
||||
getDashboards(): Observable<DashboardConfig[]> {
|
||||
|
|
Loading…
Reference in New Issue