mirror of
https://github.com/ThisTine/Snip.git
synced 2026-08-19 07:28:46 +07:00
feat: first commit
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
package frontend_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/snip/backend/internal/httpx/frontend"
|
||||
)
|
||||
|
||||
func tempDist(t *testing.T) string {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dir, "index.html"), []byte("<!doctype html><div id=root>SPA</div>"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(dir, "assets"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "assets", "app.js"), []byte("console.log('hi')"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
func TestServesSPARoutes(t *testing.T) {
|
||||
h := frontend.New(tempDist(t)).Handler()
|
||||
|
||||
for _, route := range []string{"/", "/login", "/dashboard"} {
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, httptest.NewRequest(http.MethodGet, route, nil))
|
||||
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), "SPA") {
|
||||
t.Fatalf("%s should render index, got %d", route, w.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServesAssets(t *testing.T) {
|
||||
h := frontend.New(tempDist(t)).Handler()
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/assets/app.js", nil))
|
||||
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), "console.log") {
|
||||
t.Fatalf("asset should be served, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnknownPathIs404(t *testing.T) {
|
||||
h := frontend.New(tempDist(t)).Handler()
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/some/short-code", nil))
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Fatalf("unknown path: want 404 (redirect cmd owns it), got %d", w.Code)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Package frontend serves the built SPA. It owns exactly the app routes
|
||||
// (/, /login, /dashboard) plus static assets; everything else is 404 here and
|
||||
// handled by the redirect command in production.
|
||||
package frontend
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
dist string
|
||||
fs http.Handler
|
||||
spa map[string]bool
|
||||
indexAbs string
|
||||
}
|
||||
|
||||
func New(dist string) *Server {
|
||||
return &Server{
|
||||
dist: dist,
|
||||
fs: http.FileServer(http.Dir(dist)),
|
||||
spa: map[string]bool{"/": true, "/login": true, "/dashboard": true},
|
||||
indexAbs: filepath.Join(dist, "index.html"),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Handler() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"status":"ok"}`))
|
||||
})
|
||||
mux.HandleFunc("/", s.serve)
|
||||
return mux
|
||||
}
|
||||
|
||||
func (s *Server) serve(w http.ResponseWriter, r *http.Request) {
|
||||
clean := path.Clean(r.URL.Path)
|
||||
|
||||
// App routes always render the SPA shell.
|
||||
if s.spa[clean] {
|
||||
http.ServeFile(w, r, s.indexAbs)
|
||||
return
|
||||
}
|
||||
|
||||
// Real static file (assets, favicon, …)?
|
||||
if clean != "/" {
|
||||
full := filepath.Join(s.dist, filepath.FromSlash(clean))
|
||||
if st, err := os.Stat(full); err == nil && !st.IsDir() {
|
||||
s.fs.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown path: not this command's concern.
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
Reference in New Issue
Block a user