Removed some todos and cleaned up some of the code.

This commit is contained in:
Joseph Milazzo 2025-05-10 14:07:55 -05:00
parent 9128e41bcb
commit 09dbdaa41d
18 changed files with 65 additions and 267 deletions

View file

@ -51,23 +51,11 @@ public class Program
// Check if this is the first time running and if so, rename appsettings-init.json to appsettings.json
var firstRunConfigFilePath = Path.Join(Directory.GetCurrentDirectory(), "config/appsettings-init.json");
if (File.Exists(firstRunConfigFilePath) &&
!File.Exists(Path.Join(Directory.GetCurrentDirectory(), "config/appsettings.json")))
{
File.Move(firstRunConfigFilePath, Path.Join(Directory.GetCurrentDirectory(), "config/appsettings.json"));
}
HandleFirstRunConfiguration();
// Before anything, check if JWT has been generated properly or if user still has default
if (!Configuration.CheckIfJwtTokenSet() &&
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != Environments.Development)
{
Log.Logger.Information("Generating JWT TokenKey for encrypting user sessions...");
var rBytes = new byte[256];
RandomNumberGenerator.Create().GetBytes(rBytes);
Configuration.JwtToken = Convert.ToBase64String(rBytes).Replace("/", string.Empty);
}
EnsureJwtTokenKey();
try
{
@ -81,6 +69,7 @@ public class Program
{
var logger = services.GetRequiredService<ILogger<Program>>();
var context = services.GetRequiredService<DataContext>();
var pendingMigrations = await context.Database.GetPendingMigrationsAsync();
var isDbCreated = await context.Database.CanConnectAsync();
if (isDbCreated && pendingMigrations.Any())
@ -168,6 +157,26 @@ public class Program
}
}
private static void EnsureJwtTokenKey()
{
if (Configuration.CheckIfJwtTokenSet() || Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == Environments.Development) return;
Log.Logger.Information("Generating JWT TokenKey for encrypting user sessions...");
var rBytes = new byte[256];
RandomNumberGenerator.Create().GetBytes(rBytes);
Configuration.JwtToken = Convert.ToBase64String(rBytes).Replace("/", string.Empty);
}
private static void HandleFirstRunConfiguration()
{
var firstRunConfigFilePath = Path.Join(Directory.GetCurrentDirectory(), "config/appsettings-init.json");
if (File.Exists(firstRunConfigFilePath) &&
!File.Exists(Path.Join(Directory.GetCurrentDirectory(), "config/appsettings.json")))
{
File.Move(firstRunConfigFilePath, Path.Join(Directory.GetCurrentDirectory(), "config/appsettings.json"));
}
}
private static async Task<string> GetMigrationDirectory(DataContext context, IDirectoryService directoryService)
{
string? currentVersion = null;

View file

@ -473,8 +473,7 @@ export class ActionService {
}
async deleteMultipleVolumes(volumes: Array<Volume>, callback?: BooleanActionCallback) {
// TODO: Change translation key back to "toasts.confirm-delete-multiple-volumes"
if (!await this.confirmService.confirm(translate('toasts.confirm-delete-multiple-chapters', {count: volumes.length}))) return;
if (!await this.confirmService.confirm(translate('toasts.confirm-delete-multiple-volumes', {count: volumes.length}))) return;
this.volumeService.deleteMultipleVolumes(volumes.map(v => v.id)).subscribe((success) => {
if (callback) {

View file

@ -1,20 +1,19 @@
import { HttpClient } from '@angular/common/http';
import {HttpClient, HttpParams} from '@angular/common/http';
import {Inject, inject, Injectable} from '@angular/core';
import { environment } from 'src/environments/environment';
import { UserReadStatistics } from '../statistics/_models/user-read-statistics';
import { PublicationStatusPipe } from '../_pipes/publication-status.pipe';
import {asyncScheduler, finalize, map, tap} from 'rxjs';
import { MangaFormatPipe } from '../_pipes/manga-format.pipe';
import { FileExtensionBreakdown } from '../statistics/_models/file-breakdown';
import { TopUserRead } from '../statistics/_models/top-reads';
import { ReadHistoryEvent } from '../statistics/_models/read-history-event';
import { ServerStatistics } from '../statistics/_models/server-statistics';
import { StatCount } from '../statistics/_models/stat-count';
import { PublicationStatus } from '../_models/metadata/publication-status';
import { MangaFormat } from '../_models/manga-format';
import { TextResonse } from '../_types/text-response';
import {environment} from 'src/environments/environment';
import {UserReadStatistics} from '../statistics/_models/user-read-statistics';
import {PublicationStatusPipe} from '../_pipes/publication-status.pipe';
import {asyncScheduler, map} from 'rxjs';
import {MangaFormatPipe} from '../_pipes/manga-format.pipe';
import {FileExtensionBreakdown} from '../statistics/_models/file-breakdown';
import {TopUserRead} from '../statistics/_models/top-reads';
import {ReadHistoryEvent} from '../statistics/_models/read-history-event';
import {ServerStatistics} from '../statistics/_models/server-statistics';
import {StatCount} from '../statistics/_models/stat-count';
import {PublicationStatus} from '../_models/metadata/publication-status';
import {MangaFormat} from '../_models/manga-format';
import {TextResonse} from '../_types/text-response';
import {TranslocoService} from "@jsverse/transloco";
import {KavitaPlusMetadataBreakdown} from "../statistics/_models/kavitaplus-metadata-breakdown";
import {throttleTime} from "rxjs/operators";
import {DEBOUNCE_TIME} from "../shared/_services/download.service";
import {download} from "../shared/_models/download";
@ -44,11 +43,14 @@ export class StatisticsService {
constructor(private httpClient: HttpClient, @Inject(SAVER) private save: Saver) { }
getUserStatistics(userId: number, libraryIds: Array<number> = []) {
// TODO: Convert to httpParams object
let url = 'stats/user/' + userId + '/read';
if (libraryIds.length > 0) url += '?libraryIds=' + libraryIds.join(',');
const url = `${this.baseUrl}stats/user/${userId}/read`;
return this.httpClient.get<UserReadStatistics>(this.baseUrl + url);
let params = new HttpParams();
if (libraryIds.length > 0) {
params = params.set('libraryIds', libraryIds.join(','));
}
return this.httpClient.get<UserReadStatistics>(url, { params });
}
getServerStatistics() {

View file

@ -1,11 +0,0 @@
@if (logs$ | async; as items) {
<virtual-scroller #scroll [items]="items" [bufferAmount]="1">
<div class="grid row g-0" #container>
@for (item of scroll.viewPortItems; track item.timestamp) {
<div class="card col-auto mt-2 mb-2">
{{item.timestamp | date}} [{{item.level}}] {{item.message}}
</div>
}
</div>
</virtual-scroller>
}

View file

@ -1,71 +0,0 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { HubConnection, HubConnectionBuilder } from '@microsoft/signalr';
import { BehaviorSubject, take } from 'rxjs';
import { AccountService } from 'src/app/_services/account.service';
import { environment } from 'src/environments/environment';
import { VirtualScrollerModule } from '@iharbeck/ngx-virtual-scroller';
import { AsyncPipe, DatePipe } from '@angular/common';
interface LogMessage {
timestamp: string;
level: 'Information' | 'Debug' | 'Warning' | 'Error';
message: string;
exception: string;
}
@Component({
selector: 'app-manage-logs',
templateUrl: './manage-logs.component.html',
styleUrls: ['./manage-logs.component.scss'],
standalone: true,
imports: [VirtualScrollerModule, AsyncPipe, DatePipe]
})
export class ManageLogsComponent implements OnInit, OnDestroy {
hubUrl = environment.hubUrl;
private hubConnection!: HubConnection;
logsSource = new BehaviorSubject<LogMessage[]>([]);
public logs$ = this.logsSource.asObservable();
constructor(private accountService: AccountService) { }
ngOnInit(): void {
// TODO: Come back and implement this one day
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
if (user) {
this.hubConnection = new HubConnectionBuilder()
.withUrl(this.hubUrl + 'logs', {
accessTokenFactory: () => user.token
})
.withAutomaticReconnect()
.build();
console.log('Starting log connection');
this.hubConnection
.start()
.catch(err => console.error(err));
this.hubConnection.on('SendLogAsObject', resp => {
const payload = resp.arguments[0] as LogMessage;
const logMessage = {timestamp: payload.timestamp, level: payload.level, message: payload.message, exception: payload.exception};
// NOTE: It might be better to just have a queue to show this
const values = this.logsSource.getValue();
values.push(logMessage);
this.logsSource.next(values);
});
}
});
}
ngOnDestroy(): void {
// unsubscribe from signalr connection
if (this.hubConnection) {
this.hubConnection.stop().catch(err => console.error(err));
console.log('Stopping log connection');
}
}
}

View file

@ -10,6 +10,4 @@ import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
export class UpdateSectionComponent {
@Input({required: true}) items: Array<string> = [];
@Input({required: true}) title: string = '';
// TODO: Implement a read-more-list so that we by default show a configurable number
}

View file

@ -344,10 +344,6 @@ export class CardItemComponent implements OnInit {
this.clicked.emit(this.title);
}
preventClick(event: any) {
event.stopPropagation();
event.preventDefault();
}
performAction(action: ActionItem<any>) {
if (action.action == Action.Download) {

View file

@ -3,9 +3,11 @@ import {
ChangeDetectorRef,
Component,
DestroyRef,
EventEmitter, HostListener,
EventEmitter,
HostListener,
inject,
Input, OnInit,
Input,
OnInit,
Output
} from '@angular/core';
import {ImageService} from "../../_services/image.service";
@ -14,7 +16,7 @@ import {DownloadEvent, DownloadService} from "../../shared/_services/download.se
import {EVENTS, MessageHubService} from "../../_services/message-hub.service";
import {AccountService} from "../../_services/account.service";
import {ScrollService} from "../../_services/scroll.service";
import {Action, ActionFactoryService, ActionItem} from "../../_services/action-factory.service";
import {ActionItem} from "../../_services/action-factory.service";
import {Chapter} from "../../_models/chapter";
import {Observable} from "rxjs";
import {User} from "../../_models/user";
@ -28,13 +30,10 @@ import {EntityTitleComponent} from "../entity-title/entity-title.component";
import {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component";
import {Router, RouterLink} from "@angular/router";
import {TranslocoDirective} from "@jsverse/transloco";
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
import {filter, map} from "rxjs/operators";
import {UserProgressUpdateEvent} from "../../_models/events/user-progress-update-event";
import {ReaderService} from "../../_services/reader.service";
import {LibraryType} from "../../_models/library/library";
import {Device} from "../../_models/device/device";
import {ActionService} from "../../_services/action.service";
import {MangaFormat} from "../../_models/manga-format";
@Component({
@ -60,15 +59,16 @@ export class ChapterCardComponent implements OnInit {
public readonly imageService = inject(ImageService);
public readonly bulkSelectionService = inject(BulkSelectionService);
private readonly downloadService = inject(DownloadService);
private readonly actionService = inject(ActionService);
private readonly messageHub = inject(MessageHubService);
private readonly accountService = inject(AccountService);
private readonly scrollService = inject(ScrollService);
private readonly cdRef = inject(ChangeDetectorRef);
private readonly actionFactoryService = inject(ActionFactoryService);
private readonly router = inject(Router);
private readonly readerService = inject(ReaderService);
protected readonly LibraryType = LibraryType;
protected readonly MangaFormat = MangaFormat;
@Input({required: true}) libraryId: number = 0;
@Input({required: true}) seriesId: number = 0;
@Input({required: true}) chapter!: Chapter;
@ -143,8 +143,6 @@ export class ChapterCardComponent implements OnInit {
}
ngOnInit() {
this.filterSendTo();
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(user => {
this.user = user;
});
@ -172,30 +170,6 @@ export class ChapterCardComponent implements OnInit {
this.cdRef.detectChanges();
}
filterSendTo() {
if (!this.actions || this.actions.length === 0) return;
this.actions = this.actionFactoryService.filterSendToAction(this.actions, this.chapter);
}
performAction(action: ActionItem<any>) {
if (action.action == Action.Download) {
this.downloadService.download('chapter', this.chapter);
return; // Don't propagate the download from a card
}
if (action.action == Action.SendTo) {
const device = (action._extra!.data as Device);
this.actionService.sendToDevice([this.chapter.id], device);
return;
}
if (typeof action.callback === 'function') {
action.callback(action, this.chapter);
}
}
handleClick(event: any) {
if (this.bulkSelectionService.hasSelections()) {
this.handleSelection(event);
@ -209,8 +183,4 @@ export class ChapterCardComponent implements OnInit {
event.stopPropagation();
this.readerService.readChapter(this.libraryId, this.seriesId, this.chapter, false);
}
protected readonly LibraryType = LibraryType;
protected readonly MangaFormat = MangaFormat;
}

View file

@ -1,20 +1,20 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component, ContentChild,
DestroyRef, EventEmitter,
Component,
ContentChild,
DestroyRef,
EventEmitter,
HostListener,
inject,
Input, Output, TemplateRef
Input,
Output,
TemplateRef
} from '@angular/core';
import {Action, ActionFactoryService, ActionItem} from "../../_services/action-factory.service";
import {ActionItem} from "../../_services/action-factory.service";
import {ImageService} from "../../_services/image.service";
import {BulkSelectionService} from "../bulk-selection.service";
import {LibraryService} from "../../_services/library.service";
import {DownloadService} from "../../shared/_services/download.service";
import {UtilityService} from "../../shared/_services/utility.service";
import {MessageHubService} from "../../_services/message-hub.service";
import {AccountService} from "../../_services/account.service";
import {ScrollService} from "../../_services/scroll.service";
import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
import {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component";
@ -139,11 +139,6 @@ export class PersonCardComponent {
this.clicked.emit(this.title);
}
performAction(action: ActionItem<any>) {
if (typeof action.callback === 'function') {
action.callback(action, this.entity);
}
}
handleSelection(event?: any) {
if (event) {

View file

@ -23,7 +23,7 @@ import {DownloadEvent, DownloadService} from "../../shared/_services/download.se
import {EVENTS, MessageHubService} from "../../_services/message-hub.service";
import {AccountService} from "../../_services/account.service";
import {ScrollService} from "../../_services/scroll.service";
import {Action, ActionItem} from "../../_services/action-factory.service";
import {ActionItem} from "../../_services/action-factory.service";
import {ReaderService} from "../../_services/reader.service";
import {Observable} from "rxjs";
import {User} from "../../_models/user";
@ -33,7 +33,6 @@ import {UserProgressUpdateEvent} from "../../_models/events/user-progress-update
import {Volume} from "../../_models/volume";
import {UtilityService} from "../../shared/_services/utility.service";
import {LibraryType} from "../../_models/library/library";
import {Device} from "../../_models/device/device";
import {ActionService} from "../../_services/action.service";
import {FormsModule} from "@angular/forms";
@ -143,8 +142,6 @@ export class VolumeCardComponent implements OnInit {
}
ngOnInit() {
this.filterSendTo();
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(user => {
this.user = user;
});
@ -180,30 +177,6 @@ export class VolumeCardComponent implements OnInit {
this.cdRef.detectChanges();
}
filterSendTo() {
if (!this.actions || this.actions.length === 0) return;
// TODO: See if we can handle send to for volumes
//this.actions = this.actionFactoryService.filterSendToAction(this.actions, this.volume);
}
performAction(action: ActionItem<Volume>) {
if (action.action == Action.Download) {
this.downloadService.download('volume', this.volume);
return; // Don't propagate the download from a card
}
if (action.action == Action.SendTo) {
const device = (action._extra!.data as Device);
this.actionService.sendToDevice(this.volume.chapters.map(c => c.id), device);
return;
}
if (typeof action.callback === 'function') {
action.callback(action, this.volume);
}
}
handleClick(event: any) {
if (this.bulkSelectionService.hasSelections()) {
this.handleSelection(event);

View file

@ -339,10 +339,6 @@ export class ChapterDetailComponent implements OnInit {
this.location.replaceState(newUrl)
}
openPerson(field: FilterField, value: number) {
this.filterUtilityService.applyFilter(['all-series'], field, FilterComparison.Equal, `${value}`).subscribe();
}
downloadChapter() {
if (this.downloadInProgress) return;
this.downloadService.download('chapter', this.chapter!, (d) => {
@ -360,11 +356,6 @@ export class ChapterDetailComponent implements OnInit {
this.cdRef.markForCheck();
}
performAction(action: ActionItem<Chapter>) {
if (typeof action.callback === 'function') {
action.callback(action, this.chapter!);
}
}
handleChapterActionCallback(action: ActionItem<Chapter>, chapter: Chapter) {
switch (action.action) {

View file

@ -305,12 +305,6 @@ export class CollectionDetailComponent implements OnInit, AfterContentChecked {
}
}
performAction(action: ActionItem<any>) {
if (typeof action.callback === 'function') {
action.callback(action, this.collectionTag);
}
}
openEditCollectionTagModal(collectionTag: UserCollection) {
const modalRef = this.modalService.open(EditCollectionTagsComponent, DefaultModalOptions);
modalRef.componentInstance.tag = this.collectionTag;
@ -321,7 +315,6 @@ export class CollectionDetailComponent implements OnInit, AfterContentChecked {
}
openSyncDetailDrawer() {
const ref = this.offcanvasService.open(SmartCollectionDrawerComponent, {position: 'end', panelClass: ''});
ref.componentInstance.collection = this.collectionTag;
ref.componentInstance.series = this.series;

View file

@ -297,8 +297,6 @@ export class LibraryDetailComponent implements OnInit {
}
}
performAction(action: ActionItem<any>) {
if (typeof action.callback === 'function') {
action.callback(action, undefined);

View file

@ -19,7 +19,6 @@ import {
SideNavCompanionBarComponent
} from "../sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component";
import {ReadMoreComponent} from "../shared/read-more/read-more.component";
import {TagBadgeCursor} from "../shared/tag-badge/tag-badge.component";
import {PersonRolePipe} from "../_pipes/person-role.pipe";
import {CarouselReelComponent} from "../carousel/_components/carousel-reel/carousel-reel.component";
import {FilterComparison} from "../_models/metadata/v2/filter-comparison";
@ -89,7 +88,7 @@ export class PersonDetailComponent implements OnInit {
private readonly toastr = inject(ToastrService);
private readonly messageHubService = inject(MessageHubService)
protected readonly TagBadgeCursor = TagBadgeCursor;
protected readonly FilterField = FilterField;
@ViewChild('scrollingBlock') scrollingBlock: ElementRef<HTMLDivElement> | undefined;
@ViewChild('companionBar') companionBar: ElementRef<HTMLDivElement> | undefined;
@ -278,11 +277,4 @@ export class PersonDetailComponent implements OnInit {
});
}
performAction(action: ActionItem<any>) {
if (typeof action.callback === 'function') {
action.callback(action, this.person);
}
}
protected readonly FilterField = FilterField;
}

View file

@ -273,11 +273,6 @@ export class ReadingListDetailComponent implements OnInit {
});
}
performAction(action: ActionItem<any>) {
if (typeof action.callback === 'function') {
action.callback(action, this.readingList);
}
}
readChapter(item: ReadingListItem) {
if (!this.readingList) return;
@ -387,12 +382,6 @@ export class ReadingListDetailComponent implements OnInit {
{queryParams: {readingListId: this.readingList.id, incognitoMode: incognitoMode}});
}
updateAccessibilityMode() {
this.accessibilityMode = !this.accessibilityMode;
this.cdRef.markForCheck();
}
toggleReorder() {
this.formGroup.get('edit')?.setValue(!this.formGroup.get('edit')!.value);
this.cdRef.markForCheck();

View file

@ -61,11 +61,6 @@ import {ReaderService} from 'src/app/_services/reader.service';
import {ReadingListService} from 'src/app/_services/reading-list.service';
import {ScrollService} from 'src/app/_services/scroll.service';
import {SeriesService} from 'src/app/_services/series.service';
import {
ReviewModalCloseAction,
ReviewModalCloseEvent,
ReviewModalComponent
} from '../../../_single-module/review-modal/review-modal.component';
import {PageLayoutMode} from 'src/app/_models/page-layout-mode';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {UserReview} from "../../../_single-module/review-card/user-review";
@ -73,8 +68,6 @@ import {ExternalSeriesCardComponent} from '../../../cards/external-series-card/e
import {SeriesCardComponent} from '../../../cards/series-card/series-card.component';
import {VirtualScrollerModule} from '@iharbeck/ngx-virtual-scroller';
import {BulkOperationsComponent} from '../../../cards/bulk-operations/bulk-operations.component';
import {ReviewCardComponent} from '../../../_single-module/review-card/review-card.component';
import {CarouselReelComponent} from '../../../carousel/_components/carousel-reel/carousel-reel.component';
import {translate, TranslocoDirective, TranslocoService} from "@jsverse/transloco";
import {CardActionablesComponent} from "../../../_single-module/card-actionables/card-actionables.component";
import {PublicationStatus} from "../../../_models/metadata/publication-status";
@ -1138,13 +1131,6 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
});
}
performAction(action: ActionItem<any>) {
if (typeof action.callback === 'function') {
action.callback(action, this.series);
}
}
downloadSeries() {
this.downloadService.download('series', this.series, (d) => {
this.downloadInProgress = !!d;

View file

@ -188,6 +188,7 @@ export class VolumeDetailComponent implements OnInit {
protected readonly TabID = TabID;
protected readonly FilterField = FilterField;
protected readonly Breakpoint = Breakpoint;
protected readonly encodeURIComponent = encodeURIComponent;
@ViewChild('scrollingBlock') scrollingBlock: ElementRef<HTMLDivElement> | undefined;
@ViewChild('companionBar') companionBar: ElementRef<HTMLDivElement> | undefined;
@ -571,16 +572,6 @@ export class VolumeDetailComponent implements OnInit {
this.location.replaceState(newUrl)
}
openPerson(field: FilterField, value: number) {
this.filterUtilityService.applyFilter(['all-series'], field, FilterComparison.Equal, `${value}`).subscribe();
}
performAction(action: ActionItem<Volume>) {
if (typeof action.callback === 'function') {
action.callback(action, this.volume!);
}
}
async handleChapterActionCallback(action: ActionItem<Chapter>, chapter: Chapter) {
switch (action.action) {
case(Action.MarkAsRead):
@ -699,6 +690,4 @@ export class VolumeDetailComponent implements OnInit {
this.currentlyReadingChapter = undefined;
}
}
protected readonly encodeURIComponent = encodeURIComponent;
}