fix(geplante-nachrichten): add /api prefix to all API paths, fix subscribe room token, unmask empty bot credentials, add Einzelnachrichten tab

This commit is contained in:
Matthias Hochmeister
2026-04-17 12:33:48 +02:00
parent fcca04cc39
commit d8afcc1f63
8 changed files with 429 additions and 138 deletions

View File

@@ -88,12 +88,12 @@ class ScheduledMessagesController {
try {
const { id } = req.params as Record<string, string>;
const userId = req.user!.id;
const { roomToken } = req.body;
if (!roomToken) {
res.status(400).json({ success: false, message: 'roomToken ist erforderlich' });
const { room_token } = req.body as Record<string, string>;
if (!room_token) {
res.status(400).json({ success: false, message: 'room_token ist erforderlich' });
return;
}
await scheduledMessagesService.subscribe(id, userId, roomToken);
await scheduledMessagesService.subscribe(id, userId, room_token);
res.json({ success: true });
} catch (error) {
logger.error('ScheduledMessagesController.subscribe error', { error, id: req.params.id });
@@ -101,6 +101,25 @@ class ScheduledMessagesController {
}
}
async getMyBotRoom(req: Request, res: Response): Promise<void> {
try {
const userId = req.user!.id;
const result = await scheduledMessagesService.getOrCreateBotRoom(userId);
if (result === null) {
res.status(400).json({ success: false, configured: false, message: 'Bot nicht konfiguriert' });
return;
}
if (result === 'not_connected') {
res.status(400).json({ success: false, connected: false, message: 'Nextcloud-Konto nicht verbunden' });
return;
}
res.json({ success: true, data: { room_token: result } });
} catch (error) {
logger.error('ScheduledMessagesController.getMyBotRoom error', { error });
res.status(500).json({ success: false, message: 'Bot-Raum konnte nicht ermittelt werden' });
}
}
async unsubscribe(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params as Record<string, string>;

View File

@@ -20,7 +20,7 @@ function maskValue(value: string): string {
function maskConfig(config: Record<string, string>): Record<string, string> {
const result: Record<string, string> = {};
for (const [k, v] of Object.entries(config)) {
result[k] = MASKED_FIELDS.includes(k) ? maskValue(v) : v;
result[k] = (MASKED_FIELDS.includes(k) && v) ? maskValue(v) : v;
}
return result;
}

View File

@@ -5,7 +5,7 @@ import { requirePermission } from '../middleware/rbac.middleware';
const router = Router();
// /rooms and /one-time MUST come before /:id to avoid being captured as an id param
// /rooms, /my-bot-room and /one-time MUST come before /:id to avoid being captured as an id param
router.get(
'/rooms',
authenticate,
@@ -13,6 +13,13 @@ router.get(
scheduledMessagesController.getRooms.bind(scheduledMessagesController),
);
router.get(
'/my-bot-room',
authenticate,
requirePermission('scheduled_messages:subscribe'),
scheduledMessagesController.getMyBotRoom.bind(scheduledMessagesController),
);
router.get(
'/one-time',
authenticate,

View File

@@ -521,7 +521,37 @@ async function sendVehicleEvent(vehicleId: string): Promise<void> {
}
}
// ── One-Time Messages ─────────────────────────────────────────────────────────
// ── Bot Room ──────────────────────────────────────────────────────────────────
/**
* Finds or creates a 1:1 Nextcloud Talk room between the bot and the given user.
* Returns the room token, null if bot isn't configured, or 'not_connected' if the
* user hasn't linked their Nextcloud account.
*/
async function getOrCreateBotRoom(userId: string): Promise<string | null | 'not_connected'> {
const creds = await getBotCredentials();
if (!creds) return null;
// Look up the user's Nextcloud login name
const userResult = await pool.query(
'SELECT nextcloud_login_name FROM users WHERE id = $1',
[userId],
);
const nextcloudLoginName = userResult.rows[0]?.nextcloud_login_name as string | null;
if (!nextcloudLoginName) return 'not_connected';
// Use createRoom(type=1) — Nextcloud returns existing 1:1 if it already exists
const { token } = await nextcloudService.createRoom(
1,
nextcloudLoginName,
undefined,
creds.username,
creds.appPassword,
);
return token;
}
interface OneTimeMessage {
id: string;
@@ -598,6 +628,7 @@ const scheduledMessagesService = {
getSubscriptionsForUser,
buildAndSend,
sendVehicleEvent,
getOrCreateBotRoom,
getOneTimeMessages,
createOneTimeMessage,
deleteOneTimeMessage,