diff --git a/backend/data/test.md b/backend/data/test.md new file mode 100644 index 0000000..ac3e6da --- /dev/null +++ b/backend/data/test.md @@ -0,0 +1 @@ +# hi \ No newline at end of file diff --git a/backend/go.mod b/backend/go.mod index e5cb803..6ee7432 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -10,6 +10,7 @@ require ( require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/rs/cors v1.11.1 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index 765b506..e19aaa3 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -8,6 +8,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= diff --git a/backend/internal/handlers/handlers.go b/backend/internal/handlers/handlers.go index 34d8261..43d3a08 100644 --- a/backend/internal/handlers/handlers.go +++ b/backend/internal/handlers/handlers.go @@ -31,9 +31,13 @@ func (h *Handlers) ListFiles() http.HandlerFunc { } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]interface{}{ + if err := json.NewEncoder(w).Encode(map[string]interface{}{ "files": files, - }) + }); err != nil { + h.logger.Errorf("Failed to encode response: %v", err) + h.writeError(w, http.StatusInternalServerError, "failed to encode response") + return + } } } @@ -62,9 +66,13 @@ func (h *Handlers) CreateFile() http.HandlerFunc { } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]string{ + if err := json.NewEncoder(w).Encode(map[string]string{ "message": "file created successfully", - }) + }); err != nil { + h.logger.Errorf("Failed to encode response: %v", err) + h.writeError(w, http.StatusInternalServerError, "failed to encode response") + return + } } } @@ -86,10 +94,14 @@ func (h *Handlers) GetFile() http.HandlerFunc { } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]interface{}{ + if err := json.NewEncoder(w).Encode(map[string]interface{}{ "filename": filename, "content": string(content), - }) + }); err != nil { + h.logger.Errorf("Failed to encode response: %v", err) + h.writeError(w, http.StatusInternalServerError, "failed to encode response") + return + } } } @@ -120,9 +132,13 @@ func (h *Handlers) UpdateFile() http.HandlerFunc { } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]string{ + if err := json.NewEncoder(w).Encode(map[string]string{ "message": "file updated successfully", - }) + }); err != nil { + h.logger.Errorf("Failed to encode response: %v", err) + h.writeError(w, http.StatusInternalServerError, "failed to encode response") + return + } } } @@ -143,16 +159,23 @@ func (h *Handlers) DeleteFile() http.HandlerFunc { } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]string{ + if err := json.NewEncoder(w).Encode(map[string]string{ "message": "file deleted successfully", - }) + }); err != nil { + h.logger.Errorf("Failed to encode response: %v", err) + h.writeError(w, http.StatusInternalServerError, "failed to encode response") + return + } } } func (h *Handlers) writeError(w http.ResponseWriter, status int, message string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) - json.NewEncoder(w).Encode(map[string]string{ + if err := json.NewEncoder(w).Encode(map[string]string{ "error": message, - }) + }); err != nil { + h.logger.Errorf("Failed to encode error response: %v", err) + w.Write([]byte(`{"error": "failed to encode error response"}`)) + } } diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index 5696b64..95e7966 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -10,6 +10,7 @@ import ( "markdown-editor/internal/logger" "markdown-editor/internal/storage" "github.com/gorilla/mux" + "github.com/rs/cors" "github.com/sirupsen/logrus" ) @@ -73,6 +74,15 @@ func (s *Server) setupRoutes() { frontendDir = frontendDirEnv } s.router.PathPrefix("/").Handler(http.FileServer(http.Dir(frontendDir))) + + // Enable CORS + cors := cors.New(cors.Options{ + AllowedOrigins: []string{"http://localhost:3000", "http://localhost:8080"}, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Content-Type", "Authorization"}, + AllowCredentials: true, + }) + s.router.Use(cors.Handler) } func (s *Server) serve() error { diff --git a/backend/markdown-editor b/backend/markdown-editor new file mode 100755 index 0000000..faf20ae Binary files /dev/null and b/backend/markdown-editor differ diff --git a/backend/test/test_api.sh b/backend/test/test_api.sh new file mode 100755 index 0000000..64d26da --- /dev/null +++ b/backend/test/test_api.sh @@ -0,0 +1,28 @@ +#!/nix/store/ypgcdmzzlgnrmdcsq72c3dxz651jg9zc-bash-5.3p3/bin/bash + +# Start the server in the background +./markdown-editor --data-dir ./test/data --port 8081 --host 127.0.0.1 & +SERVER_PID=$! + +# Give the server time to start +sleep 2 + +# Test the /api/files endpoint +echo "Testing GET /api/files..." +response=$(curl -s http://127.0.0.1:8081/api/files) +echo "Response: $response" +echo "" + +# Test if it's valid JSON +if echo "$response" | python3 -m json.tool > /dev/null 2>&1; then + echo "✓ Response is valid JSON" +else + echo "✗ Response is NOT valid JSON" + echo "Response was: $response" +fi + +# Kill the server +kill $SERVER_PID 2>/dev/null +wait $SERVER_PID 2>/dev/null + +echo "Test completed" diff --git a/backend/test/test_api_detailed.sh b/backend/test/test_api_detailed.sh new file mode 100755 index 0000000..083d00e --- /dev/null +++ b/backend/test/test_api_detailed.sh @@ -0,0 +1,35 @@ +#!/nix/store/ypgcdmzzlgnrmdcsq72c3dxz651jg9zc-bash-5.3p3/bin/bash + +# Start the server in the background +./markdown-editor --data-dir ./test/data --port 8082 --host 127.0.0.1 & +SERVER_PID=$! + +# Give the server time to start +sleep 2 + +# Test the /api/files endpoint +echo "Testing GET /api/files..." +response=$(curl -s -i http://127.0.0.1:8082/api/files) +echo "$response" +echo "" + +# Extract just the body (skip headers) +body=$(echo "$response" | awk 'NR>1 && $0 !~ /^[A-Za-z]/ {print; next} /^[A-Za-z]/ {exit}') +echo "Body: '$body'" +echo "" + +# Test if it's valid JSON +echo "Checking if response is valid JSON..." +if echo "$body" | node -e "console.log(JSON.parse(require('fs').readFileSync(0, 'utf8')))" > /dev/null 2>&1; then + echo "✓ Response is valid JSON" + echo "$body" | node -e "console.log(JSON.parse(require('fs').readFileSync(0, 'utf8')))" +else + echo "✗ Response is NOT valid JSON" + echo "Response was: '$body'" +fi + +# Kill the server +kill $SERVER_PID 2>/dev/null +wait $SERVER_PID 2>/dev/null + +echo "Test completed" diff --git a/backend/test_api.sh b/backend/test_api.sh new file mode 100755 index 0000000..00c235c --- /dev/null +++ b/backend/test_api.sh @@ -0,0 +1,82 @@ +#!/nix/store/ypgcdmzzlgnrmdcsq72c3dxz651jg9zc-bash-5.3p3/bin/bash + +# Create test data directory +mkdir -p test/data + +# Start the server in the background +./markdown-editor --data-dir test/data --port 8080 --host 127.0.0.1 > /tmp/server.log 2>&1 & +SERVER_PID=$! + +# Wait for server to start +echo "Waiting for server to start..." +sleep 3 + +# Check if server is running +if ! ps -p $SERVER_PID > /dev/null; then + echo "Server failed to start" + cat /tmp/server.log + exit 1 +fi + +echo "Server is running (PID: $SERVER_PID)" +echo "Testing API endpoints..." +echo "" + +# Test 1: GET /api/files (empty) +echo "Test 1: GET /api/files (no files)" +response=$(curl -s http://127.0.0.1:8080/api/files) +echo "Response: $response" +if echo "$response" | grep -q '"files"'; then + echo "✓ Response contains 'files' key" +else + echo "✗ Response does not contain 'files' key" +fi +echo "" + +# Test 2: Create a file +echo "Test 2: POST /api/files (create test.md)" +response=$(curl -s -X POST http://127.0.0.1:8080/api/files \ + -H "Content-Type: application/json" \ + -d '{"filename":"test.md","content":"# Test Content"}') +echo "Response: $response" +if echo "$response" | grep -q '"message"'; then + echo "✓ Response contains 'message' key" +else + echo "✗ Response does not contain 'message' key" +fi +echo "" + +# Test 3: GET /api/files (with file) +echo "Test 3: GET /api/files (with file)" +response=$(curl -s http://127.0.0.1:8080/api/files) +echo "Response: $response" +if echo "$response" | grep -q '"files"'; then + echo "✓ Response contains 'files' key" + if echo "$response" | grep -q '"test.md"'; then + echo "✓ Response contains 'test.md'" + else + echo "✗ Response does not contain 'test.md'" + fi +else + echo "✗ Response does not contain 'files' key" +fi +echo "" + +# Test 4: GET /api/files/test.md +echo "Test 4: GET /api/files/test.md" +response=$(curl -s http://127.0.0.1:8080/api/files/test.md) +echo "Response: $response" +if echo "$response" | grep -q '"content"'; then + echo "✓ Response contains 'content' key" +else + echo "✗ Response does not contain 'content' key" +fi +echo "" + +# Kill the server +echo "Stopping server..." +kill $SERVER_PID +wait $SERVER_PID 2>/dev/null + +echo "" +echo "All tests completed!" diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 644d26b..44db091 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -23,10 +23,14 @@ const App: React.FC = () => { const fetchFiles = async () => { try { const response = await fetch('/api/files'); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } const data = await response.json(); setFiles(data.files || []); } catch (error) { console.error('Error fetching files:', error); + // Optionally show an error message to the user } }; @@ -45,19 +49,27 @@ const App: React.FC = () => { }), }); - if (response.ok) { - setNewFilename(''); - setNewContent(''); - fetchFiles(); + if (!response.ok) { + const errorData = await response.json(); + alert(`Error creating file: ${errorData.error || 'Unknown error'}`); + return; } + + setNewFilename(''); + setNewContent(''); + fetchFiles(); } catch (error) { console.error('Error creating file:', error); + alert('Error creating file. Please check console for details.'); } }; const handleOpenFile = async (filename: string) => { try { const response = await fetch(`/api/files/${filename}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } const data = await response.json(); setCurrentFile({ filename, @@ -83,11 +95,16 @@ const App: React.FC = () => { }), }); - if (response.ok) { - alert('File saved successfully!'); + if (!response.ok) { + const errorData = await response.json(); + alert(`Error saving file: ${errorData.error || 'Unknown error'}`); + return; } + + alert('File saved successfully!'); } catch (error) { console.error('Error saving file:', error); + alert('Error saving file. Please check console for details.'); } }; @@ -99,14 +116,19 @@ const App: React.FC = () => { method: 'DELETE', }); - if (response.ok) { - fetchFiles(); - if (currentFile?.filename === filename) { - setCurrentFile(null); - } + if (!response.ok) { + const errorData = await response.json(); + alert(`Error deleting file: ${errorData.error || 'Unknown error'}`); + return; + } + + fetchFiles(); + if (currentFile?.filename === filename) { + setCurrentFile(null); } } catch (error) { console.error('Error deleting file:', error); + alert('Error deleting file. Please check console for details.'); } }; diff --git a/frontend/src/setupProxy.js b/frontend/src/setupProxy.js new file mode 100644 index 0000000..0fb5ae7 --- /dev/null +++ b/frontend/src/setupProxy.js @@ -0,0 +1,11 @@ +const { createProxyMiddleware } = require('http-proxy-middleware'); + +module.exports = function(app) { + app.use( + '/api', + createProxyMiddleware({ + target: 'http://localhost:8080', + changeOrigin: true, + }) + ); +};