From dea5ae1ff83d0350cbdabbcec18cebaf4afb0d29 Mon Sep 17 00:00:00 2001 From: alma Date: Sun, 20 Apr 2025 17:31:14 +0200 Subject: [PATCH] carnet panel3 --- app/api/nextcloud/files/route.ts | 186 ++++++++++++++++++++----------- app/carnet/page.tsx | 13 ++- components/carnet/editor.tsx | 54 +++++++-- 3 files changed, 174 insertions(+), 79 deletions(-) diff --git a/app/api/nextcloud/files/route.ts b/app/api/nextcloud/files/route.ts index f39e0bed..c82c4950 100644 --- a/app/api/nextcloud/files/route.ts +++ b/app/api/nextcloud/files/route.ts @@ -12,6 +12,34 @@ declare global { const prisma = global.prisma || new PrismaClient(); if (process.env.NODE_ENV !== 'production') global.prisma = prisma; +// Helper function to create WebDAV client +const createWebDAVClient = async (userId: string) => { + const credentials = await prisma.webDAVCredentials.findUnique({ + where: { userId }, + }); + + if (!credentials) { + throw new Error('No WebDAV credentials found'); + } + + const baseURL = process.env.NEXTCLOUD_URL; + if (!baseURL) { + throw new Error('NEXTCLOUD_URL environment variable is not set'); + } + + const normalizedBaseURL = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL; + const webdavURL = `${normalizedBaseURL}/remote.php/dav`; + + return { + client: createClient(webdavURL, { + username: credentials.username, + password: credentials.password, + authType: 'password', + }), + username: credentials.username + }; +}; + export async function GET(request: Request) { try { const session = await getServerSession(authOptions); @@ -22,84 +50,118 @@ export async function GET(request: Request) { const { searchParams } = new URL(request.url); const folder = searchParams.get('folder') || 'Notes'; - // Get WebDAV credentials - const credentials = await prisma.webDAVCredentials.findUnique({ - where: { userId: session.user.id }, - }); - - if (!credentials) { - console.error('No WebDAV credentials found for user:', session.user.id); - return NextResponse.json({ error: 'No WebDAV credentials found' }, { status: 404 }); - } - - console.log('Using credentials for user:', credentials.username); - console.log('Accessing folder:', folder); - - // Initialize WebDAV client with proper URL construction - const baseURL = process.env.NEXTCLOUD_URL; - if (!baseURL) { - throw new Error('NEXTCLOUD_URL environment variable is not set'); - } - - // Remove trailing slash if present - const normalizedBaseURL = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL; + const { client, username } = await createWebDAVClient(session.user.id); - // Construct the full WebDAV URL - const webdavURL = `${normalizedBaseURL}/remote.php/dav`; - console.log('WebDAV base URL:', webdavURL); - - const client = createClient(webdavURL, { - username: credentials.username, - password: credentials.password, - authType: 'password', - }); - try { - // List files in the specified folder - const path = `/files/${credentials.username}/Private/${folder}`; + const path = `/files/${username}/Private/${folder}`; console.log('Fetching contents from path:', path); const files = await client.getDirectoryContents(path); console.log('Raw files response:', JSON.stringify(files, null, 2)); - // Filter for .md files and format the response const markdownFiles = files - .filter((file: any) => { - const isMarkdown = file.basename.endsWith('.md'); - console.log(`File: ${file.basename}, isMarkdown: ${isMarkdown}`); - return isMarkdown; - }) - .map((file: any) => { - const fileData = { - id: file.filename, - title: file.basename.replace('.md', ''), - lastModified: new Date(file.lastmod).toISOString(), - size: file.size, - type: 'file', - mime: file.mime, - etag: file.etag - }; - console.log('Formatted file data:', JSON.stringify(fileData, null, 2)); - return fileData; - }); + .filter((file: any) => file.basename.endsWith('.md')) + .map((file: any) => ({ + id: file.filename, + title: file.basename.replace('.md', ''), + lastModified: new Date(file.lastmod).toISOString(), + size: file.size, + type: 'file', + mime: file.mime, + etag: file.etag + })); - console.log('Found markdown files:', markdownFiles.length); - console.log('Final response:', JSON.stringify(markdownFiles, null, 2)); return NextResponse.json(markdownFiles); } catch (error) { console.error('Error listing directory contents:', error); - if (error instanceof Error) { - console.error('Error details:', error.message); - console.error('Error stack:', error.stack); - } return NextResponse.json({ error: 'Failed to list directory contents' }, { status: 500 }); } } catch (error) { console.error('Error fetching files:', error); - if (error instanceof Error) { - console.error('Error details:', error.message); - console.error('Error stack:', error.stack); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); + } +} + +export async function POST(request: Request) { + try { + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } - return NextResponse.json({ error: 'Failed to fetch files' }, { status: 500 }); + + const { title, content, folder } = await request.json(); + if (!title || !content || !folder) { + return NextResponse.json({ error: 'Missing required fields' }, { status: 400 }); + } + + const { client, username } = await createWebDAVClient(session.user.id); + + try { + const path = `/files/${username}/Private/${folder}/${title}.md`; + console.log('Saving note to path:', path); + + await client.putFileContents(path, content); + + // Get the file details after saving + const fileDetails = await client.stat(path); + + return NextResponse.json({ + id: fileDetails.filename, + title: fileDetails.basename.replace('.md', ''), + lastModified: new Date(fileDetails.lastmod).toISOString(), + size: fileDetails.size, + type: 'file', + mime: fileDetails.mime, + etag: fileDetails.etag + }); + } catch (error) { + console.error('Error saving note:', error); + return NextResponse.json({ error: 'Failed to save note' }, { status: 500 }); + } + } catch (error) { + console.error('Error in POST request:', error); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); + } +} + +export async function PUT(request: Request) { + try { + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const { id, title, content, folder } = await request.json(); + if (!id || !title || !content || !folder) { + return NextResponse.json({ error: 'Missing required fields' }, { status: 400 }); + } + + const { client, username } = await createWebDAVClient(session.user.id); + + try { + const path = `/files/${username}/Private/${folder}/${title}.md`; + console.log('Updating note at path:', path); + + await client.putFileContents(path, content); + + // Get the updated file details + const fileDetails = await client.stat(path); + + return NextResponse.json({ + id: fileDetails.filename, + title: fileDetails.basename.replace('.md', ''), + lastModified: new Date(fileDetails.lastmod).toISOString(), + size: fileDetails.size, + type: 'file', + mime: fileDetails.mime, + etag: fileDetails.etag + }); + } catch (error) { + console.error('Error updating note:', error); + return NextResponse.json({ error: 'Failed to update note' }, { status: 500 }); + } + } catch (error) { + console.error('Error in PUT request:', error); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } \ No newline at end of file diff --git a/app/carnet/page.tsx b/app/carnet/page.tsx index 4b236791..aa0c8431 100644 --- a/app/carnet/page.tsx +++ b/app/carnet/page.tsx @@ -185,10 +185,7 @@ export default function CarnetPage() { {/* Notes Panel */} {showNotes && ( <> -
+
- +
+
{/* Mobile Navigation Toggle */} diff --git a/components/carnet/editor.tsx b/components/carnet/editor.tsx index 5e53e957..12d720f3 100644 --- a/components/carnet/editor.tsx +++ b/components/carnet/editor.tsx @@ -15,11 +15,13 @@ interface Note { interface EditorProps { note?: Note | null; onSave?: (note: Note) => void; + currentFolder?: string; } -export const Editor: React.FC = ({ note, onSave }) => { +export const Editor: React.FC = ({ note, onSave, currentFolder = 'Notes' }) => { const [title, setTitle] = useState(note?.title || ''); const [content, setContent] = useState(note?.content || ''); + const [isSaving, setIsSaving] = useState(false); useEffect(() => { if (note) { @@ -36,16 +38,45 @@ export const Editor: React.FC = ({ note, onSave }) => { setContent(e.target.value); }; - const handleSave = () => { - if (note?.id) { - onSave?.({ - id: note.id, - title, - content, - lastEdited: new Date(), - category: note.category, - tags: note.tags + const handleSave = async () => { + if (!title || !content) return; + + setIsSaving(true); + try { + const endpoint = note?.id ? '/api/nextcloud/files' : '/api/nextcloud/files'; + const method = note?.id ? 'PUT' : 'POST'; + + const response = await fetch(endpoint, { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + id: note?.id, + title, + content, + folder: currentFolder + }), }); + + if (!response.ok) { + throw new Error('Failed to save note'); + } + + const savedNote = await response.json(); + onSave?.({ + id: savedNote.id, + title: savedNote.title, + content, + lastEdited: new Date(savedNote.lastModified), + category: note?.category, + tags: note?.tags + }); + } catch (error) { + console.error('Error saving note:', error); + // TODO: Show error message to user + } finally { + setIsSaving(false); } }; @@ -75,8 +106,9 @@ export const Editor: React.FC = ({ note, onSave }) => {