Files
remembos/internal/web/handler.go
T
2026-02-12 17:52:32 +03:00

209 lines
4.9 KiB
Go

package web
import (
"embed"
"fmt"
"html/template"
"log/slog"
"net/http"
"strings"
"time"
"git.vakhrushev.me/av/remembos/internal/memory"
)
//go:embed templates/*.html
var templateFS embed.FS
var templates = template.Must(template.ParseFS(templateFS, "templates/*.html"))
type imageData struct {
URL string
Alt string
}
type memoryData struct {
DateFormatted string
AgoText string
Content string
Tags []string
Images []imageData
MemoURL string
Tier int
ShowCount int
AllowLoadMore bool
}
type errorData struct {
Message string
}
type Handler struct {
service *memory.Service
logger *slog.Logger
mux *http.ServeMux
memosURL string // internal Memos URL (for attachment files)
publicURL string // public Memos URL (for memo links)
allowLoadMore bool
}
func NewHandler(service *memory.Service, memosURL, publicURL string, allowLoadMore bool, logger *slog.Logger) *Handler {
pub := publicURL
if pub == "" {
pub = memosURL
}
h := &Handler{
service: service,
memosURL: strings.TrimRight(memosURL, "/"),
publicURL: strings.TrimRight(pub, "/"),
allowLoadMore: allowLoadMore,
logger: logger,
mux: http.NewServeMux(),
}
h.mux.HandleFunc("GET /", h.handleMemory)
h.mux.HandleFunc("GET /health", h.handleHealth)
h.mux.HandleFunc("POST /more", h.handleLoadMore)
return h
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.mux.ServeHTTP(w, r)
}
func (h *Handler) handleMemory(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
mem, err := h.service.GetTodayMemory(r.Context())
if err != nil {
h.logger.Error("failed to get memory", "error", err)
w.WriteHeader(http.StatusInternalServerError)
templates.ExecuteTemplate(w, "error.html", errorData{
Message: "Не удалось загрузить воспоминание",
})
return
}
if mem == nil {
w.WriteHeader(http.StatusOK)
templates.ExecuteTemplate(w, "error.html", errorData{
Message: "Нет заметок для воспоминания",
})
return
}
var images []imageData
for _, att := range mem.Memo.Attachments {
if !att.IsImage() {
continue
}
var imgURL string
if att.ExternalLink != "" {
imgURL = att.ExternalLink
} else {
imgURL = fmt.Sprintf("%s/file/%s/%s", h.memosURL, att.Name, att.Filename)
}
images = append(images, imageData{URL: imgURL, Alt: att.Filename})
}
// Link to original memo: {publicURL}/{memoName}
memoURL := fmt.Sprintf("%s/%s", h.publicURL, mem.Memo.Name)
data := memoryData{
DateFormatted: formatDate(mem.Date),
AgoText: agoText(mem.Date),
Content: mem.Memo.Content,
Tags: mem.Memo.Tags,
Images: images,
MemoURL: memoURL,
Tier: mem.Tier,
ShowCount: mem.ShowCount,
AllowLoadMore: h.allowLoadMore,
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := templates.ExecuteTemplate(w, "memory.html", data); err != nil {
h.logger.Error("template render failed", "error", err)
}
}
func (h *Handler) handleHealth(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "ok")
}
func (h *Handler) handleLoadMore(w http.ResponseWriter, r *http.Request) {
if !h.allowLoadMore {
http.Error(w, "load more is disabled", http.StatusForbidden)
return
}
_, err := h.service.LoadNewMemory(r.Context())
if err != nil {
h.logger.Error("failed to load new memory", "error", err)
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
var months = [...]string{
1: "января", 2: "февраля", 3: "марта",
4: "апреля", 5: "мая", 6: "июня",
7: "июля", 8: "августа", 9: "сентября",
10: "октября", 11: "ноября", 12: "декабря",
}
func formatDate(t time.Time) string {
return fmt.Sprintf("%d %s %d", t.Day(), months[t.Month()], t.Year())
}
func agoText(t time.Time) string {
now := time.Now()
years := now.Year() - t.Year()
monthsDiff := int(now.Month()) - int(t.Month())
if monthsDiff < 0 {
years--
monthsDiff += 12
}
if years > 0 {
return fmt.Sprintf("%s назад", pluralYears(years))
}
if monthsDiff > 0 {
return fmt.Sprintf("%s назад", pluralMonths(monthsDiff))
}
return ""
}
func pluralYears(n int) string {
mod10 := n % 10
mod100 := n % 100
switch {
case mod100 >= 11 && mod100 <= 14:
return fmt.Sprintf("%d лет", n)
case mod10 == 1:
return fmt.Sprintf("%d год", n)
case mod10 >= 2 && mod10 <= 4:
return fmt.Sprintf("%d года", n)
default:
return fmt.Sprintf("%d лет", n)
}
}
func pluralMonths(n int) string {
mod10 := n % 10
mod100 := n % 100
switch {
case mod100 >= 11 && mod100 <= 14:
return fmt.Sprintf("%d месяцев", n)
case mod10 == 1:
return fmt.Sprintf("%d месяц", n)
case mod10 >= 2 && mod10 <= 4:
return fmt.Sprintf("%d месяца", n)
default:
return fmt.Sprintf("%d месяцев", n)
}
}