oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1package docs
2
3import (
4 "database/sql"
5 "encoding/json"
6 "log/slog"
7 "net/http"
8 "strings"
9 "time"
10
11 "amadan.net/rastrillo/rastrillo"
12
13 // The one import left on rastrillo's pre-move path, on purpose:
14 // rastrillo/native's server (2a5d07b, and its main) still takes a
15 // github.com/carlosframework/rastrillo *sessions.Sessions. Both
16 // sessions packages read and write the same sessions table, so a
17 // session native sign-in mints is an ordinary session to the app
18 // (native_exchange_test.go). Move this to
19 // amadan.net/rastrillo/rastrillo/sessions when native/server moves;
20 // TestNoPreMoveRastrillo forbids every other import of the old path.
21 presessions "github.com/carlosframework/rastrillo/sessions"
22)
23
24// newNativeSessions is the sessions handle rastrillo/native's server
25// approves and mints with, over the app's own database.
26func newNativeSessions(db *sql.DB, origin string, logger *slog.Logger) (*presessions.Sessions, error) {
27 return presessions.New(presessions.Config{DB: db, Origin: origin, SigninPath: "/signin", Logger: logger})
28}
29
30func wantsNative(r *http.Request) bool {
31 return strings.Contains(r.Header.Get("Accept"), "application/json")
32}
33
34func nativeJSON(w http.ResponseWriter, r *http.Request, status int, value any) {
35 body, err := json.Marshal(value)
36 if err != nil {
37 http.Error(w, rastrillo.T(r, "docs.error.generic"), http.StatusInternalServerError)
38 return
39 }
40 w.Header().Set("Content-Type", "application/json")
41 w.Header().Set("Cache-Control", "no-store")
42 w.Header().Add("Vary", "Accept")
43 _ = writeGuardedBody(w, r, status, body)
44}
45
46func (h *handlers) nativeList(w http.ResponseWriter, r *http.Request) {
47 actor, err := h.actor(r)
48 if err != nil {
49 http.Error(w, rastrillo.T(r, "docs.error.generic"), http.StatusUnauthorized)
50 return
51 }
52 var documents []*Document
53 if query := strings.TrimSpace(r.URL.Query().Get("q")); query != "" {
54 documents, err = h.store.SearchFor(r.Context(), actor, query)
55 } else {
56 documents, err = h.store.ListFor(r.Context(), actor)
57 }
58 if err != nil {
59 http.Error(w, rastrillo.T(r, "docs.error.generic"), h.internalFailureStatus())
60 return
61 }
62 type item struct {
63 Sqid string `json:"sqid"`
64 Title string `json:"title"`
65 UpdatedAt time.Time `json:"updated_at"`
66 Shared bool `json:"shared"`
67 Thumbnail thumbView `json:"thumbnail"`
68 }
69 items := make([]item, 0, len(documents))
70 thumbnail := h.thumbnailer(r.Context())
71 for _, doc := range documents {
72 items = append(items, item{doc.Sqid, doc.Title, doc.UpdatedAt, doc.OwnerID != actor.PersonID, thumbnail(doc)})
73 }
74 nativeJSON(w, r, 200, map[string]any{"documents": items, "can_create": h.member(r)})
75}
76
77// The authentication middleware is rerun against the cookie before every
78// stream write. A context captured when the stream opened is not current proof.
79func (h *handlers) nativeStreamAllowed(r *http.Request, sqid string) bool {
80 if h.requireCurrent == nil {
81 return true
82 } // isolated handler fixtures
83 allowed := false
84 h.requireCurrent(http.HandlerFunc(func(_ http.ResponseWriter, fresh *http.Request) {
85 actor, err := h.actor(fresh)
86 if err != nil {
87 return
88 }
89 _, err = h.store.GetFor(fresh.Context(), actor, sqid)
90 allowed = err == nil
91 })).ServeHTTP(nativeDiscard{}, r)
92 return allowed
93}
94
95type nativeDiscard struct{}
96
97func (nativeDiscard) Header() http.Header { return make(http.Header) }
98func (nativeDiscard) Write(p []byte) (int, error) { return len(p), nil }
99func (nativeDiscard) WriteHeader(int) {}
100