feat: auth page

This commit is contained in:
2024-07-10 16:35:14 +03:00
parent 6833105604
commit 112e76ab45
10 changed files with 199 additions and 46 deletions

View File

@@ -1,4 +1,4 @@
import { provideHttpClient } from "@angular/common/http"; import { provideHttpClient, withFetch } from "@angular/common/http";
import { ApplicationConfig, isDevMode, provideZoneChangeDetection } from "@angular/core"; import { ApplicationConfig, isDevMode, provideZoneChangeDetection } from "@angular/core";
import { provideRouter } from "@angular/router"; import { provideRouter } from "@angular/router";
import { provideServiceWorker } from "@angular/service-worker"; import { provideServiceWorker } from "@angular/service-worker";
@@ -12,6 +12,6 @@ export const appConfig: ApplicationConfig = {
enabled: !isDevMode(), enabled: !isDevMode(),
registrationStrategy: "registerWhenStable:30000", registrationStrategy: "registerWhenStable:30000",
}), }),
provideHttpClient(), provideHttpClient(withFetch()),
], ],
}; };

View File

@@ -1,4 +1,5 @@
import { Route } from "@angular/router"; import { Route } from "@angular/router";
import { AuthComponent } from "./components/auth/auth.component";
import { DetailComponent } from "./components/detail/detail.component"; import { DetailComponent } from "./components/detail/detail.component";
import { HomeComponent } from "./components/home/home.component"; import { HomeComponent } from "./components/home/home.component";
import { ReaderComponent } from "./components/reader/reader.component"; import { ReaderComponent } from "./components/reader/reader.component";
@@ -10,4 +11,8 @@ export const appRoutes: Route[] = [
}, },
{ path: "detail", component: DetailComponent }, { path: "detail", component: DetailComponent },
{ path: "reader", component: ReaderComponent }, { path: "reader", component: ReaderComponent },
{
path: "auth",
component: AuthComponent,
},
]; ];

View File

@@ -0,0 +1,34 @@
<div class="flex flex-col m-6">
<div class="mangalib-auth">
<h2>Авторизация в MangaLib</h2>
<ol class="list-decimal flex flex-col gap-4">
<li>
<p class="font-bold">Авторизация через токен</p>
<div class="flex flex-row gap-4">
<input
type="text"
placeholder="Token"
class="outline-none border-md border px-2"
#libSocialToken
/>
<button
type="button"
(click)="setLibSocialToken()"
class="hover:bg-slate-600 bg-slate-400 p-3 rounded-md text-white"
>
Сохранить
</button>
</div>
</li>
<li>
Вход через скрипт TamperMonkey. Если нет кнопки, значит вы не установили скрипт
<a
href="https://test-front.mangalib.me"
class="hover:bg-slate-600 bg-slate-400 p-3 rounded-md text-white tamperMonkey"
>
Вход
</a>
</li>
</ol>
</div>
</div>

View File

@@ -0,0 +1,3 @@
.tamperMonkey {
display: none;
}

View File

@@ -0,0 +1,58 @@
import { CommonModule } from "@angular/common";
import { AfterViewInit, Component, ElementRef, OnDestroy, ViewChild } from "@angular/core";
import { ActivatedRoute, Router, RouterLink } from "@angular/router";
import { Subject, takeUntil } from "rxjs";
import { RulibAuthService } from "../../services/parsers/rulib/rulib.auth.service";
import { EAuthTokenService } from "./enum";
@Component({
selector: "app-auth",
templateUrl: "./auth.component.html",
styleUrls: ["./auth.component.less"],
standalone: true,
imports: [CommonModule, RouterLink],
})
export class AuthComponent implements AfterViewInit, OnDestroy {
private destroy$ = new Subject<void>();
@ViewChild("libSocialToken") libSocialToken: ElementRef<HTMLInputElement> | null = null;
constructor(
private route: ActivatedRoute,
private router: Router,
private rulibAuthService: RulibAuthService,
) {}
private setToken(service: EAuthTokenService, token: string) {
switch (service) {
case EAuthTokenService.RULIB:
this.rulibAuthService.setToken(token);
break;
default:
this.router.navigate(["/", "auth"]);
return;
}
this.router.navigate(["/"]);
}
setLibSocialToken() {
if (this.libSocialToken) {
const token = this.libSocialToken.nativeElement.value;
this.setToken(EAuthTokenService.RULIB, token);
}
}
ngAfterViewInit(): void {
this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params) => {
const { token, service } = params;
if (token && service) {
this.setToken(service as EAuthTokenService, token);
}
});
if (this.libSocialToken) {
this.libSocialToken.nativeElement.value = this.rulibAuthService.getToken();
}
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@@ -0,0 +1,3 @@
export enum EAuthTokenService {
RULIB = "rulib",
}

