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 { provideRouter } from "@angular/router";
import { provideServiceWorker } from "@angular/service-worker";
@@ -12,6 +12,6 @@ export const appConfig: ApplicationConfig = {
enabled: !isDevMode(),
registrationStrategy: "registerWhenStable:30000",
}),
provideHttpClient(),
provideHttpClient(withFetch()),
],
};

View File

@@ -1,4 +1,5 @@
import { Route } from "@angular/router";
import { AuthComponent } from "./components/auth/auth.component";
import { DetailComponent } from "./components/detail/detail.component";
import { HomeComponent } from "./components/home/home.component";
import { ReaderComponent } from "./components/reader/reader.component";
@@ -10,4 +11,8 @@ export const appRoutes: Route[] = [
},
{ path: "detail", component: DetailComponent },
{ 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">
@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) {
<h1>{{ detail_item.name }}</h1>
<h2>{{ detail_item.rus_name }}</h2>
<img [src]="detail_item.cover.default" [alt]="detail_item.slug" />
<details class="w-full">
<summary class="text-center sticky top-0 bg-white z-10 py-2">
<h3>Главы</h3>
</summary>
<div class="flex flex-col items-center pb-16">
@for (chapter of chapters.data; track $index) {
<a
routerLink="/reader"
[queryParams]="{
url: detail_item.slug_url,
chapter: chapter.number,
volume: chapter.volume,
}"
[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>
</a>
}
</div>
</details>
<a
routerLink="/reader"
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>
@if (!needAuth) {
<details class="w-full">
<summary class="text-center sticky top-0 bg-white z-10 py-2">
<h3>Главы</h3>
</summary>
<div class="flex flex-col items-center pb-16">
@for (chapter of chapters.data; track $index) {
<a
routerLink="/reader"
[queryParams]="{
url: detail_item.slug_url,
chapter: chapter.number,
volume: chapter.volume,
}"
[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>
</a>
}
</div>
</details>
<a
routerLink="/reader"
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>
}
}
</div>

View File

@@ -16,6 +16,7 @@ import { SearchService } from "../../services/search.service";
export class DetailComponent implements AfterViewInit, OnDestroy {
detail_item: Data | null = null;
chapters: IRulibChaptersResult = { data: [] };
needAuth: boolean = false;
private destroy$ = new Subject<void>();
constructor(
private route: ActivatedRoute,
@@ -27,15 +28,27 @@ export class DetailComponent implements AfterViewInit, OnDestroy {
this.searchService
.getDetails(url)
.pipe(takeUntil(this.destroy$))
.subscribe((data) => {
this.detail_item = data.data;
.subscribe({
next: (data) => {
this.detail_item = data.data;
},
error: (error) => {
console.log(error);
},
});
this.searchService
.getChapters(url)
.pipe(takeUntil(this.destroy$))
.subscribe((data) => {
this.chapters = data;
console.log(data);
.subscribe({
next: (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 { Observable, catchError, map, throwError } from "rxjs";
import { Parser } from "../parser";
import { RulibAuthService } from "./rulib.auth.service";
import { IRulibChapterResult } from "./rulib.chapter.dto";
import { IRulibChaptersResult } from "./rulib.chapters.dto";
import { IRulibDetailResult } from "./rulib.detail.dto";
@@ -19,6 +21,13 @@ export class LibSocialParserService extends Parser {
return "https://img33.imgslib.link";
}
constructor(
private rulibAuthService: RulibAuthService,
override http: HttpClient,
) {
super(http);
}
searchManga(query: string): Observable<IRulibSearchResult> {
return this.http
.get(
@@ -38,6 +47,7 @@ export class LibSocialParserService extends Parser {
return this.http
.get(
`${this.url}/api/manga/${slug_url}?fields[]=summary&fields[]=genres&fields[]=tags&fields[]=authors`,
{ headers: { Authorization: "Bearer " + this.rulibAuthService.getToken() } },
)
.pipe(
map((data: object) => {
@@ -50,19 +60,25 @@ export class LibSocialParserService extends Parser {
}
getChapters(url: string): Observable<IRulibChaptersResult> {
return this.http.get(`${this.url}/api/manga/${url}/chapters`).pipe(
map((data) => {
return data as IRulibChaptersResult;
}),
catchError((error) => {
return throwError(() => `Now found ${error}`);
}),
);
return this.http
.get(`${this.url}/api/manga/${url}/chapters`, {
headers: { Authorization: "Bearer " + this.rulibAuthService.getToken() },
})
.pipe(
map((data) => {
return data as IRulibChaptersResult;
}),
catchError((error) => {
return throwError(() => `Now found ${error}`);
}),
);
}
getChapter(url: string, chapter: string, volume: string): Observable<IRulibChapterResult> {
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(
map((data) => data as IRulibChapterResult),
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: Проверка токена
}