Ingest ExternalReviews from K+
Adds a new entity ExternalChapterMetadata, which would allow us to extend chapters to Recommendations, Ratings, etc in the future
This commit is contained in:
parent
749fb24185
commit
052b3f9fe4
29 changed files with 647 additions and 137 deletions
9
UI/Web/src/app/_models/chapter-detail.ts
Normal file
9
UI/Web/src/app/_models/chapter-detail.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import {UserReview} from "../_single-module/review-card/user-review";
|
||||
import {Rating} from "./rating";
|
||||
|
||||
export type ChapterDetail = {
|
||||
rating: number;
|
||||
hasBeenRated: boolean;
|
||||
reviews: UserReview[];
|
||||
ratings: Rating[];
|
||||
};
|
||||
|
|
@ -5,6 +5,7 @@ import {Chapter} from "../_models/chapter";
|
|||
import {TextResonse} from "../_types/text-response";
|
||||
import {UserReview} from "../_single-module/review-card/user-review";
|
||||
import {Rating} from "../_models/rating";
|
||||
import {ChapterDetail} from "../_models/chapter-detail";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
|
@ -31,8 +32,8 @@ export class ChapterService {
|
|||
return this.httpClient.post(this.baseUrl + 'chapter/update', chapter, TextResonse);
|
||||
}
|
||||
|
||||
chapterReviews(chapterId: number) {
|
||||
return this.httpClient.get<Array<UserReview>>(this.baseUrl + 'chapter/review?chapterId='+chapterId);
|
||||
chapterDetailPlus(seriesId: number, chapterId: number) {
|
||||
return this.httpClient.get<ChapterDetail>(this.baseUrl + `chapter/chapter-detail-plus?chapterId=${chapterId}&seriesId=${seriesId}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,13 +13,6 @@ export class ReviewService {
|
|||
|
||||
constructor(private httpClient: HttpClient) { }
|
||||
|
||||
getReviews(seriesId: number, chapterId?: number) {
|
||||
if (chapterId) {
|
||||
return this.httpClient.get<UserReview[]>(this.baseUrl + `review?chapterId=${chapterId}&seriesId=${seriesId}`);
|
||||
}
|
||||
return this.httpClient.get<UserReview[]>(this.baseUrl + 'review?seriesId=' + seriesId);
|
||||
}
|
||||
|
||||
deleteReview(seriesId: number, chapterId?: number) {
|
||||
if (chapterId) {
|
||||
return this.httpClient.delete(this.baseUrl + `review?chapterId=${chapterId}&seriesId=${seriesId}`);
|
||||
|
|
@ -28,15 +21,15 @@ export class ReviewService {
|
|||
return this.httpClient.delete(this.baseUrl + 'review?seriesId=' + seriesId);
|
||||
}
|
||||
|
||||
updateReview(seriesId: number, body: string, rating: number, chapterId?: number) {
|
||||
updateReview(seriesId: number, body: string, chapterId?: number) {
|
||||
if (chapterId) {
|
||||
return this.httpClient.post<UserReview>(this.baseUrl + `review?chapterId=${chapterId}&seriesId=${seriesId}`, {
|
||||
rating, body
|
||||
return this.httpClient.post<UserReview>(this.baseUrl + `review`, {
|
||||
seriesId, chapterId, body
|
||||
});
|
||||
}
|
||||
|
||||
return this.httpClient.post<UserReview>(this.baseUrl + 'review', {
|
||||
seriesId, rating, body
|
||||
seriesId, body
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import {ScrobbleProvider} from "../../_services/scrobbling.service";
|
||||
|
||||
export enum RatingAuthority {
|
||||
User = 0,
|
||||
Critic = 1
|
||||
}
|
||||
|
||||
export interface UserReview {
|
||||
seriesId: number;
|
||||
libraryId: number;
|
||||
volumeId?: number;
|
||||
chapterId?: number;
|
||||
rating: number;
|
||||
hasBeenRated: boolean;
|
||||
score: number;
|
||||
username: string;
|
||||
body: string;
|
||||
|
|
@ -15,4 +17,5 @@ export interface UserReview {
|
|||
bodyJustText?: string;
|
||||
siteUrl?: string;
|
||||
provider: ScrobbleProvider;
|
||||
authority: RatingAuthority;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,31 +35,21 @@ export class ReviewModalComponent implements OnInit {
|
|||
|
||||
protected readonly modal = inject(NgbActiveModal);
|
||||
private readonly reviewService = inject(ReviewService);
|
||||
private readonly seriesService = inject(SeriesService);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly confirmService = inject(ConfirmService);
|
||||
private readonly toastr = inject(ToastrService);
|
||||
private readonly themeService = inject(ThemeService);
|
||||
protected readonly minLength = 5;
|
||||
|
||||
@Input({required: true}) review!: UserReview;
|
||||
reviewGroup!: FormGroup;
|
||||
rating: number = 0;
|
||||
|
||||
starColor = this.themeService.getCssVariable('--rating-star-color');
|
||||
|
||||
ngOnInit(): void {
|
||||
this.reviewGroup = new FormGroup({
|
||||
reviewBody: new FormControl(this.review.body, [Validators.required, Validators.minLength(this.minLength)]),
|
||||
});
|
||||
this.rating = this.review.rating;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
updateRating($event: number) {
|
||||
this.rating = $event;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.modal.close({success: false, review: this.review, action: ReviewModalCloseAction.Close});
|
||||
}
|
||||
|
|
@ -79,7 +69,7 @@ export class ReviewModalComponent implements OnInit {
|
|||
return;
|
||||
}
|
||||
|
||||
this.reviewService.updateReview(this.review.seriesId, model.reviewBody, this.rating, this.review.chapterId).subscribe(review => {
|
||||
this.reviewService.updateReview(this.review.seriesId, model.reviewBody, this.review.chapterId).subscribe(review => {
|
||||
this.modal.close({success: true, review: review, action: ReviewModalCloseAction.Edit});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -28,11 +28,10 @@
|
|||
|
||||
|
||||
<div class="mt-2 mb-2">
|
||||
@let rating = userRating();
|
||||
<app-external-rating [seriesId]="series.id"
|
||||
[ratings]="[]"
|
||||
[userRating]="rating?.rating || 0"
|
||||
[hasUserRated]="rating !== undefined && rating.hasBeenRated"
|
||||
[userRating]="rating"
|
||||
[hasUserRated]="hasBeenRated"
|
||||
[libraryType]="libraryType!"
|
||||
[chapterId]="chapterId"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -73,10 +73,11 @@ import {ReviewModalComponent} from "../_single-module/review-modal/review-modal.
|
|||
import {ReviewsComponent} from "../_single-module/reviews/reviews.component";
|
||||
import {ExternalRatingComponent} from "../series-detail/_components/external-rating/external-rating.component";
|
||||
import {Rating} from "../_models/rating";
|
||||
import {ReviewService} from "../_services/review.service";
|
||||
|
||||
enum TabID {
|
||||
Related = 'related-tab',
|
||||
Reviews = 'review-tab', // Only applicable for books
|
||||
Reviews = 'review-tab',
|
||||
Details = 'details-tab'
|
||||
}
|
||||
|
||||
|
|
@ -111,8 +112,6 @@ enum TabID {
|
|||
DatePipe,
|
||||
DefaultDatePipe,
|
||||
CoverImageComponent,
|
||||
CarouselReelComponent,
|
||||
ReviewCardComponent,
|
||||
ReviewsComponent,
|
||||
ExternalRatingComponent
|
||||
],
|
||||
|
|
@ -165,6 +164,9 @@ export class ChapterDetailComponent implements OnInit {
|
|||
hasReadingProgress = false;
|
||||
userReviews: Array<UserReview> = [];
|
||||
plusReviews: Array<UserReview> = [];
|
||||
rating: number = 0;
|
||||
hasBeenRated: boolean = false;
|
||||
|
||||
weblinks: Array<string> = [];
|
||||
activeTabId = TabID.Details;
|
||||
/**
|
||||
|
|
@ -233,7 +235,7 @@ export class ChapterDetailComponent implements OnInit {
|
|||
series: this.seriesService.getSeries(this.seriesId),
|
||||
chapter: this.chapterService.getChapterMetadata(this.chapterId),
|
||||
libraryType: this.libraryService.getLibraryType(this.libraryId),
|
||||
reviews: this.chapterService.chapterReviews(this.chapterId),
|
||||
chapterDetail: this.chapterService.chapterDetailPlus(this.seriesId, this.chapterId),
|
||||
}).subscribe(results => {
|
||||
|
||||
if (results.chapter === null) {
|
||||
|
|
@ -245,8 +247,10 @@ export class ChapterDetailComponent implements OnInit {
|
|||
this.chapter = results.chapter;
|
||||
this.weblinks = this.chapter.webLinks.split(',');
|
||||
this.libraryType = results.libraryType;
|
||||
this.userReviews = results.reviews.filter(r => !r.isExternal);
|
||||
this.plusReviews = results.reviews.filter(r => r.isExternal);
|
||||
this.userReviews = results.chapterDetail.reviews.filter(r => !r.isExternal);
|
||||
this.plusReviews = results.chapterDetail.reviews.filter(r => r.isExternal);
|
||||
this.rating = results.chapterDetail.rating;
|
||||
this.hasBeenRated = results.chapterDetail.hasBeenRated;
|
||||
|
||||
this.themeService.setColorScape(this.chapter.primaryColor, this.chapter.secondaryColor);
|
||||
|
||||
|
|
@ -386,10 +390,6 @@ export class ChapterDetailComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
userRating() {
|
||||
return this.userReviews.find(r => r.username == this.user?.username && !r.isExternal)
|
||||
}
|
||||
|
||||
protected readonly LibraryType = LibraryType;
|
||||
protected readonly encodeURIComponent = encodeURIComponent;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,11 +31,10 @@
|
|||
|
||||
@if (libraryType !== null && series && volume.chapters.length === 1) {
|
||||
<div class="mt-2 mb-2">
|
||||
@let rating = userRating();
|
||||
<app-external-rating [seriesId]="series.id"
|
||||
[ratings]="[]"
|
||||
[userRating]="rating?.rating || 0"
|
||||
[hasUserRated]="rating !== undefined && rating.hasBeenRated"
|
||||
[userRating]="rating"
|
||||
[hasUserRated]="hasBeenRated"
|
||||
[libraryType]="libraryType"
|
||||
[chapterId]="volume.chapters[0].id"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ import {ReviewsComponent} from "../_single-module/reviews/reviews.component";
|
|||
import {ExternalRatingComponent} from "../series-detail/_components/external-rating/external-rating.component";
|
||||
import {User} from "../_models/user";
|
||||
import {ReviewService} from "../_services/review.service";
|
||||
import {ChapterService} from "../_services/chapter.service";
|
||||
|
||||
enum TabID {
|
||||
|
||||
|
|
@ -183,7 +184,7 @@ export class VolumeDetailComponent implements OnInit {
|
|||
private readonly readingListService = inject(ReadingListService);
|
||||
private readonly messageHub = inject(MessageHubService);
|
||||
private readonly location = inject(Location);
|
||||
private readonly reviewService = inject(ReviewService);
|
||||
private readonly chapterService = inject(ChapterService);
|
||||
|
||||
|
||||
protected readonly AgeRating = AgeRating;
|
||||
|
|
@ -204,8 +205,13 @@ export class VolumeDetailComponent implements OnInit {
|
|||
libraryType: LibraryType | null = null;
|
||||
activeTabId = TabID.Chapters;
|
||||
readingLists: ReadingList[] = [];
|
||||
|
||||
// Only populated if the volume has exactly one chapter
|
||||
userReviews: Array<UserReview> = [];
|
||||
plusReviews: Array<UserReview> = [];
|
||||
rating: number = 0;
|
||||
hasBeenRated: boolean = false;
|
||||
|
||||
mobileSeriesImgBackground: string | undefined;
|
||||
downloadInProgress: boolean = false;
|
||||
|
||||
|
|
@ -405,9 +411,11 @@ export class VolumeDetailComponent implements OnInit {
|
|||
this.libraryType = results.libraryType;
|
||||
|
||||
if (this.volume.chapters.length === 1) {
|
||||
this.reviewService.getReviews(this.seriesId, this.volume.chapters[0].id).subscribe(reviews => {
|
||||
this.userReviews = reviews.filter(r => !r.isExternal);
|
||||
this.plusReviews = reviews.filter(r => r.isExternal);
|
||||
this.chapterService.chapterDetailPlus(this.seriesId, this.volume.chapters[0].id).subscribe(detail => {
|
||||
this.userReviews = detail.reviews.filter(r => !r.isExternal);
|
||||
this.plusReviews = detail.reviews.filter(r => r.isExternal);
|
||||
this.rating = detail.rating;
|
||||
this.hasBeenRated = detail.hasBeenRated;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -692,9 +700,5 @@ export class VolumeDetailComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
userRating() {
|
||||
return this.userReviews.find(r => r.username === this.user?.username && !r.isExternal);
|
||||
}
|
||||
|
||||
protected readonly encodeURIComponent = encodeURIComponent;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue