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
132 lines
4.1 KiB
TypeScript
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' }))
|
|
);
|
|
}
|
|
}
|