working signup wizard redirect (Show First Run Wizard)

This commit is contained in:
Jason Kulatunga 2024-02-15 11:19:04 -08:00
parent f8292c300f
commit 8059a1a719
No known key found for this signature in database
11 changed files with 151 additions and 15 deletions

View File

@ -73,6 +73,13 @@ func (gr *GormRepository) CreateUser(ctx context.Context, user *models.User) err
return nil
}
func (gr *GormRepository) GetUserCount(ctx context.Context) (int, error) {
var count int64
result := gr.GormClient.WithContext(ctx).Model(&models.User{}).Count(&count)
return int(count), result.Error
}
func (gr *GormRepository) GetUserByUsername(ctx context.Context, username string) (*models.User, error) {
var foundUser models.User
result := gr.GormClient.WithContext(ctx).Where(models.User{Username: username}).First(&foundUser)

View File

@ -14,7 +14,7 @@ type DatabaseRepository interface {
Migrate() error
CreateUser(context.Context, *models.User) error
GetUserCount(context.Context) (int, error)
GetUserByUsername(context.Context, string) (*models.User, error)
GetCurrentUser(ctx context.Context) (*models.User, error)
DeleteCurrentUser(ctx context.Context) error

View File

@ -148,6 +148,20 @@ func (mr *MockDatabaseRepositoryMockRecorder) CreateUser(arg0, arg1 interface{})
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUser", reflect.TypeOf((*MockDatabaseRepository)(nil).CreateUser), arg0, arg1)
}
// DeleteCurrentUser mocks base method.
func (m *MockDatabaseRepository) DeleteCurrentUser(ctx context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteCurrentUser", ctx)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteCurrentUser indicates an expected call of DeleteCurrentUser.
func (mr *MockDatabaseRepositoryMockRecorder) DeleteCurrentUser(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCurrentUser", reflect.TypeOf((*MockDatabaseRepository)(nil).DeleteCurrentUser), ctx)
}
// DeleteSource mocks base method.
func (m *MockDatabaseRepository) DeleteSource(ctx context.Context, sourceId string) (int64, error) {
m.ctrl.T.Helper()
@ -209,13 +223,12 @@ func (mr *MockDatabaseRepositoryMockRecorder) GetCurrentUser(ctx interface{}) *g
}
// GetFlattenedResourceGraph mocks base method.
func (m *MockDatabaseRepository) GetFlattenedResourceGraph(ctx context.Context, graphType pkg.ResourceGraphType, options models.ResourceGraphOptions) (map[string][]*models.ResourceBase, *models.ResourceGraphMetadata, error) {
func (m *MockDatabaseRepository) GetFlattenedResourceGraph(ctx context.Context, graphType pkg.ResourceGraphType, options models.ResourceGraphOptions) (map[string][]*models.ResourceBase, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetFlattenedResourceGraph", ctx, graphType, options)
ret0, _ := ret[0].(map[string][]*models.ResourceBase)
ret1, _ := ret[1].(*models.ResourceGraphMetadata)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetFlattenedResourceGraph indicates an expected call of GetFlattenedResourceGraph.
@ -359,6 +372,21 @@ func (mr *MockDatabaseRepositoryMockRecorder) GetUserByUsername(arg0, arg1 inter
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByUsername", reflect.TypeOf((*MockDatabaseRepository)(nil).GetUserByUsername), arg0, arg1)
}
// GetUserCount mocks base method.
func (m *MockDatabaseRepository) GetUserCount(arg0 context.Context) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserCount", arg0)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetUserCount indicates an expected call of GetUserCount.
func (mr *MockDatabaseRepositoryMockRecorder) GetUserCount(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserCount", reflect.TypeOf((*MockDatabaseRepository)(nil).GetUserCount), arg0)
}
// ListBackgroundJobs mocks base method.
func (m *MockDatabaseRepository) ListBackgroundJobs(ctx context.Context, queryOptions models.BackgroundJobQueryOptions) ([]models.BackgroundJob, error) {
m.ctrl.T.Helper()
@ -389,6 +417,21 @@ func (mr *MockDatabaseRepositoryMockRecorder) ListResources(arg0, arg1 interface
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListResources", reflect.TypeOf((*MockDatabaseRepository)(nil).ListResources), arg0, arg1)
}
// LoadSystemSettings mocks base method.
func (m *MockDatabaseRepository) LoadSystemSettings(ctx context.Context) (*models.SystemSettings, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LoadSystemSettings", ctx)
ret0, _ := ret[0].(*models.SystemSettings)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// LoadSystemSettings indicates an expected call of LoadSystemSettings.
func (mr *MockDatabaseRepositoryMockRecorder) LoadSystemSettings(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadSystemSettings", reflect.TypeOf((*MockDatabaseRepository)(nil).LoadSystemSettings), ctx)
}
// LoadUserSettings mocks base method.
func (m *MockDatabaseRepository) LoadUserSettings(ctx context.Context) (*models.UserSettings, error) {
m.ctrl.T.Helper()
@ -461,6 +504,20 @@ func (mr *MockDatabaseRepositoryMockRecorder) RemoveResourceAssociation(ctx, sou
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveResourceAssociation", reflect.TypeOf((*MockDatabaseRepository)(nil).RemoveResourceAssociation), ctx, source, resourceType, resourceId, relatedSource, relatedResourceType, relatedResourceId)
}
// SaveSystemSettings mocks base method.
func (m *MockDatabaseRepository) SaveSystemSettings(ctx context.Context, newSettings *models.SystemSettings) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SaveSystemSettings", ctx, newSettings)
ret0, _ := ret[0].(error)
return ret0
}
// SaveSystemSettings indicates an expected call of SaveSystemSettings.
func (mr *MockDatabaseRepositoryMockRecorder) SaveSystemSettings(ctx, newSettings interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveSystemSettings", reflect.TypeOf((*MockDatabaseRepository)(nil).SaveSystemSettings), ctx, newSettings)
}
// SaveUserSettings mocks base method.
func (m *MockDatabaseRepository) SaveUserSettings(arg0 context.Context, arg1 *models.UserSettings) error {
m.ctrl.T.Helper()
@ -517,3 +574,17 @@ func (mr *MockDatabaseRepositoryMockRecorder) UpsertRawResource(ctx, sourceCrede
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertRawResource", reflect.TypeOf((*MockDatabaseRepository)(nil).UpsertRawResource), ctx, sourceCredentials, rawResource)
}
// UpsertRawResourceAssociation mocks base method.
func (m *MockDatabaseRepository) UpsertRawResourceAssociation(ctx context.Context, sourceId, sourceResourceType, sourceResourceId, targetSourceId, targetResourceType, targetResourceId string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpsertRawResourceAssociation", ctx, sourceId, sourceResourceType, sourceResourceId, targetSourceId, targetResourceType, targetResourceId)
ret0, _ := ret[0].(error)
return ret0
}
// UpsertRawResourceAssociation indicates an expected call of UpsertRawResourceAssociation.
func (mr *MockDatabaseRepositoryMockRecorder) UpsertRawResourceAssociation(ctx, sourceId, sourceResourceType, sourceResourceId, targetSourceId, targetResourceType, targetResourceId interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertRawResourceAssociation", reflect.TypeOf((*MockDatabaseRepository)(nil).UpsertRawResourceAssociation), ctx, sourceId, sourceResourceType, sourceResourceId, targetSourceId, targetResourceType, targetResourceId)
}

View File

@ -6,6 +6,7 @@ import (
"embed"
"encoding/json"
"fmt"
"github.com/fastenhealth/fasten-onprem/backend/pkg"
"github.com/fastenhealth/fasten-onprem/backend/pkg/config"
"github.com/fastenhealth/fasten-onprem/backend/pkg/database"
"github.com/fastenhealth/fasten-onprem/backend/pkg/event_bus"
@ -54,15 +55,32 @@ func (ae *AppEngine) Setup() (*gin.RouterGroup, *gin.Engine) {
{
api.Use(middleware.CacheMiddleware())
api.GET("/health", func(c *gin.Context) {
// This function does a quick check to see if the server is up and running
// it will also determine if we should show the first run wizard
//TODO:
// check if the /web folder is populated.
// check if access to database
//get the count of users in the DB
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
userCount, err := databaseRepo.GetUserCount(c)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
return
}
keepAliveMsg := models.NewEventKeepAlive("heartbeat")
err := ae.EventBus.PublishMessage(keepAliveMsg)
err = ae.EventBus.PublishMessage(keepAliveMsg)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
return
}
c.JSON(http.StatusOK, gin.H{
"success": err == nil,
"success": true,
"data": gin.H{
"first_run_wizard": userCount == 0,
},
})
})

View File

@ -18,14 +18,15 @@ import {environment} from '../environments/environment';
import {DesktopCallbackComponent} from './pages/desktop-callback/desktop-callback.component';
import {BackgroundJobsComponent} from './pages/background-jobs/background-jobs.component';
import {AuthSignupWizardComponent} from './pages/auth-signup-wizard/auth-signup-wizard.component';
import {ShowFirstRunWizardGuard} from './auth-guards/show-first-run-wizard-guard';
const routes: Routes = [
{ path: 'auth/signup/wizard', component: AuthSignupWizardComponent },
{ path: 'auth/signin', component: AuthSigninComponent },
{ path: 'auth/signin', component: AuthSigninComponent, canActivate: [ ShowFirstRunWizardGuard] },
{ path: 'auth/signin/callback/:idp_type', component: AuthSigninComponent },
{ path: 'auth/signup', component: AuthSignupComponent },
{ path: 'auth/signup', component: AuthSignupComponent, canActivate: [ ShowFirstRunWizardGuard] },
{ path: 'auth/signup/callback/:idp_type', component: AuthSignupComponent },
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },

View File

@ -41,6 +41,7 @@ import { BackgroundJobsComponent } from './pages/background-jobs/background-jobs
import {FhirCardModule} from './components/fhir-card/fhir-card.module';
import {FhirDatatableModule} from './components/fhir-datatable/fhir-datatable.module';
import { AuthSignupWizardComponent } from './pages/auth-signup-wizard/auth-signup-wizard.component';
import {ShowFirstRunWizardGuard} from './auth-guards/show-first-run-wizard-guard';
@NgModule({
declarations: [
@ -95,6 +96,7 @@ import { AuthSignupWizardComponent } from './pages/auth-signup-wizard/auth-signu
deps: [AuthService, Router]
},
IsAuthenticatedAuthGuard,
ShowFirstRunWizardGuard,
{
provide: HIGHLIGHT_OPTIONS,
useValue: {

View File

@ -0,0 +1,27 @@
import { Injectable } from '@angular/core';
import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router} from '@angular/router';
import {FastenApiService} from '../services/fasten-api.service';
@Injectable()
export class ShowFirstRunWizardGuard implements CanActivate {
constructor(private fastenService: FastenApiService, private router: Router) {
}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise <boolean> {
try {
//check if the server requires the first run wizard to be shown, if not, continue to login/signup
let healthData = await this.fastenService.getHealth().toPromise()
if (healthData.first_run_wizard) {
return await this.router.navigate(['/auth/signup/wizard']);
}
} catch (e) {
// if there is an error, just ignore it, and continue to the signin/signup page.
console.error("ignoring error:", e)
}
// continue as normal
return true
}
}

View File

@ -71,8 +71,8 @@
<div class="form-group form-check">
<input [(ngModel)]="newUser.agree_terms" name="agree_terms" #agree_terms="ngModel" type="checkbox" class="form-check-input" id="agreeTermsCheck" required>
<label class="form-check-label" for="agreeTermsCheck">
I have read and agree to the Fasten Health <br/> <a href="https://policy.fastenhealth.com/privacy_policy.html">Privacy Policy</a>
and <a href="https://policy.fastenhealth.com/terms.html">Terms of Service</a>
I have read and agree to the Fasten Health <br/> <a externalLink href="https://policy.fastenhealth.com/privacy_policy.html">Privacy Policy</a>
and <a externalLink href="https://policy.fastenhealth.com/terms.html">Terms of Service</a>
</label>
<div *ngIf="agree_terms.invalid && (agree_terms.dirty || agree_terms.touched)" class="alert alert-danger">

View File

@ -56,6 +56,16 @@ export class FastenApiService {
);
}
getHealth(): Observable<any> {
return this._httpClient.get<any>(`${GetEndpointAbsolutePath(globalThis.location, environment.fasten_api_endpoint_base)}/health`)
.pipe(
map((response: ResponseWrapper) => {
console.log("Health RESPONSE", response)
return response.data
})
);
}
/*
SECURE ENDPOINTS

2
go.mod
View File

@ -15,7 +15,7 @@ require (
github.com/dave/jennifer v1.6.1
github.com/dominikbraun/graph v0.15.0
github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3
github.com/fastenhealth/fasten-sources v0.5.19
github.com/fastenhealth/fasten-sources v0.5.20
github.com/fastenhealth/gofhir-models v0.0.6
github.com/gin-gonic/gin v1.9.0
github.com/go-gormigrate/gormigrate/v2 v2.1.1

4
go.sum
View File

@ -101,8 +101,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fastenhealth/fasten-sources v0.5.19 h1:E5NX1LD5P6Ejk2crAxQaA/1rtnFqoiUYYYY2c/3REHQ=
github.com/fastenhealth/fasten-sources v0.5.19/go.mod h1:wD94lmBq6F2mIwT2CBF8owU4gO7E1oY2HUX6bbovOXM=
github.com/fastenhealth/fasten-sources v0.5.20 h1:rNEmn9wmzaa3W1uWmm+uCDjOjyAwUEf7hLrDCMdLfqk=
github.com/fastenhealth/fasten-sources v0.5.20/go.mod h1:wD94lmBq6F2mIwT2CBF8owU4gO7E1oY2HUX6bbovOXM=
github.com/fastenhealth/gofhir-models v0.0.6 h1:yJYYaV1eJtHiGEfA1rXLsyOm/9hIi6s2cGoZzGfW1tM=
github.com/fastenhealth/gofhir-models v0.0.6/go.mod h1:xB8ikGxu3bUq2b1JYV+CZpHqBaLXpOizFR0eFBCunis=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=