View File

@@ -1,38 +1,44 @@
<div class="flex flex-col items-center"> <div class="flex flex-col items-center">
@if (needAuth) {
<h1>Необходима авторизация</h1>
<a routerLink="/auth" class="text-center bg-slate-600 text-white p-3 rounded-md mb-4">Войти</a>
}
@if (detail_item) { @if (detail_item) {
<h1>{{ detail_item.name }}</h1> <h1>{{ detail_item.name }}</h1>
<h2>{{ detail_item.rus_name }}</h2> <h2>{{ detail_item.rus_name }}</h2>
<img [src]="detail_item.cover.default" [alt]="detail_item.slug" /> <img [src]="detail_item.cover.default" [alt]="detail_item.slug" />
<details class="w-full"> @if (!needAuth) {
<summary class="text-center sticky top-0 bg-white z-10 py-2"> <details class="w-full">
<h3>Главы</h3> <summary class="text-center sticky top-0 bg-white z-10 py-2">
</summary> <h3>Главы</h3>
<div class="flex flex-col items-center pb-16"> </summary>
@for (chapter of chapters.data; track $index) { <div class="flex flex-col items-center pb-16">
<a @for (chapter of chapters.data; track $index) {
routerLink="/reader" <a
[queryParams]="{ routerLink="/reader"
url: detail_item.slug_url, [queryParams]="{
chapter: chapter.number, url: detail_item.slug_url,
volume: chapter.volume, chapter: chapter.number,
}" volume: chapter.volume,
[title]="chapter.name" }"
class="p-3 text-white bg-slate-600 w-[300px] mt-3 rounded-lg" [title]="chapter.name"
> class="p-3 text-white bg-slate-600 w-[300px] mt-3 rounded-lg"
<h3> >
<strong>{{ chapter.number }}.</strong> {{ chapter.name || "Нет названия" }} <h3>
</h3> <strong>{{ chapter.number }}.</strong> {{ chapter.name || "Нет названия" }}
</a> </h3>
} </a>
</div> }
</details> </div>
<a </details>
routerLink="/reader" <a
class="p-3 text-white bg-slate-600 w-[300px] mt-5 rounded-lg text-center" routerLink="/reader"
[queryParams]="{ url: detail_item.slug_url, volume: 1, chapter: 1 }" class="p-3 text-white bg-slate-600 w-[300px] mt-5 rounded-lg text-center"
> [queryParams]="{ url: detail_item.slug_url, volume: 1, chapter: 1 }"
Читать >
</a> Читать
</a>
}
} }
</div> </div>

View File

