190 lines
4.4 KiB
Go
190 lines
4.4 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
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
func NewHandler(service *memory.Service, memosURL, publicURL string, logger *slog.Logger) *Handler {
|
|
pub := publicURL
|
|
if pub == "" {
|
|
pub = memosURL
|
|
}
|
|
h := &Handler{
|
|
service: service,
|
|
memosURL: strings.TrimRight(memosURL, "/"),
|
|
publicURL: strings.TrimRight(pub, "/"),
|
|
logger: logger,
|
|
mux: http.NewServeMux(),
|
|
}
|
|
h.mux.HandleFunc("GET /", h.handleMemory)
|
|
h.mux.HandleFunc("GET /health", h.handleHealth)
|
|
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,
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|