This commit is contained in:
csehviktor
2025-08-01 05:52:06 +02:00
commit aae5a0e0fb
16 changed files with 656 additions and 0 deletions

23
handler/errors.go Normal file
View File

@@ -0,0 +1,23 @@
package handler
import (
"log/slog"
"net/http"
)
func notFound(slug string, err error, w http.ResponseWriter, r *http.Request) {
respondWithError(slug, err, w, r, http.StatusNotFound)
}
func badRequest(slug string, err error, w http.ResponseWriter, r *http.Request) {
respondWithError(slug, err, w, r, http.StatusInternalServerError)
}
func internal(slug string, err error, w http.ResponseWriter, r *http.Request) {
respondWithError(slug, err, w, r, http.StatusInternalServerError)
}
func respondWithError(slug string, err error, w http.ResponseWriter, r *http.Request, status int) {
slog.Error("http error occured", "slug", slug, "error", err, "path", r.URL.Path)
http.Error(w, slug, status)
}

78
handler/http.go Normal file
View File

@@ -0,0 +1,78 @@
package handler
import (
"log/slog"
"net/http"
"strings"
"github.com/a-h/templ"
"github.com/csehviktor/pastebin/store"
"github.com/csehviktor/pastebin/view"
)
type HttpHandler struct {
store *store.MemoryStore
}
func NewHandler(store *store.MemoryStore) *HttpHandler {
return &HttpHandler{
store: store,
}
}
func (h *HttpHandler) HandleSet(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
badRequest("invalid bin", err, w, r)
return
}
content := r.FormValue("content")
if content == "" {
badRequest("bin cant be empty", nil, w, r)
return
}
id, err := generateId()
if err != nil {
internal("could not generate id", err, w, r)
return
}
h.store.Set(id, content)
slog.Info("created bin", "id", id)
http.Redirect(w, r, "/"+id, http.StatusFound)
}
func (h *HttpHandler) HandleGet(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
ext := "txt"
if index := strings.Index(id, "."); index > 0 {
if index <= len(id) {
ext = id[index+1:]
id = id[:index]
}
}
content, exists := h.store.Get(id)
if !exists {
notFound("bin not found", nil, w, r)
return
}
highlighted, err := highlight(content, ext, "catppuccin-macchiato")
if err != nil {
internal("could not highlight content", err, w, r)
return
}
Render(view.BinPreviewPage(id, highlighted), w, r)
}
func Render(component templ.Component, w http.ResponseWriter, r *http.Request) {
err := component.Render(r.Context(), w)
if err != nil {
internal("could not render template", err, w, r)
}
}

58
handler/utils.go Normal file
View File

@@ -0,0 +1,58 @@
package handler
import (
"crypto/rand"
"strings"
"github.com/alecthomas/chroma/v2/formatters/html"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/alecthomas/chroma/v2/styles"
)
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
func generateId() (string, error) {
bytes := make([]byte, 10)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
for i := range bytes {
bytes[i] = charset[bytes[i]%byte(len(charset))]
}
return string(bytes), nil
}
func highlight(content, ext, theme string) (string, error) {
lexer := lexers.Get(ext)
if lexer == nil {
lexer = lexers.Analyse(content)
if lexer == nil {
lexer = lexers.Fallback
}
}
formatter := html.New(
html.WithLineNumbers(false),
html.Standalone(false),
html.TabWidth(4),
html.WithLineNumbers(true),
)
style := styles.Get(theme)
if style == nil {
style = styles.Fallback
}
iterator, err := lexer.Tokenise(nil, content)
if err != nil {
return "", err
}
var buf strings.Builder
err = formatter.Format(&buf, style, iterator)
if err != nil {
return "", err
}
return buf.String(), nil
}