11package main
22
33import (
4- "encoding/json"
54 "flag"
65 "fmt"
7- "io/fs"
86 "log"
97 "net/http"
108 "os"
@@ -16,6 +14,7 @@ import (
1614 "time"
1715
1816 "github.com/chinmay-sawant/gomindmapper/cmd/analyzer"
17+ "github.com/gin-gonic/gin"
1918)
2019
2120// cache holds the in-memory representation of relations + index for quick lookups.
@@ -41,58 +40,59 @@ func main() {
4140 log .Fatalf ("initial load failed: %v" , err )
4241 }
4342
44- http .HandleFunc ("/api/relations" , handleRelations )
45- http .HandleFunc ("/api/reload" , func (w http.ResponseWriter , r * http.Request ) {
46- if r .Method != http .MethodPost {
47- http .Error (w , "method not allowed" , http .StatusMethodNotAllowed )
48- return
49- }
43+ // Create Gin router
44+ router := gin .Default ()
45+
46+ // Add CORS middleware
47+ router .Use (corsMiddleware ())
48+
49+ // API routes
50+ router .GET ("/api/relations" , handleRelations )
51+ router .POST ("/api/reload" , func (c * gin.Context ) {
5052 if err := load (repoPath ); err != nil {
51- http . Error ( w , err .Error (), http . StatusInternalServerError )
53+ c . JSON ( http . StatusInternalServerError , gin. H { "error" : err .Error ()} )
5254 return
5355 }
54- writeJSON ( w , map [ string ] any {"status" : "reloaded" , "loadedAt" : global .loadedAt })
56+ c . JSON ( http . StatusOK , gin. H {"status" : "reloaded" , "loadedAt" : global .loadedAt })
5557 })
5658
57- http . HandleFunc ("/api/download" , func (w http. ResponseWriter , r * http. Request ) {
58- w .Header (). Set ("Content-Type" , "application/json" )
59- w .Header (). Set ("Content-Disposition" , "attachment; filename=function_relations.json" )
60- writeJSON ( w , global .relations )
59+ router . GET ("/api/download" , func (c * gin. Context ) {
60+ c .Header ("Content-Type" , "application/json" )
61+ c .Header ("Content-Disposition" , "attachment; filename=function_relations.json" )
62+ c . JSON ( http . StatusOK , global .relations )
6163 })
6264
63- // Serve docs folder at root
65+ // Serve docs folder at /docs
6466 docsDir := filepath .Join (repoPath , "docs" )
65- http .Handle ("/" , http .FileServer (http .Dir (docsDir )))
66-
67- // React build served at /view/* (mind-map-react/build)
68- reactBuild := filepath .Join (repoPath , "mind-map-react" , "build" )
69- if st , err := os .Stat (reactBuild ); err == nil && st .IsDir () {
70- // Wrap to provide SPA fallback
71- http .HandleFunc ("/view/" , func (w http.ResponseWriter , r * http.Request ) {
72- // Try to serve static asset
73- // strip /view/
74- rel := strings .TrimPrefix (r .URL .Path , "/view/" )
75- if rel == "" { // root of SPA
76- http .ServeFile (w , r , filepath .Join (reactBuild , "index.html" ))
77- return
78- }
79- candidate := filepath .Join (reactBuild , rel )
80- if info , err := os .Stat (candidate ); err == nil && ! info .IsDir () {
81- http .ServeFile (w , r , candidate )
82- return
83- }
84- // fallback to index.html for client routing
85- http .ServeFile (w , r , filepath .Join (reactBuild , "index.html" ))
86- })
87- // Also serve static assets without /view prefix if CRA build placed hashed assets in root of build
88- fileServer := http .FileServer (neuteredFileSystem {http .Dir (reactBuild )})
89- http .Handle ("/view/static/" , http .StripPrefix ("/view" , fileServer ))
90- } else {
91- log .Printf ("react build not found at %s (run npm run build in mind-map-react)" , reactBuild )
92- }
67+ router .Static ("/docs" , docsDir )
68+
69+ // Serve docs files at root
70+ router .GET ("/" , func (c * gin.Context ) {
71+ c .File (filepath .Join (docsDir , "index.html" ))
72+ })
73+
74+ // Serve static assets
75+ router .Static ("/assets" , filepath .Join (docsDir , "assets" ))
76+
77+ // Serve assets at /gomindmapper/assets/ path for HTML compatibility
78+ router .Static ("/gomindmapper/assets" , filepath .Join (docsDir , "assets" ))
79+
80+ // Add routes for /gomindmapper/ path - serve docs content
81+ router .GET ("/gomindmapper" , func (c * gin.Context ) {
82+ c .File (filepath .Join (docsDir , "index.html" ))
83+ })
84+ router .GET ("/gomindmapper/" , func (c * gin.Context ) {
85+ c .File (filepath .Join (docsDir , "index.html" ))
86+ })
87+ router .GET ("/gomindmapper/view" , func (c * gin.Context ) {
88+ c .File (filepath .Join (docsDir , "index.html" ))
89+ })
90+ router .GET ("/gomindmapper/view/*path" , func (c * gin.Context ) {
91+ c .File (filepath .Join (docsDir , "index.html" ))
92+ })
9393
9494 log .Printf ("server listening on %s" , addr )
95- log .Fatal (http . ListenAndServe (addr , corsMiddleware ( http . DefaultServeMux ) ))
95+ log .Fatal (router . Run (addr ))
9696}
9797
9898// load (re)scans repository, rebuilds structures and populates cache.
@@ -216,12 +216,12 @@ func filterCalls(functions []analyzer.FunctionInfo) {
216216// handleRelations returns paginated root relations with full dependency closure for each root on the page.
217217// Query params: page (1-based), pageSize
218218// Response: { page, pageSize, totalRoots, roots: [...root names...], data: [OutRelation ...] }
219- func handleRelations (w http. ResponseWriter , r * http. Request ) {
219+ func handleRelations (c * gin. Context ) {
220220 global .mu .RLock ()
221221 defer global .mu .RUnlock ()
222- page := parseInt (r . URL . Query (). Get ("page" ), 1 )
223- pageSize := parseInt (r . URL . Query (). Get ("pageSize" ), 10 )
224- includeInternals := strings .EqualFold (r . URL . Query (). Get ("includeInternals" ), "true" )
222+ page := parseInt (c . Query ("page" ), 1 )
223+ pageSize := parseInt (c . Query ("pageSize" ), 10 )
224+ includeInternals := strings .EqualFold (c . Query ("includeInternals" ), "true" )
225225 if page < 1 {
226226 page = 1
227227 }
@@ -279,7 +279,7 @@ func handleRelations(w http.ResponseWriter, r *http.Request) {
279279 return closure [i ].Name < closure [j ].Name
280280 })
281281
282- writeJSON ( w , map [ string ] any {
282+ c . JSON ( http . StatusOK , gin. H {
283283 "page" : page ,
284284 "pageSize" : pageSize ,
285285 "totalRoots" : totalRoots ,
@@ -292,15 +292,6 @@ func handleRelations(w http.ResponseWriter, r *http.Request) {
292292
293293// Helpers --------------------------------------------------------------------------------
294294
295- func writeJSON (w http.ResponseWriter , v any ) {
296- w .Header ().Set ("Content-Type" , "application/json" )
297- enc := json .NewEncoder (w )
298- enc .SetIndent ("" , " " )
299- if err := enc .Encode (v ); err != nil {
300- http .Error (w , err .Error (), http .StatusInternalServerError )
301- }
302- }
303-
304295func parseInt (s string , def int ) int {
305296 if s == "" {
306297 return def
@@ -346,33 +337,16 @@ func findFunctions(filePath, absPath, module string) ([]analyzer.FunctionInfo, e
346337 return funcs , nil
347338}
348339
349- // Basic CORS middleware
350- func corsMiddleware (next http.Handler ) http.Handler {
351- return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
352- w .Header ().Set ("Access-Control-Allow-Origin" , "*" )
353- w .Header ().Set ("Access-Control-Allow-Methods" , "GET,POST,OPTIONS" )
354- w .Header ().Set ("Access-Control-Allow-Headers" , "Content-Type" )
355- if r .Method == http .MethodOptions {
340+ // Basic CORS middleware for Gin
341+ func corsMiddleware () gin.HandlerFunc {
342+ return func (c * gin.Context ) {
343+ c .Header ("Access-Control-Allow-Origin" , "*" )
344+ c .Header ("Access-Control-Allow-Methods" , "GET,POST,OPTIONS" )
345+ c .Header ("Access-Control-Allow-Headers" , "Content-Type" )
346+ if c .Request .Method == http .MethodOptions {
347+ c .AbortWithStatus (http .StatusOK )
356348 return
357349 }
358- next .ServeHTTP (w , r )
359- })
360- }
361-
362- // neuteredFileSystem prevents directory listing (optional hardening for static assets)
363- type neuteredFileSystem struct { fs http.FileSystem }
364-
365- func (nfs neuteredFileSystem ) Open (path string ) (http.File , error ) {
366- f , err := nfs .fs .Open (path )
367- if err != nil {
368- return nil , err
369- }
370- if stat , err := f .Stat (); err == nil && stat .IsDir () {
371- // If directory, look for index.html else block listing
372- index := filepath .Join (path , "index.html" )
373- if _ , err := nfs .fs .Open (index ); err != nil {
374- return nil , fs .ErrPermission
375- }
350+ c .Next ()
376351 }
377- return f , nil
378352}
0 commit comments