Eric Bower
·
08 Apr 24
api.go
1package shared
2
3import (
4 "encoding/json"
5 "fmt"
6 "html/template"
7 "net/http"
8 "os"
9 "strings"
10
11 "github.com/charmbracelet/ssh"
12 "github.com/picosh/pico/db"
13)
14
15func CorsHeaders(headers http.Header) {
16 headers.Add("Access-Control-Allow-Origin", "*")
17 headers.Add("Vary", "Origin")
18 headers.Add("Vary", "Access-Control-Request-Method")
19 headers.Add("Vary", "Access-Control-Request-Headers")
20 headers.Add("Access-Control-Allow-Headers", "Content-Type, Accept")
21 headers.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE")
22}
23
24func UnauthorizedHandler(w http.ResponseWriter, r *http.Request) {
25 http.Error(w, "You do not have access to this site", http.StatusUnauthorized)
26}
27
28type errPayload struct {
29 Message string `json:"message"`
30}
31
32func JSONError(w http.ResponseWriter, msg string, code int) {
33 w.Header().Set("Content-Type", "application/json")
34 w.WriteHeader(code)
35 _ = json.NewEncoder(w).Encode(errPayload{Message: msg})
36}
37
38type UserApi struct {
39 *db.User
40 Fingerprint string `json:"fingerprint"`
41}
42
43func NewUserApi(user *db.User, pubkey ssh.PublicKey) *UserApi {
44 return &UserApi{
45 User: user,
46 Fingerprint: KeyForSha256(pubkey),
47 }
48}
49
50func CheckHandler(w http.ResponseWriter, r *http.Request) {
51 dbpool := GetDB(r)
52 cfg := GetCfg(r)
53
54 if cfg.IsCustomdomains() {
55 hostDomain := r.URL.Query().Get("domain")
56 appDomain := strings.Split(cfg.Domain, ":")[0]
57
58 if !strings.Contains(hostDomain, appDomain) {
59 subdomain := GetCustomDomain(hostDomain, cfg.Space)
60 if subdomain != "" {
61 u, err := dbpool.FindUserForName(subdomain)
62 if u != nil && err == nil {
63 w.WriteHeader(http.StatusOK)
64 return
65 }
66 }
67 }
68 }
69
70 w.WriteHeader(http.StatusNotFound)
71}
72
73func GetUsernameFromRequest(r *http.Request) string {
74 subdomain := GetSubdomain(r)
75 cfg := GetCfg(r)
76
77 if !cfg.IsSubdomains() || subdomain == "" {
78 return GetField(r, 0)
79 }
80 return subdomain
81}
82
83func ServeFile(file string, contentType string) http.HandlerFunc {
84 return func(w http.ResponseWriter, r *http.Request) {
85 logger := GetLogger(r)
86 cfg := GetCfg(r)
87
88 contents, err := os.ReadFile(cfg.StaticPath(fmt.Sprintf("public/%s", file)))
89 if err != nil {
90 logger.Error(err.Error())
91 http.Error(w, "file not found", 404)
92 }
93
94 w.Header().Add("Content-Type", contentType)
95
96 _, err = w.Write(contents)
97 if err != nil {
98 logger.Error(err.Error())
99 }
100 }
101}
102
103func minus(a, b int) int {
104 return a - b
105}
106
107func intRange(start, end int) []int {
108 n := end - start + 1
109 result := make([]int, n)
110 for i := 0; i < n; i++ {
111 result[i] = start + i
112 }
113 return result
114}
115
116var FuncMap = template.FuncMap{
117 "minus": minus,
118 "intRange": intRange,
119}
120
121func RenderTemplate(cfg *ConfigSite, templates []string) (*template.Template, error) {
122 files := make([]string, len(templates))
123 copy(files, templates)
124 files = append(
125 files,
126 cfg.StaticPath("html/footer.partial.tmpl"),
127 cfg.StaticPath("html/marketing-footer.partial.tmpl"),
128 cfg.StaticPath("html/base.layout.tmpl"),
129 )
130
131 ts, err := template.New("base").Funcs(FuncMap).ParseFiles(files...)
132 if err != nil {
133 return nil, err
134 }
135 return ts, nil
136}
137
138func CreatePageHandler(fname string) http.HandlerFunc {
139 return func(w http.ResponseWriter, r *http.Request) {
140 logger := GetLogger(r)
141 cfg := GetCfg(r)
142 ts, err := RenderTemplate(cfg, []string{cfg.StaticPath(fname)})
143
144 if err != nil {
145 logger.Error(err.Error())
146 http.Error(w, err.Error(), http.StatusInternalServerError)
147 return
148 }
149
150 data := PageData{
151 Site: *cfg.GetSiteData(),
152 }
153 err = ts.Execute(w, data)
154 if err != nil {
155 logger.Error(err.Error())
156 http.Error(w, err.Error(), http.StatusInternalServerError)
157 }
158 }
159}