working summary api

This commit is contained in:
Jason Kulatunga 2022-09-17 10:14:59 -07:00
parent 54016c0168
commit 02dcbcc507
12 changed files with 118 additions and 14 deletions

View File

@ -9,15 +9,17 @@ import (
//TODO: this should match the ID and username for the user. //TODO: this should match the ID and username for the user.
type JWTClaim struct { type JWTClaim struct {
Username string `json:"username"` Username string `json:"username"`
UserId string `json:"user_id"`
Email string `json:"email"` Email string `json:"email"`
jwt.StandardClaims jwt.StandardClaims
} }
func GenerateJWT(encryptionKey string, username string) (tokenString string, err error) { func GenerateJWT(encryptionKey string, username string, userId string) (tokenString string, err error) {
expirationTime := time.Now().Add(2 * time.Hour) expirationTime := time.Now().Add(2 * time.Hour)
claims := &JWTClaim{ claims := &JWTClaim{
Username: username, Username: username,
Email: username, Email: username,
UserId: userId,
StandardClaims: jwt.StandardClaims{ StandardClaims: jwt.StandardClaims{
ExpiresAt: expirationTime.Unix(), ExpiresAt: expirationTime.Unix(),
}, },

View File

@ -13,6 +13,8 @@ type DatabaseRepository interface {
GetUserByEmail(context.Context, string) (*models.User, error) GetUserByEmail(context.Context, string) (*models.User, error)
GetCurrentUser(context.Context) models.User GetCurrentUser(context.Context) models.User
GetSummary(ctx context.Context) (*models.Summary, error)
UpsertResource(context.Context, models.ResourceFhir) error UpsertResource(context.Context, models.ResourceFhir) error
GetResource(context.Context, string) (*models.ResourceFhir, error) GetResource(context.Context, string) (*models.ResourceFhir, error)
GetResourceBySourceId(context.Context, string, string) (*models.ResourceFhir, error) GetResourceBySourceId(context.Context, string, string) (*models.ResourceFhir, error)

View File

@ -109,6 +109,43 @@ func (sr *sqliteRepository) GetCurrentUser(ctx context.Context) models.User {
return currentUser return currentUser
} }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// User
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func (sr *sqliteRepository) GetSummary(ctx context.Context) (*models.Summary, error) {
// we want a count of all resources for this user by type
var resourceCountResults []map[string]interface{}
//group by resource type and return counts
// SELECT source_resource_type as resource_type, COUNT(*) as count FROM resource_fhirs WHERE source_id = "53c1e930-63af-46c9-b760-8e83cbc1abd9" GROUP BY source_resource_type;
result := sr.gormClient.WithContext(ctx).
Model(models.ResourceFhir{}).
Select("source_id, source_resource_type as resource_type, count(*) as count").
Group("source_resource_type").
Where(models.OriginBase{
UserID: sr.GetCurrentUser(ctx).ID,
}).
Scan(&resourceCountResults)
if result.Error != nil {
return nil, result.Error
}
// we want a list of all sources (when they were last updated)
sources, err := sr.GetSources(ctx)
if err != nil {
return nil, err
}
summary := &models.Summary{
Sources: sources,
ResourceTypeCounts: resourceCountResults,
}
return summary, nil
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Resource // Resource
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -248,9 +285,9 @@ func (sr *sqliteRepository) GetSourceSummary(ctx context.Context, sourceId strin
//group by resource type and return counts //group by resource type and return counts
// SELECT source_resource_type as resource_type, COUNT(*) as count FROM resource_fhirs WHERE source_id = "53c1e930-63af-46c9-b760-8e83cbc1abd9" GROUP BY source_resource_type; // SELECT source_resource_type as resource_type, COUNT(*) as count FROM resource_fhirs WHERE source_id = "53c1e930-63af-46c9-b760-8e83cbc1abd9" GROUP BY source_resource_type;
var results []map[string]interface{} var resourceTypeCounts []map[string]interface{}
sr.gormClient.WithContext(ctx). result := sr.gormClient.WithContext(ctx).
Model(models.ResourceFhir{}). Model(models.ResourceFhir{}).
Select("source_id, source_resource_type as resource_type, count(*) as count"). Select("source_id, source_resource_type as resource_type, count(*) as count").
Group("source_resource_type"). Group("source_resource_type").
@ -258,9 +295,13 @@ func (sr *sqliteRepository) GetSourceSummary(ctx context.Context, sourceId strin
UserID: sr.GetCurrentUser(ctx).ID, UserID: sr.GetCurrentUser(ctx).ID,
SourceID: sourceUUID, SourceID: sourceUUID,
}). }).
Scan(&results) Scan(&resourceTypeCounts)
sourceSummary.ResourceTypeCounts = results if result.Error != nil {
return nil, result.Error
}
sourceSummary.ResourceTypeCounts = resourceTypeCounts
return sourceSummary, nil return sourceSummary, nil
} }

View File

@ -0,0 +1,6 @@
package models
type Summary struct {
Sources []Source `json:"sources,omitempty"`
ResourceTypeCounts []map[string]interface{} `json:"resource_type_counts,omitempty"`
}

View File

@ -26,7 +26,7 @@ func AuthSignup(c *gin.Context) {
} }
// return JWT // return JWT
tokenString, err := auth.GenerateJWT(appConfig.GetString("web.jwt.encryptionkey"), user.Username) tokenString, err := auth.GenerateJWT(appConfig.GetString("web.jwt.encryptionkey"), user.Username, user.ID.String())
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false}) c.JSON(http.StatusInternalServerError, gin.H{"success": false})
return return
@ -58,7 +58,7 @@ func AuthSignin(c *gin.Context) {
} }
// return JWT // return JWT
tokenString, err := auth.GenerateJWT(appConfig.GetString("web.jwt.encryptionkey"), user.Username) tokenString, err := auth.GenerateJWT(appConfig.GetString("web.jwt.encryptionkey"), user.Username, user.ID.String())
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "an error occurred generating JWT token"}) c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "an error occurred generating JWT token"})
return return

