1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package server
16
17 import (
18 "bytes"
19 "context"
20 "encoding/json"
21 "errors"
22 "fmt"
23 "io"
24 "net/http"
25 "strings"
26 "sync"
27
28 "github.com/go-chi/chi/v5"
29 "github.com/monoculum/formam/v3"
30 "gitlab.com/flimzy/httpe"
31
32 "github.com/go-kivik/kivik/v4"
33 "github.com/go-kivik/kivik/v4/x/server/auth"
34 "github.com/go-kivik/kivik/v4/x/server/config"
35 )
36
37 func init() {
38 chi.RegisterMethod("COPY")
39 }
40
41
42 type Server struct {
43 mux *chi.Mux
44 client *kivik.Client
45 formDecoder *formam.Decoder
46 userStores userStores
47 authFuncs []auth.AuthenticateFunc
48 config config.Config
49
50
51
52 sequentialMU sync.Mutex
53 sequentialUUIDPrefix string
54 sequentialUUIDMonotonicID int32
55 }
56
57 func e(h httpe.HandlerWithError) httpe.HandlerFunc {
58 return httpe.ToHandler(h).ServeHTTP
59 }
60
61
62 func New(client *kivik.Client, options ...Option) *Server {
63 s := &Server{
64 mux: chi.NewMux(),
65 client: client,
66 formDecoder: formam.NewDecoder(&formam.DecoderOptions{
67 TagName: "form",
68 IgnoreUnknownKeys: true,
69 }),
70 config: config.Default(),
71 }
72 for _, option := range options {
73 option.apply(s)
74 }
75 s.routes(s.mux)
76 return s
77 }
78
79 func (s *Server) routes(mux *chi.Mux) {
80 mux.Use(
81 GetHead,
82 httpe.ToMiddleware(s.handleErrors),
83 )
84
85 auth := mux.With(
86 httpe.ToMiddleware(s.authMiddleware),
87 )
88
89 admin := auth.With(
90 httpe.ToMiddleware(adminRequired),
91 )
92 auth.Get("/", e(s.root()))
93 admin.Get("/_active_tasks", e(s.activeTasks()))
94 admin.Get("/_all_dbs", e(s.allDBs()))
95 auth.Get("/_dbs_info", e(s.allDBsStats()))
96 auth.Post("/_dbs_info", e(s.dbsStats()))
97 admin.Get("/_cluster_setup", e(s.clusterStatus()))
98 admin.Post("/_cluster_setup", e(s.clusterSetup()))
99 admin.Get("/_db_updates", e(s.dbUpdates()))
100 auth.Get("/_membership", e(s.notImplemented()))
101 auth.Post("/_replicate", e(s.notImplemented()))
102 auth.Get("/_scheduler/jobs", e(s.notImplemented()))
103 auth.Get("/_scheduler/docs", e(s.notImplemented()))
104 auth.Get("/_scheduler/docs/{replicator_db}", e(s.notImplemented()))
105 auth.Get("/_scheduler/docs/{replicator_db}/{doc_id}", e(s.notImplemented()))
106 auth.Get("/_node/{node-name}", e(s.notImplemented()))
107 auth.Get("/_node/{node-name}/_stats", e(s.notImplemented()))
108 auth.Get("/_node/{node-name}/_prometheus", e(s.notImplemented()))
109 auth.Get("/_node/{node-name}/_system", e(s.notImplemented()))
110 admin.Post("/_node/{node-name}/_restart", e(s.notImplemented()))
111 auth.Get("/_node/{node-name}/_versions", e(s.notImplemented()))
112 auth.Post("/_search_analyze", e(s.notImplemented()))
113 auth.Get("/_utils", e(s.notImplemented()))
114 auth.Get("/_utils/", e(s.notImplemented()))
115 mux.Get("/_up", e(s.up()))
116 mux.Get("/_uuids", e(s.uuids()))
117 mux.Get("/favicon.ico", e(s.notImplemented()))
118 auth.Get("/_reshard", e(s.notImplemented()))
119 auth.Get("/_reshard/state", e(s.notImplemented()))
120 auth.Put("/_reshard/state", e(s.notImplemented()))
121 auth.Get("/_reshard/jobs", e(s.notImplemented()))
122 auth.Get("/_reshard/jobs/{jobid}", e(s.notImplemented()))
123 auth.Post("/_reshard/jobs", e(s.notImplemented()))
124 auth.Delete("/_reshard/jobs/{jobid}", e(s.notImplemented()))
125 auth.Get("/_reshard/jobs/{jobid}/state", e(s.notImplemented()))
126 auth.Put("/_reshard/jobs/{jobid}/state", e(s.notImplemented()))
127
128
129 admin.Get("/_node/{node-name}/_config", e(s.allConfig()))
130 admin.Get("/_node/{node-name}/_config/{section}", e(s.configSection()))
131 admin.Get("/_node/{node-name}/_config/{section}/{key}", e(s.configKey()))
132 admin.Put("/_node/{node-name}/_config/{section}/{key}", e(s.setConfigKey()))
133 admin.Delete("/_node/{node-name}/_config/{section}/{key}", e(s.deleteConfigKey()))
134 admin.Post("/_node/{node-name}/_config/_reload", e(s.reloadConfig()))
135
136
137 auth.Route("/{db}", func(db chi.Router) {
138 admin := db.With(
139 httpe.ToMiddleware(adminRequired),
140 )
141 member := db.With(
142 httpe.ToMiddleware(s.dbMembershipRequired),
143 )
144 dbAdmin := member.With(
145 httpe.ToMiddleware(s.dbAdminRequired),
146 )
147
148 member.Head("/", e(s.dbExists()))
149 member.Get("/", e(s.db()))
150 admin.Put("/", e(s.createDB()))
151 admin.Delete("/", e(s.deleteDB()))
152 member.Get("/_all_docs", e(s.query()))
153 member.Post("/_all_docs/queries", e(s.query()))
154 member.Post("/_all_docs", e(s.query()))
155 member.Get("/_design_docs", e(s.query()))
156 member.Post("/_design_docs", e(s.query()))
157 member.Post("/_design_docs/queries", e(s.query()))
158 member.Get("/_local_docs", e(s.query()))
159 member.Post("/_local_docs", e(s.query()))
160 member.Post("/_local_docs/queries", e(s.query()))
161 member.Post("/_bulk_get", e(s.notImplemented()))
162 member.Post("/_bulk_docs", e(s.notImplemented()))
163 member.Post("/_find", e(s.notImplemented()))
164 member.Post("/_index", e(s.notImplemented()))
165 member.Get("/_index", e(s.notImplemented()))
166 member.Delete("/_index/{designdoc}/json/{name}", e(s.notImplemented()))
167 member.Post("/_explain", e(s.notImplemented()))
168 member.Get("/_shards", e(s.notImplemented()))
169 member.Get("/_shards/{docid}", e(s.notImplemented()))
170 member.Get("/_sync_shards", e(s.notImplemented()))
171 member.Get("/_changes", e(s.notImplemented()))
172 member.Post("/_changes", e(s.notImplemented()))
173 admin.Post("/_compact", e(s.notImplemented()))
174 admin.Post("/_compact/{ddoc}", e(s.notImplemented()))
175 member.Post("/_ensure_full_commit", e(s.notImplemented()))
176 member.Post("/_view_cleanup", e(s.notImplemented()))
177 member.Get("/_security", e(s.getSecurity()))
178 dbAdmin.Put("/_security", e(s.putSecurity()))
179 member.Post("/_purge", e(s.notImplemented()))
180 member.Get("/_purged_infos_limit", e(s.notImplemented()))
181 member.Put("/_purged_infos_limit", e(s.notImplemented()))
182 member.Post("/_missing_revs", e(s.notImplemented()))
183 member.Post("/_revs_diff", e(s.notImplemented()))
184 member.Get("/_revs_limit", e(s.notImplemented()))
185 dbAdmin.Put("/_revs_limit", e(s.notImplemented()))
186
187
188 member.Post("/", e(s.postDoc()))
189 member.Get("/{docid}", e(s.doc()))
190 member.Put("/{docid}", e(s.notImplemented()))
191 member.Delete("/{docid}", e(s.notImplemented()))
192 member.Method("COPY", "/{db}/{docid}", httpe.ToHandler(s.notImplemented()))
193 member.Delete("/{docid}", e(s.notImplemented()))
194 member.Get("/{docid}/{attname}", e(s.notImplemented()))
195 member.Get("/{docid}/{attname}", e(s.notImplemented()))
196 member.Delete("/{docid}/{attname}", e(s.notImplemented()))
197
198
199 member.Get("/_design/{ddoc}", e(s.notImplemented()))
200 dbAdmin.Put("/_design/{ddoc}", e(s.notImplemented()))
201 dbAdmin.Delete("/_design/{ddoc}", e(s.notImplemented()))
202 dbAdmin.Method("COPY", "/{db}/_design/{ddoc}", httpe.ToHandler(s.notImplemented()))
203 member.Get("/_design/{ddoc}/{attname}", e(s.notImplemented()))
204 dbAdmin.Put("/_design/{ddoc}/{attname}", e(s.notImplemented()))
205 dbAdmin.Delete("/_design/{ddoc}/{attname}", e(s.notImplemented()))
206 member.Get("/_design/{ddoc}/_view/{view}", e(s.query()))
207 member.Get("/_design/{ddoc}/_info", e(s.notImplemented()))
208 member.Post("/_design/{ddoc}/_view/{view}", e(s.query()))
209 member.Post("/_design/{ddoc}/_view/{view}/queries", e(s.query()))
210 member.Get("/_design/{ddoc}/_search/{index}", e(s.notImplemented()))
211 member.Get("/_design/{ddoc}/_search_info/{index}", e(s.notImplemented()))
212 member.Post("/_design/{ddoc}/_update/{func}", e(s.notImplemented()))
213 member.Post("/_design/{ddoc}/_update/{func}/{docid}", e(s.notImplemented()))
214 member.Get("/_design/{ddoc}/_rewrite/{path}", e(s.notImplemented()))
215 member.Put("/_design/{ddoc}/_rewrite/{path}", e(s.notImplemented()))
216 member.Post("/_design/{ddoc}/_rewrite/{path}", e(s.notImplemented()))
217 member.Delete("/_design/{ddoc}/_rewrite/{path}", e(s.notImplemented()))
218
219 member.Get("/_partition/{partition}", e(s.notImplemented()))
220 member.Get("/_partition/{partition}/_all_docs", e(s.notImplemented()))
221 member.Get("/_partition/{partition}/_design/{ddoc}/_view/{view}", e(s.notImplemented()))
222 member.Post("/_partition/{partition_id}/_find", e(s.notImplemented()))
223 member.Get("/_partition/{partition_id}/_explain", e(s.notImplemented()))
224 })
225 }
226
227 func (s *Server) handleErrors(next httpe.HandlerWithError) httpe.HandlerWithError {
228 return httpe.HandlerWithErrorFunc(func(w http.ResponseWriter, r *http.Request) error {
229 if err := next.ServeHTTPWithError(w, r); err != nil {
230 status := kivik.HTTPStatus(err)
231 ce := &couchError{}
232 if !errors.As(err, &ce) {
233 ce.Err = strings.ReplaceAll(strings.ToLower(http.StatusText(status)), " ", "_")
234 ce.Reason = err.Error()
235 }
236 return serveJSON(w, status, ce)
237 }
238 return nil
239 })
240 }
241
242 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
243 s.mux.ServeHTTP(w, r)
244 }
245
246 func serveJSON(w http.ResponseWriter, status int, payload interface{}) error {
247 body, err := json.Marshal(payload)
248 if err != nil {
249 return err
250 }
251 w.Header().Set("Content-Type", "application/json; charset=utf-8")
252 w.WriteHeader(status)
253 _, err = io.Copy(w, bytes.NewReader(body))
254 return err
255 }
256
257 func (s *Server) notImplemented() httpe.HandlerWithError {
258 return httpe.HandlerWithErrorFunc(func(http.ResponseWriter, *http.Request) error {
259 return errNotImplimented
260 })
261 }
262
263 func options(r *http.Request) kivik.Option {
264 query := r.URL.Query()
265 params := make(map[string]interface{}, len(query))
266 for k := range query {
267 params[k] = query.Get(k)
268 }
269 return kivik.Params(params)
270 }
271
272 func (s *Server) allDBs() httpe.HandlerWithError {
273 return httpe.HandlerWithErrorFunc(func(w http.ResponseWriter, r *http.Request) error {
274 dbs, err := s.client.AllDBs(r.Context(), options(r))
275 if err != nil {
276 fmt.Println(err)
277 return err
278 }
279 return serveJSON(w, http.StatusOK, dbs)
280 })
281 }
282
283 func (s *Server) conf(ctx context.Context, section, key string, target interface{}) error {
284 value, err := s.config.Key(ctx, section, key)
285 if err != nil {
286 return err
287 }
288 return json.Unmarshal([]byte(value), target)
289 }
290
View as plain text