diff --git a/client/typecheck-baseline.txt b/client/typecheck-baseline.txt
index 7b00d2e71..782d23ed7 100644
--- a/client/typecheck-baseline.txt
+++ b/client/typecheck-baseline.txt
@@ -1,7 +1,6 @@
# Client typecheck baseline
# Auto-generated by: pnpm run typecheck:client:baseline:update
# One diagnostic per line. Do not edit manually unless necessary.
-src/components/IconPicker.vue(174,39): error TS18046: 'item' is of type 'unknown'.
src/features/book/components/detail/BookDetailHeader.vue(22,5): error TS2322: Type 'number | null | undefined' is not assignable to type 'number | null'.
src/features/book/components/detail/BookDetailHeader.vue(23,5): error TS2322: Type 'number | null | undefined' is not assignable to type 'number | null'.
src/features/dashboard/components/DashboardSettingsSheet.vue(45,26): error TS2345: Argument of type '{ id: string; type: ScrollerType; label: string; enabled: boolean; order: number; limit: number; lensId?: number | undefined; } | undefined' is not assignable to parameter of type '{ id: string; type: ScrollerType; label: string; enabled: boolean; order: number; limit: number; lensId?: number | undefined; }'.
@@ -9,7 +8,6 @@ src/features/dashboard/components/DashboardSettingsSheet.vue(80,23): error TS232
src/features/dashboard/components/DashboardSettingsSheet.vue(80,5): error TS2322: Type '{ id: string; type: ScrollerType; label: string; enabled: boolean; order: number; limit: number; lensId?: number | undefined; } | undefined' is not assignable to type '{ id: string; type: ScrollerType; label: string; enabled: boolean; order: number; limit: number; lensId?: number | undefined; }'.
src/features/dashboard/components/DashboardSettingsSheet.vue(87,19): error TS2322: Type '{ id: string; type: ScrollerType; label: string; enabled: boolean; order: number; limit: number; lensId?: number | undefined; } | undefined' is not assignable to type '{ id: string; type: ScrollerType; label: string; enabled: boolean; order: number; limit: number; lensId?: number | undefined; }'.
src/features/dashboard/components/DashboardSettingsSheet.vue(87,5): error TS2322: Type '{ id: string; type: ScrollerType; label: string; enabled: boolean; order: number; limit: number; lensId?: number | undefined; } | undefined' is not assignable to type '{ id: string; type: ScrollerType; label: string; enabled: boolean; order: number; limit: number; lensId?: number | undefined; }'.
-src/features/library/components/LibraryCreatorDetails.vue(109,43): error TS18046: 'item' is of type 'unknown'.
src/features/library/components/LibraryCreatorScanner.vue(36,36): error TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
src/features/reader/components/CbzReaderView.vue(95,17): error TS2532: Object is possibly 'undefined'.
src/features/reader/components/CbzReaderView.vue(99,14): error TS2532: Object is possibly 'undefined'.
@@ -36,8 +34,6 @@ src/features/reader/composables/useFoliateInput.ts(92,39): error TS18048: 'touch
src/features/reader/composables/useFoliateInput.ts(95,27): error TS18048: 'touch' is possibly 'undefined'.
src/features/reader/composables/usePdfFind.ts(33,22): error TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
src/features/reader/composables/usePdfFind.ts(33,43): error TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
-src/features/reader/composables/usePdfOutline.ts(22,72): error TS2694: Namespace '"node_modules/.pnpm/pdfjs-dist@5.5.207/node_modules/pdfjs-dist/types/src/pdf"' has no exported member 'RefProxy'.
-src/features/reader/composables/usePdfOutline.ts(29,74): error TS2694: Namespace '"node_modules/.pnpm/pdfjs-dist@5.5.207/node_modules/pdfjs-dist/types/src/pdf"' has no exported member 'OutlineNode'.
src/features/reader/composables/usePdfRenderer.ts(96,17): error TS18048: 'e' is possibly 'undefined'.
src/features/reader/composables/usePdfRenderer.ts(97,34): error TS18048: 'f' is possibly 'undefined'.
src/features/reader/composables/usePdfRenderer.ts(98,31): error TS18048: 'a' is possibly 'undefined'.
@@ -57,8 +53,4 @@ src/features/settings/metadata-preferences/components/FieldConfigSheet.vue(57,5)
src/features/settings/metadata-preferences/components/FieldConfigSheet.vue(65,21): error TS2322: Type 'MetadataProviderKey | undefined' is not assignable to type 'MetadataProviderKey'.
src/features/settings/metadata-preferences/components/FieldConfigSheet.vue(65,5): error TS2322: Type 'MetadataProviderKey | undefined' is not assignable to type 'MetadataProviderKey'.
src/features/settings/metadata-preferences/components/ProviderChipList.vue(56,34): error TS2345: Argument of type 'MetadataProviderKey | undefined' is not assignable to parameter of type 'MetadataProviderKey'.
-src/features/settings/metadata-preferences/components/ProviderConfigPanel.vue(130,25): error TS2352: Conversion of type '{ enabled: boolean; apiKey: string; } | { enabled: boolean; domain: string; cookie: string; } | { enabled: boolean; } | { enabled: boolean; apiKey: string; }' to type 'Record
' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
-src/features/settings/metadata-preferences/components/ProviderConfigPanel.vue(138,27): error TS2352: Conversion of type '{ enabled: boolean; apiKey: string; } | { enabled: boolean; domain: string; cookie: string; } | { enabled: boolean; } | { enabled: boolean; apiKey: string; }' to type 'Record' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
-src/features/settings/PdfSettings.vue(45,17): error TS2367: This comparison appears to be unintentional because the types '"vertical" | "horizontal" | "page" | "wrapped"' and '"continuous"' have no overlap.
-src/features/settings/PdfSettings.vue(47,32): error TS2322: Type '"continuous"' is not assignable to type '"vertical" | "horizontal" | "page" | "wrapped" | undefined'.
src/lib/api.ts(55,37): error TS2353: Object literal may only specify known properties, and '_isRetry' does not exist in type 'RequestInit'.
diff --git a/packages/types/src/reader-settings.ts b/packages/types/src/reader-settings.ts
index f6bc082b3..baebe266e 100644
--- a/packages/types/src/reader-settings.ts
+++ b/packages/types/src/reader-settings.ts
@@ -8,6 +8,7 @@ export const FORMAT_TO_GROUP: Record = {
fb2: 'epub',
txt: 'epub',
pdf: 'pdf',
+ cbx: 'cbx',
cbz: 'cbx',
cbr: 'cbx',
cb7: 'cbx',
@@ -76,7 +77,7 @@ export const EPUB_READER_DEFAULTS: EpubReaderSettings = {
}
export const PDF_READER_DEFAULTS: PdfReaderSettings = {
- scrollMode: 'vertical',
+ scrollMode: 'page',
spread: 'none',
zoomMode: 'fit-page',
customScale: 1.0,
diff --git a/server/src/modules/app-settings/dto/update-oidc-config.dto.ts b/server/src/modules/app-settings/dto/update-oidc-config.dto.ts
index dc63d5552..a0dac433e 100644
--- a/server/src/modules/app-settings/dto/update-oidc-config.dto.ts
+++ b/server/src/modules/app-settings/dto/update-oidc-config.dto.ts
@@ -22,10 +22,9 @@ class AutoProvisionDto {
@IsBoolean()
allowLocalLinking: boolean;
- @IsOptional()
@IsArray()
@IsString({ each: true })
- defaultPermissionNames?: string[];
+ defaultPermissionNames: string[] = [];
}
export class UpdateOidcConfigDto {
diff --git a/server/src/modules/book/book.service.ts b/server/src/modules/book/book.service.ts
index 1b9658525..ad32b747c 100644
--- a/server/src/modules/book/book.service.ts
+++ b/server/src/modules/book/book.service.ts
@@ -3,7 +3,7 @@ import { ConfigService } from '@nestjs/config';
import { access, readdir, rm, stat } from 'fs/promises';
import { basename, extname, join } from 'path';
-import { MetadataProviderKey, resolveUploadPath } from '@projectx/types';
+import { MetadataProviderKey, Permission, resolveUploadPath } from '@projectx/types';
import type { BookKoboState, BookQuery, BooksPage, MetadataField } from '@projectx/types';
import { assembleBookCards } from './utils/assemble-book-cards';
import type { RequestUser } from '../../common/types/request-user';
@@ -44,7 +44,7 @@ export class BookService {
return user.isSuperuser;
}
- private hasPermission(user: RequestUser, permissionName: string): boolean {
+ private hasPermission(user: RequestUser, permissionName: Permission): boolean {
return user.isSuperuser || user.permissions.includes(permissionName);
}
@@ -357,7 +357,7 @@ export class BookService {
async getKoboState(id: number, user: RequestUser): Promise {
await this.verifyBookAccess(id, user);
- if (!this.hasPermission(user, 'kobo_sync')) {
+ if (!this.hasPermission(user, Permission.KoboSync)) {
return {
eligibleForKoboSync: false,
syncCollections: [],
diff --git a/server/src/modules/kobo/guards/kobo-token.guard.ts b/server/src/modules/kobo/guards/kobo-token.guard.ts
index a69496884..a8abbad83 100644
--- a/server/src/modules/kobo/guards/kobo-token.guard.ts
+++ b/server/src/modules/kobo/guards/kobo-token.guard.ts
@@ -3,6 +3,7 @@ import { eq } from 'drizzle-orm';
import type { FastifyRequest } from 'fastify';
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
+import { Permission } from '@projectx/types';
import { DB } from '../../../db/db.module';
import * as schema from '../../../db/schema';
import { PermissionService } from '../../../common/services/permission.service';
@@ -36,7 +37,7 @@ export class KoboTokenGuard implements CanActivate {
const user = await this.userService.findByIdWithPermissions(device.userId);
if (!user || !user.active) throw new UnauthorizedException('Account not found or disabled');
- if (!this.permissionService.userHas(user, 'kobo_sync')) throw new UnauthorizedException('Kobo sync permission revoked');
+ if (!this.permissionService.userHas(user, Permission.KoboSync)) throw new UnauthorizedException('Kobo sync permission revoked');
// Update last seen asynchronously so it doesn't block the request
this.db
diff --git a/server/src/modules/metadata-preferences/provider-config.service.spec.ts b/server/src/modules/metadata-preferences/provider-config.service.spec.ts
index 55381416d..4338a09c2 100644
--- a/server/src/modules/metadata-preferences/provider-config.service.spec.ts
+++ b/server/src/modules/metadata-preferences/provider-config.service.spec.ts
@@ -137,7 +137,7 @@ describe('ProviderConfigService', () => {
MetadataProviderKey.HARDCOVER,
MetadataProviderKey.OPEN_LIBRARY,
]);
- expect(statuses.find((s) => s.key === MetadataProviderKey.GOOGLE)?.hint).toContain('API key recommended');
+ expect(statuses.find((s) => s.key === MetadataProviderKey.GOOGLE)?.hint).toContain('Recommended for higher rate limits');
expect(statuses.find((s) => s.key === MetadataProviderKey.AMAZON)?.hint).toContain('Cookie recommended');
expect(statuses.find((s) => s.key === MetadataProviderKey.HARDCOVER)?.configured).toBe(false);
expect(statuses.find((s) => s.key === MetadataProviderKey.GOODREADS)?.enabled).toBe(false);
diff --git a/server/src/modules/metadata-preferences/provider-config.service.ts b/server/src/modules/metadata-preferences/provider-config.service.ts
index 9febc9c3a..62f98493d 100644
--- a/server/src/modules/metadata-preferences/provider-config.service.ts
+++ b/server/src/modules/metadata-preferences/provider-config.service.ts
@@ -89,7 +89,7 @@ export class ProviderConfigService {
label: PROVIDER_LABELS[MetadataProviderKey.GOOGLE],
enabled: cfg.google.enabled,
configured: true,
- hint: !cfg.google.apiKey ? 'API key recommended for higher rate limits' : undefined,
+ hint: !cfg.google.apiKey ? 'Recommended for higher rate limits' : undefined,
},
{
key: MetadataProviderKey.AMAZON,
diff --git a/server/src/modules/opds/opds-auth.guard.ts b/server/src/modules/opds/opds-auth.guard.ts
index e7a9b5d9b..43c1474ad 100644
--- a/server/src/modules/opds/opds-auth.guard.ts
+++ b/server/src/modules/opds/opds-auth.guard.ts
@@ -4,6 +4,7 @@ import { CanActivate, ExecutionContext, ForbiddenException, Injectable, Unauthor
import { ConfigService } from '@nestjs/config';
import type { FastifyReply, FastifyRequest } from 'fastify';
+import { Permission } from '@projectx/types';
import { PermissionService } from '../../common/services/permission.service';
import { OpdsUserService } from './opds-user.service';
import { UserService } from '../user/user.service';
@@ -53,7 +54,7 @@ export class OpdsAuthGuard implements CanActivate {
const fullUser = await this.userService.findByIdWithPermissions(userId);
if (!fullUser || !fullUser.active) throw new UnauthorizedException('Account not found or disabled');
- if (!this.permissionService.userHas(fullUser, 'opds_access')) throw new ForbiddenException('OPDS access revoked');
+ if (!this.permissionService.userHas(fullUser, Permission.OpdsAccess)) throw new ForbiddenException('OPDS access revoked');
(request as unknown as Record).opdsUser = {
opdsUserId: 0,
@@ -98,7 +99,7 @@ export class OpdsAuthGuard implements CanActivate {
throw new UnauthorizedException('Account not found');
}
- if (!this.permissionService.userHas(fullUser, 'opds_access')) {
+ if (!this.permissionService.userHas(fullUser, Permission.OpdsAccess)) {
throw new ForbiddenException('OPDS access revoked');
}