@@ -7,50 +7,62 @@ import (
77 "html/template"
88 "log"
99 "net/http"
10+ "os"
1011 "path/filepath"
1112
12- "github.com/systemshift/memex/internal/memex/storage"
13+ "github.com/go-chi/chi/v5"
14+ "github.com/go-chi/chi/v5/middleware"
15+ "github.com/systemshift/memex/internal/memex/core"
16+ "github.com/systemshift/memex/internal/memex/repository"
1317)
1418
19+ // Server handles HTTP requests and manages the repository
1520type Server struct {
16- memex * storage. MXStore
21+ repo core. Repository
1722 template * template.Template
1823}
1924
20- type GraphData struct {
21- Nodes []NodeData `json:"nodes"`
22- Edges []EdgeData `json:"edges"`
25+ // GraphResponse represents the graph visualization data
26+ type GraphResponse struct {
27+ Nodes []NodeResponse `json:"nodes"`
28+ Links []LinkResponse `json:"links"`
2329}
2430
25- type NodeData struct {
26- ID string `json:"id"`
27- Type string `json:"type"`
28- Meta map [string ]any `json:"meta"`
29- Created string `json:"created"`
31+ // NodeResponse represents a node in the graph visualization
32+ type NodeResponse struct {
33+ ID string `json:"id"`
34+ Type string `json:"type"`
35+ Meta map [string ]interface {} `json:"meta"`
36+ Created string `json:"created"`
37+ Modified string `json:"modified"`
3038}
3139
32- type EdgeData struct {
33- Source string `json:"source"`
34- Target string `json:"target"`
35- Type string `json:"type"`
36- Meta map [string ]any `json:"meta"`
40+ // LinkResponse represents a link in the graph visualization
41+ type LinkResponse struct {
42+ Source string `json:"source"`
43+ Target string `json:"target"`
44+ Type string `json:"type"`
45+ Meta map [string ]interface {} `json:"meta"`
46+ Created string `json:"created"`
47+ Modified string `json:"modified"`
3748}
3849
3950func main () {
51+ // Parse command line flags
4052 addr := flag .String ("addr" , ":3000" , "HTTP service address" )
41- repo := flag .String ("repo" , "" , "Repository path" )
53+ repoPath := flag .String ("repo" , "" , "Repository path" )
4254 flag .Parse ()
4355
44- if * repo == "" {
56+ if * repoPath == "" {
4557 log .Fatal ("Repository path required" )
4658 }
4759
48- // Open repository
49- store , err := storage . OpenMX ( * repo )
60+ // Initialize repository
61+ repo , err := repository . Open ( * repoPath )
5062 if err != nil {
5163 log .Fatalf ("Error opening repository: %v" , err )
5264 }
53- defer store .Close ()
65+ defer repo .Close ()
5466
5567 // Parse templates
5668 tmpl , err := template .ParseGlob ("cmd/memexd/templates/*.html" )
@@ -60,121 +72,173 @@ func main() {
6072
6173 // Create server
6274 server := & Server {
63- memex : store ,
75+ repo : repo ,
6476 template : tmpl ,
6577 }
6678
67- // Setup routes
68- http .HandleFunc ("/" , server .handleIndex )
69- http .HandleFunc ("/api/graph" , server .handleGraph )
70- http .HandleFunc ("/api/content/" , server .handleContent )
71- http .HandleFunc ("/node/" , server .handleNode )
79+ // Create router
80+ r := chi .NewRouter ()
7281
73- // Serve static files
74- fs := http .FileServer (http .Dir ("cmd/memexd/static" ))
75- http .Handle ("/static/" , http .StripPrefix ("/static/" , fs ))
82+ // Middleware
83+ r .Use (middleware .Logger )
84+ r .Use (middleware .Recoverer )
85+ r .Use (middleware .Compress (5 ))
86+
87+ // Routes
88+ r .Get ("/" , server .handleIndex )
89+ r .Route ("/api" , func (r chi.Router ) {
90+ r .Get ("/graph" , server .handleGraph )
91+ r .Get ("/nodes/{id}" , server .handleGetNode )
92+ r .Get ("/nodes/{id}/content" , server .handleGetContent )
93+ })
94+
95+ // Static files
96+ workDir , _ := os .Getwd ()
97+ filesDir := http .Dir (filepath .Join (workDir , "cmd/memexd/static" ))
98+ r .Handle ("/static/*" , http .StripPrefix ("/static/" , http .FileServer (filesDir )))
7699
77100 // Start server
78101 log .Printf ("Starting server on %s" , * addr )
79- log .Fatal (http .ListenAndServe (* addr , nil ))
102+ log .Fatal (http .ListenAndServe (* addr , r ))
80103}
81104
105+ // handleIndex serves the main page
82106func (s * Server ) handleIndex (w http.ResponseWriter , r * http.Request ) {
83107 if err := s .template .ExecuteTemplate (w , "index.html" , nil ); err != nil {
84108 http .Error (w , err .Error (), http .StatusInternalServerError )
85109 return
86110 }
87111}
88112
113+ // handleGraph returns the graph data for visualization
89114func (s * Server ) handleGraph (w http.ResponseWriter , r * http.Request ) {
90115 // Get all nodes
91- var graph GraphData
92- for _ , entry := range s .memex .Nodes () {
93- node , err := s .memex .GetNode (fmt .Sprintf ("%x" , entry .ID [:]))
116+ nodeIDs , err := s .repo .ListNodes ()
117+ if err != nil {
118+ http .Error (w , fmt .Sprintf ("Error listing nodes: %v" , err ), http .StatusInternalServerError )
119+ return
120+ }
121+
122+ response := GraphResponse {
123+ Nodes : make ([]NodeResponse , 0 , len (nodeIDs )),
124+ Links : make ([]LinkResponse , 0 ),
125+ }
126+
127+ // Process each node
128+ for _ , id := range nodeIDs {
129+ node , err := s .repo .GetNode (id )
94130 if err != nil {
131+ log .Printf ("Error getting node %s: %v" , id , err )
95132 continue
96133 }
97134
98- // Add node
99- graph .Nodes = append (graph .Nodes , NodeData {
100- ID : node .ID ,
101- Type : node .Type ,
102- Meta : node .Meta ,
103- Created : node .Created .Format ("2006-01-02 15:04:05" ),
135+ // Add node to response
136+ response .Nodes = append (response .Nodes , NodeResponse {
137+ ID : node .ID ,
138+ Type : node .Type ,
139+ Meta : node .Meta ,
140+ Created : node .Created .Format ("2006-01-02 15:04:05" ),
141+ Modified : node .Modified .Format ("2006-01-02 15:04:05" ),
104142 })
105143
106- // Get links
107- links , err := s .memex .GetLinks (node .ID )
144+ // Get and process links
145+ links , err := s .repo .GetLinks (node .ID )
108146 if err != nil {
147+ log .Printf ("Error getting links for node %s: %v" , id , err )
109148 continue
110149 }
111150
112- // Add edges
113151 for _ , link := range links {
114- graph .Edges = append (graph .Edges , EdgeData {
115- Source : node .ID ,
116- Target : link .Target ,
117- Type : link .Type ,
118- Meta : link .Meta ,
152+ response .Links = append (response .Links , LinkResponse {
153+ Source : link .Source ,
154+ Target : link .Target ,
155+ Type : link .Type ,
156+ Meta : link .Meta ,
157+ Created : link .Created .Format ("2006-01-02 15:04:05" ),
158+ Modified : link .Modified .Format ("2006-01-02 15:04:05" ),
119159 })
120160 }
121161 }
122162
123- // Write response
163+ // Send response
124164 w .Header ().Set ("Content-Type" , "application/json" )
125- if err := json .NewEncoder (w ).Encode (graph ); err != nil {
126- http .Error (w , err . Error ( ), http .StatusInternalServerError )
165+ if err := json .NewEncoder (w ).Encode (response ); err != nil {
166+ http .Error (w , fmt . Sprintf ( "Error encoding response: %v" , err ), http .StatusInternalServerError )
127167 return
128168 }
129169}
130170
131- func (s * Server ) handleContent (w http.ResponseWriter , r * http.Request ) {
132- // Get content hash from URL
133- hash := filepath .Base (r .URL .Path )
171+ // handleGetNode returns information about a specific node
172+ func (s * Server ) handleGetNode (w http.ResponseWriter , r * http.Request ) {
173+ id := chi .URLParam (r , "id" )
174+ if id == "" {
175+ http .Error (w , "Node ID required" , http .StatusBadRequest )
176+ return
177+ }
134178
135- // Reconstruct content from chunks
136- content , err := s .memex .ReconstructContent (hash )
179+ node , err := s .repo .GetNode (id )
137180 if err != nil {
138- http .Error (w , "Content not found" , http .StatusNotFound )
181+ http .Error (w , fmt . Sprintf ( "Error getting node: %v" , err ) , http .StatusNotFound )
139182 return
140183 }
141184
142- // Write response
143- w .Header ().Set ("Content-Type" , "text/plain" )
144- w .Write (content )
185+ // Create response with formatted timestamps
186+ response := struct {
187+ ID string `json:"id"`
188+ Type string `json:"type"`
189+ Content string `json:"content"`
190+ Meta map [string ]interface {} `json:"meta"`
191+ Created string `json:"created"`
192+ Modified string `json:"modified"`
193+ }{
194+ ID : node .ID ,
195+ Type : node .Type ,
196+ Content : string (node .Content ),
197+ Meta : node .Meta ,
198+ Created : node .Created .Format ("2006-01-02 15:04:05" ),
199+ Modified : node .Modified .Format ("2006-01-02 15:04:05" ),
200+ }
201+
202+ w .Header ().Set ("Content-Type" , "application/json" )
203+ if err := json .NewEncoder (w ).Encode (response ); err != nil {
204+ http .Error (w , fmt .Sprintf ("Error encoding response: %v" , err ), http .StatusInternalServerError )
205+ return
206+ }
145207}
146208
147- func (s * Server ) handleNode (w http.ResponseWriter , r * http.Request ) {
148- // Get node ID from URL
149- id := filepath .Base (r .URL .Path )
209+ // handleGetContent returns the content of a node
210+ func (s * Server ) handleGetContent (w http.ResponseWriter , r * http.Request ) {
211+ id := chi .URLParam (r , "id" )
212+ if id == "" {
213+ http .Error (w , "Node ID required" , http .StatusBadRequest )
214+ return
215+ }
150216
151- // Get node
152- node , err := s .memex .GetNode (id )
217+ // Get node first to check type and get metadata
218+ node , err := s .repo .GetNode (id )
153219 if err != nil {
154- http .Error (w , "Node not found" , http .StatusNotFound )
220+ http .Error (w , fmt . Sprintf ( "Error getting node: %v" , err ) , http .StatusNotFound )
155221 return
156222 }
157223
158- // If file, serve content
159- if node .Type == "file" {
160- if contentHash , ok := node .Meta ["content" ].(string ); ok {
161- content , err := s .memex .ReconstructContent (contentHash )
162- if err != nil {
163- http .Error (w , "Content not found" , http .StatusNotFound )
164- return
165- }
224+ // Get content
225+ content , err := s .repo .GetContent (id )
226+ if err != nil {
227+ http .Error (w , fmt .Sprintf ("Error getting content: %v" , err ), http .StatusNotFound )
228+ return
229+ }
166230
167- // Set filename for download
168- if filename , ok := node .Meta ["filename" ].(string ); ok {
169- w .Header ().Set ("Content-Disposition" , fmt .Sprintf ("attachment; filename=%q" , filename ))
170- }
231+ // Set content type if available in metadata
232+ if contentType , ok := node .Meta ["content-type" ].(string ); ok {
233+ w .Header ().Set ("Content-Type" , contentType )
234+ } else {
235+ w .Header ().Set ("Content-Type" , "application/octet-stream" )
236+ }
171237
172- w . Write ( content )
173- return
174- }
238+ // Set filename for download if available
239+ if filename , ok := node . Meta [ "filename" ].( string ); ok {
240+ w . Header (). Set ( "Content-Disposition" , fmt . Sprintf ( "attachment; filename=%q" , filename ))
175241 }
176242
177- // Otherwise show node info
178- w .Header ().Set ("Content-Type" , "application/json" )
179- json .NewEncoder (w ).Encode (node )
243+ w .Write (content )
180244}
0 commit comments