oficina / sheets Public

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

Plain git — no account needed to clone.

Download

Download this file

1package sheets
2
3import (
4 "encoding/json"
5 "net/http"
6 "strings"
7
8 nativeauth "amadan.net/rastrillo/native/server"
9 "amadan.net/rastrillo/rastrillo/sessions"
10
11 // The one import left on rastrillo's pre-move path, on purpose:
12 // rastrillo/native's server (2a5d07b, and its main) still takes a
13 // github.com/carlosframework/rastrillo *sessions.Sessions. Both
14 // sessions packages read and write the same frozen sessions table, so
15 // a session native sign-in mints is an ordinary session to the app. Move this to
16 // amadan.net/rastrillo/rastrillo/sessions when native/server moves.
17 presessions "github.com/carlosframework/rastrillo/sessions"
18 "github.com/go-chi/chi/v5"
19)
20
21// ConfigureNative reuses the cookie-backed auth and admission path.
22func (a *App) ConfigureNative() error {
23 d, err := a.db.DB()
24 if err != nil {
25 return err
26 }
27 s, err := presessions.New(presessions.Config{DB: d, Origin: a.origin, SigninPath: "/signin"})
28 if err != nil {
29 return err
30 }
31 a.native = nativeauth.New(s, a.origin, "Sheets", a.requireSession)
32 return nil
33}
34func wantsNative(r *http.Request) bool {
35 return strings.Contains(r.Header.Get("Accept"), "application/json")
36}
37func nativeJSON(w http.ResponseWriter, status int, value any) {
38 w.Header().Set("Content-Type", "application/json")
39 w.Header().Set("Cache-Control", "no-store")
40 w.Header().Add("Vary", "Accept")
41 w.WriteHeader(status)
42 _ = json.NewEncoder(w).Encode(value)
43}
44
45// Only explicit page payloads are serializable. No auth or shell data is exposed.
46func nativePage(w http.ResponseWriter, r *http.Request, page string, status int, data any) bool {
47 if !wantsNative(r) {
48 return false
49 }
50 switch page {
51 case "sheet", "history", "comments", "share", "table", "import":
52 nativeJSON(w, status, data)
53 return true
54 }
55 return false
56}
57func nativeNotice(w http.ResponseWriter, r *http.Request, notice string) bool {
58 if !wantsNative(r) {
59 return false
60 }
61 message := noticeText(notice)
62 if message == "" {
63 message = historyNotice(notice)
64 }
65 if message == "" {
66 message = commentNotice(notice)
67 }
68 if message == "" {
69 message = "Saved."
70 }
71 status := http.StatusOK
72 switch notice {
73 case "bad-address", "bad-role", "last-owner", "bad-link-role", "empty", "no-cell", "gone":
74 status = http.StatusUnprocessableEntity
75 }
76 nativeJSON(w, status, map[string]string{"notice": notice, "message": message})
77 return true
78}
79func (a *App) handleNativeJob(w http.ResponseWriter, r *http.Request) {
80 session, ok := sessions.Current(r)
81 if !ok {
82 a.notFound(w, r)
83 return
84 }
85 job, ok := a.jobs.Get(chi.URLParam(r, "id"), session.Subject)
86 if !ok {
87 a.notFound(w, r)
88 return
89 }
90 nativeJSON(w, 200, map[string]any{"status": job.Status, "progress": job.Progress, "error": job.Err, "location": job.Location})
91}
92func (a *App) streamAuthorized(r *http.Request, sheetID uint64) bool {
93 address, ok := a.identity(r)
94 if !ok || !a.Admits(address) {
95 return false
96 }
97 viewer, ok := a.actor(r)
98 return ok && a.mayRead(sheetID, viewer)
99}
100
101// Local and Home sign-in finish on /sheets; the shared approval continuation
102// recognises /. Translate only this middleware probe, never the app route.
103func (a *App) nativeResume(next http.Handler) http.Handler {
104 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
105 probe := r
106 if r.Method == http.MethodGet && r.URL.Path == "/sheets" {
107 probe = r.Clone(r.Context())
108 u := *r.URL
109 u.Path = "/"
110 probe.URL = &u
111 }
112 a.native.Resume(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { next.ServeHTTP(w, r) })).ServeHTTP(w, probe)
113 })
114}
115