feat: create ApiService and TokenService to refactor and delegate functions from other services
This commit is contained in:
parent
8a2ed1a733
commit
b5e0f52822
6 changed files with 209 additions and 204 deletions
47
frontend/src/app/services/api.service.ts
Normal file
47
frontend/src/app/services/api.service.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { TokenService } from './token.service';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ApiService {
|
||||
constructor(private http: HttpClient, private tokenService: TokenService) {}
|
||||
|
||||
private getAuthHeaders(): HttpHeaders {
|
||||
const token = this.tokenService.getToken();
|
||||
if (!token) {
|
||||
this.tokenService.clearSession();
|
||||
}
|
||||
return new HttpHeaders({ Authorization: `Token ${token}` });
|
||||
}
|
||||
|
||||
get<T>(endpoint: string, auth: boolean = false): Observable<T> {
|
||||
const headers = auth ? { headers: this.getAuthHeaders() } : {};
|
||||
return this.http
|
||||
.get<T>(`${environment.baseUrl}${endpoint}`, headers)
|
||||
.pipe(catchError(this.handleError));
|
||||
}
|
||||
|
||||
post<T>(endpoint: string, body: any, auth: boolean = false): Observable<T> {
|
||||
const headers = auth ? { headers: this.getAuthHeaders() } : {};
|
||||
return this.http
|
||||
.post<T>(`${environment.baseUrl}${endpoint}`, body, headers)
|
||||
.pipe(catchError(this.handleError));
|
||||
}
|
||||
|
||||
put<T>(endpoint: string, body: any, auth: boolean = false): Observable<T> {
|
||||
const headers = auth ? { headers: this.getAuthHeaders() } : {};
|
||||
return this.http
|
||||
.put<T>(`${environment.baseUrl}${endpoint}`, body, headers)
|
||||
.pipe(catchError(this.handleError));
|
||||
}
|
||||
|
||||
private handleError(error: any) {
|
||||
console.error('API error:', error);
|
||||
return throwError(() => error);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,69 @@
|
|||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { catchError, lastValueFrom, map, Observable, of } from 'rxjs';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { catchError, firstValueFrom, map, Observable, of } from 'rxjs';
|
||||
import { ApiService } from './api.service';
|
||||
import { UserService } from './user.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { TokenService } from './token.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthService {
|
||||
errorMsg: string | null = null;
|
||||
passwordFieldType: string = 'password';
|
||||
passwordIcon: string = './assets/img/close-eye.svg';
|
||||
passwordFieldType = 'password';
|
||||
passwordIcon = './assets/img/close-eye.svg';
|
||||
|
||||
constructor(private http: HttpClient, private userService: UserService) {}
|
||||
constructor(
|
||||
private router: Router,
|
||||
private apiService: ApiService,
|
||||
private userService: UserService,
|
||||
private tokenService: TokenService
|
||||
) {}
|
||||
|
||||
async login(body: any, storage: boolean) {
|
||||
const data = await firstValueFrom(
|
||||
this.apiService.post<{ token: string }>('/auth/login/', body)
|
||||
);
|
||||
if (data?.token) {
|
||||
this.tokenService.setToken(data.token, storage);
|
||||
} else {
|
||||
throw new Error('Login failed: No token received');
|
||||
}
|
||||
}
|
||||
|
||||
async logout() {
|
||||
await firstValueFrom(this.apiService.post('/auth/logout/', null, true));
|
||||
}
|
||||
|
||||
async register(body: any) {
|
||||
await firstValueFrom(this.apiService.post('/auth/register/', body));
|
||||
}
|
||||
|
||||
async verifyEmail(body: any) {
|
||||
await firstValueFrom(this.apiService.post('/auth/verify-email/', body));
|
||||
}
|
||||
|
||||
async forgotPassword(body: any) {
|
||||
await firstValueFrom(this.apiService.post('/auth/forgot-password/', body));
|
||||
}
|
||||
|
||||
async changePassword(body: any) {
|
||||
await firstValueFrom(this.apiService.post('/auth/change-password/', body));
|
||||
}
|
||||
|
||||
async checkAuthUserMail(body: any) {
|
||||
await firstValueFrom(this.apiService.post('/auth/', body));
|
||||
}
|
||||
|
||||
checkAuthUser(): Observable<boolean> {
|
||||
return this.apiService.get<any>('/auth/', true).pipe(
|
||||
map((response) => {
|
||||
this.userService.currentUserId = response;
|
||||
return true;
|
||||
}),
|
||||
catchError(() => of(false))
|
||||
);
|
||||
}
|
||||
|
||||
togglePasswordVisibility() {
|
||||
this.passwordFieldType =
|
||||
|
|
@ -26,76 +77,4 @@ export class AuthService {
|
|||
? './assets/img/open-eye.svg'
|
||||
: './assets/img/close-eye.svg';
|
||||
}
|
||||
|
||||
async register(body: any) {
|
||||
await lastValueFrom(
|
||||
this.http.post(`${environment.baseUrl}/auth/register/`, body)
|
||||
);
|
||||
}
|
||||
|
||||
async login(body: any, storage: boolean) {
|
||||
const data = (await lastValueFrom(
|
||||
this.http.post(`${environment.baseUrl}/auth/login/`, body)
|
||||
)) as { token: string };
|
||||
this.storeAuthToken(data.token, storage);
|
||||
}
|
||||
|
||||
async logout() {
|
||||
const headers = this.getAuthHeaders();
|
||||
await lastValueFrom(
|
||||
this.http.post(`${environment.baseUrl}/auth/logout/`, null, { headers })
|
||||
);
|
||||
}
|
||||
|
||||
async verifyEmail(body: any) {
|
||||
await lastValueFrom(
|
||||
this.http.post(`${environment.baseUrl}/auth/verify-email/`, body)
|
||||
);
|
||||
}
|
||||
|
||||
async forgotPassword(body: any) {
|
||||
await lastValueFrom(
|
||||
this.http.post(`${environment.baseUrl}/auth/forgot-password/`, body)
|
||||
);
|
||||
}
|
||||
|
||||
async changePassword(body: any) {
|
||||
await lastValueFrom(
|
||||
this.http.post(`${environment.baseUrl}/auth/change-password/`, body)
|
||||
);
|
||||
}
|
||||
|
||||
storeAuthToken(data: any, storage: boolean) {
|
||||
storage
|
||||
? localStorage.setItem('authToken', data.toString())
|
||||
: sessionStorage.setItem('authToken', data.toString());
|
||||
}
|
||||
|
||||
checkAuthUser(): Observable<boolean> {
|
||||
const headers = this.getAuthHeaders();
|
||||
return this.http.get<any>(`${environment.baseUrl}/auth/`, { headers }).pipe(
|
||||
map((response) => {
|
||||
this.saveUserId(response);
|
||||
return true;
|
||||
}),
|
||||
catchError(() => of(false))
|
||||
);
|
||||
}
|
||||
|
||||
saveUserId(userId: string): void {
|
||||
this.userService.currentUserId = userId;
|
||||
}
|
||||
|
||||
async checkAuthUserMail(body: any) {
|
||||
await lastValueFrom(this.http.post(`${environment.baseUrl}/auth/`, body));
|
||||
}
|
||||
|
||||
private getAuthHeaders(): HttpHeaders {
|
||||
let authToken =
|
||||
localStorage.getItem('authToken') || sessionStorage.getItem('authToken');
|
||||
|
||||
return new HttpHeaders({
|
||||
Authorization: `Token ${authToken}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, lastValueFrom, Observable } from 'rxjs';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { Router } from '@angular/router';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
catchError,
|
||||
firstValueFrom,
|
||||
map,
|
||||
Observable,
|
||||
of,
|
||||
} from 'rxjs';
|
||||
import { ApiService } from './api.service';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
|
|
@ -10,109 +16,47 @@ import { Router } from '@angular/router';
|
|||
export class MovieService {
|
||||
private movieCache: { [key: number]: { [resolution: string]: boolean } } = {};
|
||||
|
||||
constructor(private http: HttpClient, private router: Router) {}
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private authService: AuthService
|
||||
) {}
|
||||
|
||||
getAllMovies(): Promise<any> {
|
||||
const url = environment.baseUrl + '/video/';
|
||||
const headers = this.getAuthHeaders();
|
||||
return lastValueFrom(this.http.get(url, { headers }));
|
||||
return firstValueFrom(this.apiService.get('/video/', true));
|
||||
}
|
||||
|
||||
getMovieFiles(videoUrl: number): Promise<any> {
|
||||
const url = environment.baseUrl + `${videoUrl}`;
|
||||
const headers = this.getAuthHeaders();
|
||||
return lastValueFrom(this.http.get(url, { headers }));
|
||||
return firstValueFrom(this.apiService.get(`/${videoUrl}`, true));
|
||||
}
|
||||
|
||||
uploadMovie(formData: FormData) {
|
||||
const url = environment.baseUrl + '/video/upload/';
|
||||
const headers = this.getAuthHeaders();
|
||||
return lastValueFrom(this.http.post(url, formData, { headers }));
|
||||
uploadMovie(formData: FormData): Promise<any> {
|
||||
return firstValueFrom(
|
||||
this.apiService.post('/video/upload/', formData, true)
|
||||
);
|
||||
}
|
||||
|
||||
private getAuthHeaders(): HttpHeaders {
|
||||
let authToken =
|
||||
localStorage.getItem('authToken') || sessionStorage.getItem('authToken');
|
||||
|
||||
if (!authToken) {
|
||||
this.logout();
|
||||
}
|
||||
|
||||
return new HttpHeaders({
|
||||
Authorization: `Token ${authToken}`,
|
||||
});
|
||||
}
|
||||
|
||||
private logout(): void {
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a movie is uploaded in multiple resolutions.
|
||||
*
|
||||
* @param {number} videoID - The ID of the movie to check.
|
||||
* @returns {Observable<{ [resolution: string]: boolean }>} Observable emitting an object with the availability of each resolution.
|
||||
*/
|
||||
isMovieResolutionUploaded(
|
||||
videoID: number
|
||||
): Observable<{ [resolution: string]: boolean }> {
|
||||
if (this.movieCache.hasOwnProperty(videoID)) {
|
||||
const cachedResolutions = this.movieCache[videoID];
|
||||
|
||||
const missingResolutions = Object.keys(cachedResolutions).filter(
|
||||
(res) => !cachedResolutions[res]
|
||||
);
|
||||
|
||||
if (missingResolutions.length === 0) {
|
||||
return new BehaviorSubject(cachedResolutions).asObservable();
|
||||
} else {
|
||||
return this.fetchAndCacheResolutions(videoID);
|
||||
}
|
||||
} else {
|
||||
return this.fetchAndCacheResolutions(videoID);
|
||||
}
|
||||
const cachedRes = this.movieCache[videoID];
|
||||
if (cachedRes && Object.values(cachedRes).every(Boolean)) {
|
||||
return new BehaviorSubject(cachedRes).asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the availability of multiple resolutions for a specific movie.
|
||||
*
|
||||
* @param {number} videoID - The ID of the movie to fetch resolutions for.
|
||||
* @returns {Observable<{ [resolution: string]: boolean }>} Observable emitting an object with the availability of each resolution.
|
||||
*/
|
||||
private fetchAndCacheResolutions(
|
||||
videoID: number
|
||||
): Observable<{ [resolution: string]: boolean }> {
|
||||
const url = `${environment.baseUrl}/video/movie/${videoID}/`;
|
||||
const headers = this.getAuthHeaders();
|
||||
|
||||
return new Observable<{ [resolution: string]: boolean }>((observer) => {
|
||||
this.http.get(url, { headers }).subscribe(
|
||||
(response: any) => {
|
||||
return this.apiService.get(`/video/movie/${videoID}/`, true).pipe(
|
||||
map((res: any) => {
|
||||
const resolutions = {
|
||||
'360': response['360'] || false,
|
||||
'720': response['720'] || false,
|
||||
'1080': response['1080'] || false,
|
||||
'360': res['360'] || false,
|
||||
'720': res['720'] || false,
|
||||
'1080': res['1080'] || false,
|
||||
};
|
||||
this.movieCache[videoID] = resolutions;
|
||||
observer.next(resolutions);
|
||||
observer.complete();
|
||||
},
|
||||
(error) => {
|
||||
if (error.status === 401) {
|
||||
this.logout();
|
||||
}
|
||||
observer.next(
|
||||
this.movieCache[videoID] || {
|
||||
'360': false,
|
||||
'720': false,
|
||||
'1080': false,
|
||||
}
|
||||
return resolutions;
|
||||
}),
|
||||
catchError(() => {
|
||||
this.authService.logout();
|
||||
return of({ '360': false, '720': false, '1080': false });
|
||||
})
|
||||
);
|
||||
observer.complete();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
37
frontend/src/app/services/token.service.ts
Normal file
37
frontend/src/app/services/token.service.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class TokenService {
|
||||
private readonly TOKEN_KEY = 'authToken';
|
||||
|
||||
constructor(private router: Router) {}
|
||||
|
||||
setToken(token: string, storage: boolean): void {
|
||||
storage
|
||||
? localStorage.setItem(this.TOKEN_KEY, token)
|
||||
: sessionStorage.setItem(this.TOKEN_KEY, token);
|
||||
}
|
||||
|
||||
getToken(): string | null {
|
||||
const tokenFromSession = sessionStorage.getItem(this.TOKEN_KEY);
|
||||
if (tokenFromSession) {
|
||||
return tokenFromSession;
|
||||
}
|
||||
|
||||
const tokenFromLocal = localStorage.getItem(this.TOKEN_KEY);
|
||||
return tokenFromLocal;
|
||||
}
|
||||
|
||||
removeToken(): void {
|
||||
localStorage.removeItem(this.TOKEN_KEY);
|
||||
sessionStorage.removeItem(this.TOKEN_KEY);
|
||||
}
|
||||
|
||||
clearSession(): void {
|
||||
this.removeToken();
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { ApiService } from './api.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
|
|
@ -9,33 +8,31 @@ import { lastValueFrom } from 'rxjs';
|
|||
export class UserService {
|
||||
currentUserId: string | null = null;
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
constructor(private apiService: ApiService) {}
|
||||
|
||||
async getLikedAndWatchedMovies(): Promise<any> {
|
||||
const url = environment.baseUrl + `/users/${this.currentUserId}/`;
|
||||
const headers = this.getAuthHeaders();
|
||||
return lastValueFrom(this.http.get(url, { headers }));
|
||||
getLikedAndWatchedMovies(): Promise<any> {
|
||||
return firstValueFrom(
|
||||
this.apiService.get(`/users/${this.currentUserId}/`, true)
|
||||
);
|
||||
}
|
||||
|
||||
updateLikedMovies(likedMovies: any) {
|
||||
const url = `${environment.baseUrl}/users/liked/${this.currentUserId}/`;
|
||||
const headers = this.getAuthHeaders();
|
||||
return lastValueFrom(this.http.put(url, likedMovies, { headers }));
|
||||
updateLikedMovies(likedMovies: any): Promise<any> {
|
||||
return firstValueFrom(
|
||||
this.apiService.put(
|
||||
`/users/liked/${this.currentUserId}/`,
|
||||
likedMovies,
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
updateWatchedMovies(watchedMovies: any) {
|
||||
const url = `${environment.baseUrl}/users/watched/${this.currentUserId}/`;
|
||||
const headers = this.getAuthHeaders();
|
||||
return lastValueFrom(this.http.put(url, watchedMovies, { headers }));
|
||||
}
|
||||
|
||||
private getAuthHeaders(): HttpHeaders {
|
||||
let authToken = localStorage.getItem('authToken');
|
||||
if (!authToken) {
|
||||
authToken = sessionStorage.getItem('authToken');
|
||||
}
|
||||
return new HttpHeaders({
|
||||
Authorization: `Token ${authToken}`,
|
||||
});
|
||||
updateWatchedMovies(watchedMovies: any): Promise<any> {
|
||||
return firstValueFrom(
|
||||
this.apiService.put(
|
||||
`/users/watched/${this.currentUserId}/`,
|
||||
watchedMovies,
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { BtnLargeComponent } from '../buttons/btn-large/btn-large.component';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { AuthService } from '../../../services/auth.service';
|
||||
import { UserService } from '../../../services/user.service';
|
||||
import { TokenService } from '../../../services/token.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-header',
|
||||
|
|
@ -15,7 +15,10 @@ export class HeaderComponent {
|
|||
@Input() showFullLogo: boolean = true;
|
||||
@Output() moviesChange = new EventEmitter<any[]>();
|
||||
|
||||
constructor(private router: Router, private authService: AuthService) {}
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private tokenService: TokenService
|
||||
) {}
|
||||
|
||||
backToOverview(newMovies: any[]) {
|
||||
this.moviesChange.emit(newMovies);
|
||||
|
|
@ -24,9 +27,7 @@ export class HeaderComponent {
|
|||
async logout() {
|
||||
try {
|
||||
await this.authService.logout();
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
this.router.navigate(['/']);
|
||||
this.tokenService.clearSession();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue