carnet panel3
This commit is contained in:
parent
2bb0fb74a1
commit
dea5ae1ff8
@ -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 });
|
||||
}
|
||||
}
|
||||
@ -185,10 +185,7 @@ export default function CarnetPage() {
|
||||
{/* Notes Panel */}
|
||||
{showNotes && (
|
||||
<>
|
||||
<div
|
||||
className="flex flex-col h-full bg-carnet-bg"
|
||||
style={{ width: `${notesWidth}px` }}
|
||||
>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<NotesView
|
||||
onNoteSelect={handleNoteSelect}
|
||||
currentFolder={selectedFolder}
|
||||
@ -206,8 +203,12 @@ export default function CarnetPage() {
|
||||
)}
|
||||
|
||||
{/* Editor Panel */}
|
||||
<div className="flex-1 flex flex-col h-full bg-carnet-bg">
|
||||
<Editor note={selectedNote} onSave={handleNoteSave} />
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<Editor
|
||||
note={selectedNote}
|
||||
onSave={handleNoteSave}
|
||||
currentFolder={selectedFolder}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Mobile Navigation Toggle */}
|
||||
|
||||
@ -15,11 +15,13 @@ interface Note {
|
||||
interface EditorProps {
|
||||
note?: Note | null;
|
||||
onSave?: (note: Note) => void;
|
||||
currentFolder?: string;
|
||||
}
|
||||
|
||||
export const Editor: React.FC<EditorProps> = ({ note, onSave }) => {
|
||||
export const Editor: React.FC<EditorProps> = ({ 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<EditorProps> = ({ 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<EditorProps> = ({ note, onSave }) => {
|
||||
<Image className="h-4 w-4 text-carnet-text-muted" />
|
||||
</button>
|
||||
<button
|
||||
className="p-1.5 rounded hover:bg-carnet-hover"
|
||||
className={`p-1.5 rounded hover:bg-carnet-hover ${isSaving ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||
onClick={handleSave}
|
||||
disabled={isSaving}
|
||||
>
|
||||
<FileText className="h-4 w-4 text-carnet-text-muted" />
|
||||
</button>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user