Restricted Profiles (#1581)

* Added ReadingList age rating from all series and started on some unit tests for the new flows.

* Wrote more unit tests for Reading Lists

* Added ability to restrict user accounts to a given age rating via admin edit user modal and invite user. This commit contains all basic code, but no query modifications.

* When updating a reading list's title via UI, explicitly check if there is an existing RL with the same title.

* Refactored Reading List calculation to work properly in the flows it's invoked from.

* Cleaned up an unused method

* Promoted Collections no longer show tags where a Series exists within them that is above the user's age rating.

* Collection search now respects age restrictions

* Series Detail page now checks if the user has explicit access (as a user might bypass with direct url access)

* Hooked up age restriction for dashboard activity streams.

* Refactored some methods from Series Controller and Library Controller to a new Search Controller to keep things organized

* Updated Search to respect age restrictions

* Refactored all the Age Restriction queries to extensions

* Related Series no longer show up if they are out of the age restriction

* Fixed a bad mapping for the update age restriction api

* Fixed a UI state change after updating age restriction

* Fixed unit test

* Added a migration for reading lists

* Code cleanup
This commit is contained in:
Joe Milazzo 2022-10-10 12:59:20 -05:00 committed by GitHub
parent 0ad1638ec0
commit 442af965c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 4638 additions and 262 deletions

View file

@ -10,8 +10,16 @@ import { EVENTS, MessageHubService } from './message-hub.service';
import { ThemeService } from './theme.service';
import { InviteUserResponse } from '../_models/invite-user-response';
import { UserUpdateEvent } from '../_models/events/user-update-event';
import { DeviceService } from './device.service';
import { UpdateEmailResponse } from '../_models/email/update-email-response';
import { AgeRating } from '../_models/metadata/age-rating';
export enum Role {
Admin = 'Admin',
ChangePassword = 'Change Password',
Bookmark = 'Bookmark',
Download = 'Download',
ChangeRestriction = 'Change Restriction'
}
@Injectable({
providedIn: 'root'
@ -49,19 +57,23 @@ export class AccountService implements OnDestroy {
}
hasAdminRole(user: User) {
return user && user.roles.includes('Admin');
return user && user.roles.includes(Role.Admin);
}
hasChangePasswordRole(user: User) {
return user && user.roles.includes('Change Password');
return user && user.roles.includes(Role.ChangePassword);
}
hasChangeAgeRestrictionRole(user: User) {
return user && user.roles.includes(Role.ChangeRestriction);
}
hasDownloadRole(user: User) {
return user && user.roles.includes('Download');
return user && user.roles.includes(Role.Download);
}
hasBookmarkRole(user: User) {
return user && user.roles.includes('Bookmark');
return user && user.roles.includes(Role.Bookmark);
}
getRoles() {
@ -149,7 +161,7 @@ export class AccountService implements OnDestroy {
return this.httpClient.post<string>(this.baseUrl + 'account/resend-confirmation-email?userId=' + userId, {}, {responseType: 'text' as 'json'});
}
inviteUser(model: {email: string, roles: Array<string>, libraries: Array<number>}) {
inviteUser(model: {email: string, roles: Array<string>, libraries: Array<number>, ageRestriction: AgeRating}) {
return this.httpClient.post<InviteUserResponse>(this.baseUrl + 'account/invite', model);
}
@ -186,7 +198,7 @@ export class AccountService implements OnDestroy {
return this.httpClient.post(this.baseUrl + 'account/reset-password', {username, password, oldPassword}, {responseType: 'json' as 'text'});
}
update(model: {email: string, roles: Array<string>, libraries: Array<number>, userId: number}) {
update(model: {email: string, roles: Array<string>, libraries: Array<number>, userId: number, ageRestriction: AgeRating}) {
return this.httpClient.post(this.baseUrl + 'account/update', model);
}
@ -194,6 +206,10 @@ export class AccountService implements OnDestroy {
return this.httpClient.post<UpdateEmailResponse>(this.baseUrl + 'account/update/email', {email});
}
updateAgeRestriction(ageRating: AgeRating) {
return this.httpClient.post(this.baseUrl + 'account/update/age-restriction', {ageRating});
}
/**
* This will get latest preferences for a user and cache them into user store
* @returns

View file

@ -113,12 +113,4 @@ export class LibraryService {
return this.libraryTypes[libraryId];
}));
}
search(term: string) {
if (term === '') {
return of(new SearchResultGroup());
}
return this.httpClient.get<SearchResultGroup>(this.baseUrl + 'library/search?queryString=' + encodeURIComponent(term));
}
}

View file

@ -0,0 +1,31 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { of } from 'rxjs';
import { environment } from 'src/environments/environment';
import { SearchResultGroup } from '../_models/search/search-result-group';
import { Series } from '../_models/series';
@Injectable({
providedIn: 'root'
})
export class SearchService {
baseUrl = environment.apiUrl;
constructor(private httpClient: HttpClient) { }
search(term: string) {
if (term === '') {
return of(new SearchResultGroup());
}
return this.httpClient.get<SearchResultGroup>(this.baseUrl + 'search/search?queryString=' + encodeURIComponent(term));
}
getSeriesForMangaFile(mangaFileId: number) {
return this.httpClient.get<Series | null>(this.baseUrl + 'search/series-for-mangafile?mangaFileId=' + mangaFileId);
}
getSeriesForChapter(chapterId: number) {
return this.httpClient.get<Series | null>(this.baseUrl + 'search/series-for-chapter?chapterId=' + chapterId);
}
}

View file

@ -78,14 +78,6 @@ export class SeriesService {
return this.httpClient.get<ChapterMetadata>(this.baseUrl + 'series/chapter-metadata?chapterId=' + chapterId);
}
getSeriesForMangaFile(mangaFileId: number) {
return this.httpClient.get<Series | null>(this.baseUrl + 'series/series-for-mangafile?mangaFileId=' + mangaFileId);
}
getSeriesForChapter(chapterId: number) {
return this.httpClient.get<Series | null>(this.baseUrl + 'series/series-for-chapter?chapterId=' + chapterId);
}
delete(seriesId: number) {
return this.httpClient.delete<boolean>(this.baseUrl + 'series/' + seriesId);
}