repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

pico / shared
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}