429 lines
19 KiB
TypeScript
429 lines
19 KiB
TypeScript
import { Request, Response } from 'express';
|
|
import path from 'path';
|
|
import { z } from 'zod';
|
|
import nextcloudService from '../services/nextcloud.service';
|
|
import userService from '../services/user.service';
|
|
import logger from '../utils/logger';
|
|
|
|
const PollRequestSchema = z.object({
|
|
pollEndpoint: z.string().url(),
|
|
pollToken: z.string().min(1),
|
|
});
|
|
|
|
class NextcloudController {
|
|
async initiateConnect(_req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const data = await nextcloudService.initiateLoginFlow();
|
|
res.status(200).json({ success: true, data });
|
|
} catch (error) {
|
|
logger.error('initiateConnect error', { error });
|
|
res.status(500).json({ success: false, message: 'Nextcloud-Verbindung konnte nicht gestartet werden' });
|
|
}
|
|
}
|
|
|
|
async pollConnect(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const parsed = PollRequestSchema.safeParse(req.body);
|
|
if (!parsed.success) {
|
|
res.status(400).json({
|
|
success: false,
|
|
message: 'Validierungsfehler',
|
|
errors: parsed.error.flatten().fieldErrors,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const result = await nextcloudService.pollLoginFlow(parsed.data.pollEndpoint, parsed.data.pollToken);
|
|
|
|
if (!result) {
|
|
res.status(200).json({ success: true, data: { completed: false } });
|
|
return;
|
|
}
|
|
|
|
await userService.updateNextcloudCredentials(req.user!.id, result.loginName, result.appPassword);
|
|
res.status(200).json({ success: true, data: { completed: true } });
|
|
} catch (error) {
|
|
logger.error('pollConnect error', { error });
|
|
res.status(500).json({ success: false, message: 'Nextcloud-Abfrage fehlgeschlagen' });
|
|
}
|
|
}
|
|
|
|
async getConversations(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const credentials = await userService.getNextcloudCredentials(req.user!.id);
|
|
if (!credentials) {
|
|
res.status(200).json({ success: true, data: { connected: false } });
|
|
return;
|
|
}
|
|
|
|
const { totalUnread, conversations } = await nextcloudService.getConversations(
|
|
credentials.loginName,
|
|
credentials.appPassword,
|
|
);
|
|
res.status(200).json({ success: true, data: { connected: true, totalUnread, conversations } });
|
|
} catch (error: any) {
|
|
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') {
|
|
await userService.clearNextcloudCredentials(req.user!.id);
|
|
res.status(200).json({ success: true, data: { connected: false } });
|
|
return;
|
|
}
|
|
logger.error('getConversations error', { error });
|
|
res.status(500).json({ success: false, message: 'Nextcloud-Gespräche konnten nicht geladen werden' });
|
|
}
|
|
}
|
|
|
|
async disconnect(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
await userService.clearNextcloudCredentials(req.user!.id);
|
|
res.status(200).json({ success: true, data: null });
|
|
} catch (error) {
|
|
logger.error('disconnect error', { error });
|
|
res.status(500).json({ success: false, message: 'Nextcloud-Trennung fehlgeschlagen' });
|
|
}
|
|
}
|
|
|
|
async getRooms(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const credentials = await userService.getNextcloudCredentials(req.user!.id);
|
|
if (!credentials) {
|
|
res.status(200).json({ success: true, data: { connected: false, rooms: [] } });
|
|
return;
|
|
}
|
|
const rooms = await nextcloudService.getAllConversations(credentials.loginName, credentials.appPassword);
|
|
res.status(200).json({ success: true, data: { connected: true, rooms, loginName: credentials.loginName } });
|
|
} catch (error: any) {
|
|
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') {
|
|
await userService.clearNextcloudCredentials(req.user!.id);
|
|
res.status(200).json({ success: true, data: { connected: false, rooms: [] } });
|
|
return;
|
|
}
|
|
logger.error('getRooms error', { error });
|
|
res.status(500).json({ success: false, message: 'Nextcloud-Räume konnten nicht geladen werden' });
|
|
}
|
|
}
|
|
|
|
async getMessages(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const credentials = await userService.getNextcloudCredentials(req.user!.id);
|
|
if (!credentials) {
|
|
res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' });
|
|
return;
|
|
}
|
|
const token = req.params.token as string;
|
|
if (!token) {
|
|
res.status(400).json({ success: false, message: 'Room token fehlt' });
|
|
return;
|
|
}
|
|
const lookIntoFuture = req.query.lookIntoFuture === '1';
|
|
const lastKnownMessageId = req.query.lastKnownMessageId
|
|
? parseInt(req.query.lastKnownMessageId as string, 10)
|
|
: undefined;
|
|
const timeout = req.query.timeout
|
|
? Math.min(parseInt(req.query.timeout as string, 10), 25)
|
|
: 25;
|
|
const messages = await nextcloudService.getMessages(token, credentials.loginName, credentials.appPassword, {
|
|
lookIntoFuture,
|
|
lastKnownMessageId,
|
|
timeout,
|
|
});
|
|
res.status(200).json({ success: true, data: messages });
|
|
} catch (error: any) {
|
|
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') {
|
|
await userService.clearNextcloudCredentials(req.user!.id);
|
|
res.status(200).json({ success: true, data: { connected: false } });
|
|
return;
|
|
}
|
|
logger.error('getMessages error', { error });
|
|
res.status(500).json({ success: false, message: 'Nachrichten konnten nicht geladen werden' });
|
|
}
|
|
}
|
|
|
|
async sendMessage(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const credentials = await userService.getNextcloudCredentials(req.user!.id);
|
|
if (!credentials) {
|
|
res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' });
|
|
return;
|
|
}
|
|
const token = req.params.token as string;
|
|
const { message, replyTo } = req.body;
|
|
if (!token || !message || typeof message !== 'string' || message.trim().length === 0) {
|
|
res.status(400).json({ success: false, message: 'Token und Nachricht erforderlich' });
|
|
return;
|
|
}
|
|
if (message.length > 32000) {
|
|
res.status(400).json({ success: false, message: 'Nachricht zu lang' });
|
|
return;
|
|
}
|
|
const replyToNum = (typeof replyTo === 'number' && replyTo > 0) ? replyTo : undefined;
|
|
await nextcloudService.sendMessage(token, message.trim(), credentials.loginName, credentials.appPassword, replyToNum);
|
|
res.status(200).json({ success: true, data: null });
|
|
} catch (error: any) {
|
|
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') {
|
|
await userService.clearNextcloudCredentials(req.user!.id);
|
|
res.status(200).json({ success: true, data: { connected: false } });
|
|
return;
|
|
}
|
|
logger.error('sendMessage error', { error });
|
|
res.status(500).json({ success: false, message: 'Nachricht konnte nicht gesendet werden' });
|
|
}
|
|
}
|
|
|
|
async uploadFile(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const credentials = await userService.getNextcloudCredentials(req.user!.id);
|
|
if (!credentials) {
|
|
res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' });
|
|
return;
|
|
}
|
|
const token = req.params.token as string;
|
|
if (!token) {
|
|
res.status(400).json({ success: false, message: 'Room token fehlt' });
|
|
return;
|
|
}
|
|
if (!req.file) {
|
|
res.status(400).json({ success: false, message: 'Keine Datei übermittelt' });
|
|
return;
|
|
}
|
|
await nextcloudService.uploadFileToTalk(
|
|
token,
|
|
req.file.buffer,
|
|
req.file.originalname,
|
|
req.file.mimetype,
|
|
credentials.loginName,
|
|
credentials.appPassword,
|
|
);
|
|
res.status(200).json({ success: true, data: null });
|
|
} catch (error: any) {
|
|
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') {
|
|
await userService.clearNextcloudCredentials(req.user!.id);
|
|
res.status(200).json({ success: true, data: { connected: false } });
|
|
return;
|
|
}
|
|
logger.error('uploadFile error', { error });
|
|
res.status(500).json({ success: false, message: 'Datei konnte nicht hochgeladen werden' });
|
|
}
|
|
}
|
|
|
|
async downloadFile(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const credentials = await userService.getNextcloudCredentials(req.user!.id);
|
|
if (!credentials) {
|
|
res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' });
|
|
return;
|
|
}
|
|
const filePath = req.query.path as string;
|
|
if (!filePath) {
|
|
res.status(400).json({ success: false, message: 'Dateipfad fehlt' });
|
|
return;
|
|
}
|
|
// Path traversal protection
|
|
const normalized = path.normalize(filePath);
|
|
if (normalized.includes('..') || !normalized.startsWith('/')) {
|
|
res.status(400).json({ success: false, message: 'Ungültiger Dateipfad' });
|
|
return;
|
|
}
|
|
const response = await nextcloudService.downloadFile(
|
|
filePath,
|
|
credentials.loginName,
|
|
credentials.appPassword,
|
|
);
|
|
const contentType = response.headers['content-type'] ?? 'application/octet-stream';
|
|
const contentDisposition = response.headers['content-disposition']
|
|
?? `attachment; filename="${String(req.params.fileId).replace(/["\r\n\\]/g, '_')}"`;
|
|
res.setHeader('Content-Type', contentType);
|
|
res.setHeader('Content-Disposition', contentDisposition);
|
|
if (response.headers['content-length']) {
|
|
res.setHeader('Content-Length', response.headers['content-length']);
|
|
}
|
|
response.data.pipe(res);
|
|
} catch (error: any) {
|
|
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') {
|
|
await userService.clearNextcloudCredentials(req.user!.id);
|
|
res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' });
|
|
return;
|
|
}
|
|
logger.error('downloadFile error', { error });
|
|
res.status(500).json({ success: false, message: 'Datei konnte nicht heruntergeladen werden' });
|
|
}
|
|
}
|
|
|
|
async getFilePreview(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const credentials = await userService.getNextcloudCredentials(req.user!.id);
|
|
if (!credentials) {
|
|
res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' });
|
|
return;
|
|
}
|
|
const fileId = parseInt(req.params.fileId as string, 10);
|
|
if (isNaN(fileId)) {
|
|
res.status(400).json({ success: false, message: 'Ungültige Datei-ID' });
|
|
return;
|
|
}
|
|
const w = parseInt((req.query.w as string) ?? '400', 10) || 400;
|
|
const h = parseInt((req.query.h as string) ?? '400', 10) || 400;
|
|
const response = await nextcloudService.getFilePreview(
|
|
fileId,
|
|
Math.min(w, 1200),
|
|
Math.min(h, 1200),
|
|
credentials.loginName,
|
|
credentials.appPassword,
|
|
);
|
|
const contentType = response.headers['content-type'] ?? 'image/jpeg';
|
|
res.setHeader('Content-Type', contentType);
|
|
if (response.headers['content-length']) {
|
|
res.setHeader('Content-Length', response.headers['content-length']);
|
|
}
|
|
res.setHeader('Cache-Control', 'private, max-age=300');
|
|
response.data.pipe(res);
|
|
} catch (error: any) {
|
|
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') {
|
|
await userService.clearNextcloudCredentials(req.user!.id);
|
|
res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' });
|
|
return;
|
|
}
|
|
logger.error('getFilePreview error', { error });
|
|
res.status(500).json({ success: false, message: 'Vorschau konnte nicht geladen werden' });
|
|
}
|
|
}
|
|
|
|
async markRoomAsRead(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const credentials = await userService.getNextcloudCredentials(req.user!.id);
|
|
if (!credentials) {
|
|
res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' });
|
|
return;
|
|
}
|
|
const token = req.params.token as string;
|
|
if (!token) {
|
|
res.status(400).json({ success: false, message: 'Room token fehlt' });
|
|
return;
|
|
}
|
|
await nextcloudService.markAsRead(token, credentials.loginName, credentials.appPassword);
|
|
res.status(200).json({ success: true, data: null });
|
|
} catch (error: any) {
|
|
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') {
|
|
await userService.clearNextcloudCredentials(req.user!.id);
|
|
res.status(200).json({ success: true, data: { connected: false } });
|
|
return;
|
|
}
|
|
logger.error('markRoomAsRead error', { error });
|
|
res.status(500).json({ success: false, message: 'Raum konnte nicht als gelesen markiert werden' });
|
|
}
|
|
}
|
|
|
|
async searchUsers(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const credentials = await userService.getNextcloudCredentials(req.user!.id);
|
|
if (!credentials) {
|
|
res.status(200).json({ success: true, data: [] });
|
|
return;
|
|
}
|
|
const query = (req.query.search as string) ?? '';
|
|
const results = await nextcloudService.searchUsers(query, credentials.loginName, credentials.appPassword);
|
|
res.status(200).json({ success: true, data: results });
|
|
} catch (error: any) {
|
|
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') {
|
|
await userService.clearNextcloudCredentials(req.user!.id);
|
|
res.status(200).json({ success: true, data: [] });
|
|
return;
|
|
}
|
|
logger.error('searchUsers error', { error });
|
|
res.status(500).json({ success: false, message: 'Benutzersuche fehlgeschlagen' });
|
|
}
|
|
}
|
|
|
|
async createRoom(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const credentials = await userService.getNextcloudCredentials(req.user!.id);
|
|
if (!credentials) {
|
|
res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' });
|
|
return;
|
|
}
|
|
const { roomType, invite, roomName } = req.body;
|
|
if (typeof roomType !== 'number' || !invite || typeof invite !== 'string') {
|
|
res.status(400).json({ success: false, message: 'roomType und invite erforderlich' });
|
|
return;
|
|
}
|
|
const result = await nextcloudService.createRoom(roomType, invite, roomName, credentials.loginName, credentials.appPassword);
|
|
res.status(200).json({ success: true, data: result });
|
|
} catch (error: any) {
|
|
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') {
|
|
await userService.clearNextcloudCredentials(req.user!.id);
|
|
res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' });
|
|
return;
|
|
}
|
|
logger.error('createRoom error', { error });
|
|
res.status(500).json({ success: false, message: 'Raum konnte nicht erstellt werden' });
|
|
}
|
|
}
|
|
|
|
async addReaction(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const credentials = await userService.getNextcloudCredentials(req.user!.id);
|
|
if (!credentials) { res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' }); return; }
|
|
const token = req.params.token as string;
|
|
const messageId = parseInt(req.params.messageId as string, 10);
|
|
const { reaction } = req.body;
|
|
if (!token || isNaN(messageId) || !reaction) { res.status(400).json({ success: false, message: 'Parameter fehlen' }); return; }
|
|
await nextcloudService.addReaction(token, messageId, reaction, credentials.loginName, credentials.appPassword);
|
|
res.status(200).json({ success: true, data: null });
|
|
} catch (error: any) {
|
|
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') { await userService.clearNextcloudCredentials(req.user!.id); res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' }); return; }
|
|
logger.error('addReaction error', { error });
|
|
res.status(500).json({ success: false, message: 'Reaktion konnte nicht hinzugefügt werden' });
|
|
}
|
|
}
|
|
|
|
async removeReaction(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const credentials = await userService.getNextcloudCredentials(req.user!.id);
|
|
if (!credentials) { res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' }); return; }
|
|
const token = req.params.token as string;
|
|
const messageId = parseInt(req.params.messageId as string, 10);
|
|
const reaction = req.query.reaction as string;
|
|
if (!token || isNaN(messageId) || !reaction) { res.status(400).json({ success: false, message: 'Parameter fehlen' }); return; }
|
|
await nextcloudService.removeReaction(token, messageId, reaction, credentials.loginName, credentials.appPassword);
|
|
res.status(200).json({ success: true, data: null });
|
|
} catch (error: any) {
|
|
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') { await userService.clearNextcloudCredentials(req.user!.id); res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' }); return; }
|
|
logger.error('removeReaction error', { error });
|
|
res.status(500).json({ success: false, message: 'Reaktion konnte nicht entfernt werden' });
|
|
}
|
|
}
|
|
|
|
async getReactions(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const credentials = await userService.getNextcloudCredentials(req.user!.id);
|
|
if (!credentials) { res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' }); return; }
|
|
const token = req.params.token as string;
|
|
const messageId = parseInt(req.params.messageId as string, 10);
|
|
if (!token || isNaN(messageId)) { res.status(400).json({ success: false, message: 'Parameter fehlen' }); return; }
|
|
const data = await nextcloudService.getReactions(token, messageId, credentials.loginName, credentials.appPassword);
|
|
res.status(200).json({ success: true, data });
|
|
} catch (error: any) {
|
|
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') { await userService.clearNextcloudCredentials(req.user!.id); res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' }); return; }
|
|
logger.error('getReactions error', { error });
|
|
res.status(500).json({ success: false, message: 'Reaktionen konnten nicht geladen werden' });
|
|
}
|
|
}
|
|
|
|
async getPoll(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const credentials = await userService.getNextcloudCredentials(req.user!.id);
|
|
if (!credentials) { res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' }); return; }
|
|
const token = req.params.token as string;
|
|
const pollId = parseInt(req.params.pollId as string, 10);
|
|
if (!token || isNaN(pollId)) { res.status(400).json({ success: false, message: 'Parameter fehlen' }); return; }
|
|
const data = await nextcloudService.getPollDetails(token, pollId, credentials.loginName, credentials.appPassword);
|
|
res.status(200).json({ success: true, data });
|
|
} catch (error: any) {
|
|
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') { await userService.clearNextcloudCredentials(req.user!.id); res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' }); return; }
|
|
logger.error('getPoll error', { error });
|
|
res.status(500).json({ success: false, message: 'Abstimmung konnte nicht geladen werden' });
|
|
}
|
|
}
|
|
}
|
|
|
|
export default new NextcloudController();
|