repos / pico

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

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