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 { Injectable } from '@angular/core';
|
||||||
import { catchError, lastValueFrom, map, Observable, of } from 'rxjs';
|
import { catchError, firstValueFrom, map, Observable, of } from 'rxjs';
|
||||||
import { environment } from '../../environments/environment';
|
import { ApiService } from './api.service';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { TokenService } from './token.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
errorMsg: string | null = null;
|
errorMsg: string | null = null;
|
||||||
passwordFieldType: string = 'password';
|
passwordFieldType = 'password';
|
||||||
passwordIcon: string = './assets/img/close-eye.svg';
|
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() {
|
togglePasswordVisibility() {
|
||||||
this.passwordFieldType =
|
this.passwordFieldType =
|
||||||
|
|
@ -26,76 +77,4 @@ export class AuthService {
|
||||||
? './assets/img/open-eye.svg'
|
? './assets/img/open-eye.svg'
|
||||||
: './assets/img/close-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 { Injectable } from '@angular/core';
|
||||||
import { BehaviorSubject, lastValueFrom, Observable } from 'rxjs';
|
import {
|
||||||
import { environment } from '../../environments/environment';
|
BehaviorSubject,
|
||||||
import { Router } from '@angular/router';
|
catchError,
|
||||||
|
firstValueFrom,
|
||||||
|
map,
|
||||||
|
Observable,
|
||||||
|
of,
|
||||||
|
} from 'rxjs';
|
||||||
|
import { ApiService } from './api.service';
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
|
|
@ -10,109 +16,47 @@ import { Router } from '@angular/router';
|
||||||
export class MovieService {
|
export class MovieService {
|
||||||
private movieCache: { [key: number]: { [resolution: string]: boolean } } = {};
|
private movieCache: { [key: number]: { [resolution: string]: boolean } } = {};
|
||||||
|
|
||||||
constructor(private http: HttpClient, private router: Router) {}
|
constructor(
|
||||||
|
private apiService: ApiService,
|
||||||
|
private authService: AuthService
|
||||||
|
) {}
|
||||||
|
|
||||||
getAllMovies(): Promise<any> {
|
getAllMovies(): Promise<any> {
|
||||||
const url = environment.baseUrl + '/video/';
|
return firstValueFrom(this.apiService.get('/video/', true));
|
||||||
const headers = this.getAuthHeaders();
|
|
||||||
return lastValueFrom(this.http.get(url, { headers }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getMovieFiles(videoUrl: number): Promise<any> {
|
getMovieFiles(videoUrl: number): Promise<any> {
|
||||||
const url = environment.baseUrl + `${videoUrl}`;
|
return firstValueFrom(this.apiService.get(`/${videoUrl}`, true));
|
||||||
const headers = this.getAuthHeaders();
|
|
||||||
return lastValueFrom(this.http.get(url, { headers }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadMovie(formData: FormData) {
|
uploadMovie(formData: FormData): Promise<any> {
|
||||||
const url = environment.baseUrl + '/video/upload/';
|
return firstValueFrom(
|
||||||
const headers = this.getAuthHeaders();
|
this.apiService.post('/video/upload/', formData, true)
|
||||||
return lastValueFrom(this.http.post(url, formData, { headers }));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
isMovieResolutionUploaded(
|
||||||
videoID: number
|
videoID: number
|
||||||
): Observable<{ [resolution: string]: boolean }> {
|
): Observable<{ [resolution: string]: boolean }> {
|
||||||
if (this.movieCache.hasOwnProperty(videoID)) {
|
const cachedRes = this.movieCache[videoID];
|
||||||
const cachedResolutions = this.movieCache[videoID];
|
if (cachedRes && Object.values(cachedRes).every(Boolean)) {
|
||||||
|
return new BehaviorSubject(cachedRes).asObservable();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return this.apiService.get(`/video/movie/${videoID}/`, true).pipe(
|
||||||
* Fetches the availability of multiple resolutions for a specific movie.
|
map((res: any) => {
|
||||||
*
|
const resolutions = {
|
||||||
* @param {number} videoID - The ID of the movie to fetch resolutions for.
|
'360': res['360'] || false,
|
||||||
* @returns {Observable<{ [resolution: string]: boolean }>} Observable emitting an object with the availability of each resolution.
|
'720': res['720'] || false,
|
||||||
*/
|
'1080': res['1080'] || false,
|
||||||
private fetchAndCacheResolutions(
|
};
|
||||||
videoID: number
|
this.movieCache[videoID] = resolutions;
|
||||||
): Observable<{ [resolution: string]: boolean }> {
|
return resolutions;
|
||||||
const url = `${environment.baseUrl}/video/movie/${videoID}/`;
|
}),
|
||||||
const headers = this.getAuthHeaders();
|
catchError(() => {
|
||||||
|
this.authService.logout();
|
||||||
return new Observable<{ [resolution: string]: boolean }>((observer) => {
|
return of({ '360': false, '720': false, '1080': false });
|
||||||
this.http.get(url, { headers }).subscribe(
|
})
|
||||||
(response: any) => {
|
);
|
||||||
const resolutions = {
|
|
||||||
'360': response['360'] || false,
|
|
||||||
'720': response['720'] || false,
|
|
||||||
'1080': response['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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
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 { Injectable } from '@angular/core';
|
||||||
import { environment } from '../../environments/environment';
|
import { ApiService } from './api.service';
|
||||||
import { lastValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
|
|
@ -9,33 +8,31 @@ import { lastValueFrom } from 'rxjs';
|
||||||
export class UserService {
|
export class UserService {
|
||||||
currentUserId: string | null = null;
|
currentUserId: string | null = null;
|
||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
constructor(private apiService: ApiService) {}
|
||||||
|
|
||||||
async getLikedAndWatchedMovies(): Promise<any> {
|
getLikedAndWatchedMovies(): Promise<any> {
|
||||||
const url = environment.baseUrl + `/users/${this.currentUserId}/`;
|
return firstValueFrom(
|
||||||
const headers = this.getAuthHeaders();
|
this.apiService.get(`/users/${this.currentUserId}/`, true)
|
||||||
return lastValueFrom(this.http.get(url, { headers }));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLikedMovies(likedMovies: any) {
|
updateLikedMovies(likedMovies: any): Promise<any> {
|
||||||
const url = `${environment.baseUrl}/users/liked/${this.currentUserId}/`;
|
return firstValueFrom(
|
||||||
const headers = this.getAuthHeaders();
|
this.apiService.put(
|
||||||
return lastValueFrom(this.http.put(url, likedMovies, { headers }));
|
`/users/liked/${this.currentUserId}/`,
|
||||||
|
likedMovies,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateWatchedMovies(watchedMovies: any) {
|
updateWatchedMovies(watchedMovies: any): Promise<any> {
|
||||||
const url = `${environment.baseUrl}/users/watched/${this.currentUserId}/`;
|
return firstValueFrom(
|
||||||
const headers = this.getAuthHeaders();
|
this.apiService.put(
|
||||||
return lastValueFrom(this.http.put(url, watchedMovies, { headers }));
|
`/users/watched/${this.currentUserId}/`,
|
||||||
}
|
watchedMovies,
|
||||||
|
true
|
||||||
private getAuthHeaders(): HttpHeaders {
|
)
|
||||||
let authToken = localStorage.getItem('authToken');
|
);
|
||||||
if (!authToken) {
|
|
||||||
authToken = sessionStorage.getItem('authToken');
|
|
||||||
}
|
|
||||||
return new HttpHeaders({
|
|
||||||
Authorization: `Token ${authToken}`,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
import { BtnLargeComponent } from '../buttons/btn-large/btn-large.component';
|
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 { AuthService } from '../../../services/auth.service';
|
||||||
import { UserService } from '../../../services/user.service';
|
import { TokenService } from '../../../services/token.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-header',
|
selector: 'app-header',
|
||||||
|
|
@ -15,7 +15,10 @@ export class HeaderComponent {
|
||||||
@Input() showFullLogo: boolean = true;
|
@Input() showFullLogo: boolean = true;
|
||||||
@Output() moviesChange = new EventEmitter<any[]>();
|
@Output() moviesChange = new EventEmitter<any[]>();
|
||||||
|
|
||||||
constructor(private router: Router, private authService: AuthService) {}
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
|
private tokenService: TokenService
|
||||||
|
) {}
|
||||||
|
|
||||||
backToOverview(newMovies: any[]) {
|
backToOverview(newMovies: any[]) {
|
||||||
this.moviesChange.emit(newMovies);
|
this.moviesChange.emit(newMovies);
|
||||||
|
|
@ -24,9 +27,7 @@ export class HeaderComponent {
|
||||||
async logout() {
|
async logout() {
|
||||||
try {
|
try {
|
||||||
await this.authService.logout();
|
await this.authService.logout();
|
||||||
localStorage.clear();
|
this.tokenService.clearSession();
|
||||||
sessionStorage.clear();
|
|
||||||
this.router.navigate(['/']);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue