Release Polish (#1586)
* Fixed a scaling issue in the epub reader, where images could scale when they shouldn't. * Removed some caching on library/ api and added more output for a foreign key constraint * Hooked in Restricted Profile stat collection * Added a new boolean on age restrictions to explicitly allow unknowns or not. Since unknown is the default state of metadata, if users are allowed access to Unknown, age restricted content could leak. * Fixed a bug where sometimes series cover generation could fail under conditions where only specials existed. * Fixed foreign constraint issue when cleaning up series not seen at end of scan loop * Removed an additional epub parse when scanning and handled merging differently * Code smell
This commit is contained in:
parent
78762a5626
commit
9149c4cbca
46 changed files with 2504 additions and 145 deletions
6
UI/Web/src/app/_models/age-restriction.ts
Normal file
6
UI/Web/src/app/_models/age-restriction.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { AgeRating } from "./metadata/age-rating";
|
||||
|
||||
export interface AgeRestriction {
|
||||
ageRating: AgeRating;
|
||||
includeUnknowns: boolean;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { AgeRestriction } from './age-restriction';
|
||||
import { Library } from './library';
|
||||
import { AgeRating } from './metadata/age-rating';
|
||||
|
||||
export interface Member {
|
||||
id: number;
|
||||
|
@ -9,8 +9,5 @@ export interface Member {
|
|||
created: string; // datetime
|
||||
roles: string[];
|
||||
libraries: Library[];
|
||||
/**
|
||||
* If not applicable, will store a -1
|
||||
*/
|
||||
ageRestriction: AgeRating;
|
||||
ageRestriction: AgeRestriction;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { AgeRating } from './metadata/age-rating';
|
||||
import { AgeRestriction } from './age-restriction';
|
||||
import { Preferences } from './preferences/preferences';
|
||||
|
||||
// This interface is only used for login and storing/retreiving JWT from local storage
|
||||
|
@ -10,5 +10,5 @@ export interface User {
|
|||
preferences: Preferences;
|
||||
apiKey: string;
|
||||
email: string;
|
||||
ageRestriction: AgeRating;
|
||||
ageRestriction: AgeRestriction;
|
||||
}
|
|
@ -12,6 +12,7 @@ import { InviteUserResponse } from '../_models/invite-user-response';
|
|||
import { UserUpdateEvent } from '../_models/events/user-update-event';
|
||||
import { UpdateEmailResponse } from '../_models/email/update-email-response';
|
||||
import { AgeRating } from '../_models/metadata/age-rating';
|
||||
import { AgeRestriction } from '../_models/age-restriction';
|
||||
|
||||
export enum Role {
|
||||
Admin = 'Admin',
|
||||
|
@ -161,7 +162,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>, ageRestriction: AgeRating}) {
|
||||
inviteUser(model: {email: string, roles: Array<string>, libraries: Array<number>, ageRestriction: AgeRestriction}) {
|
||||
return this.httpClient.post<InviteUserResponse>(this.baseUrl + 'account/invite', model);
|
||||
}
|
||||
|
||||
|
@ -198,7 +199,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, ageRestriction: AgeRating}) {
|
||||
update(model: {email: string, roles: Array<string>, libraries: Array<number>, userId: number, ageRestriction: AgeRestriction}) {
|
||||
return this.httpClient.post(this.baseUrl + 'account/update', model);
|
||||
}
|
||||
|
||||
|
@ -206,8 +207,8 @@ 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});
|
||||
updateAgeRestriction(ageRating: AgeRating, includeUnknowns: boolean) {
|
||||
return this.httpClient.post(this.baseUrl + 'account/update/age-restriction', {ageRating, includeUnknowns});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { FormGroup, FormControl, Validators } from '@angular/forms';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { AgeRestriction } from 'src/app/_models/age-restriction';
|
||||
import { Library } from 'src/app/_models/library';
|
||||
import { Member } from 'src/app/_models/member';
|
||||
import { AgeRating } from 'src/app/_models/metadata/age-rating';
|
||||
import { AccountService } from 'src/app/_services/account.service';
|
||||
|
||||
// TODO: Rename this to EditUserModal
|
||||
@Component({
|
||||
selector: 'app-edit-user',
|
||||
templateUrl: './edit-user.component.html',
|
||||
|
@ -18,7 +17,7 @@ export class EditUserComponent implements OnInit {
|
|||
|
||||
selectedRoles: Array<string> = [];
|
||||
selectedLibraries: Array<number> = [];
|
||||
selectedRating: AgeRating = AgeRating.NotApplicable;
|
||||
selectedRestriction!: AgeRestriction;
|
||||
isSaving: boolean = false;
|
||||
|
||||
userForm: FormGroup = new FormGroup({});
|
||||
|
@ -41,8 +40,8 @@ export class EditUserComponent implements OnInit {
|
|||
this.selectedRoles = roles;
|
||||
}
|
||||
|
||||
updateRestrictionSelection(rating: AgeRating) {
|
||||
this.selectedRating = rating;
|
||||
updateRestrictionSelection(restriction: AgeRestriction) {
|
||||
this.selectedRestriction = restriction;
|
||||
}
|
||||
|
||||
updateLibrarySelection(libraries: Array<Library>) {
|
||||
|
@ -58,8 +57,7 @@ export class EditUserComponent implements OnInit {
|
|||
model.userId = this.member.id;
|
||||
model.roles = this.selectedRoles;
|
||||
model.libraries = this.selectedLibraries;
|
||||
model.ageRestriction = this.selectedRating || AgeRating.NotApplicable;
|
||||
console.log('rating: ', this.selectedRating);
|
||||
model.ageRestriction = this.selectedRestriction;
|
||||
this.accountService.update(model).subscribe(() => {
|
||||
this.modal.close(true);
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
|||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { AgeRestriction } from 'src/app/_models/age-restriction';
|
||||
import { InviteUserResponse } from 'src/app/_models/invite-user-response';
|
||||
import { Library } from 'src/app/_models/library';
|
||||
import { AgeRating } from 'src/app/_models/metadata/age-rating';
|
||||
|
@ -21,7 +22,7 @@ export class InviteUserComponent implements OnInit {
|
|||
inviteForm: FormGroup = new FormGroup({});
|
||||
selectedRoles: Array<string> = [];
|
||||
selectedLibraries: Array<number> = [];
|
||||
selectedRating: AgeRating = AgeRating.NotApplicable;
|
||||
selectedRestriction: AgeRestriction = {ageRating: AgeRating.NotApplicable, includeUnknowns: false};
|
||||
emailLink: string = '';
|
||||
|
||||
makeLink: (val: string) => string = (val: string) => {return this.emailLink};
|
||||
|
@ -48,7 +49,7 @@ export class InviteUserComponent implements OnInit {
|
|||
email,
|
||||
libraries: this.selectedLibraries,
|
||||
roles: this.selectedRoles,
|
||||
ageRestriction: this.selectedRating
|
||||
ageRestriction: this.selectedRestriction
|
||||
}).subscribe((data: InviteUserResponse) => {
|
||||
this.emailLink = data.emailLink;
|
||||
this.isSending = false;
|
||||
|
@ -69,8 +70,8 @@ export class InviteUserComponent implements OnInit {
|
|||
this.selectedLibraries = libraries.map(l => l.id);
|
||||
}
|
||||
|
||||
updateRestrictionSelection(rating: AgeRating) {
|
||||
this.selectedRating = rating;
|
||||
updateRestrictionSelection(restriction: AgeRestriction) {
|
||||
this.selectedRestriction = restriction;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -264,8 +264,7 @@ $action-bar-height: 38px;
|
|||
// This is applied to images in the backend
|
||||
::ng-deep .kavita-scale-width-container {
|
||||
width: auto;
|
||||
// * 4 is just for extra buffer which is needed based on testing. --book-reader-content-max-height is set by us on calculation of columnHeight
|
||||
max-height: calc(var(--book-reader-content-max-height) - ($action-bar-height * 4)), calc((var(--vh)*100) - ($action-bar-height * 4)) !important;
|
||||
max-height: calc(var(--book-reader-content-max-height) - ($action-bar-height)) !important;
|
||||
}
|
||||
|
||||
// This is applied to images in the backend
|
||||
|
|
|
@ -10,7 +10,11 @@
|
|||
</div>
|
||||
|
||||
<ng-container *ngIf="isViewMode">
|
||||
<span >{{user?.ageRestriction | ageRating | async}}</span>
|
||||
<span>{{user?.ageRestriction?.ageRating| ageRating | async}}
|
||||
<ng-container *ngIf="user?.ageRestriction?.ageRating !== AgeRating.NotApplicable && user?.ageRestriction?.includeUnknowns">
|
||||
<span class="ms-1 me-1">+</span> Unknowns
|
||||
</ng-container>
|
||||
</span>
|
||||
</ng-container>
|
||||
|
||||
<div #collapse="ngbCollapse" [(ngbCollapse)]="isViewMode">
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, OnInit } from '@angular/core';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { Observable, of, Subject, takeUntil, shareReplay, map, take } from 'rxjs';
|
||||
import { AgeRestriction } from 'src/app/_models/age-restriction';
|
||||
import { AgeRating } from 'src/app/_models/metadata/age-rating';
|
||||
import { User } from 'src/app/_models/user';
|
||||
import { AccountService } from 'src/app/_services/account.service';
|
||||
|
@ -16,9 +17,9 @@ export class ChangeAgeRestrictionComponent implements OnInit {
|
|||
user: User | undefined = undefined;
|
||||
hasChangeAgeRestrictionAbility: Observable<boolean> = of(false);
|
||||
isViewMode: boolean = true;
|
||||
selectedRating: AgeRating = AgeRating.NotApplicable;
|
||||
originalRating!: AgeRating;
|
||||
reset: EventEmitter<AgeRating> = new EventEmitter();
|
||||
selectedRestriction!: AgeRestriction;
|
||||
originalRestriction!: AgeRestriction;
|
||||
reset: EventEmitter<AgeRestriction> = new EventEmitter();
|
||||
|
||||
get AgeRating() { return AgeRating; }
|
||||
|
||||
|
@ -28,8 +29,9 @@ export class ChangeAgeRestrictionComponent implements OnInit {
|
|||
|
||||
ngOnInit(): void {
|
||||
this.accountService.currentUser$.pipe(takeUntil(this.onDestroy), shareReplay(), take(1)).subscribe(user => {
|
||||
if (!user) return;
|
||||
this.user = user;
|
||||
this.originalRating = this.user?.ageRestriction || AgeRating.NotApplicable;
|
||||
this.originalRestriction = this.user.ageRestriction;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
|
@ -39,8 +41,8 @@ export class ChangeAgeRestrictionComponent implements OnInit {
|
|||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
updateRestrictionSelection(rating: AgeRating) {
|
||||
this.selectedRating = rating;
|
||||
updateRestrictionSelection(restriction: AgeRestriction) {
|
||||
this.selectedRestriction = restriction;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
@ -50,18 +52,19 @@ export class ChangeAgeRestrictionComponent implements OnInit {
|
|||
|
||||
resetForm() {
|
||||
if (!this.user) return;
|
||||
this.reset.emit(this.originalRating);
|
||||
this.reset.emit(this.originalRestriction);
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
saveForm() {
|
||||
if (this.user === undefined) { return; }
|
||||
|
||||
this.accountService.updateAgeRestriction(this.selectedRating).subscribe(() => {
|
||||
this.accountService.updateAgeRestriction(this.selectedRestriction.ageRating, this.selectedRestriction.includeUnknowns).subscribe(() => {
|
||||
this.toastr.success('Age Restriction has been updated');
|
||||
this.originalRating = this.selectedRating;
|
||||
this.originalRestriction = this.selectedRestriction;
|
||||
if (this.user) {
|
||||
this.user.ageRestriction = this.selectedRating;
|
||||
this.user.ageRestriction.ageRating = this.selectedRestriction.ageRating;
|
||||
this.user.ageRestriction.includeUnknowns = this.selectedRestriction.includeUnknowns;
|
||||
}
|
||||
this.resetForm();
|
||||
this.isViewMode = true;
|
||||
|
|
|
@ -15,5 +15,17 @@
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="auto-close" role="switch" formControlName="ageRestrictionIncludeUnknowns" class="form-check-input" aria-describedby="include-unknowns-help" [value]="true" aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="auto-close">Include Unknowns</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="top" [ngbTooltip]="includeUnknownsTooltip" role="button" tabindex="0"></i>
|
||||
</div>
|
||||
|
||||
<ng-template #includeUnknownsTooltip>If true, Unknowns will be allowed with Age Restrcition. This could lead to untagged media leaking to users with Age restrictions.</ng-template>
|
||||
<span class="visually-hidden" id="include-unknowns-help">If true, Unknowns will be allowed with Age Restrcition. This could lead to untagged media leaking to users with Age restrictions.</span>
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
||||
</ng-container>
|
|
@ -1,5 +1,6 @@
|
|||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
|
||||
import { FormControl, FormGroup } from '@angular/forms';
|
||||
import { AgeRestriction } from 'src/app/_models/age-restriction';
|
||||
import { Member } from 'src/app/_models/member';
|
||||
import { AgeRating } from 'src/app/_models/metadata/age-rating';
|
||||
import { AgeRatingDto } from 'src/app/_models/metadata/age-rating-dto';
|
||||
|
@ -20,8 +21,8 @@ export class RestrictionSelectorComponent implements OnInit, OnChanges {
|
|||
* Show labels and description around the form
|
||||
*/
|
||||
@Input() showContext: boolean = true;
|
||||
@Input() reset: EventEmitter<AgeRating> | undefined;
|
||||
@Output() selected: EventEmitter<AgeRating> = new EventEmitter<AgeRating>();
|
||||
@Input() reset: EventEmitter<AgeRestriction> | undefined;
|
||||
@Output() selected: EventEmitter<AgeRestriction> = new EventEmitter<AgeRestriction>();
|
||||
|
||||
|
||||
ageRatings: Array<AgeRatingDto> = [];
|
||||
|
@ -32,22 +33,36 @@ export class RestrictionSelectorComponent implements OnInit, OnChanges {
|
|||
ngOnInit(): void {
|
||||
|
||||
this.restrictionForm = new FormGroup({
|
||||
'ageRating': new FormControl(this.member?.ageRestriction || AgeRating.NotApplicable, [])
|
||||
'ageRating': new FormControl(this.member?.ageRestriction.ageRating || AgeRating.NotApplicable || AgeRating.NotApplicable, []),
|
||||
'ageRestrictionIncludeUnknowns': new FormControl(this.member?.ageRestriction.includeUnknowns, []),
|
||||
|
||||
});
|
||||
|
||||
if (this.isAdmin) {
|
||||
this.restrictionForm.get('ageRating')?.disable();
|
||||
this.restrictionForm.get('ageRestrictionIncludeUnknowns')?.disable();
|
||||
}
|
||||
|
||||
if (this.reset) {
|
||||
this.reset.subscribe(e => {
|
||||
this.restrictionForm?.get('ageRating')?.setValue(e);
|
||||
this.restrictionForm?.get('ageRating')?.setValue(e.ageRating);
|
||||
this.restrictionForm?.get('ageRestrictionIncludeUnknowns')?.setValue(e.includeUnknowns);
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
this.restrictionForm.get('ageRating')?.valueChanges.subscribe(e => {
|
||||
this.selected.emit(parseInt(e, 10));
|
||||
this.selected.emit({
|
||||
ageRating: parseInt(e, 10),
|
||||
includeUnknowns: this.restrictionForm?.get('ageRestrictionIncludeUnknowns')?.value
|
||||
});
|
||||
});
|
||||
|
||||
this.restrictionForm.get('ageRestrictionIncludeUnknowns')?.valueChanges.subscribe(e => {
|
||||
this.selected.emit({
|
||||
ageRating: parseInt(this.restrictionForm?.get('ageRating')?.value, 10),
|
||||
includeUnknowns: e
|
||||
});
|
||||
});
|
||||
|
||||
this.metadataService.getAllAgeRatings().subscribe(ratings => {
|
||||
|
@ -60,8 +75,8 @@ export class RestrictionSelectorComponent implements OnInit, OnChanges {
|
|||
|
||||
ngOnChanges() {
|
||||
if (!this.member) return;
|
||||
console.log('changes: ');
|
||||
this.restrictionForm?.get('ageRating')?.setValue(this.member?.ageRestriction || AgeRating.NotApplicable);
|
||||
this.restrictionForm?.get('ageRating')?.setValue(this.member?.ageRestriction.ageRating || AgeRating.NotApplicable);
|
||||
this.restrictionForm?.get('ageRestrictionIncludeUnknowns')?.setValue(this.member?.ageRestriction.includeUnknowns);
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue