mercury/web/frontend/frontend.go
2023-09-15 16:33:08 +02:00

168 lines
3.9 KiB
Go

package frontend
import (
"embed"
"encoding/json"
"html/template"
"net/http"
"os"
"path/filepath"
"git.sleepycat.moe/sam/mercury/frontend"
"git.sleepycat.moe/sam/mercury/internal/database"
"git.sleepycat.moe/sam/mercury/web/api"
"git.sleepycat.moe/sam/mercury/web/app"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log"
vueglue "github.com/torenware/vite-go"
)
//go:embed app.html
var htmlTemplate string
//go:embed assets
var assets embed.FS
type Frontend struct {
*app.App
glue *vueglue.VueGlue
urlPrefix string
fileServer http.Handler
tmpl *template.Template
}
func New(app *app.App) *Frontend {
fe := &Frontend{
App: app,
}
if app.AppConfig.Core.Dev {
glue, err := vueglue.NewVueGlue(&vueglue.ViteConfig{
Environment: "development",
AssetsPath: "frontend",
EntryPoint: "src/main.tsx",
Platform: "vue",
FS: os.DirFS("frontend"),
})
if err != nil {
log.Err(err).Msg("Creating vite glue")
os.Exit(1)
}
fe.glue = glue
fe.urlPrefix = "/src/*"
} else {
glue, err := vueglue.NewVueGlue(&vueglue.ViteConfig{
Environment: "production",
URLPrefix: "/assets/",
AssetsPath: "dist",
EntryPoint: "src/main.tsx",
Platform: "vue",
FS: frontend.Embed,
})
if err != nil {
log.Err(err).Msg("Creating vite glue")
os.Exit(1)
}
fe.glue = glue
fe.urlPrefix = "/assets/*"
}
fileServer, err := fe.glue.FileServer()
if err != nil {
log.Err(err).Msg("Creating vite file server")
os.Exit(1)
}
fe.fileServer = fileServer
tmpl, err := template.New("app").Parse(htmlTemplate)
if err != nil {
log.Err(err).Msg("Parsing frontend HTML template")
os.Exit(1)
}
fe.tmpl = tmpl
return fe
}
func (app *Frontend) AssetsPath() string { return app.urlPrefix }
func (app *Frontend) ServeAssets(w http.ResponseWriter, r *http.Request) {
app.fileServer.ServeHTTP(w, r)
}
func (app *Frontend) ServeFrontend(w http.ResponseWriter, r *http.Request) {
_, ok := app.TokenFromContext(r.Context())
if !ok {
http.Redirect(w, r, "/auth/login", http.StatusSeeOther)
return
}
app.serveFrontend(w, r)
}
func (app *Frontend) ServeUser(w http.ResponseWriter, r *http.Request) {
_, ok := app.TokenFromContext(r.Context())
if !ok {
username := chi.URLParam(r, "username")
http.Redirect(w, r, "/@"+username, http.StatusSeeOther)
return
}
app.serveFrontend(w, r)
}
func (app *Frontend) ServeStatus(w http.ResponseWriter, r *http.Request) {
_, ok := app.TokenFromContext(r.Context())
if !ok {
username := chi.URLParam(r, "username")
postID := chi.URLParam(r, "postID")
http.Redirect(w, r, "/@"+username+"/posts/"+postID, http.StatusSeeOther)
return
}
app.serveFrontend(w, r)
}
func (app *Frontend) serveFrontend(w http.ResponseWriter, r *http.Request) {
token, ok := app.TokenFromContext(r.Context())
if !ok {
log.Error().Msg("app.serveFrontend was called without a token being set")
http.Redirect(w, r, "/auth/login", http.StatusSeeOther)
return
}
acct, err := app.Account().ByID(r.Context(), token.UserID)
if err != nil {
log.Err(err).Msg("fetching account")
app.ErrorTemplate(w, r, "Internal server error", "An internal server error occurred. Please try again later.")
return
}
b, err := json.Marshal(api.DBAccountToSelfAccount(acct))
if err != nil {
log.Err(err).Msg("marshaling account json")
app.ErrorTemplate(w, r, "Internal server error", "An internal server error occurred. Please try again later.")
return
}
w.Header().Set("Content-Type", "text/html")
err = app.tmpl.Execute(w, map[string]any{
"Config": database.DefaultConfig,
"Vue": app.glue,
"AccountData": template.HTML(b),
})
if err != nil {
log.Err(err).Msg("executing frontend template")
}
}
func (app *Frontend) ServeStaticAssets(w http.ResponseWriter, r *http.Request) {
if app.AppConfig.Core.Dev {
// TODO: this is unsafe
path := filepath.Join("web/frontend/assets/", chi.URLParam(r, "*"))
http.ServeFile(w, r, path)
return
}
_ = assets
}