resolve issues with new features

This commit is contained in:
Matthias Hochmeister
2026-03-12 11:37:25 +01:00
parent d5be68ca63
commit 71a04aee89
38 changed files with 699 additions and 108 deletions

View File

@@ -98,7 +98,7 @@ async function getRecentPages(): Promise<BookStackPage[]> {
const pages: BookStackPage[] = response.data?.data ?? [];
return pages.map((p) => ({
...p,
url: `${bookstack.url}/books/${p.book_slug}/page/${p.slug}`,
url: p.url && p.url.startsWith('http') ? p.url : `${bookstack.url}/books/${p.book_slug}/page/${p.slug}`,
}));
} catch (error) {
if (axios.isAxiosError(error)) {
@@ -122,7 +122,7 @@ async function searchPages(query: string): Promise<BookStackSearchResult[]> {
const response = await axios.get(
`${bookstack.url}/api/search`,
{
params: { query, count: 8 },
params: { query, count: 50 },
headers: buildHeaders(),
},
);
@@ -189,7 +189,7 @@ async function getPageById(id: number): Promise<BookStackPageDetail> {
html: page.html ?? '',
created_at: page.created_at,
updated_at: page.updated_at,
url: `${bookstack.url}/books/${page.book_slug}/page/${page.slug}`,
url: page.url && page.url.startsWith('http') ? page.url : `${bookstack.url}/books/${page.book_slug}/page/${page.slug}`,
book: page.book,
createdBy: page.created_by,
updatedBy: page.updated_by,

View File

@@ -200,30 +200,47 @@ async function getMessages(token: string, loginName: string, appPassword: string
throw new Error('NEXTCLOUD_URL is not configured or is not a valid service URL');
}
const response = await axios.get(
`${baseUrl}/ocs/v2.php/apps/spreed/api/v4/chat/${encodeURIComponent(token)}`,
{
params: { lookIntoFuture: 0, limit: 50, setReadMarker: 0 },
headers: {
'Authorization': `Basic ${Buffer.from(loginName + ':' + appPassword).toString('base64')}`,
'OCS-APIRequest': 'true',
'Accept': 'application/json',
try {
const response = await axios.get(
`${baseUrl}/ocs/v2.php/apps/spreed/api/v4/chat/${encodeURIComponent(token)}`,
{
params: { lookIntoFuture: 0, limit: 50, setReadMarker: 0 },
headers: {
'Authorization': `Basic ${Buffer.from(loginName + ':' + appPassword).toString('base64')}`,
'OCS-APIRequest': 'true',
'Accept': 'application/json',
},
},
},
);
);
const messages: any[] = response.data?.ocs?.data ?? [];
return messages.map((m: any) => ({
id: m.id,
token: m.token,
actorType: m.actorType,
actorId: m.actorId,
actorDisplayName: m.actorDisplayName,
message: m.message,
timestamp: m.timestamp,
messageType: m.messageType ?? '',
systemMessage: m.systemMessage ?? '',
}));
const messages: any[] = response.data?.ocs?.data ?? [];
return messages.map((m: any) => ({
id: m.id,
token: m.token,
actorType: m.actorType,
actorId: m.actorId,
actorDisplayName: m.actorDisplayName,
message: m.message,
timestamp: m.timestamp,
messageType: m.messageType ?? '',
systemMessage: m.systemMessage ?? '',
}));
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 401) {
const err = new Error('Nextcloud authentication invalid');
(err as any).code = 'NEXTCLOUD_AUTH_INVALID';
throw err;
}
if (axios.isAxiosError(error)) {
logger.error('NextcloudService.getMessages failed', {
status: error.response?.status,
statusText: error.response?.statusText,
});
throw new Error(`Nextcloud API error: ${error.response?.status ?? 'unknown'}`);
}
logger.error('NextcloudService.getMessages failed', { error });
throw new Error('Failed to fetch messages');
}
}
async function sendMessage(token: string, message: string, loginName: string, appPassword: string): Promise<void> {
@@ -232,18 +249,35 @@ async function sendMessage(token: string, message: string, loginName: string, ap
throw new Error('NEXTCLOUD_URL is not configured or is not a valid service URL');
}
await axios.post(
`${baseUrl}/ocs/v2.php/apps/spreed/api/v4/chat/${encodeURIComponent(token)}`,
{ message },
{
headers: {
'Authorization': `Basic ${Buffer.from(loginName + ':' + appPassword).toString('base64')}`,
'OCS-APIRequest': 'true',
'Accept': 'application/json',
'Content-Type': 'application/json',
try {
await axios.post(
`${baseUrl}/ocs/v2.php/apps/spreed/api/v4/chat/${encodeURIComponent(token)}`,
{ message },
{
headers: {
'Authorization': `Basic ${Buffer.from(loginName + ':' + appPassword).toString('base64')}`,
'OCS-APIRequest': 'true',
'Accept': 'application/json',
'Content-Type': 'application/json',
},
},
},
);
);
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 401) {
const err = new Error('Nextcloud authentication invalid');
(err as any).code = 'NEXTCLOUD_AUTH_INVALID';
throw err;
}
if (axios.isAxiosError(error)) {
logger.error('NextcloudService.sendMessage failed', {
status: error.response?.status,
statusText: error.response?.statusText,
});
throw new Error(`Nextcloud API error: ${error.response?.status ?? 'unknown'}`);
}
logger.error('NextcloudService.sendMessage failed', { error });
throw new Error('Failed to send message');
}
}
async function markAsRead(token: string, loginName: string, appPassword: string): Promise<void> {
@@ -252,16 +286,33 @@ async function markAsRead(token: string, loginName: string, appPassword: string)
throw new Error('NEXTCLOUD_URL is not configured or is not a valid service URL');
}
await axios.delete(
`${baseUrl}/ocs/v2.php/apps/spreed/api/v4/chat/${encodeURIComponent(token)}/read`,
{
headers: {
'Authorization': `Basic ${Buffer.from(loginName + ':' + appPassword).toString('base64')}`,
'OCS-APIRequest': 'true',
'Accept': 'application/json',
try {
await axios.delete(
`${baseUrl}/ocs/v2.php/apps/spreed/api/v4/chat/${encodeURIComponent(token)}/read`,
{
headers: {
'Authorization': `Basic ${Buffer.from(loginName + ':' + appPassword).toString('base64')}`,
'OCS-APIRequest': 'true',
'Accept': 'application/json',
},
},
},
);
);
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 401) {
const err = new Error('Nextcloud authentication invalid');
(err as any).code = 'NEXTCLOUD_AUTH_INVALID';
throw err;
}
if (axios.isAxiosError(error)) {
logger.error('NextcloudService.markAsRead failed', {
status: error.response?.status,
statusText: error.response?.statusText,
});
throw new Error(`Nextcloud API error: ${error.response?.status ?? 'unknown'}`);
}
logger.error('NextcloudService.markAsRead failed', { error });
throw new Error('Failed to mark conversation as read');
}
}
async function getConversations(loginName: string, appPassword: string): Promise<ConversationsResult> {

View File

@@ -0,0 +1,43 @@
import pool from '../config/database';
export interface AppSetting {
key: string;
value: any;
updated_at: string;
updated_by: string | null;
}
class SettingsService {
async getAll(): Promise<AppSetting[]> {
const result = await pool.query('SELECT * FROM app_settings ORDER BY key');
return result.rows;
}
async get(key: string): Promise<AppSetting | null> {
const result = await pool.query('SELECT * FROM app_settings WHERE key = $1', [key]);
return result.rows[0] ?? null;
}
async set(key: string, value: any, userId: string): Promise<AppSetting> {
const result = await pool.query(
`INSERT INTO app_settings (key, value, updated_by, updated_at)
VALUES ($1, $2, $3, NOW())
ON CONFLICT (key) DO UPDATE SET value = $2, updated_by = $3, updated_at = NOW()
RETURNING *`,
[key, JSON.stringify(value), userId]
);
return result.rows[0];
}
async delete(key: string): Promise<boolean> {
const result = await pool.query('DELETE FROM app_settings WHERE key = $1', [key]);
return (result.rowCount ?? 0) > 0;
}
async getExternalLinks(): Promise<Array<{name: string; url: string}>> {
const setting = await this.get('external_links');
return Array.isArray(setting?.value) ? setting.value : [];
}
}
export default new SettingsService();