initial
This commit is contained in:
592
ts/bookstack.classes.account.ts
Normal file
592
ts/bookstack.classes.account.ts
Normal file
@@ -0,0 +1,592 @@
|
||||
import * as plugins from './bookstack.plugins.js';
|
||||
import type {
|
||||
ITestConnectionResult,
|
||||
IBookStackListParams,
|
||||
IBookStackListResponse,
|
||||
IBookStackBook,
|
||||
IBookStackChapter,
|
||||
IBookStackPage,
|
||||
IBookStackShelf,
|
||||
IBookStackAttachment,
|
||||
IBookStackComment,
|
||||
IBookStackImage,
|
||||
IBookStackUser,
|
||||
IBookStackRole,
|
||||
IBookStackSearchResult,
|
||||
IBookStackAuditLogEntry,
|
||||
IBookStackRecycleBinItem,
|
||||
IBookStackContentPermission,
|
||||
IBookStackSystemInfo,
|
||||
IBookStackTag,
|
||||
} from './bookstack.interfaces.js';
|
||||
import { BookStackBook } from './bookstack.classes.book.js';
|
||||
import { BookStackChapter } from './bookstack.classes.chapter.js';
|
||||
import { BookStackPage } from './bookstack.classes.page.js';
|
||||
import { BookStackShelf } from './bookstack.classes.shelf.js';
|
||||
import { autoPaginate } from './bookstack.helpers.js';
|
||||
|
||||
export class BookStackAccount {
|
||||
private baseUrl: string;
|
||||
private tokenId: string;
|
||||
private tokenSecret: string;
|
||||
|
||||
constructor(baseUrl: string, tokenId: string, tokenSecret: string) {
|
||||
this.baseUrl = baseUrl.replace(/\/+$/, '');
|
||||
this.tokenId = tokenId;
|
||||
this.tokenSecret = tokenSecret;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// HTTP helpers (internal)
|
||||
// ===========================================================================
|
||||
|
||||
/** @internal */
|
||||
async request<T = any>(
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
|
||||
path: string,
|
||||
data?: any,
|
||||
): Promise<T> {
|
||||
const url = `${this.baseUrl}/api${path}`;
|
||||
|
||||
let builder = plugins.smartrequest.SmartRequest.create()
|
||||
.url(url)
|
||||
.header('Authorization', `Token ${this.tokenId}:${this.tokenSecret}`)
|
||||
.header('Content-Type', 'application/json');
|
||||
|
||||
if (data) {
|
||||
builder = builder.json(data);
|
||||
}
|
||||
|
||||
let response: Awaited<ReturnType<typeof builder.get>>;
|
||||
switch (method) {
|
||||
case 'GET':
|
||||
response = await builder.get();
|
||||
break;
|
||||
case 'POST':
|
||||
response = await builder.post();
|
||||
break;
|
||||
case 'PUT':
|
||||
response = await builder.put();
|
||||
break;
|
||||
case 'DELETE':
|
||||
response = await builder.delete();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`${method} ${path}: ${response.status} ${response.statusText} - ${errorText}`);
|
||||
}
|
||||
|
||||
try {
|
||||
return (await response.json()) as T;
|
||||
} catch {
|
||||
return undefined as unknown as T;
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestText(
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
|
||||
path: string,
|
||||
): Promise<string> {
|
||||
const url = `${this.baseUrl}/api${path}`;
|
||||
|
||||
let builder = plugins.smartrequest.SmartRequest.create()
|
||||
.url(url)
|
||||
.header('Authorization', `Token ${this.tokenId}:${this.tokenSecret}`)
|
||||
.header('Accept', 'text/plain');
|
||||
|
||||
let response: Awaited<ReturnType<typeof builder.get>>;
|
||||
switch (method) {
|
||||
case 'GET':
|
||||
response = await builder.get();
|
||||
break;
|
||||
case 'POST':
|
||||
response = await builder.post();
|
||||
break;
|
||||
case 'PUT':
|
||||
response = await builder.put();
|
||||
break;
|
||||
case 'DELETE':
|
||||
response = await builder.delete();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`${method} ${path}: ${response.status} ${response.statusText} - ${errorText}`);
|
||||
}
|
||||
|
||||
return response.text();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestBinary(path: string): Promise<Uint8Array> {
|
||||
const url = `${this.baseUrl}/api${path}`;
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Token ${this.tokenId}:${this.tokenSecret}`,
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`GET ${path}: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
const buf = await response.arrayBuffer();
|
||||
return new Uint8Array(buf);
|
||||
}
|
||||
|
||||
/** @internal — build a URL with list query params */
|
||||
buildListUrl(basePath: string, opts?: IBookStackListParams): string {
|
||||
const params: string[] = [];
|
||||
if (opts?.count !== undefined) params.push(`count=${opts.count}`);
|
||||
if (opts?.offset !== undefined) params.push(`offset=${opts.offset}`);
|
||||
if (opts?.sort) params.push(`sort=${encodeURIComponent(opts.sort)}`);
|
||||
if (opts?.filter) {
|
||||
for (const [key, value] of Object.entries(opts.filter)) {
|
||||
params.push(`filter[${encodeURIComponent(key)}]=${encodeURIComponent(value)}`);
|
||||
}
|
||||
}
|
||||
return params.length > 0 ? `${basePath}?${params.join('&')}` : basePath;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Connection
|
||||
// ===========================================================================
|
||||
|
||||
public async testConnection(): Promise<ITestConnectionResult> {
|
||||
try {
|
||||
await this.request<IBookStackListResponse<IBookStackBook>>('GET', '/books?count=1');
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Books
|
||||
// ===========================================================================
|
||||
|
||||
public async getBooks(opts?: IBookStackListParams): Promise<BookStackBook[]> {
|
||||
return autoPaginate(
|
||||
(offset, count) =>
|
||||
this.request<IBookStackListResponse<IBookStackBook>>(
|
||||
'GET',
|
||||
this.buildListUrl('/books', { ...opts, offset, count }),
|
||||
),
|
||||
opts,
|
||||
).then((books) => books.map((b) => new BookStackBook(this, b)));
|
||||
}
|
||||
|
||||
public async getBook(id: number): Promise<BookStackBook> {
|
||||
const raw = await this.request<IBookStackBook>('GET', `/books/${id}`);
|
||||
return new BookStackBook(this, raw);
|
||||
}
|
||||
|
||||
public async createBook(data: {
|
||||
name: string;
|
||||
description?: string;
|
||||
description_html?: string;
|
||||
tags?: IBookStackTag[];
|
||||
default_template_id?: number | null;
|
||||
}): Promise<BookStackBook> {
|
||||
const raw = await this.request<IBookStackBook>('POST', '/books', data);
|
||||
return new BookStackBook(this, raw);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Chapters
|
||||
// ===========================================================================
|
||||
|
||||
public async getChapters(opts?: IBookStackListParams): Promise<BookStackChapter[]> {
|
||||
return autoPaginate(
|
||||
(offset, count) =>
|
||||
this.request<IBookStackListResponse<IBookStackChapter>>(
|
||||
'GET',
|
||||
this.buildListUrl('/chapters', { ...opts, offset, count }),
|
||||
),
|
||||
opts,
|
||||
).then((chapters) => chapters.map((c) => new BookStackChapter(this, c)));
|
||||
}
|
||||
|
||||
public async getChapter(id: number): Promise<BookStackChapter> {
|
||||
const raw = await this.request<IBookStackChapter>('GET', `/chapters/${id}`);
|
||||
return new BookStackChapter(this, raw);
|
||||
}
|
||||
|
||||
public async createChapter(data: {
|
||||
book_id: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
description_html?: string;
|
||||
tags?: IBookStackTag[];
|
||||
priority?: number;
|
||||
default_template_id?: number | null;
|
||||
}): Promise<BookStackChapter> {
|
||||
const raw = await this.request<IBookStackChapter>('POST', '/chapters', data);
|
||||
return new BookStackChapter(this, raw);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Pages
|
||||
// ===========================================================================
|
||||
|
||||
public async getPages(opts?: IBookStackListParams): Promise<BookStackPage[]> {
|
||||
return autoPaginate(
|
||||
(offset, count) =>
|
||||
this.request<IBookStackListResponse<IBookStackPage>>(
|
||||
'GET',
|
||||
this.buildListUrl('/pages', { ...opts, offset, count }),
|
||||
),
|
||||
opts,
|
||||
).then((pages) => pages.map((p) => new BookStackPage(this, p)));
|
||||
}
|
||||
|
||||
public async getPage(id: number): Promise<BookStackPage> {
|
||||
const raw = await this.request<IBookStackPage>('GET', `/pages/${id}`);
|
||||
return new BookStackPage(this, raw);
|
||||
}
|
||||
|
||||
public async createPage(data: {
|
||||
name: string;
|
||||
book_id?: number;
|
||||
chapter_id?: number;
|
||||
html?: string;
|
||||
markdown?: string;
|
||||
tags?: IBookStackTag[];
|
||||
priority?: number;
|
||||
}): Promise<BookStackPage> {
|
||||
const raw = await this.request<IBookStackPage>('POST', '/pages', data);
|
||||
return new BookStackPage(this, raw);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Shelves
|
||||
// ===========================================================================
|
||||
|
||||
public async getShelves(opts?: IBookStackListParams): Promise<BookStackShelf[]> {
|
||||
return autoPaginate(
|
||||
(offset, count) =>
|
||||
this.request<IBookStackListResponse<IBookStackShelf>>(
|
||||
'GET',
|
||||
this.buildListUrl('/shelves', { ...opts, offset, count }),
|
||||
),
|
||||
opts,
|
||||
).then((shelves) => shelves.map((s) => new BookStackShelf(this, s)));
|
||||
}
|
||||
|
||||
public async getShelf(id: number): Promise<BookStackShelf> {
|
||||
const raw = await this.request<IBookStackShelf>('GET', `/shelves/${id}`);
|
||||
return new BookStackShelf(this, raw);
|
||||
}
|
||||
|
||||
public async createShelf(data: {
|
||||
name: string;
|
||||
description?: string;
|
||||
description_html?: string;
|
||||
books?: number[];
|
||||
tags?: IBookStackTag[];
|
||||
}): Promise<BookStackShelf> {
|
||||
const raw = await this.request<IBookStackShelf>('POST', '/shelves', data);
|
||||
return new BookStackShelf(this, raw);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Attachments
|
||||
// ===========================================================================
|
||||
|
||||
public async getAttachments(opts?: IBookStackListParams): Promise<IBookStackAttachment[]> {
|
||||
return autoPaginate(
|
||||
(offset, count) =>
|
||||
this.request<IBookStackListResponse<IBookStackAttachment>>(
|
||||
'GET',
|
||||
this.buildListUrl('/attachments', { ...opts, offset, count }),
|
||||
),
|
||||
opts,
|
||||
);
|
||||
}
|
||||
|
||||
public async getAttachment(id: number): Promise<IBookStackAttachment> {
|
||||
return this.request<IBookStackAttachment>('GET', `/attachments/${id}`);
|
||||
}
|
||||
|
||||
public async createAttachment(data: {
|
||||
name: string;
|
||||
uploaded_to: number;
|
||||
link?: string;
|
||||
}): Promise<IBookStackAttachment> {
|
||||
return this.request<IBookStackAttachment>('POST', '/attachments', data);
|
||||
}
|
||||
|
||||
public async updateAttachment(id: number, data: {
|
||||
name?: string;
|
||||
uploaded_to?: number;
|
||||
link?: string;
|
||||
}): Promise<IBookStackAttachment> {
|
||||
return this.request<IBookStackAttachment>('PUT', `/attachments/${id}`, data);
|
||||
}
|
||||
|
||||
public async deleteAttachment(id: number): Promise<void> {
|
||||
await this.request('DELETE', `/attachments/${id}`);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Comments
|
||||
// ===========================================================================
|
||||
|
||||
public async getComments(opts?: IBookStackListParams): Promise<IBookStackComment[]> {
|
||||
return autoPaginate(
|
||||
(offset, count) =>
|
||||
this.request<IBookStackListResponse<IBookStackComment>>(
|
||||
'GET',
|
||||
this.buildListUrl('/comments', { ...opts, offset, count }),
|
||||
),
|
||||
opts,
|
||||
);
|
||||
}
|
||||
|
||||
public async getComment(id: number): Promise<IBookStackComment> {
|
||||
return this.request<IBookStackComment>('GET', `/comments/${id}`);
|
||||
}
|
||||
|
||||
public async createComment(data: {
|
||||
page_id: number;
|
||||
html: string;
|
||||
reply_to?: number;
|
||||
content_ref?: string;
|
||||
}): Promise<IBookStackComment> {
|
||||
return this.request<IBookStackComment>('POST', '/comments', data);
|
||||
}
|
||||
|
||||
public async updateComment(id: number, data: {
|
||||
html?: string;
|
||||
archived?: boolean;
|
||||
}): Promise<IBookStackComment> {
|
||||
return this.request<IBookStackComment>('PUT', `/comments/${id}`, data);
|
||||
}
|
||||
|
||||
public async deleteComment(id: number): Promise<void> {
|
||||
await this.request('DELETE', `/comments/${id}`);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Image Gallery
|
||||
// ===========================================================================
|
||||
|
||||
public async getImages(opts?: IBookStackListParams): Promise<IBookStackImage[]> {
|
||||
return autoPaginate(
|
||||
(offset, count) =>
|
||||
this.request<IBookStackListResponse<IBookStackImage>>(
|
||||
'GET',
|
||||
this.buildListUrl('/image-gallery', { ...opts, offset, count }),
|
||||
),
|
||||
opts,
|
||||
);
|
||||
}
|
||||
|
||||
public async getImage(id: number): Promise<IBookStackImage> {
|
||||
return this.request<IBookStackImage>('GET', `/image-gallery/${id}`);
|
||||
}
|
||||
|
||||
public async updateImage(id: number, data: {
|
||||
name?: string;
|
||||
}): Promise<IBookStackImage> {
|
||||
return this.request<IBookStackImage>('PUT', `/image-gallery/${id}`, data);
|
||||
}
|
||||
|
||||
public async deleteImage(id: number): Promise<void> {
|
||||
await this.request('DELETE', `/image-gallery/${id}`);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Users
|
||||
// ===========================================================================
|
||||
|
||||
public async getUsers(opts?: IBookStackListParams): Promise<IBookStackUser[]> {
|
||||
return autoPaginate(
|
||||
(offset, count) =>
|
||||
this.request<IBookStackListResponse<IBookStackUser>>(
|
||||
'GET',
|
||||
this.buildListUrl('/users', { ...opts, offset, count }),
|
||||
),
|
||||
opts,
|
||||
);
|
||||
}
|
||||
|
||||
public async getUser(id: number): Promise<IBookStackUser> {
|
||||
return this.request<IBookStackUser>('GET', `/users/${id}`);
|
||||
}
|
||||
|
||||
public async createUser(data: {
|
||||
name: string;
|
||||
email: string;
|
||||
password?: string;
|
||||
roles?: number[];
|
||||
send_invite?: boolean;
|
||||
external_auth_id?: string;
|
||||
language?: string;
|
||||
}): Promise<IBookStackUser> {
|
||||
return this.request<IBookStackUser>('POST', '/users', data);
|
||||
}
|
||||
|
||||
public async updateUser(id: number, data: {
|
||||
name?: string;
|
||||
email?: string;
|
||||
password?: string;
|
||||
roles?: number[];
|
||||
external_auth_id?: string;
|
||||
language?: string;
|
||||
}): Promise<IBookStackUser> {
|
||||
return this.request<IBookStackUser>('PUT', `/users/${id}`, data);
|
||||
}
|
||||
|
||||
public async deleteUser(id: number, opts?: { migrate_ownership_id?: number }): Promise<void> {
|
||||
const path = opts?.migrate_ownership_id
|
||||
? `/users/${id}?migrate_ownership_id=${opts.migrate_ownership_id}`
|
||||
: `/users/${id}`;
|
||||
await this.request('DELETE', path);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Roles
|
||||
// ===========================================================================
|
||||
|
||||
public async getRoles(opts?: IBookStackListParams): Promise<IBookStackRole[]> {
|
||||
return autoPaginate(
|
||||
(offset, count) =>
|
||||
this.request<IBookStackListResponse<IBookStackRole>>(
|
||||
'GET',
|
||||
this.buildListUrl('/roles', { ...opts, offset, count }),
|
||||
),
|
||||
opts,
|
||||
);
|
||||
}
|
||||
|
||||
public async getRole(id: number): Promise<IBookStackRole> {
|
||||
return this.request<IBookStackRole>('GET', `/roles/${id}`);
|
||||
}
|
||||
|
||||
public async createRole(data: {
|
||||
display_name: string;
|
||||
description?: string;
|
||||
mfa_enforced?: boolean;
|
||||
external_auth_id?: string;
|
||||
permissions?: string[];
|
||||
}): Promise<IBookStackRole> {
|
||||
return this.request<IBookStackRole>('POST', '/roles', data);
|
||||
}
|
||||
|
||||
public async updateRole(id: number, data: {
|
||||
display_name?: string;
|
||||
description?: string;
|
||||
mfa_enforced?: boolean;
|
||||
external_auth_id?: string;
|
||||
permissions?: string[];
|
||||
}): Promise<IBookStackRole> {
|
||||
return this.request<IBookStackRole>('PUT', `/roles/${id}`, data);
|
||||
}
|
||||
|
||||
public async deleteRole(id: number): Promise<void> {
|
||||
await this.request('DELETE', `/roles/${id}`);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Search
|
||||
// ===========================================================================
|
||||
|
||||
public async search(query: string, opts?: { page?: number; count?: number }): Promise<IBookStackSearchResult[]> {
|
||||
let url = `/search?query=${encodeURIComponent(query)}`;
|
||||
if (opts?.page) url += `&page=${opts.page}`;
|
||||
if (opts?.count) url += `&count=${opts.count}`;
|
||||
const result = await this.request<IBookStackListResponse<IBookStackSearchResult>>('GET', url);
|
||||
return result.data;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Audit Log
|
||||
// ===========================================================================
|
||||
|
||||
public async getAuditLog(opts?: IBookStackListParams): Promise<IBookStackAuditLogEntry[]> {
|
||||
return autoPaginate(
|
||||
(offset, count) =>
|
||||
this.request<IBookStackListResponse<IBookStackAuditLogEntry>>(
|
||||
'GET',
|
||||
this.buildListUrl('/audit-log', { ...opts, offset, count }),
|
||||
),
|
||||
opts,
|
||||
);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Recycle Bin
|
||||
// ===========================================================================
|
||||
|
||||
public async getRecycleBinItems(opts?: IBookStackListParams): Promise<IBookStackRecycleBinItem[]> {
|
||||
return autoPaginate(
|
||||
(offset, count) =>
|
||||
this.request<IBookStackListResponse<IBookStackRecycleBinItem>>(
|
||||
'GET',
|
||||
this.buildListUrl('/recycle-bin', { ...opts, offset, count }),
|
||||
),
|
||||
opts,
|
||||
);
|
||||
}
|
||||
|
||||
public async restoreRecycleBinItem(deletionId: number): Promise<{ restore_count: number }> {
|
||||
return this.request<{ restore_count: number }>('PUT', `/recycle-bin/${deletionId}`);
|
||||
}
|
||||
|
||||
public async destroyRecycleBinItem(deletionId: number): Promise<{ delete_count: number }> {
|
||||
return this.request<{ delete_count: number }>('DELETE', `/recycle-bin/${deletionId}`);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Content Permissions
|
||||
// ===========================================================================
|
||||
|
||||
public async getContentPermissions(
|
||||
contentType: 'page' | 'book' | 'chapter' | 'bookshelf',
|
||||
contentId: number,
|
||||
): Promise<IBookStackContentPermission> {
|
||||
return this.request<IBookStackContentPermission>(
|
||||
'GET',
|
||||
`/content-permissions/${contentType}/${contentId}`,
|
||||
);
|
||||
}
|
||||
|
||||
public async updateContentPermissions(
|
||||
contentType: 'page' | 'book' | 'chapter' | 'bookshelf',
|
||||
contentId: number,
|
||||
data: {
|
||||
owner_id?: number;
|
||||
role_permissions?: {
|
||||
role_id: number;
|
||||
view: boolean;
|
||||
create: boolean;
|
||||
update: boolean;
|
||||
delete: boolean;
|
||||
}[];
|
||||
fallback_permissions?: {
|
||||
inheriting: boolean;
|
||||
view?: boolean;
|
||||
create?: boolean;
|
||||
update?: boolean;
|
||||
delete?: boolean;
|
||||
};
|
||||
},
|
||||
): Promise<IBookStackContentPermission> {
|
||||
return this.request<IBookStackContentPermission>(
|
||||
'PUT',
|
||||
`/content-permissions/${contentType}/${contentId}`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// System
|
||||
// ===========================================================================
|
||||
|
||||
public async getSystemInfo(): Promise<IBookStackSystemInfo> {
|
||||
return this.request<IBookStackSystemInfo>('GET', '/system');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user