Files
LoreMind/web/src/app/services/license.service.ts
IETM_FIXE\ietm6 759e47fc1f
Some checks failed
E2E Tests / e2e (push) Failing after 21s
Build & Push Images / build (brain) (push) Successful in 1m0s
Build & Push Images / build (core) (push) Successful in 1m34s
Build & Push Images / build-switcher (push) Successful in 39s
Build & Push Images / build (web) (push) Successful in 1m40s
Mise à jour vers 0.8.5 ; ajout de la bascule entre le canal bêta et le canal stable
2026-05-19 18:05:17 +02:00

132 lines
4.1 KiB
TypeScript

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, catchError, of } from 'rxjs';
/**
* Reflet de LicenseStatus (enum cote backend).
*/
export type LicenseStatus = 'NONE' | 'VALID' | 'GRACE' | 'EXPIRED' | 'UNVERIFIABLE';
export interface LicenseStatusDTO {
enabled: boolean;
status: LicenseStatus;
patreonUserId: string | null;
tierId: string | null;
instanceId: string | null;
expiresAt: string | null;
lastRefreshAttemptAt: string | null;
lastRefreshSucceeded: boolean | null;
betaChannelEnabled: boolean;
}
/**
* Etat du canal courant + dernier resultat de bascule (cf. ChannelStatusDTO cote backend).
*/
export type ChannelName = 'stable' | 'beta';
export type SwitchStatus = 'IN_PROGRESS' | 'SUCCESS' | 'ERROR';
export interface ChannelStatusDTO {
currentChannel: ChannelName;
switcherAvailable: boolean;
lastSwitch: {
id: string;
status: SwitchStatus;
channel: ChannelName;
message: string;
completedAt: string;
} | null;
}
/**
* Reflet de UpdateCheckService.BetaStatus.
*/
export interface BetaStatusDTO {
enabled: boolean;
updateAvailable: boolean;
anyUnknown: boolean;
images: Array<{
image: string;
localVersion: string | null;
remoteVersion: string | null;
status: 'UP_TO_DATE' | 'UPDATE_AVAILABLE' | 'UNKNOWN';
updateAvailable: boolean;
}>;
checkedAt: string;
disabledReason: string | null;
}
/**
* Service Angular pour la gestion de la licence Patreon.
* Tous les endpoints sont proteges par HTTP Basic (admin).
*/
@Injectable({ providedIn: 'root' })
export class LicenseService {
private readonly apiUrl = '/api/license';
private readonly authOptions = { withCredentials: true };
constructor(private http: HttpClient) {}
getStatus(): Observable<LicenseStatusDTO | null> {
return this.http.get<LicenseStatusDTO>(this.apiUrl, this.authOptions).pipe(
catchError(() => of(null))
);
}
getConnectUrl(): Observable<{ url: string } | null> {
return this.http.get<{ url: string }>(`${this.apiUrl}/connect-url`, this.authOptions).pipe(
catchError(() => of(null))
);
}
install(jwt: string): Observable<LicenseStatusDTO | { error: string }> {
return this.http.post<LicenseStatusDTO>(`${this.apiUrl}/install`, { jwt }, this.authOptions).pipe(
catchError((err) => of({ error: err?.error?.error ?? 'Echec de l\'installation' }))
);
}
disconnect(): Observable<boolean> {
return this.http.delete<void>(this.apiUrl, this.authOptions).pipe(
// Convertit en boolean : true = succes, false = erreur
// (catchError plus bas masque les detail HTTP)
catchError(() => of(false as any))
) as unknown as Observable<boolean>;
}
refresh(): Observable<LicenseStatusDTO | null> {
return this.http.post<LicenseStatusDTO>(`${this.apiUrl}/refresh`, null, this.authOptions).pipe(
catchError(() => of(null))
);
}
setBetaChannel(enabled: boolean): Observable<LicenseStatusDTO | null> {
return this.http.put<LicenseStatusDTO>(`${this.apiUrl}/beta-channel`, { enabled }, this.authOptions).pipe(
catchError(() => of(null))
);
}
checkBeta(): Observable<BetaStatusDTO | null> {
return this.http.get<BetaStatusDTO>('/api/admin/updates/check-beta', this.authOptions).pipe(
catchError(() => of(null))
);
}
/** Etat du canal courant et dernier resultat de switch (pour polling UI). */
getChannelStatus(): Observable<ChannelStatusDTO | null> {
return this.http.get<ChannelStatusDTO>(`${this.apiUrl}/channel`, this.authOptions).pipe(
catchError(() => of(null))
);
}
/**
* Declenche un switch de canal. 202 + { id, channel } si accepte,
* sinon erreur (403 = pas de licence, 503 = sidecar indispo, etc.).
*/
switchChannel(channel: ChannelName): Observable<{ id: string; channel: ChannelName } | { error: string }> {
return this.http.post<{ id: string; channel: ChannelName }>(
`${this.apiUrl}/channel/switch`, { channel }, this.authOptions
).pipe(
catchError((err) => of({ error: err?.error?.error ?? 'Echec du switch de canal' }))
);
}
}