View File

@ -0,0 +1,21 @@
package handler
import (
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/database"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"net/http"
)
func GetSummary(c *gin.Context) {
logger := c.MustGet("LOGGER").(*logrus.Entry)
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
summary, err := databaseRepo.GetSummary(c)
if err != nil {
logger.Errorln("An error occurred while retrieving summary", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
return
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": summary})
}

View File

@ -40,6 +40,7 @@ func RequireAuth() gin.HandlerFunc {
//todo, is this shared between all sessions?? //todo, is this shared between all sessions??
c.Set("AUTH_TOKEN", tokenString) c.Set("AUTH_TOKEN", tokenString)
c.Set("AUTH_USERNAME", claim.Username) c.Set("AUTH_USERNAME", claim.Username)
c.Set("AUTH_USERID", claim.UserId)
c.Next() c.Next()
} }

View File

@ -45,6 +45,8 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine {
secure := api.Group("/secure").Use(middleware.RequireAuth()) secure := api.Group("/secure").Use(middleware.RequireAuth())
{ {
secure.GET("/summary", handler.GetSummary)
secure.POST("/source", handler.CreateSource) secure.POST("/source", handler.CreateSource)
secure.POST("/source/manual", handler.CreateManualSource) secure.POST("/source/manual", handler.CreateManualSource)
secure.GET("/source", handler.ListSource) secure.GET("/source", handler.ListSource)

View File

@ -0,0 +1,7 @@
import {Source} from './source';
import {ResourceTypeCounts} from './source-summary';
export class Summary {
sources: Source[]
resource_type_counts: ResourceTypeCounts[]
}

View File

@ -55,7 +55,7 @@
</div> </div>
<div> <div>
<label>Encounters</label> <label>Encounters</label>
<h4>41</h4> <h4>{{encounterCount}}</h4>
</div> </div>
</div><!-- col --> </div><!-- col -->
<div class="col-6 d-sm-flex align-items-center"> <div class="col-6 d-sm-flex align-items-center">
@ -64,7 +64,7 @@
</div> </div>
<div> <div>
<label>All Records</label> <label>All Records</label>
<h4>9,065</h4> <h4>{{recordsCount}}</h4>
</div> </div>
</div><!-- col --> </div><!-- col -->
</div><!-- card-body --> </div><!-- card-body -->
@ -84,7 +84,7 @@
</div> </div>
<div> <div>
<label>Sources</label> <label>Sources</label>
<h4>30</h4> <h4>{{sources.length}}</h4>
</div> </div>
</div> </div>
</div><!-- col --> </div><!-- col -->

View File

@ -3,6 +3,8 @@ import {FastenApiService} from '../../services/fasten-api.service';
import {LighthouseSource} from '../../models/lighthouse/lighthouse-source'; import {LighthouseSource} from '../../models/lighthouse/lighthouse-source';
import {Source} from '../../models/fasten/source'; import {Source} from '../../models/fasten/source';
import {Router} from '@angular/router'; import {Router} from '@angular/router';
import {Summary} from '../../models/fasten/summary';
import {ResourceTypeCounts} from '../../models/fasten/source-summary';
@Component({ @Component({
selector: 'app-dashboard', selector: 'app-dashboard',
@ -12,15 +14,25 @@ import {Router} from '@angular/router';
export class DashboardComponent implements OnInit { export class DashboardComponent implements OnInit {
sources: Source[] = [] sources: Source[] = []
encounterCount: number = 0
recordsCount: number = 0
constructor(private fastenApi: FastenApiService, private router: Router) { } constructor(private fastenApi: FastenApiService, private router: Router) { }
ngOnInit() { ngOnInit() {
this.fastenApi.getSources() this.fastenApi.getSummary()
.subscribe( (sourcesList) => { .subscribe( (summary) => {
console.log(sourcesList); console.log(summary);
this.sources = sourcesList this.sources = summary.sources
//calculate the number of records
summary.resource_type_counts.forEach((resourceTypeInfo) => {
this.recordsCount += resourceTypeInfo.count
if(resourceTypeInfo.resource_type == "Encounter"){
this.encounterCount = resourceTypeInfo.count
}
})
}) })
// this.fastenApi.getResources('Patient') // this.fastenApi.getResources('Patient')

View File

@ -10,6 +10,7 @@ import {Source} from '../models/fasten/source';
import {User} from '../models/fasten/user'; import {User} from '../models/fasten/user';
import {ResourceFhir} from '../models/fasten/resource_fhir'; import {ResourceFhir} from '../models/fasten/resource_fhir';
import {SourceSummary} from '../models/fasten/source-summary'; import {SourceSummary} from '../models/fasten/source-summary';
import {Summary} from '../models/fasten/summary';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -66,6 +67,15 @@ export class FastenApiService {
/* /*
SECURE ENDPOINTS SECURE ENDPOINTS
*/ */
getSummary(): Observable<Summary> {
return this._httpClient.get<any>(`${this.getBasePath()}/api/secure/summary`, )
.pipe(
map((response: ResponseWrapper) => {
console.log("Summary RESPONSE", response)
return response.data as Summary
})
);
}
createSource(source: Source): Observable<Source> { createSource(source: Source): Observable<Source> {
return this._httpClient.post<any>(`${this.getBasePath()}/api/secure/source`, source) return this._httpClient.post<any>(`${this.getBasePath()}/api/secure/source`, source)