@@ -16,6 +16,7 @@ import { SearchService } from "../../services/search.service";
export class DetailComponent implements AfterViewInit, OnDestroy { export class DetailComponent implements AfterViewInit, OnDestroy {
detail_item: Data | null = null; detail_item: Data | null = null;
chapters: IRulibChaptersResult = { data: [] }; chapters: IRulibChaptersResult = { data: [] };
needAuth: boolean = false;
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
@@ -27,15 +28,27 @@ export class DetailComponent implements AfterViewInit, OnDestroy {
this.searchService this.searchService
.getDetails(url) .getDetails(url)
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe((data) => { .subscribe({
this.detail_item = data.data; next: (data) => {
this.detail_item = data.data;
},
error: (error) => {
console.log(error);
},
}); });
this.searchService this.searchService
.getChapters(url) .getChapters(url)
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe((data) => { .subscribe({
this.chapters = data; next: (data) => {
console.log(data); this.chapters = data;
if (data.data.length === 0) {
this.needAuth = true;
}
},
error: (error) => {
console.log(error);
},
}); });
} }

View File

@@ -1,6 +1,8 @@
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { Observable, catchError, map, throwError } from "rxjs"; import { Observable, catchError, map, throwError } from "rxjs";
import { Parser } from "../parser"; import { Parser } from "../parser";
import { RulibAuthService } from "./rulib.auth.service";
import { IRulibChapterResult } from "./rulib.chapter.dto"; import { IRulibChapterResult } from "./rulib.chapter.dto";
import { IRulibChaptersResult } from "./rulib.chapters.dto"; import { IRulibChaptersResult } from "./rulib.chapters.dto";
import { IRulibDetailResult } from "./rulib.detail.dto"; import { IRulibDetailResult } from "./rulib.detail.dto";
@@ -19,6 +21,13 @@ export class LibSocialParserService extends Parser {
return "https://img33.imgslib.link"; return "https://img33.imgslib.link";
} }
constructor(
private rulibAuthService: RulibAuthService,
override http: HttpClient,
) {
super(http);
}
searchManga(query: string): Observable<IRulibSearchResult> { searchManga(query: string): Observable<IRulibSearchResult> {
return this.http return this.http
.get( .get(
@@ -38,6 +47,7 @@ export class LibSocialParserService extends Parser {
return this.http return this.http
.get( .get(
`${this.url}/api/manga/${slug_url}?fields[]=summary&fields[]=genres&fields[]=tags&fields[]=authors`, `${this.url}/api/manga/${slug_url}?fields[]=summary&fields[]=genres&fields[]=tags&fields[]=authors`,
{ headers: { Authorization: "Bearer " + this.rulibAuthService.getToken() } },
) )
.pipe( .pipe(
map((data: object) => { map((data: object) => {
@@ -50,19 +60,25 @@ export class LibSocialParserService extends Parser {
} }
getChapters(url: string): Observable<IRulibChaptersResult> { getChapters(url: string): Observable<IRulibChaptersResult> {
return this.http.get(`${this.url}/api/manga/${url}/chapters`).pipe( return this.http
map((data) => { .get(`${this.url}/api/manga/${url}/chapters`, {
return data as IRulibChaptersResult; headers: { Authorization: "Bearer " + this.rulibAuthService.getToken() },
}), })
catchError((error) => { .pipe(
return throwError(() => `Now found ${error}`); map((data) => {
}), return data as IRulibChaptersResult;
); }),
catchError((error) => {
return throwError(() => `Now found ${error}`);
}),
);
} }
getChapter(url: string, chapter: string, volume: string): Observable<IRulibChapterResult> { getChapter(url: string, chapter: string, volume: string): Observable<IRulibChapterResult> {
return this.http return this.http
.get(`${this.url}/api/manga/${url}/chapter?number=${chapter}&volume=${volume}`) .get(`${this.url}/api/manga/${url}/chapter?number=${chapter}&volume=${volume}`, {
headers: { Authorization: "Bearer " + this.rulibAuthService.getToken() },
})
.pipe( .pipe(
map((data) => data as IRulibChapterResult), map((data) => data as IRulibChapterResult),
catchError((error) => throwError(() => `Now found ${error}`)), catchError((error) => throwError(() => `Now found ${error}`)),

View File

@@ -0,0 +1,15 @@
import { Injectable } from "@angular/core";
@Injectable({
providedIn: "root",
})
export class RulibAuthService {
setToken(token: string) {
localStorage.setItem("token", token);
}
getToken(): string {
return localStorage.getItem("token") ?? "";
}
//TODO: Проверка токена
}