Phase 2: MKCOL, DELETE, MOVE implementiert
- resolveResource() für Pfad→UUID (Datei/Ordner) - MKCOL: Ordner anlegen (createFolderByUuid) - DELETE: Datei/Ordner löschen - MOVE: Verschieben + Umbenennen mit Destination-Header Made-with: Cursor
This commit is contained in:
212
src/server.js
212
src/server.js
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* WebDAV-Server für Internxt Drive (Phase 1: PROPFIND)
|
||||
* WebDAV-Server für Internxt Drive (Phase 1+2: PROPFIND, MKCOL, DELETE, MOVE)
|
||||
*
|
||||
* Nutzt Browser-Token (INXT_TOKEN, INXT_MNEMONIC) aus .env.
|
||||
* Siehe docs/browser-token-auth.md
|
||||
@@ -8,7 +8,7 @@
|
||||
import 'dotenv/config';
|
||||
import express from 'express';
|
||||
import { createClients, refreshUser } from './internxt-client.js';
|
||||
import { pathToSegments, listFolder } from './path-resolver.js';
|
||||
import { pathToSegments, segmentsToPath, listFolder, resolveFolder, resolveResource } from './path-resolver.js';
|
||||
|
||||
const PORT = parseInt(process.env.PORT || '3005', 10);
|
||||
const token = process.env.INXT_TOKEN;
|
||||
@@ -158,6 +158,197 @@ async function handlePropfind(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt Root-UUID und Storage (mit Token-Check).
|
||||
*/
|
||||
async function getContext() {
|
||||
const { storage } = createClients(token);
|
||||
const refresh = await refreshUser(token);
|
||||
const user = refresh.user;
|
||||
const rootUuid = user?.rootFolderUuid || user?.rootFolderId || user?.root_folder_id;
|
||||
if (!rootUuid) throw new Error('Root-Ordner nicht gefunden');
|
||||
return { storage, rootUuid };
|
||||
}
|
||||
|
||||
/**
|
||||
* MKCOL Handler – Ordner anlegen
|
||||
*/
|
||||
async function handleMkcol(req, res) {
|
||||
let path = req.url || '/';
|
||||
try {
|
||||
path = decodeURIComponent(path);
|
||||
} catch (_) {}
|
||||
if (!path.startsWith('/')) path = '/' + path;
|
||||
if (path === '/') {
|
||||
res.status(403).send('Root kann nicht erstellt werden');
|
||||
return;
|
||||
}
|
||||
if (path.endsWith('/')) path = path.slice(0, -1);
|
||||
|
||||
const segments = pathToSegments(path);
|
||||
if (segments.length === 0) {
|
||||
res.status(403).send('Root bereits vorhanden');
|
||||
return;
|
||||
}
|
||||
|
||||
const parentPath = segmentsToPath(segments.slice(0, -1));
|
||||
const newName = segments[segments.length - 1];
|
||||
|
||||
try {
|
||||
const { storage, rootUuid } = await getContext();
|
||||
const parent = await resolveFolder(storage, rootUuid, parentPath);
|
||||
if (!parent) {
|
||||
res.status(409).send('Übergeordneter Ordner existiert nicht');
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = await resolveResource(storage, rootUuid, path);
|
||||
if (existing) {
|
||||
res.status(405).send('Ressource existiert bereits');
|
||||
return;
|
||||
}
|
||||
|
||||
const [createPromise] = storage.createFolderByUuid({
|
||||
parentFolderUuid: parent.uuid,
|
||||
plainName: newName,
|
||||
});
|
||||
await createPromise;
|
||||
res.status(201).send();
|
||||
} catch (err) {
|
||||
console.error('MKCOL Fehler:', err.message);
|
||||
if (err.message?.includes('Token') || err.response?.status === 401) {
|
||||
res.status(401).send('Nicht autorisiert');
|
||||
return;
|
||||
}
|
||||
res.status(500).send(err.message || 'Interner Fehler');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE Handler – Datei oder Ordner löschen
|
||||
*/
|
||||
async function handleDelete(req, res) {
|
||||
let path = req.url || '/';
|
||||
try {
|
||||
path = decodeURIComponent(path);
|
||||
} catch (_) {}
|
||||
if (!path.startsWith('/')) path = '/' + path;
|
||||
if (path.endsWith('/')) path = path.slice(0, -1);
|
||||
if (path === '/') {
|
||||
res.status(403).send('Root kann nicht gelöscht werden');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { storage, rootUuid } = await getContext();
|
||||
const resource = await resolveResource(storage, rootUuid, path);
|
||||
if (!resource) {
|
||||
res.status(404).send('Nicht gefunden');
|
||||
return;
|
||||
}
|
||||
|
||||
if (resource.type === 'folder') {
|
||||
await storage.deleteFolderByUuid(resource.uuid);
|
||||
} else {
|
||||
await storage.deleteFileByUuid(resource.uuid);
|
||||
}
|
||||
res.status(204).send();
|
||||
} catch (err) {
|
||||
console.error('DELETE Fehler:', err.message);
|
||||
if (err.message?.includes('Token') || err.response?.status === 401) {
|
||||
res.status(401).send('Nicht autorisiert');
|
||||
return;
|
||||
}
|
||||
res.status(500).send(err.message || 'Interner Fehler');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MOVE Handler – Datei oder Ordner verschieben/umbenennen
|
||||
*/
|
||||
async function handleMove(req, res) {
|
||||
let path = req.url || '/';
|
||||
const destinationHeader = req.headers['destination'];
|
||||
if (!destinationHeader) {
|
||||
res.status(400).send('Destination-Header fehlt');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
path = decodeURIComponent(path);
|
||||
} catch (_) {}
|
||||
if (!path.startsWith('/')) path = '/' + path;
|
||||
if (path.endsWith('/')) path = path.slice(0, -1);
|
||||
if (path === '/') {
|
||||
res.status(403).send('Root kann nicht verschoben werden');
|
||||
return;
|
||||
}
|
||||
|
||||
let destPath;
|
||||
try {
|
||||
const destUrl = new URL(destinationHeader);
|
||||
destPath = decodeURIComponent(destUrl.pathname || '/');
|
||||
} catch (_) {
|
||||
res.status(400).send('Ungültige Destination-URL');
|
||||
return;
|
||||
}
|
||||
if (!destPath.startsWith('/')) destPath = '/' + destPath;
|
||||
if (destPath.endsWith('/')) destPath = destPath.slice(0, -1);
|
||||
|
||||
const overwrite = (req.headers['overwrite'] || 'T').toUpperCase() === 'T';
|
||||
|
||||
try {
|
||||
const { storage, rootUuid } = await getContext();
|
||||
const source = await resolveResource(storage, rootUuid, path);
|
||||
if (!source) {
|
||||
res.status(404).send('Quelle nicht gefunden');
|
||||
return;
|
||||
}
|
||||
|
||||
const destSegments = pathToSegments(destPath);
|
||||
const destParentPath = segmentsToPath(destSegments.slice(0, -1));
|
||||
const destName = destSegments[destSegments.length - 1];
|
||||
|
||||
const destParent = await resolveFolder(storage, rootUuid, destParentPath);
|
||||
if (!destParent) {
|
||||
res.status(409).send('Zielordner existiert nicht');
|
||||
return;
|
||||
}
|
||||
|
||||
const existingDest = await resolveResource(storage, rootUuid, destPath);
|
||||
if (existingDest) {
|
||||
if (!overwrite) {
|
||||
res.status(412).send('Ziel existiert, Overwrite nicht erlaubt');
|
||||
return;
|
||||
}
|
||||
if (existingDest.type === 'folder') {
|
||||
await storage.deleteFolderByUuid(existingDest.uuid);
|
||||
} else {
|
||||
await storage.deleteFileByUuid(existingDest.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
const payload = { destinationFolder: destParent.uuid };
|
||||
if (destName && destName !== source.name) {
|
||||
payload.name = destName;
|
||||
}
|
||||
|
||||
if (source.type === 'folder') {
|
||||
await storage.moveFolderByUuid(source.uuid, payload);
|
||||
} else {
|
||||
await storage.moveFileByUuid(source.uuid, payload);
|
||||
}
|
||||
res.status(201).send();
|
||||
} catch (err) {
|
||||
console.error('MOVE Fehler:', err.message);
|
||||
if (err.message?.includes('Token') || err.response?.status === 401) {
|
||||
res.status(401).send('Nicht autorisiert');
|
||||
return;
|
||||
}
|
||||
res.status(500).send(err.message || 'Interner Fehler');
|
||||
}
|
||||
}
|
||||
|
||||
// WebDAV Endpoints
|
||||
app.options('*', (req, res) => {
|
||||
res.set('DAV', '1, 2');
|
||||
@@ -180,15 +371,24 @@ app.use((req, res, next) => {
|
||||
return;
|
||||
}
|
||||
if (req.method === 'DELETE') {
|
||||
res.status(501).send('DELETE noch nicht implementiert – Phase 2');
|
||||
handleDelete(req, res).catch((err) => {
|
||||
console.error('DELETE unhandled:', err);
|
||||
if (!res.headersSent) res.status(500).send(err.message);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (req.method === 'MKCOL') {
|
||||
res.status(501).send('MKCOL noch nicht implementiert – Phase 2');
|
||||
handleMkcol(req, res).catch((err) => {
|
||||
console.error('MKCOL unhandled:', err);
|
||||
if (!res.headersSent) res.status(500).send(err.message);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (req.method === 'MOVE') {
|
||||
res.status(501).send('MOVE noch nicht implementiert – Phase 2');
|
||||
handleMove(req, res).catch((err) => {
|
||||
console.error('MOVE unhandled:', err);
|
||||
if (!res.headersSent) res.status(500).send(err.message);
|
||||
});
|
||||
return;
|
||||
}
|
||||
next();
|
||||
@@ -196,6 +396,6 @@ app.use((req, res, next) => {
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Internxt WebDAV Server – http://127.0.0.1:${PORT}`);
|
||||
console.log('Phase 1: PROPFIND (Verzeichnis auflisten) aktiv.');
|
||||
console.log('Phase 1+2: PROPFIND, MKCOL, DELETE, MOVE aktiv.');
|
||||
console.log('Verwendung: z.B. Windows Explorer → Netzlaufwerk verbinden');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user