diff --git a/backend/pkg/constants.go b/backend/pkg/constants.go index 92328b37..be5af0d3 100644 --- a/backend/pkg/constants.go +++ b/backend/pkg/constants.go @@ -10,6 +10,7 @@ type DatabaseRepositoryType string type InstallationVerificationStatus string type InstallationQuotaStatus string type UserRole string +type Permission string const ( ResourceListPageSize int = 20 @@ -54,4 +55,7 @@ const ( UserRoleUser UserRole = "user" UserRoleAdmin UserRole = "admin" + + PermissionManageSources Permission = "manage_sources" + PermissionRead Permission = "read" ) diff --git a/backend/pkg/database/gorm_common.go b/backend/pkg/database/gorm_common.go index 8fa02e8d..7b8298d3 100644 --- a/backend/pkg/database/gorm_common.go +++ b/backend/pkg/database/gorm_common.go @@ -192,6 +192,104 @@ func (gr *GormRepository) GetUsers(ctx context.Context) ([]models.User, error) { return sanitizedUsers, result.Error } +func (gr *GormRepository) GetUser(ctx context.Context, userID uuid.UUID) (*models.FrontendUser, error) { + var dbUser models.User + var user models.FrontendUser + result := gr.GormClient.WithContext(ctx).First(&dbUser, userID) + if result.Error != nil { + return nil, result.Error + } + + user.ID = dbUser.ID + user.FullName = dbUser.FullName + user.Email = dbUser.Email + user.Username = dbUser.Username + user.Role = dbUser.Role + + // Populate ACLs for the user + var acls []models.UserPermission + if err := gr.GormClient.WithContext(ctx). + Where("user_id = ?", user.ID). + Find(&acls).Error; err != nil { + return nil, err + } + user.Permissions = make(map[string]map[string]bool) + + for _, acl := range acls { + if _, exists := user.Permissions[acl.TargetUserID.String()]; !exists { + user.Permissions[acl.TargetUserID.String()] = make(map[string]bool) + } + user.Permissions[acl.TargetUserID.String()][string(acl.Permission)] = true + } + + return &user, nil +} + +func (gr *GormRepository) UpdateUserAndPermissions(ctx context.Context, user models.FrontendUser) error { + // Lookup user from the db + var dbUser models.User + result := gr.GormClient.WithContext(ctx).First(&dbUser, user.ID) + if result.Error != nil { + return result.Error + } + // Update fields on User + result = gr.GormClient.WithContext(ctx).Model(dbUser).Updates(map[string]interface{}{"full_name": user.FullName, "username": user.Username, "email": user.Email, "role": user.Role}) + if result.Error != nil { + return result.Error + } + // Update User Permissions + var existingPermissions []models.UserPermission + if err := gr.GormClient.WithContext(ctx). + Where("user_id = ?", user.ID). + Find(&existingPermissions).Error; err != nil { + return err + } + for targetUserId, permissions := range user.Permissions { + for permission, value := range permissions { + if !value { + continue + } + // Check if the permission already exists + exists := false + for _, existingPermission := range existingPermissions { + if existingPermission.TargetUserID.String() == targetUserId && string(existingPermission.Permission) == permission { + exists = true + break + } + } + if !exists { + // Add new permission + p := models.UserPermission{ + UserID: user.ID, + TargetUserID: uuid.Must(uuid.Parse(targetUserId)), + Permission: pkg.Permission(permission), + } + err := gr.GormClient.WithContext(ctx).Create(&p).Error + if err != nil { + return err + } + } + } + } + + // Remove permissions that are no longer in user.Permissions + for _, existingPermission := range existingPermissions { + targetUserId := existingPermission.TargetUserID.String() + permission := string(existingPermission.Permission) + + // Check if the permission still exists in the new user.Permissions + if _, exists := user.Permissions[targetUserId]; !exists || !user.Permissions[targetUserId][permission] { + // Permission no longer exists, so delete it + err := gr.GormClient.WithContext(ctx).Delete(&existingPermission).Error + if err != nil { + return err + } + } + } + + return nil +} + // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/backend/pkg/database/gorm_repository_migrations.go b/backend/pkg/database/gorm_repository_migrations.go index db60d0b8..e12c21fc 100644 --- a/backend/pkg/database/gorm_repository_migrations.go +++ b/backend/pkg/database/gorm_repository_migrations.go @@ -11,6 +11,7 @@ import ( _20240114103850 "github.com/fastenhealth/fasten-onprem/backend/pkg/database/migrations/20240114103850" _20240208112210 "github.com/fastenhealth/fasten-onprem/backend/pkg/database/migrations/20240208112210" _20240813222836 "github.com/fastenhealth/fasten-onprem/backend/pkg/database/migrations/20240813222836" + _20240827214347 "github.com/fastenhealth/fasten-onprem/backend/pkg/database/migrations/20240827214347" "github.com/fastenhealth/fasten-onprem/backend/pkg/models" databaseModel "github.com/fastenhealth/fasten-onprem/backend/pkg/models/database" sourceCatalog "github.com/fastenhealth/fasten-sources/catalog" @@ -225,6 +226,20 @@ func (gr *GormRepository) Migrate() error { return nil }, }, + { + ID: "20240827214347", // add UserPermission model + Migrate: func(tx *gorm.DB) error { + + err := tx.AutoMigrate( + &_20240827214347.UserPermission{}, + ) + if err != nil { + return err + } + + return nil + }, + }, }) // run when database is empty diff --git a/backend/pkg/database/interface.go b/backend/pkg/database/interface.go index b4936303..df83e58f 100644 --- a/backend/pkg/database/interface.go +++ b/backend/pkg/database/interface.go @@ -20,6 +20,8 @@ type DatabaseRepository interface { GetCurrentUser(ctx context.Context) (*models.User, error) DeleteCurrentUser(ctx context.Context) error GetUsers(ctx context.Context) ([]models.User, error) + GetUser(ctx context.Context, userId uuid.UUID) (*models.FrontendUser, error) + UpdateUserAndPermissions(ctx context.Context, user models.FrontendUser) error GetSummary(ctx context.Context) (*models.Summary, error) diff --git a/backend/pkg/database/migrations/20240827214347/user_permission.go b/backend/pkg/database/migrations/20240827214347/user_permission.go new file mode 100644 index 00000000..e0337700 --- /dev/null +++ b/backend/pkg/database/migrations/20240827214347/user_permission.go @@ -0,0 +1,20 @@ +package _20240827214347 + +import ( + "github.com/fastenhealth/fasten-onprem/backend/pkg/models" + "github.com/google/uuid" +) + +type Permission string + +const ( + PermissionManageSources Permission = "manage_sources" + PermissionRead Permission = "read" +) + +type UserPermission struct { + models.ModelBase + UserID uuid.UUID `json:"user_id" gorm:"type:uuid"` + TargetUserID uuid.UUID `json:"target_user_id" gorm:"type:uuid"` + Permission Permission `json:"permission"` +} diff --git a/backend/pkg/database/mock/mock_database.go b/backend/pkg/database/mock/mock_database.go index 64dd3a57..c66bcde3 100644 --- a/backend/pkg/database/mock/mock_database.go +++ b/backend/pkg/database/mock/mock_database.go @@ -357,6 +357,21 @@ func (mr *MockDatabaseRepositoryMockRecorder) GetSummary(ctx interface{}) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSummary", reflect.TypeOf((*MockDatabaseRepository)(nil).GetSummary), ctx) } +// GetUser mocks base method. +func (m *MockDatabaseRepository) GetUser(ctx context.Context, userId uuid.UUID) (*models.FrontendUser, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUser", ctx, userId) + ret0, _ := ret[0].(*models.FrontendUser) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUser indicates an expected call of GetUser. +func (mr *MockDatabaseRepositoryMockRecorder) GetUser(ctx, userId interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUser", reflect.TypeOf((*MockDatabaseRepository)(nil).GetUser), ctx, userId) +} + // GetUserByUsername mocks base method. func (m *MockDatabaseRepository) GetUserByUsername(arg0 context.Context, arg1 string) (*models.User, error) { m.ctrl.T.Helper() @@ -575,6 +590,20 @@ func (mr *MockDatabaseRepositoryMockRecorder) UpdateSource(ctx, sourceCreds inte return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateSource", reflect.TypeOf((*MockDatabaseRepository)(nil).UpdateSource), ctx, sourceCreds) } +// UpdateUserAndPermissions mocks base method. +func (m *MockDatabaseRepository) UpdateUserAndPermissions(ctx context.Context, user models.FrontendUser) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateUserAndPermissions", ctx, user) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateUserAndPermissions indicates an expected call of UpdateUserAndPermissions. +func (mr *MockDatabaseRepositoryMockRecorder) UpdateUserAndPermissions(ctx, user interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserAndPermissions", reflect.TypeOf((*MockDatabaseRepository)(nil).UpdateUserAndPermissions), ctx, user) +} + // UpsertRawResource mocks base method. func (m *MockDatabaseRepository) UpsertRawResource(ctx context.Context, sourceCredentials models0.SourceCredential, rawResource models0.RawResourceFhir) (bool, error) { m.ctrl.T.Helper() diff --git a/backend/pkg/models/user.go b/backend/pkg/models/user.go index 3e81b819..1c86b810 100644 --- a/backend/pkg/models/user.go +++ b/backend/pkg/models/user.go @@ -11,14 +11,19 @@ import ( type User struct { ModelBase - FullName string `json:"full_name"` - Username string `json:"username" gorm:"unique"` - Password string `json:"password"` + FullName string `json:"full_name"` + Username string `json:"username" gorm:"unique"` + Password string `json:"password"` + Picture string `json:"picture"` + Email string `json:"email"` + Role pkg.UserRole `json:"role"` +} - //additional optional metadata that Fasten stores with users - Picture string `json:"picture"` - Email string `json:"email"` - Role pkg.UserRole `json:"role"` +// FrontendUser is User with the addition of Permissions arranged +// as we want for sending to and from the frontend +type FrontendUser struct { + User + Permissions map[string]map[string]bool `json:"permissions"` } func (user *User) HashPassword(password string) error { diff --git a/backend/pkg/models/user_permission.go b/backend/pkg/models/user_permission.go new file mode 100644 index 00000000..3d5cd76d --- /dev/null +++ b/backend/pkg/models/user_permission.go @@ -0,0 +1,13 @@ +package models + +import ( + "github.com/fastenhealth/fasten-onprem/backend/pkg" + "github.com/google/uuid" +) + +type UserPermission struct { + ModelBase + UserID uuid.UUID `json:"user_id" gorm:"type:uuid"` + TargetUserID uuid.UUID `json:"target_user_id" gorm:"type:uuid"` + Permission pkg.Permission `json:"permission"` +} diff --git a/backend/pkg/web/handler/users.go b/backend/pkg/web/handler/users.go index 465eba14..025b1577 100644 --- a/backend/pkg/web/handler/users.go +++ b/backend/pkg/web/handler/users.go @@ -8,6 +8,7 @@ import ( "github.com/fastenhealth/fasten-onprem/backend/pkg/database" "github.com/fastenhealth/fasten-onprem/backend/pkg/models" "github.com/gin-gonic/gin" + "github.com/google/uuid" "gorm.io/gorm" ) @@ -54,3 +55,43 @@ func CreateUser(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"success": true, "data": newUser}) } + +func GetUser(c *gin.Context) { + if !IsAdmin(c) { + c.JSON(http.StatusUnauthorized, gin.H{"success": false, "error": "Unauthorized"}) + return + } + + databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository) + + user, err := databaseRepo.GetUser(c, uuid.MustParse(c.Param("userId"))) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()}) + return + } + + c.JSON(200, gin.H{"success": true, "data": user}) +} + +func UpdateUser(c *gin.Context) { + if !IsAdmin(c) { + c.JSON(http.StatusUnauthorized, gin.H{"success": false, "error": "Unauthorized"}) + return + } + + databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository) + + var user models.FrontendUser + if err := c.ShouldBindJSON(&user); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": err.Error()}) + return + } + + err := databaseRepo.UpdateUserAndPermissions(c, user) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()}) + return + } + + c.JSON(200, gin.H{"success": true, "data": user}) +} diff --git a/backend/pkg/web/server.go b/backend/pkg/web/server.go index 54842a8e..a612e7f2 100644 --- a/backend/pkg/web/server.go +++ b/backend/pkg/web/server.go @@ -128,6 +128,8 @@ func (ae *AppEngine) Setup() (*gin.RouterGroup, *gin.Engine) { secure.GET("/users", handler.GetUsers) secure.POST("/users", handler.CreateUser) + secure.GET("/users/:userId", handler.GetUser) + secure.POST("/users/:userId", handler.UpdateUser) //server-side-events handler (only supported on mac/linux) // TODO: causes deadlock on Windows diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index d0e257f1..a60eef18 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -21,6 +21,7 @@ import { ResourceCreatorComponent } from './pages/resource-creator/resource-crea import { ResourceDetailComponent } from './pages/resource-detail/resource-detail.component'; import { SourceDetailComponent } from './pages/source-detail/source-detail.component'; import { UserCreateComponent } from './pages/user-create/user-create.component'; +import { UserEditComponent } from "./pages/user-edit/user-edit.component"; import { UserListComponent } from './pages/user-list/user-list.component'; const routes: Routes = [ @@ -55,6 +56,7 @@ const routes: Routes = [ { path: 'users', component: UserListComponent, canActivate: [ IsAuthenticatedAuthGuard, IsAdminAuthGuard ] }, { path: 'users/new', component: UserCreateComponent, canActivate: [ IsAuthenticatedAuthGuard, IsAdminAuthGuard ] }, + { path: 'users/:user_id', component: UserEditComponent, canActivate: [ IsAuthenticatedAuthGuard, IsAdminAuthGuard ] }, // { path: 'general-pages', loadChildren: () => import('./general-pages/general-pages.module').then(m => m.GeneralPagesModule) }, // { path: 'ui-elements', loadChildren: () => import('./ui-elements/ui-elements.module').then(m => m.UiElementsModule) }, diff --git a/frontend/src/app/models/fasten/user.ts b/frontend/src/app/models/fasten/user.ts index 52739048..58ba6256 100644 --- a/frontend/src/app/models/fasten/user.ts +++ b/frontend/src/app/models/fasten/user.ts @@ -1,8 +1,18 @@ +export const POSSIBLE_PERMISSIONS = [ + { name: 'Manage Sources', value: 'manage_sources' }, + { name: 'Read', value: 'read' }, +] + export class User { - user_id?: number + id?: string full_name?: string username?: string email?: string password?: string role?: string + permissions?: { + [targetUserId: string]: { + [key in typeof POSSIBLE_PERMISSIONS[number]['value']]: boolean; + } + } } diff --git a/frontend/src/app/pages/user-edit/user-edit.component.html b/frontend/src/app/pages/user-edit/user-edit.component.html new file mode 100644 index 00000000..47e9eaa9 --- /dev/null +++ b/frontend/src/app/pages/user-edit/user-edit.component.html @@ -0,0 +1,63 @@ +
+
+
+

Edit User

+ +
+
+ Loading... +
+
+ + + +
+
+ + +
+
+ + +
+
+ + +
+ Warning: This will allow full system access including the ability to manage other users. +
+
+
+ + +
+ +
Access to other Users
+ +
+
+
{{ otherUser.full_name }}
+
+
+ + +
+
+
+
+ + +
+
+
+
diff --git a/frontend/src/app/pages/user-edit/user-edit.component.scss b/frontend/src/app/pages/user-edit/user-edit.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/app/pages/user-edit/user-edit.component.ts b/frontend/src/app/pages/user-edit/user-edit.component.ts new file mode 100644 index 00000000..4f84fef2 --- /dev/null +++ b/frontend/src/app/pages/user-edit/user-edit.component.ts @@ -0,0 +1,91 @@ +import { CommonModule } from '@angular/common'; +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { forkJoin } from 'rxjs'; +import { ToastNotification, ToastType } from '../../models/fasten/toast'; +import { POSSIBLE_PERMISSIONS, User } from '../../models/fasten/user'; +import { AuthService } from '../../services/auth.service'; +import { FastenApiService } from '../../services/fasten-api.service'; +import { ToastService } from '../../services/toast.service'; + +@Component({ + selector: 'app-user-edit', + templateUrl: './user-edit.component.html', + styleUrls: ['./user-edit.component.scss'], + standalone: true, + imports: [CommonModule, ReactiveFormsModule] +}) +export class UserEditComponent implements OnInit { + userForm: FormGroup; + loading = false; + userId: string; + userList: User[]; + errorMessage: string | null = null; + permissionsList = POSSIBLE_PERMISSIONS; + + constructor( + private fb: FormBuilder, + private authService: AuthService, + private fastenApi: FastenApiService, + private toastService: ToastService, + private router: Router, + private route: ActivatedRoute, + ) { } + + ngOnInit(): void { + this.loading = true; + this.userId = this.route.snapshot.paramMap.get('user_id'); + forkJoin([ + this.fastenApi.getAllUsers(), + this.fastenApi.getUser(this.userId) + ]).subscribe(([allUsers, user]) => { + this.userList = allUsers.filter(user => user.id !== this.userId).sort((a, b) => a.full_name.localeCompare(b.full_name)); + this.userForm = this.fb.group({ + full_name: [user.full_name, [Validators.required, Validators.minLength(2)]], + username: [user.username, [Validators.required, Validators.minLength(4)]], + email: [user.email, [Validators.email]], + role: [user.role, Validators.required], + permissions: this.fb.group({}) + }); + this.userList.forEach(otherUser => { + const pfg = (this.userForm.get('permissions') as FormGroup); + pfg.addControl(otherUser.id, this.fb.group({})); + this.permissionsList.forEach(permission => { + const isChecked = user.permissions?.[otherUser.id]?.[permission.value] ?? false; + (pfg.get(otherUser.id) as FormGroup).addControl(permission.value, new FormControl(isChecked)); + }); + }); + this.loading = false; + }, + (error) => { + this.errorMessage = error.message; + this.loading = false; + }); + + } + + onSubmit() { + if (this.userForm.valid) { + this.loading = true; + this.errorMessage = null; + + const user: User = this.userForm.value; + user.id = this.userId; + this.authService.updateUser(user).subscribe( + (response) => { + this.loading = false; + const toastNotification = new ToastNotification(); + toastNotification.type = ToastType.Success; + toastNotification.message = 'User updated successfully'; + this.toastService.show(toastNotification); + this.router.navigate(['/users']); + }, + (error) => { + this.loading = false; + this.errorMessage = 'Error updating user: ' + error.message; + } + ); + } + } +} diff --git a/frontend/src/app/pages/user-list/user-list.component.html b/frontend/src/app/pages/user-list/user-list.component.html index b7b89f46..912645c8 100644 --- a/frontend/src/app/pages/user-list/user-list.component.html +++ b/frontend/src/app/pages/user-list/user-list.component.html @@ -2,11 +2,13 @@

User List

+
Loading...
+ @@ -14,6 +16,7 @@ + @@ -22,6 +25,9 @@ +
Username Email RoleActions
{{ user.username }} {{ user.email }} {{ user.role }} + Edit +
diff --git a/frontend/src/app/pages/user-list/user-list.component.ts b/frontend/src/app/pages/user-list/user-list.component.ts index f3f1dc6d..2345d172 100644 --- a/frontend/src/app/pages/user-list/user-list.component.ts +++ b/frontend/src/app/pages/user-list/user-list.component.ts @@ -24,13 +24,15 @@ export class UserListComponent implements OnInit { loadUsers(): void { this.loading = true; - this.fastenApi.getAllUsers().subscribe((users: User[]) => { - this.users = users; - this.loading = false; - }, - error => { + this.fastenApi.getAllUsers().subscribe( + (users: User[]) => { + this.users = users; + this.loading = false; + }, + (error: Error) => { console.error('Error loading users:', error); this.loading = false; - }); + } + ); } } diff --git a/frontend/src/app/services/auth.service.ts b/frontend/src/app/services/auth.service.ts index 7082ac9d..56ff3ef4 100644 --- a/frontend/src/app/services/auth.service.ts +++ b/frontend/src/app/services/auth.service.ts @@ -146,6 +146,21 @@ export class AuthService { ); } + public updateUser(user: User): Observable { + let fastenApiEndpointBase = GetEndpointAbsolutePath(globalThis.location, environment.fasten_api_endpoint_base); + return this._httpClient.post(`${fastenApiEndpointBase}/secure/users/${user.id}`, user) + .pipe( + catchError((error) => { + if (error.status === 400) { + // Extract error information from the response body + const errorBody = error.error; + return throwError(new Error(errorBody.error || error.message)); + } + return throwError(error); + }) + ); + } + //TODO: now that we've moved to remote-first database, we can refactor and simplify this function significantly. public async IsAuthenticated(): Promise { let authToken = this.GetAuthToken() diff --git a/frontend/src/app/services/fasten-api.service.ts b/frontend/src/app/services/fasten-api.service.ts index 9ceb8f7f..b1c5191c 100644 --- a/frontend/src/app/services/fasten-api.service.ts +++ b/frontend/src/app/services/fasten-api.service.ts @@ -1,33 +1,27 @@ -import {Inject, Injectable} from '@angular/core'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import {Observable, of} from 'rxjs'; -import { Router } from '@angular/router'; -import {map} from 'rxjs/operators'; -import {ResponseWrapper} from '../models/response-wrapper'; -import {Source} from '../models/fasten/source'; -import {User} from '../models/fasten/user'; -import {ResourceFhir} from '../models/fasten/resource_fhir'; -import {SourceSummary} from '../models/fasten/source-summary'; -import {Summary} from '../models/fasten/summary'; -import {AuthService} from './auth.service'; -import {GetEndpointAbsolutePath} from '../../lib/utils/endpoint_absolute_path'; -import {environment} from '../../environments/environment'; -import {ValueSet} from 'fhir/r4'; -import {AttachmentModel} from '../../lib/models/datatypes/attachment-model'; -import {BinaryModel} from '../../lib/models/resources/binary-model'; -import {HTTP_CLIENT_TOKEN} from "../dependency-injection"; -import * as fhirpath from 'fhirpath'; -import _ from 'lodash'; -import {DashboardConfig} from '../models/widget/dashboard-config'; -import {DashboardWidgetQuery} from '../models/widget/dashboard-widget-query'; -import {ResourceGraphResponse} from '../models/fasten/resource-graph-response'; -import { fetchEventSource } from '@microsoft/fetch-event-source'; -import {BackgroundJob, BackgroundJobSyncData} from '../models/fasten/background-job'; -import {SupportRequest} from '../models/fasten/support-request'; -import { - List -} from 'fhir/r4'; -import {FormRequestHealthSystem} from '../models/fasten/form-request-health-system'; +import { HttpClient } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { List, ValueSet } from 'fhir/r4'; +import { Observable, of } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; +import { environment } from '../../environments/environment'; +import { AttachmentModel } from '../../lib/models/datatypes/attachment-model'; +import { BinaryModel } from '../../lib/models/resources/binary-model'; +import { GetEndpointAbsolutePath } from '../../lib/utils/endpoint_absolute_path'; +import { HTTP_CLIENT_TOKEN } from "../dependency-injection"; +import { BackgroundJob, BackgroundJobSyncData } from '../models/fasten/background-job'; +import { FormRequestHealthSystem } from '../models/fasten/form-request-health-system'; +import { ResourceGraphResponse } from '../models/fasten/resource-graph-response'; +import { ResourceFhir } from '../models/fasten/resource_fhir'; +import { Source } from '../models/fasten/source'; +import { SourceSummary } from '../models/fasten/source-summary'; +import { Summary } from '../models/fasten/summary'; +import { SupportRequest } from '../models/fasten/support-request'; +import { User } from '../models/fasten/user'; +import { ResponseWrapper } from '../models/response-wrapper'; +import { DashboardConfig } from '../models/widget/dashboard-config'; +import { DashboardWidgetQuery } from '../models/widget/dashboard-widget-query'; +import { AuthService } from './auth.service'; @Injectable({ providedIn: 'root' }) @@ -368,4 +362,17 @@ export class FastenApiService { ); } + getUser(userId: string): Observable { + return this._httpClient.get(`${GetEndpointAbsolutePath(globalThis.location, environment.fasten_api_endpoint_base)}/secure/users/${userId}`) + .pipe( + map((response: ResponseWrapper) => { + if (!response.success) { + throw new Error(response.error) + } + return response.data as User + }), + // catchError(() => of(null)) + ); + } + }