fix: acc state

This commit is contained in:
2025-06-13 12:24:05 +02:00
parent 4aedf3a8c3
commit 14f355df53
8 changed files with 156 additions and 34 deletions

View File

@@ -5,7 +5,7 @@ import { MatPaginator } from '@angular/material/paginator'
import { UserEditComponent } from './user-edit/user-edit.component'
import { LocalStorageService } from 'src/app/services/local-storage.service'
import { Group } from 'src/app/types/group'
import User from 'src/app/types/user'
import { User } from 'src/app/admin-view/account-mgmt/account.model'
import { AccountMgmtService } from './account-mgmt.service'
import { STATE } from 'src/app/types/state'
@@ -17,7 +17,7 @@ import { STATE } from 'src/app/types/state'
})
export class AccountMgmtComponent implements AfterViewInit {
protected groups: Group[] = []
users: MatTableDataSource<Omit<User, 'pass'>>
users: MatTableDataSource<User>
@ViewChild(MatPaginator) paginator!: MatPaginator
constructor(
@@ -25,7 +25,7 @@ export class AccountMgmtComponent implements AfterViewInit {
private dialog: MatDialog,
protected readonly ls: LocalStorageService
) {
this.users = new MatTableDataSource<Omit<User, 'pass'>>()
this.users = new MatTableDataSource<User>()
this.users.filterPredicate = (
data: Record<string, any>,
filter: string

View File

@@ -4,6 +4,7 @@ import { AccountMgmtService } from './account-mgmt.service';
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { environment } from 'src/environments/environment';
import { firstValueFrom, skip } from 'rxjs';
describe('AccountMgmtService', () => {
let service: AccountMgmtService;
@@ -26,7 +27,7 @@ describe('AccountMgmtService', () => {
it('should get user accounts', () => {
service.refresh()
const req = httpTesting.expectOne(environment.apiEndpoint+"/admin/accs", "Request to load all users")
const req = httpTesting.expectOne(environment.apiEndpoint + "/admin/accs", "Request to load all users")
expect(req.request.method).toBe("GET")
@@ -34,10 +35,62 @@ describe('AccountMgmtService', () => {
httpTesting.verify()
})
it('should create a user account and refresh list', () => {
service.postAcc({
uname: "test",
groups: []
describe('create user', () => {
xit('should create a user account and refresh list', () => {
const test_user = {
uname: "test",
groups: []
}
service.postAcc(test_user).subscribe(v => {
expect(v).toEqual(jasmine.objectContaining(test_user))
})
const req = httpTesting.expectOne(environment.apiEndpoint + "/admin/accs", "Request new user")
expect(req.request.method).toBe("POST")
req.flush({
...test_user,
_id: "test_id"
})
const req2 = httpTesting.expectOne(environment.apiEndpoint + "/admin/accs", "Request to load all users")
expect(req2.request.method).toBe("GET")
// service.accs.pipe(skip(1)).subscribe(v => {
// expect(v).toContain(createdUser)
// })
req2.flush([
{
...test_user,
_id: "test_id"
}
])
httpTesting.verify()
})
})
describe("delete user", () => {
it('should refresh accounts and not to contain deleted user', async () => {
service.deleteAcc("test").subscribe()
const req = httpTesting.expectOne(environment.apiEndpoint + "/admin/accs/test", "Request delete user")
expect(req.request.method).toBe("DELETE")
req.flush({ status: 200 })
const req2 = httpTesting.expectOne(environment.apiEndpoint + "/admin/accs", "Request to load all users")
expect(req2.request.method).toBe("GET")
service.accs.pipe(skip(1)).subscribe(v => {
expect(v).not.toContain(jasmine.objectContaining({ _id: "test" }))
})
req2.flush([])
httpTesting.verify()
})
})
});

View File

@@ -1,11 +1,11 @@
import { HttpClient } from '@angular/common/http';
import { Injectable, signal } from '@angular/core';
import { BehaviorSubject, catchError, of } from 'rxjs';
import { Group } from 'src/app/types/group';
import { BehaviorSubject, catchError, map, of, tap } from 'rxjs';
import { STATE } from 'src/app/types/state';
import { Status } from 'src/app/types/status';
import User from 'src/app/types/user';
import { User, UserAPI } from 'src/app/admin-view/account-mgmt/account.model';
import { environment } from 'src/environments/environment';
import { DateTime } from 'luxon';
@Injectable({
providedIn: 'root'
@@ -14,12 +14,14 @@ export class AccountMgmtService {
constructor(private http: HttpClient) { }
private _accs = new BehaviorSubject<Omit<User, 'pass'>[]>([])
private _accs = new BehaviorSubject<User[]>([])
public readonly accs = this._accs.asObservable()
private _state = signal(STATE.NOT_LOADED);
public readonly state = this._state.asReadonly();
private _error = signal<string | undefined>(undefined);
public readonly error = this._error.asReadonly();
private _selAcc = new BehaviorSubject<User | null>(null)
public readonly selAcc = this._selAcc.asObservable()
public refresh() {
this.getAccs()
@@ -27,28 +29,41 @@ export class AccountMgmtService {
private getAccs() {
this._state.set(STATE.PENDING)
this.http.get<{
users: Omit<User, 'pass'>[]
groups: Group[]
}>(environment.apiEndpoint + `/admin/accs`, { withCredentials: true })
.pipe(catchError((err: Error) => {
this._state.set(STATE.ERROR)
this._error.set(err.message)
return of()
}))
.subscribe(v => {
this.http.get
<UserAPI[]>
(environment.apiEndpoint + `/admin/accs`, { withCredentials: true })
.pipe(
catchError((err: Error) => {
this._state.set(STATE.ERROR)
this._error.set(err.message)
return of()
}),
map<UserAPI[], User[]>(v => {
return v.map(i => ({
...i,
regDate: DateTime.fromISO(i.regDate)
}))
})
).subscribe(v => {
this._error.set(undefined)
this._accs.next(v.users ?? [])
this._accs.next(v ?? [])
this._state.set(STATE.LOADED)
})
}
postAcc(item: Omit<User, "pass" | "_id" | "regDate">) {
return this.http.post<Omit<User, "pass">>(
selectAccount(acc: User) {
this._selAcc.next(acc)
}
//#region legacy
postAcc(item: Omit<User, "_id" | "regDate">) {
return this.http.post<User>(
environment.apiEndpoint + `/admin/accs`,
item,
{ withCredentials: true }
)
).pipe(tap(v => {
if (v instanceof Array) this.refresh()
}))
}
putAcc(id: string, update: Partial<User>) {
@@ -72,11 +87,14 @@ export class AccountMgmtService {
environment.apiEndpoint + `/admin/accs/${id}`,
{ withCredentials: true }
)
.pipe(tap(v => {
if (v.status == 200) this.refresh()
}))
}
getUser(id: string) {
return this.http.get<
Omit<User, 'pass' | 'regDate'> & { lockout: boolean; regDate: string }
Omit<User, 'regDate'> & { lockout: boolean; regDate: string }
>(environment.apiEndpoint + `/admin/accs/${id}`, {
withCredentials: true,
})
@@ -88,5 +106,5 @@ export class AccountMgmtService {
{ withCredentials: true }
)
}
//#endregion
}

View File

@@ -1,9 +1,8 @@
import { DateTime } from 'luxon'
export default interface User {
export interface User {
_id: string
uname: string
pass: string
room?: string
admin?: number
locked?: boolean
@@ -13,3 +12,5 @@ export default interface User {
regDate: DateTime
defaultPage?: string
}
export type UserAPI = Omit<User, "regDate"> & {regDate: "string"}

View File

@@ -17,7 +17,7 @@
<mat-form-field appearance="outline" color="accent">
<mat-label>Grupy</mat-label>
<mat-select multiple formControlName="groups">
@for (item of groups; track $index) {
@for (item of adsyn.groups; track $index) {
<mat-option [value]="item._id">{{item.name}}</mat-option>
}
</mat-select>

View File

@@ -13,6 +13,7 @@ import { UserResetComponent } from '../user-reset/user-reset.component'
import { catchError, throwError } from 'rxjs'
import { DateTime } from 'luxon'
import { AccountMgmtService } from '../account-mgmt.service'
import { AdminSyncService } from '../../admin-sync.service'
export namespace UserEditComponent {
export type InputData = { type: 'new' | 'edit'; id?: string; groups: Group[] }
@@ -37,7 +38,6 @@ export class UserEditComponent {
groups: new FormControl<Array<string>>([]),
flags: new FormControl<Array<number>>([]),
})
groups: Group[]
id?: string
regDate?: DateTime
constructor(
@@ -46,9 +46,9 @@ export class UserEditComponent {
readonly ls: LocalStorageService,
readonly acu: AccountMgmtService,
private dialog: MatDialog,
private sb: MatSnackBar
private sb: MatSnackBar,
protected adsyn: AdminSyncService
) {
this.groups = data.groups
if (data.type == 'edit') {
this.id = data.id
this.acu.getUser(data.id!).subscribe(r => {

View File

@@ -0,0 +1,23 @@
import { TestBed } from '@angular/core/testing';
import { AdminSyncService } from './admin-sync.service';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
describe('AdminSyncService', () => {
let service: AdminSyncService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
provideHttpClient(),
provideHttpClientTesting()
]
});
service = TestBed.inject(AdminSyncService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,27 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Group } from '../types/group';
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root'
})
export class AdminSyncService {
constructor(private http: HttpClient) { }
private _data: any
private sync() {
this.http.get(environment.apiEndpoint + `/admin/sync`, { withCredentials: true }).subscribe(v => {
this._data = v
})
}
public get groups(): Group[] {
var groups = this._data?.groups
if (!groups) this.sync()
return groups
}
}