diff --git a/migrate-add-edited-at-to-notes.mjs b/migrate-add-edited-at-to-notes.mjs new file mode 100644 index 0000000..bdeb495 --- /dev/null +++ b/migrate-add-edited-at-to-notes.mjs @@ -0,0 +1,27 @@ +import db from "./src/lib/db.js"; + +export default function migrateAddEditedAtToNotes() { + try { + // Check if edited_at column already exists + const columns = db.prepare("PRAGMA table_info(notes)").all(); + const hasEditedAt = columns.some(col => col.name === 'edited_at'); + + if (!hasEditedAt) { + // Add the edited_at column + db.exec(` + ALTER TABLE notes ADD COLUMN edited_at TEXT; + `); + console.log("Migration completed: Added edited_at column to notes table"); + } else { + console.log("Migration skipped: edited_at column already exists"); + } + } catch (error) { + console.error("Migration failed:", error); + throw error; + } +} + +// Run the migration if this file is executed directly +if (import.meta.url === `file://${process.argv[1]}`) { + migrateAddEditedAtToNotes(); +} \ No newline at end of file diff --git a/src/app/api/notes/[id]/route.js b/src/app/api/notes/[id]/route.js index dafd5c5..7ae0782 100644 --- a/src/app/api/notes/[id]/route.js +++ b/src/app/api/notes/[id]/route.js @@ -68,5 +68,70 @@ async function deleteNoteHandler(req, { params }) { } } +async function updateNoteHandler(req, { params }) { + const { id } = await params; + const noteId = id; + const { note: noteText } = await req.json(); + + if (!noteText || !noteId) { + return NextResponse.json({ error: "Missing note or ID" }, { status: 400 }); + } + + try { + // Get original note for audit log and permission check + const originalNote = db + .prepare("SELECT * FROM notes WHERE note_id = ?") + .get(noteId); + + if (!originalNote) { + return NextResponse.json({ error: "Note not found" }, { status: 404 }); + } + + // Check if user has permission to update this note + // Users can update their own notes, or admins can update any note + const userRole = req.user?.role; + const userId = req.user?.id; + + if (userRole !== 'admin' && originalNote.created_by !== userId) { + return NextResponse.json({ error: "Unauthorized to update this note" }, { status: 403 }); + } + + // Update the note + db.prepare( + ` + UPDATE notes SET note = ?, edited_at = datetime('now', 'localtime') WHERE note_id = ? + ` + ).run(noteText, noteId); + + // Log note update + await logApiActionSafe( + req, + AUDIT_ACTIONS.NOTE_UPDATE, + RESOURCE_TYPES.NOTE, + noteId, + req.auth, + { + originalNote: { + note_length: originalNote?.note?.length || 0, + project_id: originalNote?.project_id, + task_id: originalNote?.task_id, + }, + updatedNote: { + note_length: noteText.length, + }, + } + ); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Error updating note:", error); + return NextResponse.json( + { error: "Failed to update note", details: error.message }, + { status: 500 } + ); + } +} + // Protected route - require user authentication export const DELETE = withUserAuth(deleteNoteHandler); +export const PUT = withUserAuth(updateNoteHandler); diff --git a/src/app/api/notes/route.js b/src/app/api/notes/route.js index eb281c1..1952f4a 100644 --- a/src/app/api/notes/route.js +++ b/src/app/api/notes/route.js @@ -136,50 +136,7 @@ async function deleteNoteHandler(req, { params }) { return NextResponse.json({ success: true }); } -async function updateNoteHandler(req, { params }) { - const { id } = await params; - const noteId = id; - const { note } = await req.json(); - - if (!note || !noteId) { - return NextResponse.json({ error: "Missing note or ID" }, { status: 400 }); - } - - // Get original note for audit log - const originalNote = db - .prepare("SELECT * FROM notes WHERE note_id = ?") - .get(noteId); - - db.prepare( - ` - UPDATE notes SET note = ? WHERE note_id = ? - ` - ).run(note, noteId); - - // Log note update - await logApiActionSafe( - req, - AUDIT_ACTIONS.NOTE_UPDATE, - RESOURCE_TYPES.NOTE, - noteId, - req.auth, // Use req.auth instead of req.session - { - originalNote: { - note_length: originalNote?.note?.length || 0, - project_id: originalNote?.project_id, - task_id: originalNote?.task_id, - }, - updatedNote: { - note_length: note.length, - }, - } - ); - - return NextResponse.json({ success: true }); -} - // Protected routes - require authentication export const GET = withReadAuth(getNotesHandler); export const POST = withUserAuth(createNoteHandler); export const DELETE = withUserAuth(deleteNoteHandler); -export const PUT = withUserAuth(updateNoteHandler); diff --git a/src/app/projects/[id]/page.js b/src/app/projects/[id]/page.js index a23c89d..f7d68bf 100644 --- a/src/app/projects/[id]/page.js +++ b/src/app/projects/[id]/page.js @@ -28,6 +28,8 @@ export default function ProjectViewPage() { const [notes, setNotes] = useState([]); const [loading, setLoading] = useState(true); const [uploadedFiles, setUploadedFiles] = useState([]); + const [editingNoteId, setEditingNoteId] = useState(null); + const [editText, setEditText] = useState(''); // Helper function to parse note text with links const parseNoteText = (text) => { @@ -69,14 +71,14 @@ export default function ProjectViewPage() { setNotes(prevNotes => [newNote, ...prevNotes]); }; - // Helper function to check if user can delete a note - const canDeleteNote = (note) => { + // Helper function to check if user can modify a note (edit or delete) + const canModifyNote = (note) => { if (!session?.user) return false; - // Admins can delete any note + // Admins can modify any note if (session.user.role === 'admin') return true; - // Users can delete their own notes + // Users can modify their own notes return note.created_by === session.user.id; }; @@ -113,6 +115,34 @@ export default function ProjectViewPage() { ); }; + // Helper function to save edited note + const handleSaveNote = async (noteId) => { + try { + const res = await fetch(`/api/notes/${noteId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ note: editText }), + }); + if (res.ok) { + // Update the note in local state + setNotes(prevNotes => + prevNotes.map(note => + note.note_id === noteId ? { ...note, note: editText, edited_at: new Date().toISOString() } : note + ) + ); + setEditingNoteId(null); + setEditText(''); + } else { + alert('Błąd podczas aktualizacji notatki'); + } + } catch (error) { + console.error('Error updating note:', error); + alert('Błąd podczas aktualizacji notatki'); + } + }; + useEffect(() => { const fetchData = async () => { if (!params.id) return; @@ -839,49 +869,109 @@ export default function ProjectViewPage() { {n.created_by_name} )} + {n.edited_at && ( + + • edytowane + + )} - {canDeleteNote(n) && ( - + + + + + + )} -
- {parseNoteText(n.note)} -
+ {editingNoteId === n.note_id ? ( +
+