1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
package server
import (
"bytes"
"context"
"io"
"log/slog"
"net/http"
"slices"
"strings"
"sync"
"time"
"github.com/jpappel/atlas/pkg/data"
"github.com/jpappel/atlas/pkg/index"
"github.com/jpappel/atlas/pkg/query"
)
type Server interface {
ListenAndServe() error
Shutdown(context.Context) error
}
func info(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`<h1>Atlas Server</h1>
<p>This is the experimental atlas server!
Try POSTing a query to <pre>/search</pre></p>
<hr>
<p>You can sort the results using the query param <pre>sortBy</pre>
<ul>
<li>path</li>
<li>title</li>
<li>date</li>
<li>headings</li>
<li>filetime</li>
<li>meta</li>
</ul>
You can change the order using <pre>sortOrder</pre> with <pre>asc</pre> or <pre>desc</pre>
</p>
<form action="/search" method="post">
<fieldset><legend>Submit a Query</legend>
<label for="query">Query:</label>
<input type="text" name="query" required />
<label for="sortBy">Sort By:</label>
<select name="sortBy">
<option value="path">Path</option>
<option value="title">Title</option>
<option value="date">Date</option>
<option value="headings">Headings</option>
<option value="filetime">Filetime</option>
<option value="meta">Metadata</option>
</select>
<label for="sortOrder">Sort Order:</label>
<select name="sortOrder">
<option value="">Ascending</option>
<option value="desc">Descending</option>
</select>
<input type="submit" />
</fieldset>
</form>
`))
}
func NewMux(db *data.Query) *http.ServeMux {
mux := http.NewServeMux()
outputBufPool := &sync.Pool{}
outputBufPool.New = func() any {
return &bytes.Buffer{}
}
mux.HandleFunc("/", info)
mux.HandleFunc("POST /search", func(w http.ResponseWriter, r *http.Request) {
b := &strings.Builder{}
r.ParseForm()
if v := r.Form.Get("query"); v != "" {
slog.Debug("parsing form, got value", slog.String("value", v))
b.WriteString(v)
} else if _, err := io.Copy(b, r.Body); err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Error processing request"))
slog.Error("Error reading request body", slog.String("err", err.Error()))
return
}
artifact, err := query.Compile(b.String(), 0, 1)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
slog.Error("Error compiling query", slog.String("err", err.Error()))
return
}
pathDocs, err := db.Execute(r.Context(), artifact)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Error executing query"))
slog.Error("Error executing query", slog.String("err", err.Error()))
return
}
docs := make([]*index.Document, 0, len(pathDocs))
var maxFileTime time.Time
for _, doc := range pathDocs {
docs = append(docs, doc)
if doc.FileTime.After(maxFileTime) {
maxFileTime = doc.FileTime
}
}
queryParams := r.URL.Query()
if queryParams.Has("sortBy") {
slog.Debug("Parsing sorting info from query params")
sortBy := queryParams.Get("sortBy")
sortOrder := queryParams.Get("sortOrder")
docCmp, ok := index.NewDocCmp(sortBy, sortOrder == "desc" || sortOrder == "descending")
if ok {
slices.SortFunc(docs, docCmp)
}
} else if r.Form.Has("sortBy") {
slog.Debug("Parsing sorting info from form data")
sortBy := r.Form.Get("sortBy")
sortOrder := r.Form.Get("sortOrder")
docCmp, ok := index.NewDocCmp(sortBy, sortOrder == "desc" || sortOrder == "descending")
if ok {
slices.SortFunc(docs, docCmp)
}
}
if !maxFileTime.IsZero() {
w.Header().Add("Last-Modified", maxFileTime.UTC().Format(http.TimeFormat))
}
buf, ok := outputBufPool.Get().(*bytes.Buffer)
if !ok {
panic("Expected *bytes.Buffer in pool")
}
_, err = query.JsonOutput{}.OutputTo(buf, docs)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Error while writing output"))
slog.Error("Error writing json output", slog.String("err", err.Error()))
}
http.ServeContent(w, r, "result.json", maxFileTime, bytes.NewReader(buf.Bytes()))
})
return mux
}
|