test(auth): add authorization-matrix end-to-end suite

This commit is contained in:
neon
2026-04-01 15:49:30 -06:00
parent efea4ba846
commit 914f8a3440
22 changed files with 6219 additions and 38 deletions
+8
View File
@@ -57,6 +57,14 @@ export const E2E_SUITES = Object.freeze({
prepareDedicatedDatabase: true,
useDedicatedDatabase: true,
},
"authorization-matrix": {
id: "authorization-matrix",
description: "Authorization matrix suite",
vitestTarget: "test/authorization-matrix.e2e-spec.ts",
junitOutput: `${TEST_RESULTS_DIR}/authorization-matrix-e2e-junit.xml`,
prepareDedicatedDatabase: true,
useDedicatedDatabase: true,
},
});
export function listE2ESuites() {
@@ -48,6 +48,7 @@ describe('Architecture boundaries', () => {
'src/modules/catalog/catalog.service.ts',
'src/modules/cover/cover.service.ts',
'src/modules/file-write/file-write-settings.service.ts',
'src/modules/kobo/services/kobo-book-access.service.ts',
'src/modules/kobo/services/kobo-device.service.ts',
'src/modules/kobo/services/kobo-download.service.ts',
'src/modules/kobo/services/kobo-reading-state.service.ts',
@@ -28,33 +28,36 @@ export class KoboDeviceController {
async thumbnailFull(
@Param('bookId') bookId: string,
@Headers('if-none-match') ifNoneMatch: string | undefined,
@CurrentUser() user: RequestUser,
@KoboDevice() device: KoboDeviceContext,
@Req() req: FastifyRequest,
@Res() reply: FastifyReply,
) {
await this.serveThumbnailOrProxy(bookId, ifNoneMatch, device, req, reply);
await this.serveThumbnailOrProxy(bookId, ifNoneMatch, user, device, req, reply);
}
@Get('v1/books/:bookId/thumbnail/:width/:height/false/image.jpg')
async thumbnailSimple(
@Param('bookId') bookId: string,
@Headers('if-none-match') ifNoneMatch: string | undefined,
@CurrentUser() user: RequestUser,
@KoboDevice() device: KoboDeviceContext,
@Req() req: FastifyRequest,
@Res() reply: FastifyReply,
) {
await this.serveThumbnailOrProxy(bookId, ifNoneMatch, device, req, reply);
await this.serveThumbnailOrProxy(bookId, ifNoneMatch, user, device, req, reply);
}
@Get('v1/books/:bookId/:version/thumbnail/:width/:height/false/image.jpg')
async thumbnailVersioned(
@Param('bookId') bookId: string,
@Headers('if-none-match') ifNoneMatch: string | undefined,
@CurrentUser() user: RequestUser,
@KoboDevice() device: KoboDeviceContext,
@Req() req: FastifyRequest,
@Res() reply: FastifyReply,
) {
await this.serveThumbnailOrProxy(bookId, ifNoneMatch, device, req, reply);
await this.serveThumbnailOrProxy(bookId, ifNoneMatch, user, device, req, reply);
}
@Get('v1/books/:bookId/download')
@@ -109,12 +112,13 @@ export class KoboDeviceController {
private async serveThumbnailOrProxy(
bookId: string,
ifNoneMatch: string | undefined,
user: RequestUser,
device: KoboDeviceContext,
req: FastifyRequest,
reply: FastifyReply,
) {
const id = parseInt(bookId, 10);
if (isNaN(id)) return this.proxyService.forward(req, reply, device.deviceToken);
await this.thumbnailService.serveThumbnail(id, ifNoneMatch, reply);
await this.thumbnailService.serveThumbnail(user.id, id, ifNoneMatch, reply);
}
}
+2
View File
@@ -7,6 +7,7 @@ import { KoboDeviceController } from './kobo-device.controller';
import { KoboSyncController } from './kobo-sync.controller';
import { KoboUserController } from './kobo-user.controller';
import { KoboTokenGuard } from './guards/kobo-token.guard';
import { KoboBookAccessService } from './services/kobo-book-access.service';
import { KepubifyBinaryService } from './services/kepubify-binary.service';
import { KoboDeviceService } from './services/kobo-device.service';
import { KoboDownloadService } from './services/kobo-download.service';
@@ -24,6 +25,7 @@ import { KoboThumbnailService } from './services/kobo-thumbnail.service';
KepubifyBinaryService,
KoboDeviceService,
KoboSettingsService,
KoboBookAccessService,
KoboSyncService,
KoboReadingStateService,
KoboThumbnailService,
@@ -0,0 +1,41 @@
import { ForbiddenException, Inject, Injectable } from '@nestjs/common';
import { eq } from 'drizzle-orm';
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
import { DB } from '../../../db/db.module';
import * as schema from '../../../db/schema';
type Db = NodePgDatabase<typeof schema>;
@Injectable()
export class KoboBookAccessService {
constructor(@Inject(DB) private readonly db: Db) {}
async getAccessibleLibraryIds(userId: number): Promise<number[] | null> {
const user = await this.db.query.users.findFirst({
where: eq(schema.users.id, userId),
columns: { isSuperuser: true },
});
if (user?.isSuperuser) return null;
const rows = await this.db
.select({ libraryId: schema.userLibraryAccess.libraryId })
.from(schema.userLibraryAccess)
.where(eq(schema.userLibraryAccess.userId, userId));
return rows.map((row) => row.libraryId);
}
async assertBookAccessible(userId: number, bookId: number): Promise<void> {
const book = await this.db.query.books.findFirst({
where: eq(schema.books.id, bookId),
columns: { id: true, libraryId: true },
});
if (!book) return;
const accessibleLibraryIds = await this.getAccessibleLibraryIds(userId);
if (accessibleLibraryIds !== null && !accessibleLibraryIds.includes(book.libraryId)) {
throw new ForbiddenException('No access to this book');
}
}
}
@@ -4,7 +4,7 @@ import { mkdir, stat } from 'fs/promises';
import { join } from 'path';
import { promisify } from 'util';
import { ForbiddenException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
import { Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { and, eq } from 'drizzle-orm';
import type { FastifyReply } from 'fastify';
@@ -12,6 +12,7 @@ import { NodePgDatabase } from 'drizzle-orm/node-postgres';
import { DB } from '../../../db/db.module';
import * as schema from '../../../db/schema';
import { KoboBookAccessService } from './kobo-book-access.service';
import { KepubifyBinaryService } from './kepubify-binary.service';
import { KoboSettingsService } from './kobo-settings.service';
@@ -35,6 +36,7 @@ export class KoboDownloadService {
private readonly config: ConfigService,
private readonly kepubifyBinaryService: KepubifyBinaryService,
private readonly settingsService: KoboSettingsService,
private readonly bookAccessService: KoboBookAccessService,
) {
this.booksPath = this.config.get<string>('storage.booksPath')!;
this.kepubCachePath = join(this.booksPath, '.kepub-cache');
@@ -44,8 +46,7 @@ export class KoboDownloadService {
const book = await this.db.query.books.findFirst({ where: eq(schema.books.id, bookId) });
if (!book) throw new NotFoundException('Book not found');
const accessible = await this.isBookAccessibleToUser(userId, bookId);
if (!accessible) throw new ForbiddenException('No access to this book');
await this.bookAccessService.assertBookAccessible(userId, bookId);
const file = await this.db.query.bookFiles.findFirst({
where: and(eq(schema.bookFiles.bookId, bookId), eq(schema.bookFiles.id, book.primaryFileId ?? -1)),
@@ -106,22 +107,4 @@ export class KoboDownloadService {
return this.streamFile(sourcePath, fileId, 'epub', reply);
}
}
private async isBookAccessibleToUser(userId: number, bookId: number): Promise<boolean> {
const book = await this.db.query.books.findFirst({ where: eq(schema.books.id, bookId) });
if (!book) return false;
const isSuperuser = await this.checkSuperuser(userId);
if (isSuperuser) return true;
const access = await this.db.query.userLibraryAccess.findFirst({
where: and(eq(schema.userLibraryAccess.userId, userId), eq(schema.userLibraryAccess.libraryId, book.libraryId)),
});
return !!access;
}
private async checkSuperuser(userId: number): Promise<boolean> {
const user = await this.db.query.users.findFirst({ where: eq(schema.users.id, userId) });
return user?.isSuperuser ?? false;
}
}
@@ -1,9 +1,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { and, eq, isNull, or, sql } from 'drizzle-orm';
import { and, eq, inArray, isNull, or, sql } from 'drizzle-orm';
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
import { DB } from '../../../db';
import * as schema from '../../../db/schema';
import { KoboBookAccessService } from './kobo-book-access.service';
type Db = NodePgDatabase<typeof schema>;
type JsonObj = Record<string, unknown>;
@@ -19,9 +20,14 @@ function mergeSubObject(incoming: JsonObj | null | undefined, existing: JsonObj
@Injectable()
export class KoboReadingStateService {
constructor(@Inject(DB) private readonly db: Db) {}
constructor(
@Inject(DB) private readonly db: Db,
private readonly bookAccessService: KoboBookAccessService,
) {}
async upsertState(userId: number, bookId: number, payload: Record<string, unknown>, readingThreshold: number, finishedThreshold: number) {
await this.bookAccessService.assertBookAccessible(userId, bookId);
const entitlementId = String(bookId);
const now = new Date().toISOString();
@@ -96,6 +102,8 @@ export class KoboReadingStateService {
}
async getRawState(userId: number, bookId: number): Promise<unknown> {
await this.bookAccessService.assertBookAccessible(userId, bookId);
const row = await this.db.query.koboReadingStates.findFirst({
where: and(eq(schema.koboReadingStates.userId, userId), eq(schema.koboReadingStates.bookId, bookId)),
});
@@ -114,6 +122,14 @@ export class KoboReadingStateService {
}
async getAndMarkStatesNeedingPush(userId: number, readingThreshold: number, finishedThreshold: number): Promise<unknown[]> {
const accessibleLibraryIds = await this.bookAccessService.getAccessibleLibraryIds(userId);
const libraryAccessFilter =
accessibleLibraryIds === null
? undefined
: accessibleLibraryIds.length === 0
? sql`false`
: inArray(schema.books.libraryId, accessibleLibraryIds);
const rows = await this.db
.select({
bookId: schema.books.id,
@@ -126,6 +142,7 @@ export class KoboReadingStateService {
.where(
and(
eq(schema.readingProgress.userId, userId),
libraryAccessFilter,
or(
isNull(schema.koboReadingStates.progressSyncedAt),
sql`${schema.readingProgress.updatedAt} > ${schema.koboReadingStates.progressSyncedAt}`,
@@ -5,6 +5,7 @@ import { NodePgDatabase } from 'drizzle-orm/node-postgres';
import { DB } from '../../../db/db.module';
import * as schema from '../../../db/schema';
import { KoboBookAccessService } from './kobo-book-access.service';
import { KoboReadingStateService } from './kobo-reading-state.service';
type Db = NodePgDatabase<typeof schema>;
@@ -46,6 +47,7 @@ function encodeSyncToken(snapshotId: number): string {
export class KoboSyncService {
constructor(
@Inject(DB) private readonly db: Db,
private readonly bookAccessService: KoboBookAccessService,
private readonly readingStateService: KoboReadingStateService,
) {}
@@ -401,6 +403,14 @@ export class KoboSyncService {
}
private async fetchEligibleBooks(userId: number): Promise<KoboBookEntry[]> {
const accessibleLibraryIds = await this.bookAccessService.getAccessibleLibraryIds(userId);
const libraryAccessFilter =
accessibleLibraryIds === null
? undefined
: accessibleLibraryIds.length === 0
? sql`false`
: inArray(schema.books.libraryId, accessibleLibraryIds);
const rows = await this.db
.select({
bookId: schema.books.id,
@@ -430,7 +440,7 @@ export class KoboSyncService {
eq(schema.collections.syncToKobo, true),
),
)
.where(and(eq(schema.books.status, 'present'), sql`lower(${schema.bookFiles.format}) in ('epub')`))
.where(and(eq(schema.books.status, 'present'), sql`lower(${schema.bookFiles.format}) in ('epub')`, libraryAccessFilter))
.groupBy(
schema.books.id,
schema.bookMetadata.title,
@@ -4,16 +4,22 @@ import { readdir, stat } from 'fs/promises';
import { createReadStream } from 'fs';
import { join } from 'path';
import type { FastifyReply } from 'fastify';
import { KoboBookAccessService } from './kobo-book-access.service';
@Injectable()
export class KoboThumbnailService {
private readonly booksPath: string;
constructor(private readonly config: ConfigService) {
constructor(
private readonly config: ConfigService,
private readonly bookAccessService: KoboBookAccessService,
) {
this.booksPath = this.config.get<string>('storage.booksPath')!;
}
async serveThumbnail(bookId: number, ifNoneMatch: string | undefined, reply: FastifyReply) {
async serveThumbnail(userId: number, bookId: number, ifNoneMatch: string | undefined, reply: FastifyReply) {
await this.bookAccessService.assertBookAccessible(userId, bookId);
const thumbnailPath = join(this.booksPath, 'covers', String(bookId), 'thumbnail.jpg');
try {
const { mtimeMs } = await stat(thumbnailPath);
@@ -21,11 +21,12 @@ export class StatisticsRepository {
return rows.map((r) => r.libraryId);
}
private intersectLibraryIds(accessible: number[] | null, requested: number[] | undefined): number[] | null {
if (!requested || requested.length === 0) return accessible;
if (accessible === null) return requested;
private intersectLibraryIds(accessible: number[] | null, requested: number[] | number | undefined): number[] | null {
const requestedIds = Array.isArray(requested) ? requested : requested == null ? [] : [requested];
if (requestedIds.length === 0) return accessible;
if (accessible === null) return requestedIds;
const set = new Set(accessible);
return requested.filter((id) => set.has(id));
return requestedIds.filter((id) => set.has(id));
}
private libraryFilter(libraryIds: number[] | null) {
@@ -59,11 +59,12 @@ export class UserStatisticsRepository {
return rows.map((r) => r.libraryId);
}
private intersectLibraryIds(accessible: number[] | null, requested: number[] | undefined): number[] | null {
if (!requested || requested.length === 0) return accessible;
if (accessible === null) return requested;
private intersectLibraryIds(accessible: number[] | null, requested: number[] | number | undefined): number[] | null {
const requestedIds = Array.isArray(requested) ? requested : requested == null ? [] : [requested];
if (requestedIds.length === 0) return accessible;
if (accessible === null) return requestedIds;
const set = new Set(accessible);
return requested.filter((id) => set.has(id));
return requestedIds.filter((id) => set.has(id));
}
private libraryFilter(libraryIds: number[] | null) {
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,94 @@
import { mkdir, mkdtemp, rm, writeFile } from 'fs/promises';
import { tmpdir } from 'os';
import { dirname, join } from 'path';
export { createEpubFixture } from '../metadata-write/metadata-write-fixture-builder';
export interface AuthorizationMatrixFixtureRoot {
rootPath: string;
booksPath: string;
stagingPath: string;
cleanup: () => Promise<void>;
}
export interface Fb2FixtureInput {
title?: string;
authors?: string[];
language?: string;
genre?: string;
description?: string;
year?: number;
}
function assertRelativePath(path: string): void {
if (path.startsWith('/')) {
throw new Error(`Fixture paths must be relative. Received "${path}"`);
}
}
export async function createAuthorizationFixtureRoot(prefix = 'authorization-matrix-e2e-'): Promise<AuthorizationMatrixFixtureRoot> {
const rootPath = await mkdtemp(join(tmpdir(), prefix));
const booksPath = join(rootPath, 'books');
const stagingPath = join(booksPath, 'staging');
await mkdir(stagingPath, { recursive: true });
return {
rootPath,
booksPath,
stagingPath,
cleanup: async () => {
await rm(rootPath, { recursive: true, force: true });
},
};
}
export async function writeFixtureFile(rootPath: string, relativePath: string, content: string | Buffer): Promise<string> {
assertRelativePath(relativePath);
const absolutePath = join(rootPath, relativePath);
await mkdir(dirname(absolutePath), { recursive: true });
await writeFile(absolutePath, content);
return absolutePath;
}
export function buildFb2Fixture(input: Fb2FixtureInput = {}): string {
const title = input.title ?? 'Authorization Matrix Fixture Title';
const authors = input.authors ?? ['Authorization Matrix Fixture Author'];
const language = input.language ?? 'en';
const genre = input.genre ?? 'fiction';
const description = input.description ?? 'Fixture description';
const year = input.year ?? 2024;
const authorXml = authors
.map((name) => {
const [firstName = 'Unknown', ...rest] = name.trim().split(/\s+/);
const lastName = rest.length > 0 ? rest.join(' ') : 'Author';
return `<author><first-name>${escapeXml(firstName)}</first-name><last-name>${escapeXml(lastName)}</last-name></author>`;
})
.join('');
return `<?xml version="1.0" encoding="utf-8"?>
<FictionBook xmlns:l="http://www.w3.org/1999/xlink">
<description>
<title-info>
<genre>${escapeXml(genre)}</genre>
${authorXml}
<book-title>${escapeXml(title)}</book-title>
<annotation><p>${escapeXml(description)}</p></annotation>
<date>${year}</date>
<lang>${escapeXml(language)}</lang>
</title-info>
<publish-info>
<year>${year}</year>
</publish-info>
</description>
<body>
<section><p>fixture content</p></section>
</body>
</FictionBook>
`;
}
function escapeXml(value: string): string {
return value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&apos;');
}
@@ -0,0 +1,530 @@
import { randomUUID } from 'crypto';
import { hash } from 'bcryptjs';
import fastifyCookie from '@fastify/cookie';
import fastifyMultipart from '@fastify/multipart';
import { Test } from '@nestjs/testing';
import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify';
import { mkdir, stat } from 'fs/promises';
import { and, eq } from 'drizzle-orm';
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
import type { Permission, StagingMetadata } from '@projectx/types';
import { AppModule } from '../../../src/app.module';
import { DB } from '../../../src/db';
import * as schema from '../../../src/db/schema';
import { MetadataService } from '../../../src/modules/metadata/metadata.service';
import { StagingWatcherService } from '../../../src/modules/staging/staging-watcher.service';
import { seedLibrary, waitForScanCompletion } from '../app-harness';
import {
buildFb2Fixture,
createAuthorizationFixtureRoot,
type AuthorizationMatrixFixtureRoot,
writeFixtureFile,
} from './authorization-matrix-fixture-builder';
type Db = NodePgDatabase<typeof schema>;
const ADMIN_SETUP_DTO = {
username: 'authorization-matrix-e2e-admin',
name: 'Authorization Matrix E2E Admin',
email: 'authorization-matrix-e2e-admin@example.com',
password: 'AuthorizationMatrixAdmin123',
};
interface EnvSnapshot {
booksPath: string | undefined;
stagingPath: string | undefined;
}
export interface AuthorizationMatrixE2EContext {
app: NestFastifyApplication;
db: Db;
adminToken: string;
fixture: AuthorizationMatrixFixtureRoot;
envSnapshot: EnvSnapshot;
}
export interface CreatedLibrary {
libraryId: number;
libraryFolderId: number;
folderPath: string;
}
export interface TestUserSession {
userId: number;
username: string;
password: string;
accessToken: string;
}
export interface LocatedBookFile {
libraryId: number;
bookId: number;
bookFileId: number;
absolutePath: string;
relPath: string | null;
format: string | null;
}
export interface CreateStagingRowInput {
fileName?: string;
content?: string | Buffer;
status?: typeof schema.stagingFiles.$inferInsert.status;
format?: string;
embeddedMetadata?: StagingMetadata | null;
selectedMetadata?: StagingMetadata | null;
fetchedMetadata?: StagingMetadata | null;
targetLibraryId?: number | null;
targetFolderId?: number | null;
confidence?: number | null;
errorMessage?: string | null;
metadataEditedAt?: Date | null;
}
export interface CreateReadingSessionInput {
userId: number;
bookFileId: number;
startedAt: Date;
endedAt: Date;
sessionId?: string;
progressDelta?: number | null;
endProgress?: number | null;
}
export function authHeader(token: string): Record<string, string> {
return { authorization: `Bearer ${token}` };
}
export async function createAuthorizationMatrixE2EContext(): Promise<AuthorizationMatrixE2EContext> {
const fixture = await createAuthorizationFixtureRoot();
const envSnapshot: EnvSnapshot = {
booksPath: process.env.BOOKS_PATH,
stagingPath: process.env.STAGING_PATH,
};
process.env.BOOKS_PATH = fixture.booksPath;
process.env.STAGING_PATH = fixture.stagingPath;
const moduleFixture = await Test.createTestingModule({
imports: [AppModule],
})
.overrideProvider(MetadataService)
.useValue(makeMetadataNoopMock())
.compile();
const app = moduleFixture.createNestApplication<NestFastifyApplication>(new FastifyAdapter());
app.setGlobalPrefix('api/v1');
await app.register(fastifyCookie as never);
await app.register(fastifyMultipart as never);
await app.init();
await app.getHttpAdapter().getInstance().ready();
await stopStagingWatcher(app);
const db = app.get<Db>(DB);
const adminToken = await getAdminToken(app, db);
await setSettingValue(db, 'staging_auto_fetch_metadata', 'false');
await setSettingValue(db, 'staging_auto_finalize_enabled', 'false');
await setSettingValue(db, 'opds_enabled', 'true');
return {
app,
db,
adminToken,
fixture,
envSnapshot,
};
}
export async function closeAuthorizationMatrixE2EContext(ctx: AuthorizationMatrixE2EContext): Promise<void> {
await ctx.app.close();
await ctx.fixture.cleanup();
restoreEnv(ctx.envSnapshot);
}
export async function createLibraryWithFolder(
ctx: AuthorizationMatrixE2EContext,
options: {
mode?: 'book_per_file' | 'book_per_folder';
allowedFormats?: string[];
name?: string;
} = {},
): Promise<CreatedLibrary> {
const folderPath = `${ctx.fixture.booksPath}/library-${randomUUID()}`;
await mkdir(folderPath, { recursive: true });
const { libraryId, libraryFolderId } = await seedLibrary(ctx.db, {
rootPath: folderPath,
mode: options.mode ?? 'book_per_file',
allowedFormats: options.allowedFormats ?? [],
watch: false,
name: options.name,
});
return {
libraryId,
libraryFolderId,
folderPath,
};
}
export async function triggerAndWaitForLibraryScan(
ctx: AuthorizationMatrixE2EContext,
libraryId: number,
timeoutMs = 45_000,
): Promise<typeof schema.scanJobs.$inferSelect> {
const response = await ctx.app.inject({
method: 'POST',
url: `/api/v1/scanner/libraries/${libraryId}/scan`,
headers: authHeader(ctx.adminToken),
});
if (response.statusCode !== 202) {
throw new Error(`Scan endpoint failed: ${response.statusCode} ${response.body}`);
}
const body = response.json() as { jobId?: number };
if (!body.jobId) {
throw new Error(`Scan endpoint returned no jobId: ${response.body}`);
}
return waitForScanCompletion(ctx.db, body.jobId, timeoutMs);
}
export async function createUserAndLogin(
ctx: AuthorizationMatrixE2EContext,
options: {
permissions?: Permission[];
isSuperuser?: boolean;
isDefaultPassword?: boolean;
active?: boolean;
username?: string;
password?: string;
email?: string;
} = {},
): Promise<TestUserSession> {
const suffix = randomUUID().replaceAll('-', '');
const username = options.username ?? `authorization-matrix-user-${suffix}`;
const password = options.password ?? 'AuthorizationMatrixUser123';
const email = options.email ?? `${username}@example.com`;
const passwordHash = await hash(password, 12);
const [created] = await ctx.db
.insert(schema.users)
.values({
username,
name: `Authorization Matrix User ${suffix}`,
email,
passwordHash,
active: options.active ?? true,
isSuperuser: options.isSuperuser ?? false,
isDefaultPassword: options.isDefaultPassword ?? false,
provisioningMethod: 'local',
})
.returning({ id: schema.users.id });
const permissions = options.permissions ?? [];
if (permissions.length > 0) {
await ctx.db.insert(schema.userPermissions).values(permissions.map((permissionName) => ({ userId: created.id, permissionName })));
}
const accessToken = await loginForToken(ctx.app, username, password);
if (!accessToken) {
throw new Error(`Login failed for ${username}`);
}
return {
userId: created.id,
username,
password,
accessToken,
};
}
export async function grantLibraryAccess(
ctx: AuthorizationMatrixE2EContext,
userId: number,
libraryId: number,
accessLevel: 'viewer' | 'editor' | 'owner' = 'viewer',
): Promise<void> {
await ctx.db
.insert(schema.userLibraryAccess)
.values({ userId, libraryId, accessLevel })
.onConflictDoUpdate({
target: [schema.userLibraryAccess.userId, schema.userLibraryAccess.libraryId],
set: { accessLevel },
});
}
export async function replaceUserPermissions(ctx: AuthorizationMatrixE2EContext, userId: number, permissions: Permission[]): Promise<void> {
await ctx.db.delete(schema.userPermissions).where(eq(schema.userPermissions.userId, userId));
if (permissions.length === 0) return;
await ctx.db.insert(schema.userPermissions).values(permissions.map((permissionName) => ({ userId, permissionName })));
}
export async function setUserActive(ctx: AuthorizationMatrixE2EContext, userId: number, active: boolean): Promise<void> {
await ctx.db.update(schema.users).set({ active }).where(eq(schema.users.id, userId));
}
export async function setSetting(ctx: AuthorizationMatrixE2EContext, key: string, value: string): Promise<void> {
await setSettingValue(ctx.db, key, value);
}
export async function locateBookByAbsolutePath(ctx: AuthorizationMatrixE2EContext, absolutePath: string): Promise<LocatedBookFile> {
const [row] = await ctx.db
.select({
libraryId: schema.books.libraryId,
bookId: schema.books.id,
bookFileId: schema.bookFiles.id,
absolutePath: schema.bookFiles.absolutePath,
relPath: schema.bookFiles.relPath,
format: schema.bookFiles.format,
})
.from(schema.bookFiles)
.innerJoin(schema.books, eq(schema.books.id, schema.bookFiles.bookId))
.where(and(eq(schema.books.status, 'present'), eq(schema.bookFiles.absolutePath, absolutePath)))
.limit(1);
if (!row) {
throw new Error(`No present book file found for path "${absolutePath}"`);
}
return row;
}
export async function createBookCoverArtifacts(
ctx: AuthorizationMatrixE2EContext,
bookId: number,
options: {
coverExtension?: 'jpg' | 'png';
coverContent?: Buffer;
thumbnailContent?: Buffer;
} = {},
): Promise<void> {
const coverExtension = options.coverExtension ?? 'jpg';
const coverContent = options.coverContent ?? Buffer.from(`cover-${bookId}`, 'utf8');
const thumbnailContent = options.thumbnailContent ?? Buffer.from(`thumbnail-${bookId}`, 'utf8');
await writeFixtureFile(ctx.fixture.booksPath, `covers/${bookId}/cover.${coverExtension}`, coverContent);
await writeFixtureFile(ctx.fixture.booksPath, `covers/${bookId}/thumbnail.jpg`, thumbnailContent);
}
export async function createStagingRow(
ctx: AuthorizationMatrixE2EContext,
input: CreateStagingRowInput = {},
): Promise<typeof schema.stagingFiles.$inferSelect> {
const fileName = input.fileName ?? `authz-staging-${randomUUID()}.fb2`;
const content = input.content ?? buildFb2Fixture({ title: `Authorization Matrix ${randomUUID()}`, authors: ['Fixture Author'] });
const format = input.format ?? fileName.split('.').pop()?.toLowerCase() ?? 'fb2';
const absolutePath = await writeFixtureFile(ctx.fixture.booksPath, `staging/${fileName}`, content);
const fileStat = await stat(absolutePath);
const [row] = await ctx.db
.insert(schema.stagingFiles)
.values({
fileName,
absolutePath,
fileSize: fileStat.size,
format,
status: input.status ?? 'ready',
embeddedMetadata: input.embeddedMetadata ?? null,
selectedMetadata: input.selectedMetadata ?? null,
fetchedMetadata: input.fetchedMetadata ?? null,
targetLibraryId: input.targetLibraryId ?? null,
targetFolderId: input.targetFolderId ?? null,
confidence: input.confidence ?? null,
errorMessage: input.errorMessage ?? null,
metadataEditedAt: input.metadataEditedAt ?? null,
})
.returning();
return row;
}
export async function createKoboDevice(
ctx: AuthorizationMatrixE2EContext,
userId: number,
name = `kobo-device-${randomUUID()}`,
): Promise<typeof schema.koboDevices.$inferSelect> {
const token = randomUUID().replaceAll('-', '');
const [device] = await ctx.db
.insert(schema.koboDevices)
.values({
userId,
name,
token,
})
.returning();
return device;
}
export async function createOpdsUser(
ctx: AuthorizationMatrixE2EContext,
input: {
userId: number;
username?: string;
password?: string;
sortOrder?: typeof schema.opdsUsers.$inferInsert.sortOrder;
},
): Promise<{ row: typeof schema.opdsUsers.$inferSelect; password: string }> {
const username = input.username ?? `opds-${randomUUID().replaceAll('-', '').slice(0, 12)}`;
const password = input.password ?? 'OpdsPassword123';
const passwordHash = await hash(password, 12);
const [row] = await ctx.db
.insert(schema.opdsUsers)
.values({
userId: input.userId,
username,
passwordHash,
sortOrder: input.sortOrder ?? 'recent',
})
.returning();
return { row, password };
}
export async function createReadingSession(
ctx: AuthorizationMatrixE2EContext,
input: CreateReadingSessionInput,
): Promise<typeof schema.readingSessions.$inferSelect> {
const durationSeconds = Math.round((input.endedAt.getTime() - input.startedAt.getTime()) / 1000);
const [row] = await ctx.db
.insert(schema.readingSessions)
.values({
userId: input.userId,
bookFileId: input.bookFileId,
sessionId: input.sessionId ?? `session-${randomUUID()}`,
startedAt: input.startedAt,
endedAt: input.endedAt,
durationSeconds,
progressDelta: input.progressDelta ?? null,
endProgress: input.endProgress ?? null,
})
.returning();
return row;
}
export async function uploadLibraryFile(
ctx: AuthorizationMatrixE2EContext,
input: {
token: string;
libraryId: number;
fileName: string;
content: string | Buffer;
folderId?: number;
contentType?: string;
},
) {
const contentBuffer = typeof input.content === 'string' ? Buffer.from(input.content, 'utf8') : input.content;
const { body, boundary } = buildMultipartBody(input.fileName, contentBuffer, input.contentType ?? 'application/octet-stream');
const folderQuery = input.folderId ? `?folderId=${input.folderId}` : '';
return ctx.app.inject({
method: 'POST',
url: `/api/v1/libraries/${input.libraryId}/upload${folderQuery}`,
headers: {
...authHeader(input.token),
'content-type': `multipart/form-data; boundary=${boundary}`,
'content-length': String(body.length),
},
payload: body,
});
}
function buildMultipartBody(fileName: string, content: Buffer, contentType: string): { body: Buffer; boundary: string } {
const boundary = `----projectx-authz-${randomUUID()}`;
const preamble = Buffer.from(
`--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${fileName}"\r\nContent-Type: ${contentType}\r\n\r\n`,
'utf8',
);
const closing = Buffer.from(`\r\n--${boundary}--\r\n`, 'utf8');
return { body: Buffer.concat([preamble, content, closing]), boundary };
}
function makeMetadataNoopMock(): Pick<
MetadataService,
'extractAndSave' | 'refreshCoverForBook' | 'extractAudioFileDuration' | 'aggregateAudioDuration' | 'extractAudioChaptersAndNarrators'
> {
return {
extractAndSave: () => Promise.resolve(undefined),
refreshCoverForBook: () => Promise.resolve(false),
extractAudioFileDuration: () => Promise.resolve(undefined),
aggregateAudioDuration: () => Promise.resolve(undefined),
extractAudioChaptersAndNarrators: () => Promise.resolve(undefined),
};
}
async function stopStagingWatcher(app: NestFastifyApplication): Promise<void> {
const watcher = app.get(StagingWatcherService);
await watcher.onModuleDestroy();
}
async function getAdminToken(app: NestFastifyApplication, db: Db): Promise<string> {
const response = await app.inject({
method: 'POST',
url: '/api/v1/auth/setup',
payload: ADMIN_SETUP_DTO,
});
if (response.statusCode === 409) {
const existingToken = await loginForToken(app, ADMIN_SETUP_DTO.username, ADMIN_SETUP_DTO.password);
if (existingToken) return existingToken;
const suffix = randomUUID().replaceAll('-', '');
const fallbackUsername = `authorization-matrix-admin-${suffix}`;
const fallbackPassword = ADMIN_SETUP_DTO.password;
const passwordHash = await hash(fallbackPassword, 12);
await db.insert(schema.users).values({
username: fallbackUsername,
name: 'Authorization Matrix E2E Admin',
email: `${fallbackUsername}@example.com`,
passwordHash,
isSuperuser: true,
isDefaultPassword: false,
provisioningMethod: 'local',
});
const fallbackToken = await loginForToken(app, fallbackUsername, fallbackPassword);
if (fallbackToken) return fallbackToken;
throw new Error('Setup is already complete and fallback admin login failed');
}
if (response.statusCode !== 201) {
throw new Error(`Unable to complete setup: ${response.statusCode} ${response.body}`);
}
const body = response.json() as { accessToken?: string };
if (!body.accessToken) {
throw new Error('Setup succeeded but accessToken is missing');
}
return body.accessToken;
}
async function loginForToken(app: NestFastifyApplication, username: string, password: string): Promise<string | null> {
const response = await app.inject({
method: 'POST',
url: '/api/v1/auth/login',
payload: { username, password },
});
if (response.statusCode !== 200) return null;
const body = response.json() as { accessToken?: string };
return body.accessToken ?? null;
}
async function setSettingValue(db: Db, key: string, value: string): Promise<void> {
await db.insert(schema.appSettings).values({ key, value }).onConflictDoUpdate({
target: schema.appSettings.key,
set: { value },
});
}
function restoreEnv(snapshot: EnvSnapshot): void {
if (snapshot.booksPath === undefined) delete process.env.BOOKS_PATH;
else process.env.BOOKS_PATH = snapshot.booksPath;
if (snapshot.stagingPath === undefined) delete process.env.STAGING_PATH;
else process.env.STAGING_PATH = snapshot.stagingPath;
}
@@ -0,0 +1,33 @@
{
"totalRoutes": 319,
"byPermission": {
"(none)": 161,
"Permission.ManageMetadataConfig": 32,
"Permission.EmailSend": 30,
"Permission.LibraryEditMetadata": 17,
"Permission.StagingAccess": 17,
"Permission.ManageAppSettings": 16,
"Permission.ManageLibraries": 13,
"Permission.ManageUsers": 10,
"Permission.ManageEmail": 8,
"Permission.KoboSync": 7,
"Permission.OpdsAccess": 4,
"Permission.ViewAuditLog": 1,
"Permission.LibraryDeleteBooks": 1,
"Permission.LibraryDownload": 1,
"Permission.LibraryUpload": 1
},
"byLibraryAccess": {
"'viewer'": 3,
"'editor'": 1
},
"routeChunks": [
"routes-01.json",
"routes-02.json",
"routes-03.json",
"routes-04.json",
"routes-05.json",
"routes-06.json",
"routes-07.json"
]
}
@@ -0,0 +1,650 @@
[
{
"file": "server/src/modules/annotation/annotation.controller.ts",
"className": "AnnotationController",
"methodName": "getAnnotations",
"httpMethod": "GET",
"path": "/books/:bookId/annotations",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/annotation/annotation.controller.ts",
"className": "AnnotationController",
"methodName": "createAnnotation",
"httpMethod": "POST",
"path": "/books/:bookId/annotations",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/annotation/annotation.controller.ts",
"className": "AnnotationController",
"methodName": "updateAnnotation",
"httpMethod": "PATCH",
"path": "/books/:bookId/annotations/:annotationId",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/annotation/annotation.controller.ts",
"className": "AnnotationController",
"methodName": "deleteAnnotation",
"httpMethod": "DELETE",
"path": "/books/:bookId/annotations/:annotationId",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/app-settings/app-settings.controller.ts",
"className": "AppSettingsController",
"methodName": "listSettings",
"httpMethod": "GET",
"path": "/app-settings",
"permissions": [
"Permission.ManageAppSettings"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/app-settings/app-settings.controller.ts",
"className": "AppSettingsController",
"methodName": "update",
"httpMethod": "PATCH",
"path": "/app-settings/:key",
"permissions": [
"Permission.ManageAppSettings"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/app-settings/app-settings.controller.ts",
"className": "AppSettingsController",
"methodName": "getUploadPattern",
"httpMethod": "GET",
"path": "/app-settings/upload-pattern",
"permissions": [
"Permission.ManageAppSettings"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/app-settings/app-settings.controller.ts",
"className": "AppSettingsController",
"methodName": "setUploadPattern",
"httpMethod": "PUT",
"path": "/app-settings/upload-pattern",
"permissions": [
"Permission.ManageAppSettings"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/app-settings/app-settings.controller.ts",
"className": "AppSettingsController",
"methodName": "getDownloadPattern",
"httpMethod": "GET",
"path": "/app-settings/download-pattern",
"permissions": [
"Permission.ManageAppSettings"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/app-settings/app-settings.controller.ts",
"className": "AppSettingsController",
"methodName": "setDownloadPattern",
"httpMethod": "PUT",
"path": "/app-settings/download-pattern",
"permissions": [
"Permission.ManageAppSettings"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/app-settings/app-settings.controller.ts",
"className": "AppSettingsController",
"methodName": "getOidcPublicConfig",
"httpMethod": "GET",
"path": "/app-settings/oidc/public",
"permissions": [
"Permission.ManageAppSettings"
],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/app-settings/app-settings.controller.ts",
"className": "AppSettingsController",
"methodName": "getOidcConfig",
"httpMethod": "GET",
"path": "/app-settings/oidc",
"permissions": [
"Permission.ManageAppSettings"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/app-settings/app-settings.controller.ts",
"className": "AppSettingsController",
"methodName": "updateOidcConfig",
"httpMethod": "PUT",
"path": "/app-settings/oidc",
"permissions": [
"Permission.ManageAppSettings"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/app-settings/app-settings.controller.ts",
"className": "AppSettingsController",
"methodName": "testOidcConnection",
"httpMethod": "POST",
"path": "/app-settings/oidc/test",
"permissions": [
"Permission.ManageAppSettings"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/app-settings/app-settings.controller.ts",
"className": "AppSettingsController",
"methodName": "getFileWriteSettings",
"httpMethod": "GET",
"path": "/app-settings/file-write-settings",
"permissions": [
"Permission.ManageAppSettings"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/app-settings/app-settings.controller.ts",
"className": "AppSettingsController",
"methodName": "updateFileWriteSettings",
"httpMethod": "PUT",
"path": "/app-settings/file-write-settings",
"permissions": [
"Permission.ManageAppSettings"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/audit/audit.controller.ts",
"className": "AuditController",
"methodName": "getAuditLogs",
"httpMethod": "GET",
"path": "/audit-log",
"permissions": [
"Permission.ViewAuditLog"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/auth/auth.controller.ts",
"className": "AuthController",
"methodName": "register",
"httpMethod": "POST",
"path": "/auth/register",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/auth/auth.controller.ts",
"className": "AuthController",
"methodName": "setupStatus",
"httpMethod": "GET",
"path": "/auth/setup-status",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/auth/auth.controller.ts",
"className": "AuthController",
"methodName": "setup",
"httpMethod": "POST",
"path": "/auth/setup",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/auth/auth.controller.ts",
"className": "AuthController",
"methodName": "login",
"httpMethod": "POST",
"path": "/auth/login",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/auth/auth.controller.ts",
"className": "AuthController",
"methodName": "refresh",
"httpMethod": "POST",
"path": "/auth/refresh",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/auth/auth.controller.ts",
"className": "AuthController",
"methodName": "logout",
"httpMethod": "POST",
"path": "/auth/logout",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/auth/auth.controller.ts",
"className": "AuthController",
"methodName": "me",
"httpMethod": "GET",
"path": "/auth/me",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": true,
"useGuards": []
},
{
"file": "server/src/modules/auth/auth.controller.ts",
"className": "AuthController",
"methodName": "getSessions",
"httpMethod": "GET",
"path": "/auth/sessions",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/auth/auth.controller.ts",
"className": "AuthController",
"methodName": "revokeSession",
"httpMethod": "DELETE",
"path": "/auth/sessions/:id",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/auth/auth.controller.ts",
"className": "AuthController",
"methodName": "forgotPassword",
"httpMethod": "POST",
"path": "/auth/forgot-password",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/auth/auth.controller.ts",
"className": "AuthController",
"methodName": "resetPassword",
"httpMethod": "POST",
"path": "/auth/reset-password",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/auth/auth.controller.ts",
"className": "AuthController",
"methodName": "changePassword",
"httpMethod": "POST",
"path": "/auth/change-password",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": true,
"useGuards": []
},
{
"file": "server/src/modules/auth/auth.controller.ts",
"className": "AuthController",
"methodName": "oidcGenerateState",
"httpMethod": "POST",
"path": "/auth/oidc/state",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/auth/auth.controller.ts",
"className": "AuthController",
"methodName": "oidcCallback",
"httpMethod": "POST",
"path": "/auth/oidc/callback",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/auth/auth.controller.ts",
"className": "AuthController",
"methodName": "oidcBackchannelLogout",
"httpMethod": "POST",
"path": "/auth/oidc/backchannel-logout",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "findAll",
"httpMethod": "GET",
"path": "/authors",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "findInsights",
"httpMethod": "GET",
"path": "/authors/insights",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "findDuplicateSuggestions",
"httpMethod": "GET",
"path": "/authors/suggestions/duplicates",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "listMetadataProviders",
"httpMethod": "GET",
"path": "/authors/metadata/providers",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "searchMetadata",
"httpMethod": "GET",
"path": "/authors/metadata/search",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "lookupMetadata",
"httpMethod": "GET",
"path": "/authors/metadata/lookup",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "bulkRefreshMetadata",
"httpMethod": "POST",
"path": "/authors/bulk-refresh-metadata",
"permissions": [
"Permission.LibraryEditMetadata"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "getEnrichmentConfig",
"httpMethod": "GET",
"path": "/authors/enrichment/config",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "setEnrichmentConfig",
"httpMethod": "PUT",
"path": "/authors/enrichment/config",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "previewCount",
"httpMethod": "POST",
"path": "/authors/enrichment/preview-count",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "enqueueBackfill",
"httpMethod": "POST",
"path": "/authors/enrichment/backfill",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "enqueueBackfillAll",
"httpMethod": "POST",
"path": "/authors/enrichment/backfill-all",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "pauseEnrichment",
"httpMethod": "POST",
"path": "/authors/enrichment/pause",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "resumeEnrichment",
"httpMethod": "POST",
"path": "/authors/enrichment/resume",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "cancelEnrichment",
"httpMethod": "POST",
"path": "/authors/enrichment/cancel",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "retryFailedEnrichment",
"httpMethod": "POST",
"path": "/authors/enrichment/retry-failed",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "getFailedEnrichment",
"httpMethod": "GET",
"path": "/authors/enrichment/failed",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "findOne",
"httpMethod": "GET",
"path": "/authors/:id",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
}
]
@@ -0,0 +1,656 @@
[
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "findBooks",
"httpMethod": "GET",
"path": "/authors/:id/books",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "getThumbnail",
"httpMethod": "GET",
"path": "/authors/:id/thumbnail",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "getImage",
"httpMethod": "GET",
"path": "/authors/:id/image",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "refreshEnrichment",
"httpMethod": "POST",
"path": "/authors/:id/enrichment/refresh",
"permissions": [
"Permission.LibraryEditMetadata"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "update",
"httpMethod": "PATCH",
"path": "/authors/:id",
"permissions": [
"Permission.LibraryEditMetadata"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "merge",
"httpMethod": "POST",
"path": "/authors/merge",
"permissions": [
"Permission.LibraryEditMetadata"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/authors/authors.controller.ts",
"className": "AuthorsController",
"methodName": "delete",
"httpMethod": "DELETE",
"path": "/authors",
"permissions": [
"Permission.LibraryEditMetadata"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "embedAll",
"httpMethod": "POST",
"path": "/books/embed-all",
"permissions": [
"Permission.ManageAppSettings"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "deleteBooks",
"httpMethod": "DELETE",
"path": "/books",
"permissions": [
"Permission.LibraryDeleteBooks"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "searchBooks",
"httpMethod": "GET",
"path": "/books/search",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "globalQuery",
"httpMethod": "POST",
"path": "/books/query",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "bulkRefreshMetadata",
"httpMethod": "POST",
"path": "/books/bulk-refresh-metadata",
"permissions": [
"Permission.LibraryEditMetadata"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "bulkReExtractCover",
"httpMethod": "POST",
"path": "/books/bulk-re-extract-cover",
"permissions": [
"Permission.LibraryEditMetadata"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "reExtractCover",
"httpMethod": "POST",
"path": "/books/:id/re-extract-cover",
"permissions": [
"Permission.LibraryEditMetadata"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "exportBooks",
"httpMethod": "POST",
"path": "/books/export",
"permissions": [
"Permission.LibraryDownload"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "getCover",
"httpMethod": "GET",
"path": "/books/:id/cover",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "getThumbnail",
"httpMethod": "GET",
"path": "/books/:id/thumbnail",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "serveFile",
"httpMethod": "GET",
"path": "/books/files/:fileId/serve",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "getFileProgress",
"httpMethod": "GET",
"path": "/books/files/:fileId/progress",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "saveFileProgress",
"httpMethod": "POST",
"path": "/books/files/:fileId/progress",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "getAudioProgress",
"httpMethod": "GET",
"path": "/books/:id/audio-progress",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "saveAudioProgress",
"httpMethod": "PATCH",
"path": "/books/:id/audio-progress",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "updateMetadata",
"httpMethod": "PATCH",
"path": "/books/:id/metadata",
"permissions": [
"Permission.LibraryEditMetadata"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "refreshMetadata",
"httpMethod": "POST",
"path": "/books/:id/refresh-metadata",
"permissions": [
"Permission.LibraryEditMetadata"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "getWriteLog",
"httpMethod": "GET",
"path": "/books/:id/write-log",
"permissions": [
"Permission.LibraryEditMetadata"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "getKoboState",
"httpMethod": "GET",
"path": "/books/:id/kobo-state",
"permissions": [
"Permission.KoboSync"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "getBookProgress",
"httpMethod": "GET",
"path": "/books/:id/progress",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "setReadStatus",
"httpMethod": "PATCH",
"path": "/books/:id/status",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book/book.controller.ts",
"className": "BookController",
"methodName": "getDetail",
"httpMethod": "GET",
"path": "/books/:id",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book-metadata-fetch/book-metadata-fetch.controller.ts",
"className": "BookMetadataFetchController",
"methodName": "getConfig",
"httpMethod": "GET",
"path": "/book-metadata-fetch/config",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book-metadata-fetch/book-metadata-fetch.controller.ts",
"className": "BookMetadataFetchController",
"methodName": "updateConfig",
"httpMethod": "PUT",
"path": "/book-metadata-fetch/config",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book-metadata-fetch/book-metadata-fetch.controller.ts",
"className": "BookMetadataFetchController",
"methodName": "getLibraryConfig",
"httpMethod": "GET",
"path": "/book-metadata-fetch/config/libraries/:id",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book-metadata-fetch/book-metadata-fetch.controller.ts",
"className": "BookMetadataFetchController",
"methodName": "updateLibraryConfig",
"httpMethod": "PUT",
"path": "/book-metadata-fetch/config/libraries/:id",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book-metadata-fetch/book-metadata-fetch.controller.ts",
"className": "BookMetadataFetchController",
"methodName": "getStatus",
"httpMethod": "GET",
"path": "/book-metadata-fetch/status",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book-metadata-fetch/book-metadata-fetch.controller.ts",
"className": "BookMetadataFetchController",
"methodName": "triggerGlobal",
"httpMethod": "POST",
"path": "/book-metadata-fetch/run",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book-metadata-fetch/book-metadata-fetch.controller.ts",
"className": "BookMetadataFetchController",
"methodName": "triggerForLibrary",
"httpMethod": "POST",
"path": "/book-metadata-fetch/run/:libraryId",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book-metadata-fetch/book-metadata-fetch.controller.ts",
"className": "BookMetadataFetchController",
"methodName": "pause",
"httpMethod": "POST",
"path": "/book-metadata-fetch/pause",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book-metadata-fetch/book-metadata-fetch.controller.ts",
"className": "BookMetadataFetchController",
"methodName": "resume",
"httpMethod": "POST",
"path": "/book-metadata-fetch/resume",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book-metadata-fetch/book-metadata-fetch.controller.ts",
"className": "BookMetadataFetchController",
"methodName": "cancel",
"httpMethod": "POST",
"path": "/book-metadata-fetch/cancel",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book-metadata-fetch/book-metadata-fetch.controller.ts",
"className": "BookMetadataFetchController",
"methodName": "retryFailed",
"httpMethod": "POST",
"path": "/book-metadata-fetch/retry-failed",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book-metadata-fetch/book-metadata-fetch.controller.ts",
"className": "BookMetadataFetchController",
"methodName": "previewCount",
"httpMethod": "POST",
"path": "/book-metadata-fetch/preview-count",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/book-metadata-fetch/book-metadata-fetch.controller.ts",
"className": "BookMetadataFetchController",
"methodName": "getFailedItems",
"httpMethod": "GET",
"path": "/book-metadata-fetch/failed",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/bookmark/bookmark.controller.ts",
"className": "BookmarkController",
"methodName": "getBookmarks",
"httpMethod": "GET",
"path": "/books/:bookId/bookmarks",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/bookmark/bookmark.controller.ts",
"className": "BookmarkController",
"methodName": "createBookmark",
"httpMethod": "POST",
"path": "/books/:bookId/bookmarks",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/bookmark/bookmark.controller.ts",
"className": "BookmarkController",
"methodName": "deleteBookmark",
"httpMethod": "DELETE",
"path": "/books/:bookId/bookmarks/:bookmarkId",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/catalog/catalog.controller.ts",
"className": "CatalogController",
"methodName": "searchAuthors",
"httpMethod": "GET",
"path": "/metadata/authors",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/catalog/catalog.controller.ts",
"className": "CatalogController",
"methodName": "searchGenres",
"httpMethod": "GET",
"path": "/metadata/genres",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/catalog/catalog.controller.ts",
"className": "CatalogController",
"methodName": "searchTags",
"httpMethod": "GET",
"path": "/metadata/tags",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/catalog/catalog.controller.ts",
"className": "CatalogController",
"methodName": "searchNarrators",
"httpMethod": "GET",
"path": "/metadata/narrators",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/catalog/catalog.controller.ts",
"className": "CatalogController",
"methodName": "searchPublishers",
"httpMethod": "GET",
"path": "/metadata/publishers",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
}
]
@@ -0,0 +1,676 @@
[
{
"file": "server/src/modules/catalog/catalog.controller.ts",
"className": "CatalogController",
"methodName": "searchSeries",
"httpMethod": "GET",
"path": "/metadata/series",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/catalog/catalog.controller.ts",
"className": "CatalogController",
"methodName": "searchLanguages",
"httpMethod": "GET",
"path": "/metadata/languages",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/catalog/catalog.controller.ts",
"className": "CatalogController",
"methodName": "searchCollections",
"httpMethod": "GET",
"path": "/metadata/collections",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/collection/collection.controller.ts",
"className": "CollectionController",
"methodName": "findAll",
"httpMethod": "GET",
"path": "/collections",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/collection/collection.controller.ts",
"className": "CollectionController",
"methodName": "findOne",
"httpMethod": "GET",
"path": "/collections/:id",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/collection/collection.controller.ts",
"className": "CollectionController",
"methodName": "create",
"httpMethod": "POST",
"path": "/collections",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/collection/collection.controller.ts",
"className": "CollectionController",
"methodName": "reorder",
"httpMethod": "POST",
"path": "/collections/reorder",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/collection/collection.controller.ts",
"className": "CollectionController",
"methodName": "update",
"httpMethod": "PATCH",
"path": "/collections/:id",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/collection/collection.controller.ts",
"className": "CollectionController",
"methodName": "remove",
"httpMethod": "DELETE",
"path": "/collections/:id",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/collection/collection.controller.ts",
"className": "CollectionController",
"methodName": "addBooks",
"httpMethod": "POST",
"path": "/collections/:id/books",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/collection/collection.controller.ts",
"className": "CollectionController",
"methodName": "removeBooks",
"httpMethod": "DELETE",
"path": "/collections/:id/books",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/collection/collection.controller.ts",
"className": "CollectionController",
"methodName": "getBooks",
"httpMethod": "GET",
"path": "/collections/:id/books",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/cover/cover.controller.ts",
"className": "CoverController",
"methodName": "searchCovers",
"httpMethod": "GET",
"path": "/books/cover/search",
"permissions": [
"Permission.LibraryEditMetadata"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/cover/cover.controller.ts",
"className": "CoverController",
"methodName": "proxyImage",
"httpMethod": "GET",
"path": "/books/cover/proxy",
"permissions": [
"Permission.LibraryEditMetadata"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/cover/cover.controller.ts",
"className": "CoverController",
"methodName": "uploadCover",
"httpMethod": "POST",
"path": "/books/:id/cover",
"permissions": [
"Permission.LibraryEditMetadata"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/cover/cover.controller.ts",
"className": "CoverController",
"methodName": "uploadCoverFromUrl",
"httpMethod": "POST",
"path": "/books/:id/cover/from-url",
"permissions": [
"Permission.LibraryEditMetadata"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/cover/cover.controller.ts",
"className": "CoverController",
"methodName": "deleteCover",
"httpMethod": "DELETE",
"path": "/books/:id/cover",
"permissions": [
"Permission.LibraryEditMetadata"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/dashboard/dashboard.controller.ts",
"className": "DashboardController",
"methodName": "getScroller",
"httpMethod": "GET",
"path": "/dashboard/scrollers/:type",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-admin-log.controller.ts",
"className": "EmailAdminLogController",
"methodName": "findAll",
"httpMethod": "GET",
"path": "/email/admin/log",
"permissions": [
"Permission.ManageEmail"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-preferences.controller.ts",
"className": "EmailPreferencesController",
"methodName": "findForUser",
"httpMethod": "GET",
"path": "/email/preferences",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-preferences.controller.ts",
"className": "EmailPreferencesController",
"methodName": "upsert",
"httpMethod": "PUT",
"path": "/email/preferences",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-provider.controller.ts",
"className": "EmailProviderController",
"methodName": "findAll",
"httpMethod": "GET",
"path": "/email/providers",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-provider.controller.ts",
"className": "EmailProviderController",
"methodName": "findOne",
"httpMethod": "GET",
"path": "/email/providers/:id",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-provider.controller.ts",
"className": "EmailProviderController",
"methodName": "create",
"httpMethod": "POST",
"path": "/email/providers",
"permissions": [
"Permission.ManageEmail"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-provider.controller.ts",
"className": "EmailProviderController",
"methodName": "update",
"httpMethod": "PUT",
"path": "/email/providers/:id",
"permissions": [
"Permission.ManageEmail"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-provider.controller.ts",
"className": "EmailProviderController",
"methodName": "remove",
"httpMethod": "DELETE",
"path": "/email/providers/:id",
"permissions": [
"Permission.ManageEmail"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-provider.controller.ts",
"className": "EmailProviderController",
"methodName": "setDefault",
"httpMethod": "PATCH",
"path": "/email/providers/:id/default",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-provider.controller.ts",
"className": "EmailProviderController",
"methodName": "toggleShared",
"httpMethod": "PATCH",
"path": "/email/providers/:id/share",
"permissions": [
"Permission.ManageEmail"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-provider.controller.ts",
"className": "EmailProviderController",
"methodName": "testConnection",
"httpMethod": "POST",
"path": "/email/providers/:id/test",
"permissions": [
"Permission.ManageEmail"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-provider.controller.ts",
"className": "EmailProviderController",
"methodName": "setSystemProvider",
"httpMethod": "PATCH",
"path": "/email/providers/:id/system",
"permissions": [
"Permission.ManageEmail"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-provider.controller.ts",
"className": "EmailProviderController",
"methodName": "clearSystemProvider",
"httpMethod": "DELETE",
"path": "/email/providers/system",
"permissions": [
"Permission.ManageEmail"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-recipient-group.controller.ts",
"className": "EmailRecipientGroupController",
"methodName": "findAll",
"httpMethod": "GET",
"path": "/email/recipient-groups",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-recipient-group.controller.ts",
"className": "EmailRecipientGroupController",
"methodName": "findOne",
"httpMethod": "GET",
"path": "/email/recipient-groups/:id",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-recipient-group.controller.ts",
"className": "EmailRecipientGroupController",
"methodName": "create",
"httpMethod": "POST",
"path": "/email/recipient-groups",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-recipient-group.controller.ts",
"className": "EmailRecipientGroupController",
"methodName": "update",
"httpMethod": "PUT",
"path": "/email/recipient-groups/:id",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-recipient-group.controller.ts",
"className": "EmailRecipientGroupController",
"methodName": "remove",
"httpMethod": "DELETE",
"path": "/email/recipient-groups/:id",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-recipient-group.controller.ts",
"className": "EmailRecipientGroupController",
"methodName": "addMember",
"httpMethod": "POST",
"path": "/email/recipient-groups/:id/members",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-recipient-group.controller.ts",
"className": "EmailRecipientGroupController",
"methodName": "removeMember",
"httpMethod": "DELETE",
"path": "/email/recipient-groups/:id/members/:recipientId",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-recipient.controller.ts",
"className": "EmailRecipientController",
"methodName": "findAll",
"httpMethod": "GET",
"path": "/email/recipients",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-recipient.controller.ts",
"className": "EmailRecipientController",
"methodName": "findOne",
"httpMethod": "GET",
"path": "/email/recipients/:id",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-recipient.controller.ts",
"className": "EmailRecipientController",
"methodName": "create",
"httpMethod": "POST",
"path": "/email/recipients",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-recipient.controller.ts",
"className": "EmailRecipientController",
"methodName": "update",
"httpMethod": "PUT",
"path": "/email/recipients/:id",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-recipient.controller.ts",
"className": "EmailRecipientController",
"methodName": "remove",
"httpMethod": "DELETE",
"path": "/email/recipients/:id",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-recipient.controller.ts",
"className": "EmailRecipientController",
"methodName": "setDefault",
"httpMethod": "PATCH",
"path": "/email/recipients/:id/default",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-send-log.controller.ts",
"className": "EmailSendLogController",
"methodName": "findForUser",
"httpMethod": "GET",
"path": "/email/log",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-send-log.controller.ts",
"className": "EmailSendLogController",
"methodName": "remove",
"httpMethod": "DELETE",
"path": "/email/log/:id",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-send-log.controller.ts",
"className": "EmailSendLogController",
"methodName": "resend",
"httpMethod": "POST",
"path": "/email/log/:id/resend",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-send.controller.ts",
"className": "EmailSendController",
"methodName": "send",
"httpMethod": "POST",
"path": "/email/send",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-send.controller.ts",
"className": "EmailSendController",
"methodName": "quickSend",
"httpMethod": "POST",
"path": "/email/send/quick/:bookId",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-template.controller.ts",
"className": "EmailTemplateController",
"methodName": "findAll",
"httpMethod": "GET",
"path": "/email/templates",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
}
]
@@ -0,0 +1,686 @@
[
{
"file": "server/src/modules/email/email-template.controller.ts",
"className": "EmailTemplateController",
"methodName": "findOne",
"httpMethod": "GET",
"path": "/email/templates/:id",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-template.controller.ts",
"className": "EmailTemplateController",
"methodName": "create",
"httpMethod": "POST",
"path": "/email/templates",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-template.controller.ts",
"className": "EmailTemplateController",
"methodName": "update",
"httpMethod": "PUT",
"path": "/email/templates/:id",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-template.controller.ts",
"className": "EmailTemplateController",
"methodName": "remove",
"httpMethod": "DELETE",
"path": "/email/templates/:id",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-template.controller.ts",
"className": "EmailTemplateController",
"methodName": "setDefault",
"httpMethod": "PATCH",
"path": "/email/templates/:id/default",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/email/email-template.controller.ts",
"className": "EmailTemplateController",
"methodName": "preview",
"httpMethod": "POST",
"path": "/email/templates/:id/preview",
"permissions": [
"Permission.EmailSend"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/health/health.controller.ts",
"className": "HealthController",
"methodName": "check",
"httpMethod": "GET",
"path": "/",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/kobo/kobo-auth.controller.ts",
"className": "KoboAuthController",
"methodName": "authDevice",
"httpMethod": "POST",
"path": "/kobo/:deviceToken/v1/auth/device",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"KoboTokenGuard"
]
},
{
"file": "server/src/modules/kobo/kobo-auth.controller.ts",
"className": "KoboAuthController",
"methodName": "authRefresh",
"httpMethod": "POST",
"path": "/kobo/:deviceToken/v1/auth/refresh",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"KoboTokenGuard"
]
},
{
"file": "server/src/modules/kobo/kobo-device.controller.ts",
"className": "KoboDeviceController",
"methodName": "thumbnailFull",
"httpMethod": "GET",
"path": "/kobo/:deviceToken/v1/books/:bookId/thumbnail/:width/:height/:quality/:isGreyscale/image.jpg",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"KoboTokenGuard"
]
},
{
"file": "server/src/modules/kobo/kobo-device.controller.ts",
"className": "KoboDeviceController",
"methodName": "thumbnailSimple",
"httpMethod": "GET",
"path": "/kobo/:deviceToken/v1/books/:bookId/thumbnail/:width/:height/false/image.jpg",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"KoboTokenGuard"
]
},
{
"file": "server/src/modules/kobo/kobo-device.controller.ts",
"className": "KoboDeviceController",
"methodName": "thumbnailVersioned",
"httpMethod": "GET",
"path": "/kobo/:deviceToken/v1/books/:bookId/:version/thumbnail/:width/:height/false/image.jpg",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"KoboTokenGuard"
]
},
{
"file": "server/src/modules/kobo/kobo-device.controller.ts",
"className": "KoboDeviceController",
"methodName": "download",
"httpMethod": "GET",
"path": "/kobo/:deviceToken/v1/books/:bookId/download",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"KoboTokenGuard"
]
},
{
"file": "server/src/modules/kobo/kobo-device.controller.ts",
"className": "KoboDeviceController",
"methodName": "affiliate",
"httpMethod": "GET",
"path": "/kobo/:deviceToken/v1/affiliate",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"KoboTokenGuard"
]
},
{
"file": "server/src/modules/kobo/kobo-device.controller.ts",
"className": "KoboDeviceController",
"methodName": "remainingBookSeries",
"httpMethod": "GET",
"path": "/kobo/:deviceToken/v1/products/books/series/:seriesId",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"KoboTokenGuard"
]
},
{
"file": "server/src/modules/kobo/kobo-device.controller.ts",
"className": "KoboDeviceController",
"methodName": "productNextRead",
"httpMethod": "GET",
"path": "/kobo/:deviceToken/v1/products/:productId/nextread",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"KoboTokenGuard"
]
},
{
"file": "server/src/modules/kobo/kobo-device.controller.ts",
"className": "KoboDeviceController",
"methodName": "getTests",
"httpMethod": "POST",
"path": "/kobo/:deviceToken/v1/analytics/gettests",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"KoboTokenGuard"
]
},
{
"file": "server/src/modules/kobo/kobo-device.controller.ts",
"className": "KoboDeviceController",
"methodName": "analyticsEvent",
"httpMethod": "POST",
"path": "/kobo/:deviceToken/v1/analytics/event",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"KoboTokenGuard"
]
},
{
"file": "server/src/modules/kobo/kobo-device.controller.ts",
"className": "KoboDeviceController",
"methodName": "proxy",
"httpMethod": "ALL",
"path": "/kobo/:deviceToken/*",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"KoboTokenGuard"
]
},
{
"file": "server/src/modules/kobo/kobo-sync.controller.ts",
"className": "KoboSyncController",
"methodName": "initialization",
"httpMethod": "GET",
"path": "/kobo/:deviceToken/v1/initialization",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"KoboTokenGuard"
]
},
{
"file": "server/src/modules/kobo/kobo-sync.controller.ts",
"className": "KoboSyncController",
"methodName": "librarySync",
"httpMethod": "GET",
"path": "/kobo/:deviceToken/v1/library/sync",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"KoboTokenGuard"
]
},
{
"file": "server/src/modules/kobo/kobo-sync.controller.ts",
"className": "KoboSyncController",
"methodName": "getBookMetadata",
"httpMethod": "GET",
"path": "/kobo/:deviceToken/v1/library/:bookId/metadata",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"KoboTokenGuard"
]
},
{
"file": "server/src/modules/kobo/kobo-sync.controller.ts",
"className": "KoboSyncController",
"methodName": "deleteFromLibrary",
"httpMethod": "DELETE",
"path": "/kobo/:deviceToken/v1/library/:bookId",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"KoboTokenGuard"
]
},
{
"file": "server/src/modules/kobo/kobo-sync.controller.ts",
"className": "KoboSyncController",
"methodName": "getReadingState",
"httpMethod": "GET",
"path": "/kobo/:deviceToken/v1/library/:bookId/state",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"KoboTokenGuard"
]
},
{
"file": "server/src/modules/kobo/kobo-sync.controller.ts",
"className": "KoboSyncController",
"methodName": "updateReadingState",
"httpMethod": "PUT",
"path": "/kobo/:deviceToken/v1/library/:bookId/state",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"KoboTokenGuard"
]
},
{
"file": "server/src/modules/kobo/kobo-user.controller.ts",
"className": "KoboUserController",
"methodName": "listDevices",
"httpMethod": "GET",
"path": "/kobo/devices",
"permissions": [
"Permission.KoboSync"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/kobo/kobo-user.controller.ts",
"className": "KoboUserController",
"methodName": "createDevice",
"httpMethod": "POST",
"path": "/kobo/devices",
"permissions": [
"Permission.KoboSync"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/kobo/kobo-user.controller.ts",
"className": "KoboUserController",
"methodName": "renameDevice",
"httpMethod": "PATCH",
"path": "/kobo/devices/:id",
"permissions": [
"Permission.KoboSync"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/kobo/kobo-user.controller.ts",
"className": "KoboUserController",
"methodName": "revokeDevice",
"httpMethod": "DELETE",
"path": "/kobo/devices/:id",
"permissions": [
"Permission.KoboSync"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/kobo/kobo-user.controller.ts",
"className": "KoboUserController",
"methodName": "getSettings",
"httpMethod": "GET",
"path": "/kobo/settings",
"permissions": [
"Permission.KoboSync"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/kobo/kobo-user.controller.ts",
"className": "KoboUserController",
"methodName": "updateSettings",
"httpMethod": "PATCH",
"path": "/kobo/settings",
"permissions": [
"Permission.KoboSync"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/lens/lens.controller.ts",
"className": "LensController",
"methodName": "findAll",
"httpMethod": "GET",
"path": "/lenses",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/lens/lens.controller.ts",
"className": "LensController",
"methodName": "findOne",
"httpMethod": "GET",
"path": "/lenses/:id",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/lens/lens.controller.ts",
"className": "LensController",
"methodName": "create",
"httpMethod": "POST",
"path": "/lenses",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/lens/lens.controller.ts",
"className": "LensController",
"methodName": "reorder",
"httpMethod": "POST",
"path": "/lenses/reorder",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/lens/lens.controller.ts",
"className": "LensController",
"methodName": "update",
"httpMethod": "PATCH",
"path": "/lenses/:id",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/lens/lens.controller.ts",
"className": "LensController",
"methodName": "remove",
"httpMethod": "DELETE",
"path": "/lenses/:id",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/lens/lens.controller.ts",
"className": "LensController",
"methodName": "executeLens",
"httpMethod": "GET",
"path": "/lenses/:id/books",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/library/library.controller.ts",
"className": "LibraryController",
"methodName": "findAll",
"httpMethod": "GET",
"path": "/libraries",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/library/library.controller.ts",
"className": "LibraryController",
"methodName": "findOne",
"httpMethod": "GET",
"path": "/libraries/:id",
"permissions": [],
"libraryAccess": [
"'viewer'"
],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/library/library.controller.ts",
"className": "LibraryController",
"methodName": "queryBooks",
"httpMethod": "POST",
"path": "/libraries/:id/books",
"permissions": [],
"libraryAccess": [
"'viewer'"
],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/library/library.controller.ts",
"className": "LibraryController",
"methodName": "create",
"httpMethod": "POST",
"path": "/libraries",
"permissions": [
"Permission.ManageLibraries"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/library/library.controller.ts",
"className": "LibraryController",
"methodName": "update",
"httpMethod": "PATCH",
"path": "/libraries/:id",
"permissions": [
"Permission.ManageLibraries"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/library/library.controller.ts",
"className": "LibraryController",
"methodName": "remove",
"httpMethod": "DELETE",
"path": "/libraries/:id",
"permissions": [
"Permission.ManageLibraries"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/library/library.controller.ts",
"className": "LibraryController",
"methodName": "prescan",
"httpMethod": "POST",
"path": "/libraries/prescan",
"permissions": [
"Permission.ManageLibraries"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/library/library.controller.ts",
"className": "LibraryController",
"methodName": "reorder",
"httpMethod": "POST",
"path": "/libraries/reorder",
"permissions": [
"Permission.ManageLibraries"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/library/library.controller.ts",
"className": "LibraryController",
"methodName": "getStats",
"httpMethod": "GET",
"path": "/libraries/:id/stats",
"permissions": [],
"libraryAccess": [
"'viewer'"
],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/library/library.controller.ts",
"className": "LibraryController",
"methodName": "writeMetadataToFiles",
"httpMethod": "POST",
"path": "/libraries/:id/write-metadata-to-files",
"permissions": [
"Permission.LibraryEditMetadata"
],
"libraryAccess": [
"'editor'"
],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/library/library.controller.ts",
"className": "LibraryController",
"methodName": "getAccess",
"httpMethod": "GET",
"path": "/libraries/:libraryId/access",
"permissions": [
"Permission.ManageLibraries"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/library/library.controller.ts",
"className": "LibraryController",
"methodName": "grantAccess",
"httpMethod": "POST",
"path": "/libraries/:libraryId/access",
"permissions": [
"Permission.ManageLibraries"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
}
]
@@ -0,0 +1,672 @@
[
{
"file": "server/src/modules/library/library.controller.ts",
"className": "LibraryController",
"methodName": "updateAccess",
"httpMethod": "PATCH",
"path": "/libraries/:libraryId/access/:userId",
"permissions": [
"Permission.ManageLibraries"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/library/library.controller.ts",
"className": "LibraryController",
"methodName": "revokeAccess",
"httpMethod": "DELETE",
"path": "/libraries/:libraryId/access/:userId",
"permissions": [
"Permission.ManageLibraries"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/metadata-fetch/metadata-fetch.controller.ts",
"className": "MetadataFetchController",
"methodName": "listProviders",
"httpMethod": "GET",
"path": "/metadata-fetch/providers",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/metadata-fetch/metadata-fetch.controller.ts",
"className": "MetadataFetchController",
"methodName": "listProviderRuntime",
"httpMethod": "GET",
"path": "/metadata-fetch/providers/runtime",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/metadata-fetch/metadata-fetch.controller.ts",
"className": "MetadataFetchController",
"methodName": "lookup",
"httpMethod": "GET",
"path": "/metadata-fetch/lookup",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/metadata-preferences/metadata-preferences.controller.ts",
"className": "MetadataPreferencesController",
"methodName": "getGlobal",
"httpMethod": "GET",
"path": "/metadata-preferences/global",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/metadata-preferences/metadata-preferences.controller.ts",
"className": "MetadataPreferencesController",
"methodName": "setGlobal",
"httpMethod": "PUT",
"path": "/metadata-preferences/global",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/metadata-preferences/metadata-preferences.controller.ts",
"className": "MetadataPreferencesController",
"methodName": "getForLibrary",
"httpMethod": "GET",
"path": "/metadata-preferences/libraries/:id",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/metadata-preferences/metadata-preferences.controller.ts",
"className": "MetadataPreferencesController",
"methodName": "setLibraryOverrides",
"httpMethod": "PUT",
"path": "/metadata-preferences/libraries/:id",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/metadata-preferences/metadata-preferences.controller.ts",
"className": "MetadataPreferencesController",
"methodName": "resetGlobal",
"httpMethod": "DELETE",
"path": "/metadata-preferences/global",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/metadata-preferences/metadata-preferences.controller.ts",
"className": "MetadataPreferencesController",
"methodName": "resetLibraryToGlobal",
"httpMethod": "DELETE",
"path": "/metadata-preferences/libraries/:id",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/metadata-preferences/provider-config.controller.ts",
"className": "ProviderConfigController",
"methodName": "getConfig",
"httpMethod": "GET",
"path": "/metadata-preferences/providers",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/metadata-preferences/provider-config.controller.ts",
"className": "ProviderConfigController",
"methodName": "updateConfig",
"httpMethod": "PUT",
"path": "/metadata-preferences/providers",
"permissions": [
"Permission.ManageMetadataConfig"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/metadata-score/metadata-score.controller.ts",
"className": "MetadataScoreController",
"methodName": "getWeights",
"httpMethod": "GET",
"path": "/metadata-score/weights",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/metadata-score/metadata-score.controller.ts",
"className": "MetadataScoreController",
"methodName": "updateWeights",
"httpMethod": "PATCH",
"path": "/metadata-score/weights",
"permissions": [
"Permission.ManageAppSettings"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/metadata-score/metadata-score.controller.ts",
"className": "MetadataScoreController",
"methodName": "recalculate",
"httpMethod": "POST",
"path": "/metadata-score/recalculate",
"permissions": [
"Permission.ManageAppSettings"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/metadata-score/metadata-score.controller.ts",
"className": "MetadataScoreController",
"methodName": "getRecalculationStatus",
"httpMethod": "GET",
"path": "/metadata-score/recalculate/status",
"permissions": [
"Permission.ManageAppSettings"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/opds/opds-user.controller.ts",
"className": "OpdsUserController",
"methodName": "findAll",
"httpMethod": "GET",
"path": "/opds-users",
"permissions": [
"Permission.OpdsAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/opds/opds-user.controller.ts",
"className": "OpdsUserController",
"methodName": "create",
"httpMethod": "POST",
"path": "/opds-users",
"permissions": [
"Permission.OpdsAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/opds/opds-user.controller.ts",
"className": "OpdsUserController",
"methodName": "update",
"httpMethod": "PATCH",
"path": "/opds-users/:id",
"permissions": [
"Permission.OpdsAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/opds/opds-user.controller.ts",
"className": "OpdsUserController",
"methodName": "delete",
"httpMethod": "DELETE",
"path": "/opds-users/:id",
"permissions": [
"Permission.OpdsAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/opds/opds.controller.ts",
"className": "OpdsController",
"methodName": "root",
"httpMethod": "GET",
"path": "/opds",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"OpdsEnabledGuard, OpdsAuthGuard"
]
},
{
"file": "server/src/modules/opds/opds.controller.ts",
"className": "OpdsController",
"methodName": "libraries",
"httpMethod": "GET",
"path": "/opds/libraries",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"OpdsEnabledGuard, OpdsAuthGuard"
]
},
{
"file": "server/src/modules/opds/opds.controller.ts",
"className": "OpdsController",
"methodName": "collections",
"httpMethod": "GET",
"path": "/opds/collections",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"OpdsEnabledGuard, OpdsAuthGuard"
]
},
{
"file": "server/src/modules/opds/opds.controller.ts",
"className": "OpdsController",
"methodName": "lenses",
"httpMethod": "GET",
"path": "/opds/lenses",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"OpdsEnabledGuard, OpdsAuthGuard"
]
},
{
"file": "server/src/modules/opds/opds.controller.ts",
"className": "OpdsController",
"methodName": "authors",
"httpMethod": "GET",
"path": "/opds/authors",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"OpdsEnabledGuard, OpdsAuthGuard"
]
},
{
"file": "server/src/modules/opds/opds.controller.ts",
"className": "OpdsController",
"methodName": "series",
"httpMethod": "GET",
"path": "/opds/series",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"OpdsEnabledGuard, OpdsAuthGuard"
]
},
{
"file": "server/src/modules/opds/opds.controller.ts",
"className": "OpdsController",
"methodName": "catalog",
"httpMethod": "GET",
"path": "/opds/catalog",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"OpdsEnabledGuard, OpdsAuthGuard"
]
},
{
"file": "server/src/modules/opds/opds.controller.ts",
"className": "OpdsController",
"methodName": "recent",
"httpMethod": "GET",
"path": "/opds/recent",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"OpdsEnabledGuard, OpdsAuthGuard"
]
},
{
"file": "server/src/modules/opds/opds.controller.ts",
"className": "OpdsController",
"methodName": "surprise",
"httpMethod": "GET",
"path": "/opds/surprise",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"OpdsEnabledGuard, OpdsAuthGuard"
]
},
{
"file": "server/src/modules/opds/opds.controller.ts",
"className": "OpdsController",
"methodName": "searchDescription",
"httpMethod": "GET",
"path": "/opds/search.opds",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"OpdsEnabledGuard, OpdsAuthGuard"
]
},
{
"file": "server/src/modules/opds/opds.controller.ts",
"className": "OpdsController",
"methodName": "cover",
"httpMethod": "GET",
"path": "/opds/:bookId/cover",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"OpdsEnabledGuard, OpdsAuthGuard"
]
},
{
"file": "server/src/modules/opds/opds.controller.ts",
"className": "OpdsController",
"methodName": "thumbnail",
"httpMethod": "GET",
"path": "/opds/:bookId/thumbnail",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"OpdsEnabledGuard, OpdsAuthGuard"
]
},
{
"file": "server/src/modules/opds/opds.controller.ts",
"className": "OpdsController",
"methodName": "download",
"httpMethod": "GET",
"path": "/opds/:bookId/download",
"permissions": [],
"libraryAccess": [],
"isPublic": true,
"allowDefault": false,
"useGuards": [
"OpdsEnabledGuard, OpdsAuthGuard"
]
},
{
"file": "server/src/modules/path/path.controller.ts",
"className": "PathController",
"methodName": "listDirectories",
"httpMethod": "GET",
"path": "/path",
"permissions": [
"Permission.ManageLibraries"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/reader/cbz/cbz.controller.ts",
"className": "CbzController",
"methodName": "getPageCount",
"httpMethod": "GET",
"path": "/cbz/files/:fileId/pages",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/reader/cbz/cbz.controller.ts",
"className": "CbzController",
"methodName": "getPage",
"httpMethod": "GET",
"path": "/cbz/files/:fileId/pages/:pageIndex",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/reader/epub/epub.controller.ts",
"className": "EpubController",
"methodName": "getBookInfo",
"httpMethod": "GET",
"path": "/epub/:bookId/info",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/reader/epub/epub.controller.ts",
"className": "EpubController",
"methodName": "getFile",
"httpMethod": "GET",
"path": "/epub/:bookId/file/*",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/reader-preferences/reader-preferences.controller.ts",
"className": "ReaderPreferencesController",
"methodName": "getPreference",
"httpMethod": "GET",
"path": "/reader/preferences/:bookFileId",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/reader-preferences/reader-preferences.controller.ts",
"className": "ReaderPreferencesController",
"methodName": "upsertPreference",
"httpMethod": "PUT",
"path": "/reader/preferences/:bookFileId",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/reader-preferences/reader-preferences.controller.ts",
"className": "ReaderPreferencesController",
"methodName": "deletePreference",
"httpMethod": "DELETE",
"path": "/reader/preferences/:bookFileId",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/reader-preferences/reader-preferences.controller.ts",
"className": "ReaderPreferencesController",
"methodName": "getAllDefaults",
"httpMethod": "GET",
"path": "/reader/defaults",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/reader-preferences/reader-preferences.controller.ts",
"className": "ReaderPreferencesController",
"methodName": "upsertDefault",
"httpMethod": "PUT",
"path": "/reader/defaults/:formatGroup",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/reader-preferences/reader-preferences.controller.ts",
"className": "ReaderPreferencesController",
"methodName": "deleteDefault",
"httpMethod": "DELETE",
"path": "/reader/defaults/:formatGroup",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/reading-session/reading-session.controller.ts",
"className": "ReadingSessionController",
"methodName": "saveSession",
"httpMethod": "POST",
"path": "/books/files/:fileId/sessions",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/recommendation/recommendation.controller.ts",
"className": "RecommendationController",
"methodName": "getRecommendations",
"httpMethod": "GET",
"path": "/books/:id/recommendations",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/scanner/scanner.controller.ts",
"className": "ScannerController",
"methodName": "scan",
"httpMethod": "POST",
"path": "/scanner/libraries/:id/scan",
"permissions": [
"Permission.ManageLibraries"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/scanner/scanner.controller.ts",
"className": "ScannerController",
"methodName": "refreshCovers",
"httpMethod": "POST",
"path": "/scanner/libraries/:id/refresh-covers",
"permissions": [
"Permission.ManageLibraries"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/staging/staging.controller.ts",
"className": "StagingController",
"methodName": "listFiles",
"httpMethod": "GET",
"path": "/staging/files",
"permissions": [
"Permission.StagingAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
}
]
@@ -0,0 +1,654 @@
[
{
"file": "server/src/modules/staging/staging.controller.ts",
"className": "StagingController",
"methodName": "getSummary",
"httpMethod": "GET",
"path": "/staging/summary",
"permissions": [
"Permission.StagingAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/staging/staging.controller.ts",
"className": "StagingController",
"methodName": "getStatistics",
"httpMethod": "GET",
"path": "/staging/statistics",
"permissions": [
"Permission.StagingAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/staging/staging.controller.ts",
"className": "StagingController",
"methodName": "getFile",
"httpMethod": "GET",
"path": "/staging/files/:id",
"permissions": [
"Permission.StagingAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/staging/staging.controller.ts",
"className": "StagingController",
"methodName": "getCover",
"httpMethod": "GET",
"path": "/staging/files/:id/cover",
"permissions": [
"Permission.StagingAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/staging/staging.controller.ts",
"className": "StagingController",
"methodName": "upload",
"httpMethod": "POST",
"path": "/staging/upload",
"permissions": [
"Permission.StagingAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/staging/staging.controller.ts",
"className": "StagingController",
"methodName": "updateFile",
"httpMethod": "PATCH",
"path": "/staging/files/:id",
"permissions": [
"Permission.StagingAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/staging/staging.controller.ts",
"className": "StagingController",
"methodName": "discardFile",
"httpMethod": "DELETE",
"path": "/staging/files/:id",
"permissions": [
"Permission.StagingAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/staging/staging.controller.ts",
"className": "StagingController",
"methodName": "bulkDiscard",
"httpMethod": "POST",
"path": "/staging/files/discard",
"permissions": [
"Permission.StagingAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/staging/staging.controller.ts",
"className": "StagingController",
"methodName": "applyFetched",
"httpMethod": "POST",
"path": "/staging/files/apply-fetched",
"permissions": [
"Permission.StagingAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/staging/staging.controller.ts",
"className": "StagingController",
"methodName": "retryFetch",
"httpMethod": "POST",
"path": "/staging/files/retry-fetch",
"permissions": [
"Permission.StagingAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/staging/staging.controller.ts",
"className": "StagingController",
"methodName": "setTarget",
"httpMethod": "POST",
"path": "/staging/files/set-target",
"permissions": [
"Permission.StagingAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/staging/staging.controller.ts",
"className": "StagingController",
"methodName": "selectionSummary",
"httpMethod": "POST",
"path": "/staging/files/selection-summary",
"permissions": [
"Permission.StagingAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/staging/staging.controller.ts",
"className": "StagingController",
"methodName": "bulkEdit",
"httpMethod": "POST",
"path": "/staging/files/bulk-edit",
"permissions": [
"Permission.StagingAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/staging/staging.controller.ts",
"className": "StagingController",
"methodName": "previewNames",
"httpMethod": "POST",
"path": "/staging/files/preview-names",
"permissions": [
"Permission.StagingAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/staging/staging.controller.ts",
"className": "StagingController",
"methodName": "finalize",
"httpMethod": "POST",
"path": "/staging/finalize",
"permissions": [
"Permission.StagingAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/staging/staging.controller.ts",
"className": "StagingController",
"methodName": "rescan",
"httpMethod": "POST",
"path": "/staging/rescan",
"permissions": [
"Permission.StagingAccess"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getFormatDistribution",
"httpMethod": "GET",
"path": "/statistics/format-distribution",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getLanguageDistribution",
"httpMethod": "GET",
"path": "/statistics/language-distribution",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getBooksAddedOverTime",
"httpMethod": "GET",
"path": "/statistics/books-added-over-time",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getStorageByFormat",
"httpMethod": "GET",
"path": "/statistics/storage-by-format",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getMetadataScoreDistribution",
"httpMethod": "GET",
"path": "/statistics/metadata-score-distribution",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getLibraryMetadataCompleteness",
"httpMethod": "GET",
"path": "/statistics/library-metadata-completeness",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getFormatShareOverTime",
"httpMethod": "GET",
"path": "/statistics/format-share-over-time",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getPageCountDistribution",
"httpMethod": "GET",
"path": "/statistics/page-count-distribution",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getPublicationDecade",
"httpMethod": "GET",
"path": "/statistics/publication-decade",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getPublicationYearTimeline",
"httpMethod": "GET",
"path": "/statistics/publication-year-timeline",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getTopAuthors",
"httpMethod": "GET",
"path": "/statistics/top-authors",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getMetadataCompleteness",
"httpMethod": "GET",
"path": "/statistics/metadata-completeness",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getGenreDistribution",
"httpMethod": "GET",
"path": "/statistics/genre-distribution",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getSummary",
"httpMethod": "GET",
"path": "/statistics/summary",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getGenreCooccurrence",
"httpMethod": "GET",
"path": "/statistics/genre-cooccurrence",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getMetadataFreshnessGauge",
"httpMethod": "GET",
"path": "/statistics/metadata-freshness-gauge",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getLibraryIntegrityGauge",
"httpMethod": "GET",
"path": "/statistics/library-integrity-gauge",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getAcquisitionLagScatter",
"httpMethod": "GET",
"path": "/statistics/acquisition-lag-scatter",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getLargestBooks",
"httpMethod": "GET",
"path": "/statistics/largest-books",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/statistics/statistics.controller.ts",
"className": "StatisticsController",
"methodName": "getTopSeries",
"httpMethod": "GET",
"path": "/statistics/top-series",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/upload/upload.controller.ts",
"className": "UploadController",
"methodName": "uploadBook",
"httpMethod": "POST",
"path": "/libraries/:id/upload",
"permissions": [
"Permission.LibraryUpload"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user/user.controller.ts",
"className": "UserController",
"methodName": "findAll",
"httpMethod": "GET",
"path": "/users",
"permissions": [
"Permission.ManageUsers"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user/user.controller.ts",
"className": "UserController",
"methodName": "findAssignable",
"httpMethod": "GET",
"path": "/users/assignable",
"permissions": [
"Permission.ManageLibraries"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user/user.controller.ts",
"className": "UserController",
"methodName": "updateMe",
"httpMethod": "PATCH",
"path": "/users/me",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user/user.controller.ts",
"className": "UserController",
"methodName": "uploadMyAvatar",
"httpMethod": "POST",
"path": "/users/me/avatar",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user/user.controller.ts",
"className": "UserController",
"methodName": "deleteMyAvatar",
"httpMethod": "DELETE",
"path": "/users/me/avatar",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user/user.controller.ts",
"className": "UserController",
"methodName": "findById",
"httpMethod": "GET",
"path": "/users/:id",
"permissions": [
"Permission.ManageUsers"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user/user.controller.ts",
"className": "UserController",
"methodName": "getAvatar",
"httpMethod": "GET",
"path": "/users/:id/avatar",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user/user.controller.ts",
"className": "UserController",
"methodName": "createUser",
"httpMethod": "POST",
"path": "/users",
"permissions": [
"Permission.ManageUsers"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user/user.controller.ts",
"className": "UserController",
"methodName": "updateUser",
"httpMethod": "PATCH",
"path": "/users/:id",
"permissions": [
"Permission.ManageUsers"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user/user.controller.ts",
"className": "UserController",
"methodName": "deleteUser",
"httpMethod": "DELETE",
"path": "/users/:id",
"permissions": [
"Permission.ManageUsers"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user/user.controller.ts",
"className": "UserController",
"methodName": "setPermissions",
"httpMethod": "PUT",
"path": "/users/:id/permissions",
"permissions": [
"Permission.ManageUsers"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user/user.controller.ts",
"className": "UserController",
"methodName": "setSuperuser",
"httpMethod": "PUT",
"path": "/users/:id/superuser",
"permissions": [
"Permission.ManageUsers"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user/user.controller.ts",
"className": "UserController",
"methodName": "getLibraries",
"httpMethod": "GET",
"path": "/users/:id/libraries",
"permissions": [
"Permission.ManageUsers"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
}
]
@@ -0,0 +1,234 @@
[
{
"file": "server/src/modules/user/user.controller.ts",
"className": "UserController",
"methodName": "setLibraries",
"httpMethod": "PUT",
"path": "/users/:id/libraries",
"permissions": [
"Permission.ManageUsers"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user/user.controller.ts",
"className": "UserController",
"methodName": "adminResetPassword",
"httpMethod": "POST",
"path": "/users/:id/reset-password",
"permissions": [
"Permission.ManageUsers"
],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getSummary",
"httpMethod": "GET",
"path": "/user-statistics/summary",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getDailyReading",
"httpMethod": "GET",
"path": "/user-statistics/daily-reading",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getReadingHeatmap",
"httpMethod": "GET",
"path": "/user-statistics/reading-heatmap",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getPeakHours",
"httpMethod": "GET",
"path": "/user-statistics/peak-hours",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getFavoriteDays",
"httpMethod": "GET",
"path": "/user-statistics/favorite-days",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getSessionTimeline",
"httpMethod": "GET",
"path": "/user-statistics/session-timeline",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "updateSessionTimelineSession",
"httpMethod": "PATCH",
"path": "/user-statistics/session-timeline/:sessionId",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getCompletionTimeline",
"httpMethod": "GET",
"path": "/user-statistics/completion-timeline",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getGoalTrajectory",
"httpMethod": "GET",
"path": "/user-statistics/goal-trajectory",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getProgressFunnel",
"httpMethod": "GET",
"path": "/user-statistics/progress-funnel",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getCompletionLatency",
"httpMethod": "GET",
"path": "/user-statistics/completion-latency",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getGenreReadingTime",
"httpMethod": "GET",
"path": "/user-statistics/genre-reading-time",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getReadingPace",
"httpMethod": "GET",
"path": "/user-statistics/reading-pace",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getReadingSurvival",
"httpMethod": "GET",
"path": "/user-statistics/reading-survival",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getCompletionRace",
"httpMethod": "GET",
"path": "/user-statistics/completion-race",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getSessionArchetypes",
"httpMethod": "GET",
"path": "/user-statistics/session-archetypes",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
},
{
"file": "server/src/modules/user-statistics/user-statistics.controller.ts",
"className": "UserStatisticsController",
"methodName": "getAuthorGenreChord",
"httpMethod": "GET",
"path": "/user-statistics/author-genre-chord",
"permissions": [],
"libraryAccess": [],
"isPublic": false,
"allowDefault": false,
"useGuards": []
}
]